0.7.22
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,6 +38,7 @@ config.json
|
|||||||
|
|
||||||
|
|
||||||
# sqlite
|
# sqlite
|
||||||
|
data
|
||||||
dbdata
|
dbdata
|
||||||
*.db
|
*.db
|
||||||
*.db-shm
|
*.db-shm
|
||||||
|
|||||||
@@ -52,3 +52,4 @@
|
|||||||
0.7.19 - Ajout d’une première couche holdings observés avec agrégation par couple wallet/token et branchement automatique dans le pipeline de résolution transactionnelle
|
0.7.19 - Ajout d’une première couche holdings observés avec agrégation par couple wallet/token et branchement automatique dans le pipeline de résolution transactionnelle
|
||||||
0.7.20 - Ajout d’une première couche candles / OHLCV avec matérialisation en base des timeframes usuels et régénération à la demande pour un timeframe arbitraire depuis les trade events
|
0.7.20 - Ajout d’une première couche candles / OHLCV avec matérialisation en base des timeframes usuels et régénération à la demande pour un timeframe arbitraire depuis les trade events
|
||||||
0.7.21 - Ajout d’une première couche de signaux analytiques enrichis par paire avec persistance dédiée et détection de first trade, trade burst, buy/sell imbalance, price jump et volume spike
|
0.7.21 - Ajout d’une première couche de signaux analytiques enrichis par paire avec persistance dédiée et détection de first trade, trade burst, buy/sell imbalance, price jump et volume spike
|
||||||
|
0.7.22 - Ajout d’une première fenêtre `Demo Pipeline` dans `kb_app` pour l’inspection en lecture seule du pipeline `0.7.x`, avec recherche par signature, token mint, pair id ou pool address, affichage structuré des transactions résolues, événements DEX décodés, pools, paires, listings, launch origins, pool origins, wallets et holdings observés, trade events, pair metrics, candles et signaux analytiques déjà persistés, ainsi que conservation d’une instance partagée de la base SQLite pour éviter la réouverture et la réinitialisation du schéma à chaque commande UI
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.7.21"
|
version = "0.7.22"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||||
|
|||||||
95
ROADMAP.md
95
ROADMAP.md
@@ -622,7 +622,7 @@ Réalisé :
|
|||||||
- ajout d’une collecte de cibles `accountSubscribe` à partir des pools actifs connus,
|
- ajout d’une collecte de cibles `accountSubscribe` à partir des pools actifs connus,
|
||||||
- ajout d’une couche d’observations techniques WS hybrides pour `logs / program / account`,
|
- ajout d’une couche d’observations techniques WS hybrides pour `logs / program / account`,
|
||||||
- ajout d’une première déduplication en mémoire des notifications techniques reçues en parallèle,
|
- ajout d’une première déduplication en mémoire des notifications techniques reçues en parallèle,
|
||||||
- ajout d’une façade runtime pour exposer ce comportement au futur branchement `ws_manager`.
|
- ajout d’une façade runtime pour exposer ce comportement au branchement `ws_manager`.
|
||||||
|
|
||||||
### 6.050. Version `0.7.18` — Backfill historique ciblé par token
|
### 6.050. Version `0.7.18` — Backfill historique ciblé par token
|
||||||
Réalisé :
|
Réalisé :
|
||||||
@@ -663,17 +663,73 @@ Réalisé :
|
|||||||
- branchement automatique dans le pipeline de résolution transactionnelle.
|
- branchement automatique dans le pipeline de résolution transactionnelle.
|
||||||
|
|
||||||
### 6.054. Version `0.7.22` — `kb_app` : inspection et tests du pipeline `0.7.x`
|
### 6.054. Version `0.7.22` — `kb_app` : inspection et tests du pipeline `0.7.x`
|
||||||
Objectif : permettre depuis l’application desktop de tester, inspecter et valider tout le pipeline `0.7.x` sans recourir uniquement aux logs bruts ou à SQLite.
|
Réalisé :
|
||||||
|
|
||||||
|
- ajout d’une fenêtre dédiée `Demo Pipeline` dans `kb_app`,
|
||||||
|
- inspection du pipeline persistant par `signature`,
|
||||||
|
- inspection du pipeline persistant par `token mint`,
|
||||||
|
- inspection du pipeline persistant par `pair id`,
|
||||||
|
- inspection du pipeline persistant par `pool address`,
|
||||||
|
- affichage structuré des transactions résolues, événements DEX décodés, pools, paires, listings, launch origins, pool origins, wallets observés, holdings observés, trade events, pair metrics, candles et signaux analytiques,
|
||||||
|
- possibilité d’utiliser un timeframe custom pour régénérer à la demande les candles non matérialisées,
|
||||||
|
- conservation d’une instance partagée de `KbDatabase` dans `kb_app` afin d’éviter la réouverture de la base et la réinitialisation du schéma à chaque commande UI,
|
||||||
|
- validation pratique de l’inspection du pipeline `0.7.x` sans dépendre uniquement des logs bruts ou de la consultation manuelle de SQLite.
|
||||||
|
|
||||||
|
### 6.055. Version `0.7.23` — `kb_app` : backfill token et inspection ciblée
|
||||||
|
Objectif : piloter le backfill historique depuis l’interface desktop et afficher le résultat de façon exploitable.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
|
|
||||||
- ajouter une ou plusieurs vues `kb_app` dédiées à l’inspection des transactions résolues, événements DEX décodés, pools, paires, launch origins, pool origins, wallets observés, holdings observés et trade events,
|
- ajouter une vue de saisie d’un `token_mint`,
|
||||||
- permettre la recherche par signature, pool, paire, token mint ou wallet,
|
- permettre le déclenchement manuel du `KbTokenBackfillService`,
|
||||||
- afficher les liens entre objets techniques et objets métier,
|
- afficher le résumé du backfill : signatures mint, pools retrouvés, signatures de pools, transactions résolues, decoded events, trade events, candles et analytic signals,
|
||||||
- permettre de lancer manuellement certains backfills ou inspections ciblées,
|
- permettre une navigation simple entre token, pools, paires et événements liés,
|
||||||
- fournir un socle UI pour tester en pratique tout ce qui a été construit dans la série `0.7.x`.
|
- préparer la réexécution ciblée de backfills sans casser l’idempotence du modèle.
|
||||||
|
|
||||||
### 6.055. Version `0.7.x` — Couverture DEX v1
|
### 6.056. Version `0.7.24` — `kb_app` : visualisation candles / OHLCV
|
||||||
|
Objectif : fournir une vue graphique exploitable des candles via `echarts`.
|
||||||
|
|
||||||
|
À faire :
|
||||||
|
|
||||||
|
- ajouter une vue de sélection de paire,
|
||||||
|
- permettre le choix du timeframe,
|
||||||
|
- lire les candles matérialisées pour les timeframes usuels,
|
||||||
|
- permettre la régénération à la demande pour un timeframe arbitraire,
|
||||||
|
- afficher les chandeliers, les volumes et la navigation temporelle,
|
||||||
|
- préparer l’affichage d’overlays analytiques.
|
||||||
|
|
||||||
|
### 6.057. Version `0.7.25` — `kb_app` : overlays analytiques
|
||||||
|
Objectif : rendre visibles les signaux analytiques directement sur les graphes et vues de marché.
|
||||||
|
|
||||||
|
À faire :
|
||||||
|
|
||||||
|
- afficher les signaux analytiques par bucket au-dessus ou autour des candles,
|
||||||
|
- ajouter des marqueurs pour `first_trade_seen`, `trade_burst_60s`, `buy_sell_imbalance_60s`, `price_jump_up_60s`, `price_jump_down_60s` et `volume_spike_60s`,
|
||||||
|
- permettre le filtrage par type de signal et par sévérité,
|
||||||
|
- afficher un panneau latéral listant les signaux liés à une paire et à un timeframe.
|
||||||
|
|
||||||
|
### 6.058. Version `0.7.26` — `kb_app` : vues consolidées token / pair / pool
|
||||||
|
Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
|
||||||
|
|
||||||
|
À faire :
|
||||||
|
|
||||||
|
- ajouter une fiche token,
|
||||||
|
- ajouter une fiche paire,
|
||||||
|
- ajouter une fiche pool,
|
||||||
|
- relier dans l’UI les launch origins, pool origins, wallets observés, holdings observés, candles et analytic signals,
|
||||||
|
- préparer une navigation transversale entre objets techniques et objets métier.
|
||||||
|
|
||||||
|
### 6.059. Version `0.7.27` — Finition UI `0.7.x`
|
||||||
|
Objectif : stabiliser la couche desktop de validation avant l’ouverture de `0.8.x`.
|
||||||
|
|
||||||
|
À faire :
|
||||||
|
|
||||||
|
- consolider les vues ajoutées dans `kb_app`,
|
||||||
|
- améliorer la navigation, les filtres et la pagination,
|
||||||
|
- ajouter les derniers raffinements de confort et de lisibilité,
|
||||||
|
- préparer une base UI suffisamment stable pour la future phase d’analyse et filtrage `0.8.x`.
|
||||||
|
|
||||||
|
### 6.060. Version `0.7.x` — Couverture DEX v1
|
||||||
Objectif : structurer les connecteurs DEX autour d’un pipeline complet de résolution, décodage et normalisation métier.
|
Objectif : structurer les connecteurs DEX autour d’un pipeline complet de résolution, décodage et normalisation métier.
|
||||||
|
|
||||||
Protocoles cibles :
|
Protocoles cibles :
|
||||||
@@ -700,7 +756,7 @@ Résultat attendu :
|
|||||||
- préparation d’une détection temps réel hybride et d’un backfill ciblé compatible avec les mêmes objets métier,
|
- préparation d’une détection temps réel hybride et d’un backfill ciblé compatible avec les mêmes objets métier,
|
||||||
- préparation d’agrégats DEX plus riches, de candles / OHLCV et d’une UI d’inspection du pipeline `0.7.x`.
|
- préparation d’agrégats DEX plus riches, de candles / OHLCV et d’une UI d’inspection du pipeline `0.7.x`.
|
||||||
|
|
||||||
### 6.056. Version `0.8.x` — Analyse et filtrage
|
### 6.061. Version `0.8.x` — Analyse et filtrage
|
||||||
Objectif : transformer les événements bruts en signaux exploitables.
|
Objectif : transformer les événements bruts en signaux exploitables.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -712,7 +768,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
|
|||||||
- premiers patterns,
|
- premiers patterns,
|
||||||
- enrichissement des signaux analytiques préparés en fin de `0.7.x`.
|
- enrichissement des signaux analytiques préparés en fin de `0.7.x`.
|
||||||
|
|
||||||
### 6.057. Version `1.x.y` — Wallets et swap préparatoire
|
### 6.062. Version `1.x.y` — Wallets et swap préparatoire
|
||||||
Objectif : préparer la couche d’action.
|
Objectif : préparer la couche d’action.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -723,7 +779,7 @@ Objectif : préparer la couche d’action.
|
|||||||
- préparation d’ordres et de swaps,
|
- préparation d’ordres et de swaps,
|
||||||
- simulation et garde-fous.
|
- simulation et garde-fous.
|
||||||
|
|
||||||
### 6.058. Version `2.x.y` — Trading semi-automatisé
|
### 6.063. Version `2.x.y` — Trading semi-automatisé
|
||||||
Objectif : brancher l’analyse à l’action tout en gardant des garde-fous explicites.
|
Objectif : brancher l’analyse à l’action tout en gardant des garde-fous explicites.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -734,7 +790,7 @@ Objectif : brancher l’analyse à l’action tout en gardant des garde-fous exp
|
|||||||
- confirmations explicites ou semi-automatiques,
|
- confirmations explicites ou semi-automatiques,
|
||||||
- journaux d’exécution.
|
- journaux d’exécution.
|
||||||
|
|
||||||
### 6.059. Version `3.x.y` — Yellowstone gRPC
|
### 6.064. Version `3.x.y` — Yellowstone gRPC
|
||||||
Objectif : ajouter le connecteur gRPC dédié.
|
Objectif : ajouter le connecteur gRPC dédié.
|
||||||
|
|
||||||
À faire :
|
À faire :
|
||||||
@@ -822,11 +878,10 @@ Le projet doit maintenir au minimum :
|
|||||||
|
|
||||||
La priorité immédiate est désormais la suivante :
|
La priorité immédiate est désormais la suivante :
|
||||||
|
|
||||||
1. finaliser complètement la fin de série `0.7.x` avant l’ouverture de `0.8.x`,
|
1. poursuivre la fin de série `0.7.x` côté `kb_app` avant l’ouverture de `0.8.x`,
|
||||||
2. ajouter un renforcement temps réel hybride avec `logsSubscribe`, `programSubscribe` et `accountSubscribe` en parallèle des sources déjà exploitées,
|
2. ajouter un pilotage UI du backfill historique ciblé par `token_mint`,
|
||||||
3. conserver la résolution transactionnelle comme source de normalisation commune,
|
3. ajouter une vue graphique des candles / OHLCV avec `echarts`,
|
||||||
4. ajouter ensuite un mode de backfill historique ciblé par `token_mint` pour des tokens encore actifs donnés explicitement,
|
4. ajouter les overlays des signaux analytiques sur les candles,
|
||||||
5. compléter la couche métier avec des `holdings observés`,
|
5. consolider les vues métier `token / pair / pool` dans `kb_app`,
|
||||||
6. ajouter des `candles / OHLCV` et une première couche de signaux analytiques plus riches,
|
6. stabiliser l’ergonomie, les filtres et la navigation de l’UI d’inspection,
|
||||||
7. doter `kb_app` d’une vraie UI d’inspection et de test pour l’ensemble du pipeline `0.7.x`,
|
7. préparer enfin l’arrivée de Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle existant.
|
||||||
8. préparer enfin l’arrivée de Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle existant.
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"splash",
|
"splash",
|
||||||
"demo_ws",
|
"demo_ws",
|
||||||
"demo_http",
|
"demo_http",
|
||||||
"demo_ws_manager"
|
"demo_ws_manager",
|
||||||
|
"demo_pipeline"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
|
|||||||
255
kb_app/frontend/demo_pipeline.html
Normal file
255
kb_app/frontend/demo_pipeline.html
Normal 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">
|
||||||
|
© 2026 SASEDEV — Demo Pipeline
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module" src="ts/demo_pipeline.ts" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -28,6 +28,9 @@
|
|||||||
<button id="openDemoWsManagerButton" type="button" class="btn btn-outline-primary">
|
<button id="openDemoWsManagerButton" type="button" class="btn btn-outline-primary">
|
||||||
Demo Ws Manager
|
Demo Ws Manager
|
||||||
</button>
|
</button>
|
||||||
|
<button id="openDemoPipelineButton" type="button" class="btn btn-primary">
|
||||||
|
Ouvrir Demo Pipeline
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -72,6 +75,9 @@
|
|||||||
<button id="openDemoWsManagerButtonSecondary" type="button" class="btn btn-primary">
|
<button id="openDemoWsManagerButtonSecondary" type="button" class="btn btn-primary">
|
||||||
Ouvrir Demo Ws Manager
|
Ouvrir Demo Ws Manager
|
||||||
</button>
|
</button>
|
||||||
|
<button id="openDemoPipelineButtonSecondary" type="button" class="btn btn-primary">
|
||||||
|
Ouvrir Demo Pipeline
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
420
kb_app/frontend/ts/demo_pipeline.ts
Normal file
420
kb_app/frontend/ts/demo_pipeline.ts
Normal 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)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -31,8 +31,19 @@ async function openDemoWsManagerWindow(): Promise<void> {
|
|||||||
console.error("open_demo_ws_manager_window failed:", error);
|
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 () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
void takeoverConsole();
|
void takeoverConsole();
|
||||||
|
|
||||||
|
debug("main window loaded");
|
||||||
|
|
||||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||||
if (sidebarToggle) {
|
if (sidebarToggle) {
|
||||||
// restaurer l’état depuis localStorage
|
// restaurer l’état depuis localStorage
|
||||||
@@ -74,6 +85,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const openDemoHttpButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoHttpButtonSecondary");
|
const openDemoHttpButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoHttpButtonSecondary");
|
||||||
const openDemoWsManagerButton = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButton");
|
const openDemoWsManagerButton = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButton");
|
||||||
const openDemoWsManagerButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButtonSecondary");
|
const openDemoWsManagerButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButtonSecondary");
|
||||||
|
const openDemoPipelineButton = document.querySelector<HTMLButtonElement>("#openDemoPipelineButton");
|
||||||
|
const openDemoPipelineButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoPipelineButtonSecondary");
|
||||||
|
|
||||||
if (openDemoWsButton) {
|
if (openDemoWsButton) {
|
||||||
openDemoWsButton.addEventListener("click", () => {
|
openDemoWsButton.addEventListener("click", () => {
|
||||||
@@ -111,6 +124,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("window loaded");
|
if (openDemoPipelineButton) {
|
||||||
|
openDemoPipelineButton.addEventListener("click", () => {
|
||||||
|
void openDemoPipelineWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openDemoPipelineButtonSecondary) {
|
||||||
|
openDemoPipelineButtonSecondary.addEventListener("click", () => {
|
||||||
|
void openDemoPipelineWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -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"]}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kb-app",
|
"name": "kb-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.6.6",
|
"version": "0.7.22",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
1494
kb_app/src/demo_pipeline.rs
Normal file
1494
kb_app/src/demo_pipeline.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
mod demo_http;
|
mod demo_http;
|
||||||
|
mod demo_pipeline;
|
||||||
mod demo_ws;
|
mod demo_ws;
|
||||||
mod demo_ws_manager;
|
mod demo_ws_manager;
|
||||||
mod splash;
|
mod splash;
|
||||||
@@ -36,16 +37,18 @@ impl KbWsRuntimeState {
|
|||||||
/// Shared application state stored inside Tauri.
|
/// Shared application state stored inside Tauri.
|
||||||
struct KbAppState {
|
struct KbAppState {
|
||||||
config: kb_lib::KbConfig,
|
config: kb_lib::KbConfig,
|
||||||
|
database: std::sync::Arc<kb_lib::KbDatabase>,
|
||||||
ws_runtime: tokio::sync::Mutex<KbWsRuntimeState>,
|
ws_runtime: tokio::sync::Mutex<KbWsRuntimeState>,
|
||||||
demo_ws_runtime: std::sync::Arc<tokio::sync::Mutex<crate::demo_ws::KbDemoWsRuntimeState>>,
|
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>,
|
ws_manager: std::sync::Arc<kb_lib::WsManager>,
|
||||||
http_pool: kb_lib::HttpEndpointPool,
|
http_pool: kb_lib::HttpEndpointPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the desktop application.
|
/// Runs the desktop application.
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[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_path = kb_lib::KbConfig::default_path();
|
||||||
let config_result = kb_lib::KbConfig::load_from_path(&config_path);
|
let config_result = kb_lib::KbConfig::load_from_path(&config_path);
|
||||||
let config = match config_result {
|
let config = match config_result {
|
||||||
@@ -56,20 +59,20 @@ pub fn run() {
|
|||||||
config_path.display(),
|
config_path.display(),
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
return;
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let prepare_result = config.prepare_filesystem();
|
let prepare_result = config.prepare_filesystem();
|
||||||
if let Err(error) = prepare_result {
|
if let Err(error) = prepare_result {
|
||||||
eprintln!("kb_app filesystem preparation error: {error}");
|
eprintln!("kb_app filesystem preparation error: {error}");
|
||||||
return;
|
return Err(error);
|
||||||
}
|
}
|
||||||
let tracing_guard_result = kb_lib::init_tracing(&config.logging);
|
let tracing_guard_result = kb_lib::init_tracing(&config.logging);
|
||||||
let _tracing_guard = match tracing_guard_result {
|
let _tracing_guard = match tracing_guard_result {
|
||||||
Ok(guard) => guard,
|
Ok(guard) => guard,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("kb_app tracing initialization error: {error}");
|
eprintln!("kb_app tracing initialization error: {error}");
|
||||||
return;
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -77,6 +80,11 @@ pub fn run() {
|
|||||||
environment = %config.app.environment,
|
environment = %config.app.environment,
|
||||||
"starting desktop application"
|
"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_result = kb_lib::HttpEndpointPool::from_config(&config);
|
||||||
let http_pool = match http_pool_result {
|
let http_pool = match http_pool_result {
|
||||||
Ok(http_pool) => http_pool,
|
Ok(http_pool) => http_pool,
|
||||||
@@ -95,6 +103,7 @@ pub fn run() {
|
|||||||
};
|
};
|
||||||
let app_state = KbAppState {
|
let app_state = KbAppState {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
|
database: std::sync::Arc::new(database),
|
||||||
ws_runtime: tokio::sync::Mutex::new(KbWsRuntimeState::new()),
|
ws_runtime: tokio::sync::Mutex::new(KbWsRuntimeState::new()),
|
||||||
demo_ws_runtime: std::sync::Arc::new(tokio::sync::Mutex::new(
|
demo_ws_runtime: std::sync::Arc::new(tokio::sync::Mutex::new(
|
||||||
crate::demo_ws::KbDemoWsRuntimeState::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_stop_all,
|
||||||
crate::demo_ws_manager::demo_ws_manager_start_role,
|
crate::demo_ws_manager::demo_ws_manager_start_role,
|
||||||
crate::demo_ws_manager::demo_ws_manager_stop_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.plugin(tracing_builder.build::<tauri::Wry>());
|
||||||
tauri_builder = tauri_builder.setup(|app| {
|
tauri_builder = tauri_builder.setup(|app| {
|
||||||
@@ -202,7 +216,11 @@ pub fn run() {
|
|||||||
let run_result = tauri_builder.run(tauri::generate_context!());
|
let run_result = tauri_builder.run(tauri::generate_context!());
|
||||||
if let Err(error) = run_result {
|
if let Err(error) = run_result {
|
||||||
tracing::error!("error while running tauri application: {error:?}");
|
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(
|
fn emit_splash_order(
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ use fs2::FileExt;
|
|||||||
|
|
||||||
/// Entrypoint of the kb app binary.
|
/// Entrypoint of the kb app binary.
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async
|
async fn main() -> std::process::ExitCode {
|
||||||
fn main() -> std::process::ExitCode
|
|
||||||
{
|
|
||||||
let mut lock_path = std::env::temp_dir();
|
let mut lock_path = std::env::temp_dir();
|
||||||
lock_path.push("com_khadhroony_solana_rust.lock");
|
lock_path.push("com_khadhroony_solana_rust.lock");
|
||||||
let lock_file = match std::fs::File::create(lock_path) {
|
let lock_file = match std::fs::File::create(lock_path) {
|
||||||
@@ -24,7 +22,7 @@ fn main() -> std::process::ExitCode
|
|||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
eprintln!("Cannot create lock!");
|
eprintln!("Cannot create lock!");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
// trying to aquire an exclusive lock
|
// trying to aquire an exclusive lock
|
||||||
if lock_file.try_lock_exclusive().is_err() {
|
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() {
|
if rustls::crypto::CryptoProvider::get_default().is_none() {
|
||||||
let provider_result = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
let provider_result = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
match provider_result {
|
match provider_result {
|
||||||
Ok(()) => {},
|
Ok(()) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("kb_app rustls provider init error: {:?}", error);
|
eprintln!("kb_app rustls provider init error: {:?}", error);
|
||||||
return std::process::ExitCode::FAILURE;
|
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
|
std::process::ExitCode::SUCCESS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "kb-bapp",
|
"productName": "kb-bapp",
|
||||||
"version": "0.6.6",
|
"version": "0.7.22",
|
||||||
"identifier": "com.sasedev.kb-app",
|
"identifier": "com.sasedev.kb-app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
@@ -78,6 +78,20 @@
|
|||||||
"create": false,
|
"create": false,
|
||||||
"transparent": false,
|
"transparent": false,
|
||||||
"decorations": true
|
"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": {
|
"security": {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ impl KbConfig {
|
|||||||
wallets_directory.display()
|
wallets_directory.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let sqlite_path = self.data.sqlite_path_buf();
|
let sqlite_path = self.database.sqlite.path_buf();
|
||||||
let sqlite_parent_option = sqlite_path.parent();
|
let sqlite_parent_option = sqlite_path.parent();
|
||||||
if let Some(sqlite_parent) = sqlite_parent_option {
|
if let Some(sqlite_parent) = sqlite_parent_option {
|
||||||
if !sqlite_parent.as_os_str().is_empty() {
|
if !sqlite_parent.as_os_str().is_empty() {
|
||||||
@@ -509,6 +509,13 @@ pub struct KbSqliteDatabaseConfig {
|
|||||||
pub use_wal: bool,
|
pub use_wal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KbSqliteDatabaseConfig {
|
||||||
|
/// Returns the resolved SQLite database path.
|
||||||
|
pub fn path_buf(&self) -> std::path::PathBuf {
|
||||||
|
kb_resolve_workspace_relative_path(&self.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Database configuration.
|
/// Database configuration.
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ pub(crate) fn sqlite_database_url_from_config(
|
|||||||
"database.sqlite.path must not be empty".to_string(),
|
"database.sqlite.path must not be empty".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(format!("sqlite://{}", path))
|
let database_path = config.sqlite.path_buf();
|
||||||
|
Ok(format!("sqlite://{}", database_path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a SQLite pool according to configuration.
|
/// Opens a SQLite pool according to configuration.
|
||||||
@@ -30,7 +31,7 @@ pub(crate) async fn connect_sqlite(
|
|||||||
"database.sqlite.max_connections must be > 0".to_string(),
|
"database.sqlite.max_connections must be > 0".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let database_path = std::path::Path::new(path);
|
let database_path = config.sqlite.path_buf();
|
||||||
let parent_option = database_path.parent();
|
let parent_option = database_path.parent();
|
||||||
if let Some(parent) = parent_option {
|
if let Some(parent) = parent_option {
|
||||||
if !parent.as_os_str().is_empty() {
|
if !parent.as_os_str().is_empty() {
|
||||||
@@ -45,7 +46,7 @@ pub(crate) async fn connect_sqlite(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut connect_options = sqlx::sqlite::SqliteConnectOptions::new()
|
let mut connect_options = sqlx::sqlite::SqliteConnectOptions::new()
|
||||||
.filename(database_path)
|
.filename(&database_path)
|
||||||
.create_if_missing(config.sqlite.create_if_missing)
|
.create_if_missing(config.sqlite.create_if_missing)
|
||||||
.foreign_keys(true)
|
.foreign_keys(true)
|
||||||
.busy_timeout(std::time::Duration::from_millis(
|
.busy_timeout(std::time::Duration::from_millis(
|
||||||
@@ -61,7 +62,7 @@ pub(crate) async fn connect_sqlite(
|
|||||||
Ok(pool) => Ok(pool),
|
Ok(pool) => Ok(pool),
|
||||||
Err(error) => Err(crate::KbError::Db(format!(
|
Err(error) => Err(crate::KbError::Db(format!(
|
||||||
"cannot open sqlite database '{}': {}",
|
"cannot open sqlite database '{}': {}",
|
||||||
path, error
|
database_path.display(), error
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user