This commit is contained in:
2026-05-01 00:29:32 +02:00
parent b3b0e882b2
commit c542aa9d32
17 changed files with 2347 additions and 49 deletions

View File

@@ -7,7 +7,8 @@
"splash",
"demo_ws",
"demo_http",
"demo_ws_manager"
"demo_ws_manager",
"demo_pipeline"
],
"permissions": [
"core:default",

View File

@@ -0,0 +1,255 @@
<!-- file: kb_app/frontend/demo_pipeline.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Khadhroony-BoBoBot — Demo Pipeline</title>
<link rel="stylesheet" href="sass/main.scss" />
</head>
<body class="bg-body-tertiary">
<header class="app-header">
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
<div class="container my-0">
<a class="navbar-brand d-flex align-items-center" href="/">
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Demo Pipeline</span>
</a>
</div>
</nav>
</header>
<main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container vcentered sketchy-translucid py-4">
<div class="row g-4">
<div class="col-12 col-xxl-4">
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<h1 class="h4 mb-3">Inspection par signature</h1>
<p class="text-body-secondary mb-3">
Cette fenêtre inspecte la donnée déjà persistée dans <code>kb_lib</code> pour une
signature donnée et affiche létat du pipeline <code>0.7.x</code>.
</p>
<div class="mb-3">
<label for="demoPipelineSignatureInput" class="form-label">Signature</label>
<input id="demoPipelineSignatureInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Signature Solana déjà présente dans la base locale" />
</div>
<div class="mb-3">
<label for="demoPipelineCustomTimeframeInput" class="form-label">
Timeframe custom optionnel (secondes)
</label>
<input id="demoPipelineCustomTimeframeInput" type="number" min="1" step="1" class="form-control" placeholder="Ex: 120" />
<div class="form-text">
Les timeframes matérialisés restent chargés depuis la base. Une valeur custom déclenche
une régénération à la demande.
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-4">
<button id="demoPipelineInspectButton" type="button" class="btn btn-primary">
Inspecter
</button>
<button id="demoPipelineClearButton" type="button" class="btn btn-outline-secondary">
Vider
</button>
</div>
<hr class="my-4" />
<div class="mb-3">
<label for="demoPipelineTokenMintInput" class="form-label">Token mint</label>
<input id="demoPipelineTokenMintInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Mint SPL déjà présent dans la base locale" />
</div>
<div class="d-flex flex-wrap gap-2 mb-4">
<button id="demoPipelineInspectTokenButton" type="button" class="btn btn-outline-primary">
Inspecter token
</button>
</div>
<hr class="my-4" />
<div class="mb-3">
<label for="demoPipelinePairIdInput" class="form-label">Pair id</label>
<input
id="demoPipelinePairIdInput"
type="number"
min="1"
step="1"
class="form-control"
placeholder="Identifiant interne de la paire"
/>
</div>
<div class="d-flex flex-wrap gap-2 mb-3">
<button id="demoPipelineInspectPairButton" type="button" class="btn btn-outline-primary">
Inspecter pair
</button>
</div>
<div class="mb-3">
<label for="demoPipelinePoolAddressInput" class="form-label">Pool address</label>
<input
id="demoPipelinePoolAddressInput"
type="text"
class="form-control font-monospace"
spellcheck="false"
placeholder="Adresse du pool déjà présent dans la base locale"
/>
</div>
<div class="d-flex flex-wrap gap-2 mb-4">
<button id="demoPipelineInspectPoolButton" type="button" class="btn btn-outline-primary">
Inspecter pool
</button>
</div>
<hr class="my-4" />
<div class="small text-body-secondary">
<div><strong>But :</strong> vérifier rapidement la cohérence du pipeline <code>0.7.x</code>.</div>
<div><strong>Portée :</strong> lecture seule depuis SQLite via <code>kb_lib</code>.</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-xxl-8">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5 mb-3">Résumé</h2>
<textarea id="demoPipelineSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
</div>
<div class="accordion" id="demoPipelineAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingTransaction">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTransaction" aria-expanded="true" aria-controls="collapseTransaction">
Transaction résolue
</button>
</h2>
<div id="collapseTransaction" class="accordion-collapse collapse show" aria-labelledby="headingTransaction" data-bs-parent="#demoPipelineAccordion">
<div class="accordion-body">
<textarea id="demoPipelineTransactionTextarea" class="form-control font-monospace" rows="14" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingDecodedEvents">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDecodedEvents" aria-expanded="false" aria-controls="collapseDecodedEvents">
Decoded events
</button>
</h2>
<div id="collapseDecodedEvents" class="accordion-collapse collapse" aria-labelledby="headingDecodedEvents" data-bs-parent="#demoPipelineAccordion">
<div class="accordion-body">
<textarea id="demoPipelineDecodedEventsTextarea" class="form-control font-monospace" rows="14" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingPools">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePools" aria-expanded="false" aria-controls="collapsePools">
Pools / pairs / origins
</button>
</h2>
<div id="collapsePools" class="accordion-collapse collapse" aria-labelledby="headingPools" data-bs-parent="#demoPipelineAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Pools</label>
<textarea id="demoPipelinePoolsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Pairs</label>
<textarea id="demoPipelinePairsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Launch attributions</label>
<textarea id="demoPipelineLaunchAttributionsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
<div>
<label class="form-label">Pool origins</label>
<textarea id="demoPipelinePoolOriginsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingWallets">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWallets" aria-expanded="false" aria-controls="collapseWallets">
Wallets / participations / holdings
</button>
</h2>
<div id="collapseWallets" class="accordion-collapse collapse" aria-labelledby="headingWallets" data-bs-parent="#demoPipelineAccordion">
<div class="accordion-body">
<textarea id="demoPipelineWalletsTextarea" class="form-control font-monospace" rows="16" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTrades">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTrades" aria-expanded="false" aria-controls="collapseTrades">
Trades / metrics / candles / analytic signals
</button>
</h2>
<div id="collapseTrades" class="accordion-collapse collapse" aria-labelledby="headingTrades" data-bs-parent="#demoPipelineAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Trade events</label>
<textarea id="demoPipelineTradeEventsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Pair metrics</label>
<textarea id="demoPipelinePairMetricsTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Pair candles</label>
<textarea id="demoPipelinePairCandlesTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
</div>
<div>
<label class="form-label">Pair analytic signals</label>
<textarea id="demoPipelinePairAnalyticSignalsTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h5 mb-0">Log UI</h2>
<button id="demoPipelineClearLogButton" type="button" class="btn btn-outline-secondary btn-sm">Clear log</button>
</div>
<textarea id="demoPipelineLogTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">
<div class="container h-100 d-flex align-items-center">
<div class="row flex-grow-1 align-items-center">
<div class="col-12 col-md-6 text-center text-small my-1 my-md-0">
&copy; 2026 SASEDEV — Demo Pipeline
</div>
</div>
</div>
</footer>
<script type="module" src="ts/demo_pipeline.ts" defer></script>
</body>
</html>

View File

@@ -28,6 +28,9 @@
<button id="openDemoWsManagerButton" type="button" class="btn btn-outline-primary">
Demo Ws Manager
</button>
<button id="openDemoPipelineButton" type="button" class="btn btn-primary">
Ouvrir Demo Pipeline
</button>
</div>
</div>
</nav>
@@ -72,6 +75,9 @@
<button id="openDemoWsManagerButtonSecondary" type="button" class="btn btn-primary">
Ouvrir Demo Ws Manager
</button>
<button id="openDemoPipelineButtonSecondary" type="button" class="btn btn-primary">
Ouvrir Demo Pipeline
</button>
</div>
<hr />

View File

@@ -0,0 +1,420 @@
// file: kb_app/frontend/ts/demo_pipeline.ts
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
interface DemoPipelineInspectRequest {
signature: string;
customTimeframeSeconds: number | null;
}
interface DemoPipelineInspectPayload {
signature: string;
summaryJson: string;
transactionJson: string;
decodedEventsJson: string;
poolsJson: string;
pairsJson: string;
launchAttributionsJson: string;
poolOriginsJson: string;
walletsJson: string;
tradeEventsJson: string;
pairMetricsJson: string;
pairCandlesJson: string;
pairAnalyticSignalsJson: string;
}
interface DemoPipelineInspectTokenRequest {
tokenMint: string;
customTimeframeSeconds: number | null;
}
interface DemoPipelineInspectPairRequest {
pairId: number;
customTimeframeSeconds: number | null;
}
interface DemoPipelineInspectPoolRequest {
poolAddress: string;
customTimeframeSeconds: number | null;
}
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date();
const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });
const lines = textarea.value === "" ? [] : textarea.value.split("\n");
lines.push(`[${timestamp}] ${line}`);
const maxLines = 300;
textarea.value = lines.slice(-maxLines).join("\n");
textarea.scrollTop = textarea.scrollHeight;
}
function clearInspection(
summaryTextarea: HTMLTextAreaElement,
transactionTextarea: HTMLTextAreaElement,
decodedEventsTextarea: HTMLTextAreaElement,
poolsTextarea: HTMLTextAreaElement,
pairsTextarea: HTMLTextAreaElement,
launchAttributionsTextarea: HTMLTextAreaElement,
poolOriginsTextarea: HTMLTextAreaElement,
walletsTextarea: HTMLTextAreaElement,
tradeEventsTextarea: HTMLTextAreaElement,
pairMetricsTextarea: HTMLTextAreaElement,
pairCandlesTextarea: HTMLTextAreaElement,
pairAnalyticSignalsTextarea: HTMLTextAreaElement,
): void {
summaryTextarea.value = "";
transactionTextarea.value = "";
decodedEventsTextarea.value = "";
poolsTextarea.value = "";
pairsTextarea.value = "";
launchAttributionsTextarea.value = "";
poolOriginsTextarea.value = "";
walletsTextarea.value = "";
tradeEventsTextarea.value = "";
pairMetricsTextarea.value = "";
pairCandlesTextarea.value = "";
pairAnalyticSignalsTextarea.value = "";
}
function readCustomTimeframeSeconds(
input: HTMLInputElement,
logTextarea: HTMLTextAreaElement,
): number | null | undefined {
const customTimeframeText = input.value.trim();
if (customTimeframeText === "") {
return null;
}
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return undefined;
}
return parsed;
}
document.addEventListener("DOMContentLoaded", async () => {
void takeoverConsole();
debug("demo_pipeline window loaded");
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
if (sidebarToggle) {
// restaurer létat depuis localStorage
if (localStorage.getItem('sidebar-toggle') === 'true') {
document.body.classList.add('sidenav-toggled');
}
sidebarToggle.addEventListener('click', (event) => {
event.preventDefault();
document.body.classList.toggle('sidenav-toggled');
localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
});
}
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
const toastElList = document.querySelectorAll('.toast');
Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
const gobackto = location.pathname + location.search;
document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
const href = a.getAttribute("href");
if (!href) return; // pas de href => on ignore
const url = new URL(href, location.origin);
url.searchParams.set("gobackto", gobackto);
// conserve une URL relative (path + query)
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
});
const signatureInput = document.querySelector<HTMLInputElement>("#demoPipelineSignatureInput");
const customTimeframeInput = document.querySelector<HTMLInputElement>("#demoPipelineCustomTimeframeInput");
const inspectButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectButton");
const clearButton = document.querySelector<HTMLButtonElement>("#demoPipelineClearButton");
const clearLogButton = document.querySelector<HTMLButtonElement>("#demoPipelineClearLogButton");
const summaryTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineSummaryTextarea");
const transactionTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineTransactionTextarea");
const decodedEventsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineDecodedEventsTextarea");
const poolsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePoolsTextarea");
const pairsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairsTextarea");
const launchAttributionsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineLaunchAttributionsTextarea");
const poolOriginsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePoolOriginsTextarea");
const walletsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineWalletsTextarea");
const tradeEventsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineTradeEventsTextarea");
const pairMetricsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairMetricsTextarea");
const pairCandlesTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairCandlesTextarea");
const pairAnalyticSignalsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairAnalyticSignalsTextarea");
const logTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineLogTextarea");
const tokenMintInput = document.querySelector<HTMLInputElement>("#demoPipelineTokenMintInput");
const inspectTokenButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectTokenButton");
const pairIdInput = document.querySelector<HTMLInputElement>("#demoPipelinePairIdInput");
const inspectPairButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPairButton");
const poolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelinePoolAddressInput");
const inspectPoolButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPoolButton");
if (
!pairIdInput ||
!inspectPairButton ||
!poolAddressInput ||
!inspectPoolButton ||
!tokenMintInput ||
!inspectTokenButton ||
!signatureInput ||
!customTimeframeInput ||
!inspectButton ||
!clearButton ||
!clearLogButton ||
!summaryTextarea ||
!transactionTextarea ||
!decodedEventsTextarea ||
!poolsTextarea ||
!pairsTextarea ||
!launchAttributionsTextarea ||
!poolOriginsTextarea ||
!walletsTextarea ||
!tradeEventsTextarea ||
!pairMetricsTextarea ||
!pairCandlesTextarea ||
!pairAnalyticSignalsTextarea ||
!logTextarea
) {
console.error("demo_pipeline DOM is incomplete");
return;
}
clearButton.addEventListener("click", () => {
clearInspection(
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
);
signatureInput.value = "";
customTimeframeInput.value = "";
tokenMintInput.value = "";
pairIdInput.value = "";
poolAddressInput.value = "";
appendLogLine(logTextarea, "[ui] inspection state cleared");
});
clearLogButton.addEventListener("click", () => {
logTextarea.value = "";
});
inspectButton.addEventListener("click", async () => {
const signature = signatureInput.value.trim();
if (signature === "") {
appendLogLine(logTextarea, "[ui] signature is required");
return;
}
let customTimeframeSeconds: number | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
customTimeframeSeconds = parsed;
}
appendLogLine(
logTextarea,
`[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectRequest = {
signature,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_signature", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
decodedEventsTextarea.value = payload.decodedEventsJson;
poolsTextarea.value = payload.poolsJson;
pairsTextarea.value = payload.pairsJson;
launchAttributionsTextarea.value = payload.launchAttributionsJson;
poolOriginsTextarea.value = payload.poolOriginsJson;
walletsTextarea.value = payload.walletsJson;
tradeEventsTextarea.value = payload.tradeEventsJson;
pairMetricsTextarea.value = payload.pairMetricsJson;
pairCandlesTextarea.value = payload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson;
appendLogLine(logTextarea, `[ui] inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] inspect error: ${String(error)}`);
}
});
inspectTokenButton.addEventListener("click", async () => {
const tokenMint = tokenMintInput.value.trim();
if (tokenMint === "") {
appendLogLine(logTextarea, "[ui] token mint is required");
return;
}
let customTimeframeSeconds: number | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
customTimeframeSeconds = parsed;
}
appendLogLine(
logTextarea,
`[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectTokenRequest = {
tokenMint,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_token_mint", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
decodedEventsTextarea.value = payload.decodedEventsJson;
poolsTextarea.value = payload.poolsJson;
pairsTextarea.value = payload.pairsJson;
launchAttributionsTextarea.value = payload.launchAttributionsJson;
poolOriginsTextarea.value = payload.poolOriginsJson;
walletsTextarea.value = payload.walletsJson;
tradeEventsTextarea.value = payload.tradeEventsJson;
pairMetricsTextarea.value = payload.pairMetricsJson;
pairCandlesTextarea.value = payload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson;
appendLogLine(logTextarea, `[ui] token inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] token inspect error: ${String(error)}`);
}
});
inspectPairButton.addEventListener("click", async () => {
const pairIdText = pairIdInput.value.trim();
if (pairIdText === "") {
appendLogLine(logTextarea, "[ui] pair id is required");
return;
}
const parsedPairId = Number.parseInt(pairIdText, 10);
if (Number.isNaN(parsedPairId) || parsedPairId <= 0) {
appendLogLine(logTextarea, `[ui] invalid pair id '${pairIdText}'`);
return;
}
const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea);
if (customTimeframeSeconds === undefined) {
return;
}
appendLogLine(
logTextarea,
`[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectPairRequest = {
pairId: parsedPairId,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pair_id", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
decodedEventsTextarea.value = payload.decodedEventsJson;
poolsTextarea.value = payload.poolsJson;
pairsTextarea.value = payload.pairsJson;
launchAttributionsTextarea.value = payload.launchAttributionsJson;
poolOriginsTextarea.value = payload.poolOriginsJson;
walletsTextarea.value = payload.walletsJson;
tradeEventsTextarea.value = payload.tradeEventsJson;
pairMetricsTextarea.value = payload.pairMetricsJson;
pairCandlesTextarea.value = payload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson;
appendLogLine(logTextarea, `[ui] pair inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] pair inspect error: ${String(error)}`);
}
});
inspectPoolButton.addEventListener("click", async () => {
const poolAddress = poolAddressInput.value.trim();
if (poolAddress === "") {
appendLogLine(logTextarea, "[ui] pool address is required");
return;
}
const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea);
if (customTimeframeSeconds === undefined) {
return;
}
appendLogLine(
logTextarea,
`[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectPoolRequest = {
poolAddress,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pool_address", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
decodedEventsTextarea.value = payload.decodedEventsJson;
poolsTextarea.value = payload.poolsJson;
pairsTextarea.value = payload.pairsJson;
launchAttributionsTextarea.value = payload.launchAttributionsJson;
poolOriginsTextarea.value = payload.poolOriginsJson;
walletsTextarea.value = payload.walletsJson;
tradeEventsTextarea.value = payload.tradeEventsJson;
pairMetricsTextarea.value = payload.pairMetricsJson;
pairCandlesTextarea.value = payload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson;
appendLogLine(logTextarea, `[ui] pool inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] pool inspect error: ${String(error)}`);
}
});
});

View File

@@ -31,8 +31,19 @@ async function openDemoWsManagerWindow(): Promise<void> {
console.error("open_demo_ws_manager_window failed:", error);
}
}
async function openDemoPipelineWindow(): Promise<void> {
try {
await invoke("open_demo_pipeline_window");
} catch (error) {
console.error("open_demo_pipeline_window failed:", error);
}
}
document.addEventListener("DOMContentLoaded", async () => {
void takeoverConsole();
debug("main window loaded");
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
if (sidebarToggle) {
// restaurer létat depuis localStorage
@@ -66,14 +77,16 @@ document.addEventListener("DOMContentLoaded", async () => {
// conserve une URL relative (path + query)
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
});
const openDemoWsButton = document.querySelector<HTMLButtonElement>("#openDemoWsButton");
const openDemoWsButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsButtonSecondary");
const openDemoHttpButton = document.querySelector<HTMLButtonElement>("#openDemoHttpButton");
const openDemoHttpButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoHttpButtonSecondary");
const openDemoWsManagerButton = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButton");
const openDemoWsManagerButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButtonSecondary");
const openDemoPipelineButton = document.querySelector<HTMLButtonElement>("#openDemoPipelineButton");
const openDemoPipelineButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoPipelineButtonSecondary");
if (openDemoWsButton) {
openDemoWsButton.addEventListener("click", () => {
@@ -86,7 +99,7 @@ document.addEventListener("DOMContentLoaded", async () => {
void openDemoWsWindow();
});
}
if (openDemoHttpButton) {
openDemoHttpButton.addEventListener("click", () => {
void openDemoHttpWindow();
@@ -110,7 +123,17 @@ document.addEventListener("DOMContentLoaded", async () => {
void openDemoWsManagerWindow();
});
}
debug("window loaded");
if (openDemoPipelineButton) {
openDemoPipelineButton.addEventListener("click", () => {
void openDemoPipelineWindow();
});
}
if (openDemoPipelineButtonSecondary) {
openDemoPipelineButtonSecondary.addEventListener("click", () => {
void openDemoPipelineWindow();
});
}
});

View File

@@ -1 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","splash","demo_ws","demo_http","demo_ws_manager"],"permissions":["core:default","tracing:default"]}}
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main","splash","demo_ws","demo_http","demo_ws_manager","demo_pipeline"],"permissions":["core:default","tracing:default"]}}

View File

@@ -1,7 +1,7 @@
{
"name": "kb-app",
"private": true,
"version": "0.6.6",
"version": "0.7.22",
"type": "module",
"scripts": {
"dev": "vite",
@@ -26,4 +26,4 @@
"typescript": "^5.9",
"vite": "^8.0"
}
}
}

1494
kb_app/src/demo_pipeline.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
#![warn(missing_docs)]
mod demo_http;
mod demo_pipeline;
mod demo_ws;
mod demo_ws_manager;
mod splash;
@@ -36,16 +37,18 @@ impl KbWsRuntimeState {
/// Shared application state stored inside Tauri.
struct KbAppState {
config: kb_lib::KbConfig,
database: std::sync::Arc<kb_lib::KbDatabase>,
ws_runtime: tokio::sync::Mutex<KbWsRuntimeState>,
demo_ws_runtime: std::sync::Arc<tokio::sync::Mutex<crate::demo_ws::KbDemoWsRuntimeState>>,
demo_ws_manager_runtime: std::sync::Arc<tokio::sync::Mutex<crate::demo_ws_manager::KbDemoWsManagerRuntimeState>>,
demo_ws_manager_runtime:
std::sync::Arc<tokio::sync::Mutex<crate::demo_ws_manager::KbDemoWsManagerRuntimeState>>,
ws_manager: std::sync::Arc<kb_lib::WsManager>,
http_pool: kb_lib::HttpEndpointPool,
}
/// Runs the desktop application.
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
pub async fn run() -> Result<(), kb_lib::KbError> {
let config_path = kb_lib::KbConfig::default_path();
let config_result = kb_lib::KbConfig::load_from_path(&config_path);
let config = match config_result {
@@ -56,20 +59,20 @@ pub fn run() {
config_path.display(),
error
);
return;
return Err(error);
}
};
let prepare_result = config.prepare_filesystem();
if let Err(error) = prepare_result {
eprintln!("kb_app filesystem preparation error: {error}");
return;
return Err(error);
}
let tracing_guard_result = kb_lib::init_tracing(&config.logging);
let _tracing_guard = match tracing_guard_result {
Ok(guard) => guard,
Err(error) => {
eprintln!("kb_app tracing initialization error: {error}");
return;
return Err(error);
}
};
tracing::info!(
@@ -77,6 +80,11 @@ pub fn run() {
environment = %config.app.environment,
"starting desktop application"
);
let database_result = kb_lib::KbDatabase::connect_and_initialize(&config.database).await;
let database = match database_result {
Ok(database) => database,
Err(error) => return Err(error),
};
let http_pool_result = kb_lib::HttpEndpointPool::from_config(&config);
let http_pool = match http_pool_result {
Ok(http_pool) => http_pool,
@@ -95,6 +103,7 @@ pub fn run() {
};
let app_state = KbAppState {
config: config.clone(),
database: std::sync::Arc::new(database),
ws_runtime: tokio::sync::Mutex::new(KbWsRuntimeState::new()),
demo_ws_runtime: std::sync::Arc::new(tokio::sync::Mutex::new(
crate::demo_ws::KbDemoWsRuntimeState::new(),
@@ -128,6 +137,11 @@ pub fn run() {
crate::demo_ws_manager::demo_ws_manager_stop_all,
crate::demo_ws_manager::demo_ws_manager_start_role,
crate::demo_ws_manager::demo_ws_manager_stop_role,
crate::demo_pipeline::open_demo_pipeline_window,
crate::demo_pipeline::demo_pipeline_inspect_signature,
crate::demo_pipeline::demo_pipeline_inspect_token_mint,
crate::demo_pipeline::demo_pipeline_inspect_pair_id,
crate::demo_pipeline::demo_pipeline_inspect_pool_address,
]);
tauri_builder = tauri_builder.plugin(tracing_builder.build::<tauri::Wry>());
tauri_builder = tauri_builder.setup(|app| {
@@ -202,7 +216,11 @@ pub fn run() {
let run_result = tauri_builder.run(tauri::generate_context!());
if let Err(error) = run_result {
tracing::error!("error while running tauri application: {error:?}");
return Err(kb_lib::KbError::InvalidState(format!(
"error while running tauri application: {error:?}"
)));
}
Ok(())
}
fn emit_splash_order(

View File

@@ -14,9 +14,7 @@ use fs2::FileExt;
/// Entrypoint of the kb app binary.
#[tokio::main]
async
fn main() -> std::process::ExitCode
{
async fn main() -> std::process::ExitCode {
let mut lock_path = std::env::temp_dir();
lock_path.push("com_khadhroony_solana_rust.lock");
let lock_file = match std::fs::File::create(lock_path) {
@@ -24,7 +22,7 @@ fn main() -> std::process::ExitCode
Err(_err) => {
eprintln!("Cannot create lock!");
std::process::exit(1);
},
}
};
// trying to aquire an exclusive lock
if lock_file.try_lock_exclusive().is_err() {
@@ -34,13 +32,17 @@ fn main() -> std::process::ExitCode
if rustls::crypto::CryptoProvider::get_default().is_none() {
let provider_result = rustls::crypto::aws_lc_rs::default_provider().install_default();
match provider_result {
Ok(()) => {},
Ok(()) => {}
Err(error) => {
eprintln!("kb_app rustls provider init error: {:?}", error);
return std::process::ExitCode::FAILURE;
},
}
}
}
kb_app_lib::run();
}
let run_result = kb_app_lib::run().await;
if let Err(error) = run_result {
eprintln!("application error: {}", error);
std::process::exit(1);
}
std::process::ExitCode::SUCCESS
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "kb-bapp",
"version": "0.6.6",
"version": "0.7.22",
"identifier": "com.sasedev.kb-app",
"build": {
"beforeDevCommand": "npm run dev",
@@ -78,6 +78,20 @@
"create": false,
"transparent": false,
"decorations": true
},
{
"label": "demo_pipeline",
"url": "demo_pipeline.html",
"title": "Demo Pipeline",
"width": 1480,
"height": 920,
"minWidth": 1000,
"minHeight": 700,
"center": true,
"visible": false,
"create": false,
"transparent": false,
"decorations": true
}
],
"security": {