v0.1.0-pre.058
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_backfill.html -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<div aria-labelledby="programBackfillHeading" class="accordion-collapse collapse" data-bs-parent="#backfillAccordion" id="programBackfillCollapse">
|
||||
<div class="accordion-body">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-12"><label class="form-label" for="programAddressInput">Program ID</label><input class="form-control font-monospace" id="programAddressInput" /></div>
|
||||
<div class="col-12"><label class="form-label" for="programAddressInput">Program ID</label><input class="form-control font-monospace" id="programAddressInput" list="programAddressOptions" autocomplete="off" /><datalist id="programAddressOptions"></datalist></div>
|
||||
<div class="col-12"><label class="form-label" for="programAnchorInput">Signature d'ancrage</label><input class="form-control font-monospace" id="programAnchorInput" />
|
||||
<div class="form-text">Optionnelle pour « Avant, plus anciennes » : vide, la recherche commence aux transactions les plus récentes. Obligatoire pour « Après, plus récentes ».</div>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="row g-4 justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h1 class="h3 card-title">Extraction transactionnelle canonical vers core</h1>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="row g-4 justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 mb-3">Sélection bornée</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_http.html -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="row g-4 justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 card-title">Requête HTTP Solana standard</h1>
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
<div class="col-12 col-lg-4">
|
||||
<label for="programContainsInput" class="form-label">Program ID contient</label>
|
||||
<input id="programContainsInput" class="form-control font-monospace" type="text" list="knownProgramDatalist" autocomplete="off" />
|
||||
<datalist id="knownProgramDatalist"></datalist>
|
||||
</div>
|
||||
<div class="col-6 col-lg-1">
|
||||
<label for="programLimitInput" class="form-label">Limite</label>
|
||||
@@ -439,7 +440,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<datalist id="knownProgramDatalist"></datalist>
|
||||
<script type="module" src="ts/demo_sql_replay_candidates.ts" defer></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_ws.html -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 8 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="row g-4 justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 card-title">Souscription WebSocket Solana standard</h1>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb_app_demo/frontend/sass/splash.scss
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
@font-face {
|
||||
font-family: 'Dos Amazigh';
|
||||
@@ -66,6 +66,7 @@ body {
|
||||
width: 50%;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
@@ -77,17 +78,25 @@ body {
|
||||
|
||||
#messages-container {
|
||||
position: absolute;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 10px 10px 40px;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#debug-info::-webkit-scrollbar,
|
||||
#messages-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.splash-message {
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_backfill.ts
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -210,6 +210,17 @@ async function cancelBackfill(): Promise<void> {
|
||||
|
||||
async function loadOptions(): Promise<void> {
|
||||
const options = await invoke<DemoBackfillOptionsPayload>("demo_backfill_options");
|
||||
const optionsWithPrograms = options as DemoBackfillOptionsPayload & {
|
||||
programs: Array<{ code: string; programId: string }>;
|
||||
};
|
||||
const programOptions = element<HTMLDataListElement>("#programAddressOptions");
|
||||
programOptions.replaceChildren();
|
||||
for (const program of optionsWithPrograms.programs) {
|
||||
const option = document.createElement("option");
|
||||
option.value = program.programId;
|
||||
option.label = program.code;
|
||||
programOptions.append(option);
|
||||
}
|
||||
const roleSelect = element<HTMLSelectElement>("#backfillRoleSelect");
|
||||
roleSelect.replaceChildren();
|
||||
for (const role of options.roles) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/splash.ts
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import type { SplashOrder } from "./bindings/kb_app_demo_desktop/splash/SplashOrder.ts";
|
||||
|
||||
@@ -105,6 +106,7 @@ async function initializeSplash(): Promise<void> {
|
||||
await listen<SplashOrder>("splash", event => {
|
||||
void handleSplashOrder(event.payload);
|
||||
});
|
||||
await invoke("splash_frontend_ready");
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Shared Tauri application state and startup initialization.
|
||||
|
||||
@@ -23,6 +23,7 @@ pub(crate) struct AppState {
|
||||
demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>,
|
||||
demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
|
||||
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
|
||||
startup_sequence_started: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl crate::AppState {
|
||||
@@ -68,6 +69,7 @@ impl crate::AppState {
|
||||
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
|
||||
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
startup_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,6 +174,19 @@ impl crate::AppState {
|
||||
return &self.demo_execution_solana_core_cancel_requested;
|
||||
}
|
||||
|
||||
/// Starts the splash startup sequence at most once.
|
||||
pub(crate) fn begin_startup_sequence(&self) -> bool {
|
||||
return self
|
||||
.startup_sequence_started
|
||||
.compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
)
|
||||
.is_ok();
|
||||
}
|
||||
|
||||
/// Returns the number of logging routes held by the logging guard.
|
||||
pub(crate) fn logging_route_count(&self) -> usize {
|
||||
let lock_result = self.logging_guard.lock();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_backfill.rs
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
|
||||
|
||||
@@ -20,6 +20,20 @@ pub(crate) struct DemoBackfillRoleOption {
|
||||
pub(crate) providers: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// One known program suggested by the free-form Program ID field.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_backfill/DemoBackfillProgramOption.ts"
|
||||
)]
|
||||
pub(crate) struct DemoBackfillProgramOption {
|
||||
/// Stable program code.
|
||||
pub(crate) code: std::string::String,
|
||||
/// Canonical program identifier.
|
||||
pub(crate) program_id: std::string::String,
|
||||
}
|
||||
|
||||
/// Initial options shown by the backfill demo.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -30,6 +44,8 @@ pub(crate) struct DemoBackfillRoleOption {
|
||||
pub(crate) struct DemoBackfillOptionsPayload {
|
||||
/// Selectable endpoint roles.
|
||||
pub(crate) roles: std::vec::Vec<crate::DemoBackfillRoleOption>,
|
||||
/// Known programs offered as non-blocking autocomplete suggestions.
|
||||
pub(crate) programs: std::vec::Vec<crate::DemoBackfillProgramOption>,
|
||||
/// Preferred role when configured.
|
||||
pub(crate) default_role: std::option::Option<std::string::String>,
|
||||
/// Default transaction commitment.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
|
||||
// version: 28
|
||||
// version: 29
|
||||
|
||||
//! Tauri commands and UI payloads for contextual instruction decode replay.
|
||||
|
||||
@@ -720,22 +720,22 @@ mod tests {
|
||||
fn native_memo_token2022_elgamal_classic_token_and_ata_decoders_are_registered() {
|
||||
let decoders = crate::available_decoders();
|
||||
assert_eq!(decoders.len(), 7);
|
||||
let names = decoders
|
||||
let mut names = decoders
|
||||
.iter()
|
||||
.map(|decoder| return decoder.identity().name)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(
|
||||
names,
|
||||
std::vec![
|
||||
"solana_native_classifier".to_string(),
|
||||
"spl_associated_token_account".to_string(),
|
||||
"spl_elgamal_registry".to_string(),
|
||||
"spl_memo".to_string(),
|
||||
"spl_token".to_string(),
|
||||
"spl_token2022".to_string(),
|
||||
"metadata_metaplex_token_metadata".to_string(),
|
||||
]
|
||||
);
|
||||
names.sort();
|
||||
let mut expected_names = std::vec![
|
||||
"metadata_metaplex_token_metadata".to_string(),
|
||||
"solana_native_classifier".to_string(),
|
||||
"spl_associated_token_account".to_string(),
|
||||
"spl_elgamal_registry".to_string(),
|
||||
"spl_memo".to_string(),
|
||||
"spl_token".to_string(),
|
||||
"spl_token2022".to_string(),
|
||||
];
|
||||
expected_names.sort();
|
||||
assert_eq!(names, expected_names);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_common.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Shared SQL demo helpers and serializable payloads.
|
||||
|
||||
@@ -201,25 +201,23 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
|
||||
return;
|
||||
},
|
||||
};
|
||||
if !options.auto_initialize_schema {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"PostgreSQL schema auto-initialization disabled.",
|
||||
"info",
|
||||
false,
|
||||
);
|
||||
let masked_dsn = options.masked_dsn();
|
||||
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "PostgreSQL schema auto-initialization disabled");
|
||||
return;
|
||||
}
|
||||
let initialize_schema = options.auto_initialize_schema;
|
||||
let masked_dsn = options.masked_dsn();
|
||||
options.auto_initialize_schema = false;
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"Checking PostgreSQL raw/core/decode tables...",
|
||||
"Connexion PostgreSQL et vérification des tables raw/core/decode...",
|
||||
"info",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
if !initialize_schema {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"Auto-initialisation désactivée : vérification sans création de table.",
|
||||
"info",
|
||||
true,
|
||||
);
|
||||
}
|
||||
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "start PostgreSQL schema initialization");
|
||||
let store_result = kb_store::PostgresStore::connect(options).await;
|
||||
let store = match store_result {
|
||||
@@ -241,12 +239,14 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
|
||||
return;
|
||||
},
|
||||
};
|
||||
let initialize_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = initialize_result {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
|
||||
return;
|
||||
if initialize_schema {
|
||||
let initialize_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = initialize_result {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
let after_result = store.known_table_diagnostics().await;
|
||||
let after_tables = match after_result {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_ws.rs
|
||||
// version: 14
|
||||
// version: 16
|
||||
|
||||
//! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`.
|
||||
|
||||
@@ -431,17 +431,26 @@ fn spawn_demo_ws_event_bridge(
|
||||
};
|
||||
match event {
|
||||
kb_onchain_transport::WsSessionEvent::Notification { notification, .. } => {
|
||||
let compact_payload = match serde_json::to_string(¬ification) {
|
||||
std::result::Result::Ok(payload) => payload,
|
||||
std::result::Result::Err(error) => {
|
||||
format!("cannot serialize typed WebSocket notification: {error}")
|
||||
},
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
payload = %compact_payload,
|
||||
"demo WebSocket notification received"
|
||||
);
|
||||
if rate_limiter.should_emit(&app_handle) {
|
||||
let payload = match serde_json::to_string_pretty(¬ification) {
|
||||
let ui_payload = match serde_json::to_string_pretty(¬ification) {
|
||||
std::result::Result::Ok(payload) => payload,
|
||||
std::result::Result::Err(error) => {
|
||||
format!("cannot serialize typed WebSocket notification: {error}")
|
||||
},
|
||||
std::result::Result::Err(_) => compact_payload,
|
||||
};
|
||||
emit_demo_ws_message(
|
||||
&app_handle,
|
||||
"notification",
|
||||
truncate_payload(payload),
|
||||
truncate_payload(ui_payload),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -640,20 +649,27 @@ fn build_standard_ws_request(
|
||||
std::result::Result::Ok(target) => target,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let config = match parse_optional_json_as::<
|
||||
let mut config = match parse_optional_json_as::<
|
||||
kb_onchain_transport::WsAccountSubscribeConfig,
|
||||
>(config_json, "configJson")
|
||||
{
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Ok(config) => config.unwrap_or_default(),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if config.encoding.is_none() {
|
||||
config.encoding =
|
||||
std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64);
|
||||
}
|
||||
if let std::result::Result::Err(error) =
|
||||
reject_present(filter_json, "filterJson", method)
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Account(
|
||||
kb_onchain_transport::AccountSubscribeRequest { pubkey: target, config },
|
||||
kb_onchain_transport::AccountSubscribeRequest {
|
||||
pubkey: target,
|
||||
config: std::option::Option::Some(config),
|
||||
},
|
||||
))
|
||||
},
|
||||
"blockSubscribe" => {
|
||||
@@ -710,15 +726,22 @@ fn build_standard_ws_request(
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let config = match parse_optional_json_as::<
|
||||
let mut config = match parse_optional_json_as::<
|
||||
kb_onchain_transport::WsProgramSubscribeConfig,
|
||||
>(config_json, "configJson")
|
||||
{
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Ok(config) => config.unwrap_or_default(),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if config.encoding.is_none() {
|
||||
config.encoding =
|
||||
std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64);
|
||||
}
|
||||
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Program(
|
||||
kb_onchain_transport::ProgramSubscribeRequest { program_id: target, config },
|
||||
kb_onchain_transport::ProgramSubscribeRequest {
|
||||
program_id: target,
|
||||
config: std::option::Option::Some(config),
|
||||
},
|
||||
))
|
||||
},
|
||||
"signatureSubscribe" => {
|
||||
@@ -879,6 +902,42 @@ fn truncate_payload(payload: std::string::String) -> std::string::String {
|
||||
mod tests {
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
#[test]
|
||||
fn account_and_program_subscriptions_default_to_base64_without_overriding_explicit_encoding() {
|
||||
let account = super::build_standard_ws_request(
|
||||
"accountSubscribe",
|
||||
std::option::Option::Some("11111111111111111111111111111111".to_string()),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let account = match account {
|
||||
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Account(value)) => {
|
||||
value
|
||||
},
|
||||
_ => panic!("accountSubscribe request was not built"),
|
||||
};
|
||||
assert_eq!(
|
||||
account.config.and_then(|value| return value.encoding),
|
||||
std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64)
|
||||
);
|
||||
let program = super::build_standard_ws_request(
|
||||
"programSubscribe",
|
||||
std::option::Option::Some(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string()),
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(r#"{"encoding":"jsonParsed"}"#.to_string()),
|
||||
);
|
||||
let program = match program {
|
||||
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Program(value)) => {
|
||||
value
|
||||
},
|
||||
_ => panic!("programSubscribe request was not built"),
|
||||
};
|
||||
assert_eq!(
|
||||
program.config.and_then(|value| return value.encoding),
|
||||
std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::JsonParsed)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tauri_numeric_bindings_use_json_compatible_numbers() {
|
||||
let config = ts_rs::Config::default();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -39,6 +39,8 @@ pub(crate) use self::app_state::AppState;
|
||||
pub(crate) use self::demo_backfill::DemoBackfillObserver;
|
||||
/// Initial options shown by the backfill window.
|
||||
pub(crate) use self::demo_backfill::DemoBackfillOptionsPayload;
|
||||
/// One known program suggested by the backfill Program ID autocomplete.
|
||||
pub(crate) use self::demo_backfill::DemoBackfillProgramOption;
|
||||
/// One progress event emitted to the backfill window.
|
||||
pub(crate) use self::demo_backfill::DemoBackfillProgressPayload;
|
||||
/// UI request for one bounded backfill campaign.
|
||||
@@ -327,6 +329,8 @@ pub(crate) use self::splash::SPLASH_MINIMUM_MS;
|
||||
pub(crate) use self::splash::SplashOrder;
|
||||
/// Emits one order to the splash frontend.
|
||||
pub(crate) use self::splash::emit_splash_order;
|
||||
/// Runs the startup sequence after the splash frontend is ready.
|
||||
pub(crate) use self::splash::splash_frontend_ready_inner;
|
||||
/// Waits for the minimum splash duration.
|
||||
pub(crate) use self::splash::wait_until_minimum;
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// file: kb-app-demo-desktop/src/splash.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Splash-window payloads and startup sequencing helpers.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Minimum splash duration before the main window is shown.
|
||||
pub(crate) const SPLASH_MINIMUM_MS: u64 = 3100;
|
||||
pub(crate) const SPLASH_MINIMUM_MS: u64 = 12100;
|
||||
/// Splash fade duration.
|
||||
pub(crate) const SPLASH_FADE_MS: u32 = 3000;
|
||||
pub(crate) const SPLASH_FADE_MS: u32 = 12000;
|
||||
/// Delay after fade-out before destroying the splash window.
|
||||
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 3100;
|
||||
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 12100;
|
||||
|
||||
/// Command sent from Rust to the splash frontend.
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
||||
@@ -50,6 +51,96 @@ pub(crate) fn emit_splash_order(
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the startup sequence after the splash frontend installed its listener.
|
||||
pub(crate) async fn splash_frontend_ready_inner(
|
||||
app: tauri::AppHandle,
|
||||
window: tauri::WebviewWindow,
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
if window.label() != "splash" {
|
||||
return std::result::Result::Err(
|
||||
"splash readiness may only originate from splash".to_string(),
|
||||
);
|
||||
}
|
||||
if !state.begin_startup_sequence() {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
let main_window = match app.get_webview_window("main") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("main window is missing".to_string());
|
||||
},
|
||||
};
|
||||
let started_at = tokio::time::Instant::now();
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"fadein",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Chargement de la configuration terminé."),
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_log",
|
||||
std::option::Option::Some(
|
||||
format!("Profil actif : {}", state.active_profile().name).as_str(),
|
||||
),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_msg",
|
||||
std::option::Option::Some(
|
||||
format!("Logging initialisé : {} routes actives.", state.logging_route_count())
|
||||
.as_str(),
|
||||
),
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Pool HTTP initialisé."),
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::initialize_postgres_schema_for_startup(state, &window).await;
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Initialisation terminée."),
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"fadeout",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
|
||||
if let std::result::Result::Err(error) = main_window.show() {
|
||||
return std::result::Result::Err(format!("cannot show main window: {error:?}"));
|
||||
}
|
||||
if let std::result::Result::Err(error) = main_window.set_focus() {
|
||||
return std::result::Result::Err(format!("cannot focus main window: {error:?}"));
|
||||
}
|
||||
if let std::result::Result::Err(error) = window.destroy() {
|
||||
return std::result::Result::Err(format!("cannot destroy splash window: {error:?}"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Waits until the configured minimum splash duration has elapsed.
|
||||
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
|
||||
let minimum = std::time::Duration::from_millis(minimum_ms);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 10
|
||||
// version: 12
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -36,6 +36,7 @@ pub fn run() -> kb_core::Result<()> {
|
||||
builder = builder.manage(app_state);
|
||||
builder = builder.invoke_handler(tauri::generate_handler![
|
||||
emit_frontend_log,
|
||||
splash_frontend_ready,
|
||||
load_project_readme,
|
||||
open_demo_backfill_window,
|
||||
demo_backfill_options,
|
||||
@@ -104,99 +105,18 @@ pub fn run() -> kb_core::Result<()> {
|
||||
});
|
||||
builder = builder.plugin(tracing_builder.build::<tauri::Wry>());
|
||||
builder = builder.setup(|app| {
|
||||
let splash_window = match app.get_webview_window("splash") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"splash window is missing",
|
||||
)));
|
||||
},
|
||||
};
|
||||
let main_window = match app.get_webview_window("main") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"main window is missing",
|
||||
)));
|
||||
},
|
||||
};
|
||||
let app_handle = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let started_at = tokio::time::Instant::now();
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_log",
|
||||
std::option::Option::Some("Configuration chargée"),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_log",
|
||||
std::option::Option::Some("Sous-système de logs initialisé"),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_log",
|
||||
std::option::Option::Some("Pool HTTP initialisé"),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let state = app_handle.state::<crate::AppState>();
|
||||
crate::initialize_postgres_schema_for_startup(&state, &splash_window).await;
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"fadein",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Initialisation..."),
|
||||
std::option::Option::Some("info"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Loading complete..."),
|
||||
std::option::Option::Some("success"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
||||
if cfg!(debug_assertions) {
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_log",
|
||||
std::option::Option::Some("Start Fade-out"),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"fadeout",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
|
||||
if let std::result::Result::Err(error) = splash_window.destroy() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot destroy splash window: {error:?}");
|
||||
}
|
||||
if let std::result::Result::Err(error) = main_window.show() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot show main window: {error:?}");
|
||||
}
|
||||
if let std::result::Result::Err(error) = main_window.set_focus() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot focus main window: {error:?}");
|
||||
}
|
||||
});
|
||||
if app.get_webview_window("splash").is_none() {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"splash window is missing",
|
||||
)));
|
||||
}
|
||||
if app.get_webview_window("main").is_none() {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"main window is missing",
|
||||
)));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
});
|
||||
let run_result = builder.run(tauri::generate_context!());
|
||||
@@ -208,6 +128,15 @@ pub fn run() -> kb_core::Result<()> {
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn splash_frontend_ready(
|
||||
app: tauri::AppHandle,
|
||||
window: tauri::WebviewWindow,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return crate::splash_frontend_ready_inner(app, window, &state).await;
|
||||
}
|
||||
|
||||
fn install_default_rustls_provider() -> kb_core::Result<()> {
|
||||
if rustls::crypto::CryptoProvider::get_default().is_some() {
|
||||
return std::result::Result::Ok(());
|
||||
@@ -759,8 +688,18 @@ fn demo_backfill_options(
|
||||
} else {
|
||||
roles.first().map(|item| return item.role.clone())
|
||||
};
|
||||
let programs = kb_program_ids::registered_program_ids()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return crate::DemoBackfillProgramOption {
|
||||
code: entry.code().to_string(),
|
||||
program_id: entry.program_id().to_string(),
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
return crate::DemoBackfillOptionsPayload {
|
||||
roles,
|
||||
programs,
|
||||
default_role,
|
||||
default_commitment: "confirmed".to_string(),
|
||||
default_page_size: 100,
|
||||
|
||||
Reference in New Issue
Block a user