v0.1.0-pre.048
This commit is contained in:
96
kb-app-demo-desktop/frontend/demo_http.html
Normal file
96
kb-app-demo-desktop/frontend/demo_http.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!-- file: kb-app-demo-desktop/frontend/demo_http.html -->
|
||||||
|
<!-- version: 3 -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Khadhroony Bot3 - HTTP JSON-RPC</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">
|
||||||
|
<div class="navbar-brand d-flex align-items-center">
|
||||||
|
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
|
||||||
|
<span class="ps-2 fs-4 fw-bold text-primary font-logo">HTTP JSON-RPC</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="app-main">
|
||||||
|
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
<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>
|
||||||
|
<p class="text-body-secondary">Cette démo route une requête JSON-RPC HTTP par rôle d'endpoint.</p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="httpRoleSelect">Rôle</label>
|
||||||
|
<select id="httpRoleSelect" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="httpMethodSelect">Méthode</label>
|
||||||
|
<select id="httpMethodSelect" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="httpFirstArgInput">Premier argument</label>
|
||||||
|
<input id="httpFirstArgInput" class="form-control" placeholder="Signature, pubkey ou transaction encodée" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="httpConfigInput">Config JSON</label>
|
||||||
|
<textarea id="httpConfigInput" class="form-control font-monospace" rows="4" placeholder='{"commitment":"confirmed"}'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="httpParamsInput">Params JSON complet</label>
|
||||||
|
<textarea id="httpParamsInput" class="form-control font-monospace" rows="4" placeholder='["pubkey", {"encoding":"jsonParsed"}]'></textarea>
|
||||||
|
<div class="form-text">Si renseigné, ce tableau JSON remplace le premier argument et la config JSON.</div>
|
||||||
|
</div>
|
||||||
|
<button id="executeHttpButton" class="btn btn-primary">Exécuter</button>
|
||||||
|
<button id="refreshHttpPoolButton" class="btn btn-outline-secondary ms-2">Rafraîchir endpoints</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-xl-7">
|
||||||
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h2 class="h5 card-title mb-0">Endpoints HTTP</h2>
|
||||||
|
<button id="copyHttpPoolButton" class="btn btn-sm btn-outline-secondary">Copier</button>
|
||||||
|
</div>
|
||||||
|
<textarea id="httpPoolOutput" class="form-control font-monospace" rows="10" readonly>Chargement...</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h2 class="h5 card-title mb-0">Résultat</h2>
|
||||||
|
<button id="copyHttpResultButton" class="btn btn-sm btn-outline-secondary">Copier</button>
|
||||||
|
</div>
|
||||||
|
<textarea id="httpResultOutput" class="form-control font-monospace" rows="18" readonly>Aucune requête exécutée.</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 text-center text-small my-1 my-md-0">© 2026 SASEDEV</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module" src="ts/demo_http.ts" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: kb-app-demo-desktop/frontend/main.html -->
|
<!-- file: kb-app-demo-desktop/frontend/main.html -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
Démos
|
Démos
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a id="openDemoHttpLink" class="dropdown-item" href="#">HTTP JSON-RPC</a></li>
|
||||||
<li><a id="openDemoBackfillLink" class="dropdown-item" href="#">Backfill HTTP</a></li>
|
<li><a id="openDemoBackfillLink" class="dropdown-item" href="#">Backfill HTTP</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
181
kb-app-demo-desktop/frontend/ts/demo_http.ts
Normal file
181
kb-app-demo-desktop/frontend/ts/demo_http.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
// file: kb-app-demo-desktop/frontend/ts/demo_http.ts
|
||||||
|
// version: 4
|
||||||
|
|
||||||
|
import * as bootstrap from "bootstrap";
|
||||||
|
import "simplebar";
|
||||||
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
|
||||||
|
import type { DemoHttpExecutionPayload } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpExecutionPayload.ts";
|
||||||
|
import type { DemoHttpMethodOption } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpMethodOption.ts";
|
||||||
|
import type { DemoHttpOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpOptionsPayload.ts";
|
||||||
|
import type { DemoHttpRequest } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpRequest.ts";
|
||||||
|
import type { DemoHttpRoleOption } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpRoleOption.ts";
|
||||||
|
|
||||||
|
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||||
|
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||||
|
|
||||||
|
let roleOptions: DemoHttpRoleOption[] = [];
|
||||||
|
let methodOptions: DemoHttpMethodOption[] = [];
|
||||||
|
|
||||||
|
function textInputValue(selector: string): string {
|
||||||
|
const element = document.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(selector);
|
||||||
|
return element ? element.value.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTextarea(selector: string, value: string): void {
|
||||||
|
const element = document.querySelector<HTMLTextAreaElement>(selector);
|
||||||
|
if (element) {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonText(value: unknown): string {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return JSON.stringify(value, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function roleSupportsMethod(role: DemoHttpRoleOption, method: DemoHttpMethodOption): boolean {
|
||||||
|
return role.requestKinds.includes("*") || role.requestKinds.includes(method.requestKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedRole(): DemoHttpRoleOption | null {
|
||||||
|
const role = textInputValue("#httpRoleSelect");
|
||||||
|
return roleOptions.find(option => option.role === role) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateSelect(selector: string, values: Array<{ value: string; label: string }>): void {
|
||||||
|
const select = document.querySelector<HTMLSelectElement>(selector);
|
||||||
|
if (!select) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
select.textContent = "";
|
||||||
|
for (const item of values) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = item.value;
|
||||||
|
option.textContent = item.label;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshMethodList(): void {
|
||||||
|
const role = selectedRole();
|
||||||
|
const filtered = role ? methodOptions.filter(method => roleSupportsMethod(role, method)) : methodOptions;
|
||||||
|
populateSelect("#httpMethodSelect", filtered.map(method => ({
|
||||||
|
value: method.method,
|
||||||
|
label: `${method.method} — ${method.label}`,
|
||||||
|
})));
|
||||||
|
refreshMethodFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshMethodFields(): void {
|
||||||
|
const methodName = textInputValue("#httpMethodSelect");
|
||||||
|
const method = methodOptions.find(option => option.method === methodName) ?? null;
|
||||||
|
const firstArg = document.querySelector<HTMLInputElement>("#httpFirstArgInput");
|
||||||
|
const config = document.querySelector<HTMLTextAreaElement>("#httpConfigInput");
|
||||||
|
const params = document.querySelector<HTMLTextAreaElement>("#httpParamsInput");
|
||||||
|
if (firstArg) {
|
||||||
|
firstArg.disabled = method ? !method.requiresFirstArg : false;
|
||||||
|
firstArg.placeholder = method && method.requiresFirstArg ? "Argument requis" : "Non requis pour cette méthode";
|
||||||
|
}
|
||||||
|
if (config) {
|
||||||
|
config.disabled = method ? !method.supportsConfigJson : false;
|
||||||
|
}
|
||||||
|
if (params) {
|
||||||
|
params.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): string {
|
||||||
|
const header = {
|
||||||
|
endpointName: payload.endpointName,
|
||||||
|
provider: payload.provider,
|
||||||
|
endpointUrl: payload.endpointUrl,
|
||||||
|
role: payload.role,
|
||||||
|
method: payload.method,
|
||||||
|
requestKind: payload.requestKind,
|
||||||
|
methodClass: payload.methodClass,
|
||||||
|
};
|
||||||
|
return `${JSON.stringify(header, null, 2)}\n\n--- response ---\n${payload.responseJson}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyTextarea(selector: string): Promise<void> {
|
||||||
|
const element = document.querySelector<HTMLTextAreaElement>(selector);
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await navigator.clipboard.writeText(element.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshHttpOptions(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const options = await invoke<DemoHttpOptionsPayload>("demo_http_options");
|
||||||
|
roleOptions = options.roles;
|
||||||
|
methodOptions = options.methods;
|
||||||
|
populateSelect("#httpRoleSelect", roleOptions.map(role => ({
|
||||||
|
value: role.role,
|
||||||
|
label: role.role,
|
||||||
|
})));
|
||||||
|
refreshMethodList();
|
||||||
|
} catch (caughtError) {
|
||||||
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||||
|
writeTextarea("#httpResultOutput", `Erreur options HTTP : ${message}`);
|
||||||
|
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP options loading failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshHttpPool(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const snapshots = await invoke("demo_http_list_pool_clients");
|
||||||
|
writeTextarea("#httpPoolOutput", jsonText(snapshots));
|
||||||
|
} catch (caughtError) {
|
||||||
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||||
|
writeTextarea("#httpPoolOutput", `Erreur : ${message}`);
|
||||||
|
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP pool refresh failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeHttpRequest(): Promise<void> {
|
||||||
|
const request: DemoHttpRequest = {
|
||||||
|
role: textInputValue("#httpRoleSelect"),
|
||||||
|
method: textInputValue("#httpMethodSelect"),
|
||||||
|
firstArg: textInputValue("#httpFirstArgInput") || null,
|
||||||
|
configJson: textInputValue("#httpConfigInput") || null,
|
||||||
|
paramsJson: textInputValue("#httpParamsInput") || null,
|
||||||
|
};
|
||||||
|
writeTextarea("#httpResultOutput", "Exécution en cours...");
|
||||||
|
try {
|
||||||
|
const response = await invoke<DemoHttpExecutionPayload>("demo_http_execute_request", { request });
|
||||||
|
writeTextarea("#httpResultOutput", formatHttpExecutionPayload(response));
|
||||||
|
frontendDebug("kb-app-demo-desktop.frontend.demo_http", `HTTP request completed: ${response.method}`);
|
||||||
|
} catch (caughtError) {
|
||||||
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||||
|
writeTextarea("#httpResultOutput", `Erreur : ${message}`);
|
||||||
|
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP request failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_http");
|
||||||
|
frontendDebug("kb-app-demo-desktop.frontend.demo_http", "HTTP demo window loaded");
|
||||||
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||||
|
document.querySelector<HTMLSelectElement>("#httpRoleSelect")?.addEventListener("change", refreshMethodList);
|
||||||
|
document.querySelector<HTMLSelectElement>("#httpMethodSelect")?.addEventListener("change", refreshMethodFields);
|
||||||
|
document.querySelector<HTMLButtonElement>("#executeHttpButton")?.addEventListener("click", () => {
|
||||||
|
void executeHttpRequest();
|
||||||
|
});
|
||||||
|
document.querySelector<HTMLButtonElement>("#refreshHttpPoolButton")?.addEventListener("click", () => {
|
||||||
|
void refreshHttpPool();
|
||||||
|
});
|
||||||
|
document.querySelector<HTMLButtonElement>("#copyHttpPoolButton")?.addEventListener("click", () => {
|
||||||
|
void copyTextarea("#httpPoolOutput");
|
||||||
|
});
|
||||||
|
document.querySelector<HTMLButtonElement>("#copyHttpResultButton")?.addEventListener("click", () => {
|
||||||
|
void copyTextarea("#httpResultOutput");
|
||||||
|
});
|
||||||
|
void refreshHttpOptions();
|
||||||
|
void refreshHttpPool();
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/frontend/ts/main.ts
|
// file: kb-app-demo-desktop/frontend/ts/main.ts
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
import * as bootstrap from "bootstrap";
|
import * as bootstrap from "bootstrap";
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from "markdown-it";
|
||||||
@@ -37,6 +37,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openDemoHttpLink = document.querySelector<HTMLAnchorElement>("#openDemoHttpLink");
|
||||||
|
if (openDemoHttpLink) {
|
||||||
|
openDemoHttpLink.addEventListener("click", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
void invoke("open_demo_http_window");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const openDemoBackfillLink = document.querySelector<HTMLAnchorElement>("#openDemoBackfillLink");
|
const openDemoBackfillLink = document.querySelector<HTMLAnchorElement>("#openDemoBackfillLink");
|
||||||
if (openDemoBackfillLink) {
|
if (openDemoBackfillLink) {
|
||||||
openDemoBackfillLink.addEventListener("click", event => {
|
openDemoBackfillLink.addEventListener("click", event => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/demo_backfill.rs
|
// file: kb-app-demo-desktop/src/demo_backfill.rs
|
||||||
// version: 10
|
// version: 11
|
||||||
|
|
||||||
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
|
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ pub(crate) struct DemoBackfillRoleOption {
|
|||||||
)]
|
)]
|
||||||
pub(crate) struct DemoBackfillOptionsPayload {
|
pub(crate) struct DemoBackfillOptionsPayload {
|
||||||
/// Selectable endpoint roles.
|
/// Selectable endpoint roles.
|
||||||
pub(crate) roles: std::vec::Vec<DemoBackfillRoleOption>,
|
pub(crate) roles: std::vec::Vec<crate::DemoBackfillRoleOption>,
|
||||||
/// Preferred role when configured.
|
/// Preferred role when configured.
|
||||||
pub(crate) default_role: std::option::Option<std::string::String>,
|
pub(crate) default_role: std::option::Option<std::string::String>,
|
||||||
/// Default transaction commitment.
|
/// Default transaction commitment.
|
||||||
@@ -213,7 +213,7 @@ impl std::ops::Drop for crate::DemoBackfillRunGuard<'_> {
|
|||||||
|
|
||||||
pub(crate) fn build_role_options(
|
pub(crate) fn build_role_options(
|
||||||
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
|
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
|
||||||
) -> std::vec::Vec<DemoBackfillRoleOption> {
|
) -> std::vec::Vec<crate::DemoBackfillRoleOption> {
|
||||||
let mut role_methods = std::collections::BTreeMap::<
|
let mut role_methods = std::collections::BTreeMap::<
|
||||||
std::string::String,
|
std::string::String,
|
||||||
(
|
(
|
||||||
@@ -242,7 +242,7 @@ pub(crate) fn build_role_options(
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
output.push(DemoBackfillRoleOption {
|
output.push(crate::DemoBackfillRoleOption {
|
||||||
role,
|
role,
|
||||||
providers: providers.into_iter().collect(),
|
providers: providers.into_iter().collect(),
|
||||||
});
|
});
|
||||||
|
|||||||
400
kb-app-demo-desktop/src/demo_http.rs
Normal file
400
kb-app-demo-desktop/src/demo_http.rs
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
// file: kb-app-demo-desktop/src/demo_http.rs
|
||||||
|
// version: 9
|
||||||
|
|
||||||
|
//! HTTP JSON-RPC demo commands.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: derive-import
|
||||||
|
|
||||||
|
/// One selectable role shown by the HTTP demo.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(
|
||||||
|
export,
|
||||||
|
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpRoleOption.ts"
|
||||||
|
)]
|
||||||
|
pub(crate) struct DemoHttpRoleOption {
|
||||||
|
/// Endpoint role code.
|
||||||
|
pub(crate) role: std::string::String,
|
||||||
|
/// Request kinds accepted by this role.
|
||||||
|
pub(crate) request_kinds: std::vec::Vec<std::string::String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One selectable HTTP JSON-RPC method shown by the HTTP demo.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(
|
||||||
|
export,
|
||||||
|
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpMethodOption.ts"
|
||||||
|
)]
|
||||||
|
pub(crate) struct DemoHttpMethodOption {
|
||||||
|
/// JSON-RPC method name.
|
||||||
|
pub(crate) method: std::string::String,
|
||||||
|
/// Derived request kind used for endpoint routing.
|
||||||
|
pub(crate) request_kind: std::string::String,
|
||||||
|
/// Human-readable method label.
|
||||||
|
pub(crate) label: std::string::String,
|
||||||
|
/// Whether this method needs the first argument field.
|
||||||
|
pub(crate) requires_first_arg: bool,
|
||||||
|
/// Whether this method supports an optional configuration object.
|
||||||
|
pub(crate) supports_config_json: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP demo options derived from configuration and local method presets.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(
|
||||||
|
export,
|
||||||
|
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpOptionsPayload.ts"
|
||||||
|
)]
|
||||||
|
pub(crate) struct DemoHttpOptionsPayload {
|
||||||
|
/// Selectable roles.
|
||||||
|
pub(crate) roles: std::vec::Vec<crate::DemoHttpRoleOption>,
|
||||||
|
/// Selectable methods.
|
||||||
|
pub(crate) methods: std::vec::Vec<crate::DemoHttpMethodOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request payload for one HTTP JSON-RPC demo execution.
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(
|
||||||
|
export,
|
||||||
|
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpRequest.ts"
|
||||||
|
)]
|
||||||
|
pub(crate) struct DemoHttpRequest {
|
||||||
|
/// Required endpoint role used by the HTTP pool.
|
||||||
|
pub(crate) role: std::string::String,
|
||||||
|
/// JSON-RPC method name.
|
||||||
|
pub(crate) method: std::string::String,
|
||||||
|
/// Optional first argument string.
|
||||||
|
pub(crate) first_arg: std::option::Option<std::string::String>,
|
||||||
|
/// Optional JSON configuration string appended after the first argument.
|
||||||
|
pub(crate) config_json: std::option::Option<std::string::String>,
|
||||||
|
/// Optional raw JSON-RPC params array. When present, it overrides firstArg and configJson.
|
||||||
|
pub(crate) params_json: std::option::Option<std::string::String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response payload for one HTTP JSON-RPC demo execution.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(
|
||||||
|
export,
|
||||||
|
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpExecutionPayload.ts"
|
||||||
|
)]
|
||||||
|
pub(crate) struct DemoHttpExecutionPayload {
|
||||||
|
/// Selected endpoint name.
|
||||||
|
pub(crate) endpoint_name: std::string::String,
|
||||||
|
/// Selected provider name.
|
||||||
|
pub(crate) provider: std::string::String,
|
||||||
|
/// Selected endpoint URL.
|
||||||
|
pub(crate) endpoint_url: std::string::String,
|
||||||
|
/// Required role used by the selection.
|
||||||
|
pub(crate) role: std::string::String,
|
||||||
|
/// JSON-RPC method name.
|
||||||
|
pub(crate) method: std::string::String,
|
||||||
|
/// Derived request kind.
|
||||||
|
pub(crate) request_kind: std::string::String,
|
||||||
|
/// Classified method family.
|
||||||
|
pub(crate) method_class: std::string::String,
|
||||||
|
/// Pretty JSON response text.
|
||||||
|
pub(crate) response_json: std::string::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn demo_http_execute_request_inner(
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
request: crate::DemoHttpRequest,
|
||||||
|
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
|
||||||
|
let role = request.role.trim().to_string();
|
||||||
|
if role.is_empty() {
|
||||||
|
return std::result::Result::Err("demo HTTP role must not be empty".to_string());
|
||||||
|
}
|
||||||
|
let method = request.method.trim().to_string();
|
||||||
|
if method.is_empty() {
|
||||||
|
return std::result::Result::Err("demo HTTP method must not be empty".to_string());
|
||||||
|
}
|
||||||
|
let params_json_value = match parse_optional_params_json(request.params_json) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let config_json_value = match parse_optional_json(request.config_json) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let params = match build_demo_http_params(
|
||||||
|
&method,
|
||||||
|
request.first_arg.as_deref(),
|
||||||
|
config_json_value,
|
||||||
|
params_json_value,
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(params) => params,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let selected_client = match state.http_pool().select_client_for_role_and_method(&role, &method)
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(client) => client,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||||
|
};
|
||||||
|
let response_value =
|
||||||
|
match selected_client.execute_json_rpc_result_raw(method.clone(), params).await {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||||
|
};
|
||||||
|
let response_json = match serde_json::to_string_pretty(&response_value) {
|
||||||
|
std::result::Result::Ok(text) => text,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||||
|
};
|
||||||
|
let method_class = kb_onchain_transport::HttpClient::classify_method(&method);
|
||||||
|
return std::result::Result::Ok(crate::DemoHttpExecutionPayload {
|
||||||
|
endpoint_name: selected_client.endpoint_name().to_string(),
|
||||||
|
provider: selected_client.provider().to_string(),
|
||||||
|
endpoint_url: selected_client.endpoint_url().to_string(),
|
||||||
|
role,
|
||||||
|
request_kind: kb_onchain_transport::request_kind_from_method(&method),
|
||||||
|
method,
|
||||||
|
method_class: method_class_to_string(method_class).to_string(),
|
||||||
|
response_json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_http_role_options(
|
||||||
|
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
|
||||||
|
) -> std::vec::Vec<crate::DemoHttpRoleOption> {
|
||||||
|
let mut roles = std::collections::BTreeMap::<
|
||||||
|
std::string::String,
|
||||||
|
std::collections::BTreeSet<std::string::String>,
|
||||||
|
>::new();
|
||||||
|
for snapshot in snapshots {
|
||||||
|
for role in snapshot.roles {
|
||||||
|
if !role.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let role_entry = roles.entry(role.role).or_default();
|
||||||
|
for request_kind in role.request_kinds {
|
||||||
|
role_entry.insert(request_kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut options = std::vec::Vec::new();
|
||||||
|
for (role, request_kinds) in roles {
|
||||||
|
options.push(crate::DemoHttpRoleOption {
|
||||||
|
role,
|
||||||
|
request_kinds: request_kinds.into_iter().collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> {
|
||||||
|
let methods = [
|
||||||
|
("getAccountInfo", "Compte Solana", true, true),
|
||||||
|
("getBalance", "Balance SOL", true, true),
|
||||||
|
("getBlock", "Bloc par slot", true, true),
|
||||||
|
("getBlockCommitment", "Commitment d’un bloc", true, false),
|
||||||
|
("getBlockHeight", "Hauteur de bloc", false, true),
|
||||||
|
("getBlockProduction", "Production de blocs", false, true),
|
||||||
|
("getBlocks", "Liste de blocs", true, true),
|
||||||
|
("getBlocksWithLimit", "Liste de blocs avec limite", true, true),
|
||||||
|
("getClusterNodes", "Nœuds du cluster", false, false),
|
||||||
|
("getEpochInfo", "Époque courante", false, true),
|
||||||
|
("getEpochSchedule", "Planning des époques", false, false),
|
||||||
|
("getFeeForMessage", "Frais pour message", true, true),
|
||||||
|
("getFirstAvailableBlock", "Premier bloc disponible", false, false),
|
||||||
|
("getGenesisHash", "Genesis hash", false, false),
|
||||||
|
("getHealth", "Santé du nœud", false, false),
|
||||||
|
("getHighestSnapshotSlot", "Dernier snapshot", false, false),
|
||||||
|
("getIdentity", "Identité du nœud", false, false),
|
||||||
|
("getInflationGovernor", "Gouverneur inflation", false, true),
|
||||||
|
("getInflationRate", "Taux d’inflation", false, false),
|
||||||
|
("getInflationReward", "Récompense inflation", true, true),
|
||||||
|
("getLargestAccounts", "Plus gros comptes", false, true),
|
||||||
|
("getLatestBlockhash", "Dernier blockhash", false, true),
|
||||||
|
("getLeaderSchedule", "Planning leaders", false, true),
|
||||||
|
("getMaxRetransmitSlot", "Slot retransmis maximum", false, false),
|
||||||
|
("getMaxShredInsertSlot", "Shred insert slot maximum", false, false),
|
||||||
|
("getMinimumBalanceForRentExemption", "Rent exemption", true, true),
|
||||||
|
("getMultipleAccounts", "Comptes multiples", true, true),
|
||||||
|
("getProgramAccounts", "Comptes de programme", true, true),
|
||||||
|
("getRecentPerformanceSamples", "Samples performance", false, true),
|
||||||
|
("getRecentPrioritizationFees", "Frais de priorité récents", false, true),
|
||||||
|
("getSignaturesForAddress", "Signatures par adresse", true, true),
|
||||||
|
("getSignatureStatuses", "Statuts signatures", true, true),
|
||||||
|
("getSlot", "Slot courant", false, true),
|
||||||
|
("getSlotLeader", "Leader du slot", false, true),
|
||||||
|
("getSlotLeaders", "Leaders de slots", true, true),
|
||||||
|
("getStakeActivation", "Activation stake", true, true),
|
||||||
|
("getStakeMinimumDelegation", "Minimum delegation", false, true),
|
||||||
|
("getSupply", "Supply SOL", false, true),
|
||||||
|
("getTokenAccountBalance", "Balance token account", true, true),
|
||||||
|
("getTokenAccountsByDelegate", "Token accounts par delegate", true, true),
|
||||||
|
("getTokenAccountsByOwner", "Token accounts par owner", true, true),
|
||||||
|
("getTokenLargestAccounts", "Plus gros comptes token", true, true),
|
||||||
|
("getTokenSupply", "Supply token", true, true),
|
||||||
|
("getTransaction", "Transaction", true, true),
|
||||||
|
("getTransactionCount", "Nombre de transactions", false, true),
|
||||||
|
("getVersion", "Version du nœud", false, false),
|
||||||
|
("getVoteAccounts", "Vote accounts", false, true),
|
||||||
|
("isBlockhashValid", "Validité blockhash", true, true),
|
||||||
|
("requestAirdrop", "Airdrop devnet", true, true),
|
||||||
|
("sendTransaction", "Envoi de transaction", true, true),
|
||||||
|
("simulateTransaction", "Simulation transaction", true, true),
|
||||||
|
];
|
||||||
|
let mut options = std::vec::Vec::new();
|
||||||
|
for (method, label, requires_first_arg, supports_config_json) in methods {
|
||||||
|
options.push(crate::DemoHttpMethodOption {
|
||||||
|
method: method.to_string(),
|
||||||
|
request_kind: kb_onchain_transport::request_kind_from_method(method),
|
||||||
|
label: label.to_string(),
|
||||||
|
requires_first_arg,
|
||||||
|
supports_config_json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_optional_json(
|
||||||
|
config_json: std::option::Option<std::string::String>,
|
||||||
|
) -> std::result::Result<std::option::Option<serde_json::Value>, std::string::String> {
|
||||||
|
let config_text = match config_json {
|
||||||
|
std::option::Option::Some(value) => value.trim().to_string(),
|
||||||
|
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||||
|
};
|
||||||
|
if config_text.is_empty() {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
}
|
||||||
|
let parse_result = serde_json::from_str::<serde_json::Value>(&config_text);
|
||||||
|
return match parse_result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
std::result::Result::Err(format!("invalid configJson: {error}"))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_optional_params_json(
|
||||||
|
params_json: std::option::Option<std::string::String>,
|
||||||
|
) -> std::result::Result<std::option::Option<std::vec::Vec<serde_json::Value>>, std::string::String>
|
||||||
|
{
|
||||||
|
let params_text = match params_json {
|
||||||
|
std::option::Option::Some(value) => value.trim().to_string(),
|
||||||
|
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||||
|
};
|
||||||
|
if params_text.is_empty() {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
}
|
||||||
|
let parse_result = serde_json::from_str::<serde_json::Value>(¶ms_text);
|
||||||
|
let value = match parse_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(format!("invalid paramsJson: {error}"));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let array = match value.as_array() {
|
||||||
|
std::option::Option::Some(array) => array.clone(),
|
||||||
|
std::option::Option::None => {
|
||||||
|
return std::result::Result::Err("paramsJson must be a JSON array".to_string());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(std::option::Option::Some(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_demo_http_params(
|
||||||
|
method: &str,
|
||||||
|
first_arg: std::option::Option<&str>,
|
||||||
|
config_json: std::option::Option<serde_json::Value>,
|
||||||
|
params_json: std::option::Option<std::vec::Vec<serde_json::Value>>,
|
||||||
|
) -> std::result::Result<std::vec::Vec<serde_json::Value>, std::string::String> {
|
||||||
|
if let std::option::Option::Some(params) = params_json {
|
||||||
|
return std::result::Result::Ok(params);
|
||||||
|
}
|
||||||
|
let needs_first_arg = method == "getBalance"
|
||||||
|
|| method == "getAccountInfo"
|
||||||
|
|| method == "getBlock"
|
||||||
|
|| method == "getBlockCommitment"
|
||||||
|
|| method == "getBlocks"
|
||||||
|
|| method == "getBlocksWithLimit"
|
||||||
|
|| method == "getFeeForMessage"
|
||||||
|
|| method == "getInflationReward"
|
||||||
|
|| method == "getMinimumBalanceForRentExemption"
|
||||||
|
|| method == "getMultipleAccounts"
|
||||||
|
|| method == "getProgramAccounts"
|
||||||
|
|| method == "getSignaturesForAddress"
|
||||||
|
|| method == "getSignatureStatuses"
|
||||||
|
|| method == "getSlotLeaders"
|
||||||
|
|| method == "getStakeActivation"
|
||||||
|
|| method == "getTokenAccountBalance"
|
||||||
|
|| method == "getTokenAccountsByDelegate"
|
||||||
|
|| method == "getTokenAccountsByOwner"
|
||||||
|
|| method == "getTokenLargestAccounts"
|
||||||
|
|| method == "getTokenSupply"
|
||||||
|
|| method == "getTransaction"
|
||||||
|
|| method == "isBlockhashValid"
|
||||||
|
|| method == "requestAirdrop"
|
||||||
|
|| method == "sendTransaction"
|
||||||
|
|| method == "simulateTransaction";
|
||||||
|
if needs_first_arg {
|
||||||
|
let first_arg_value = match first_arg {
|
||||||
|
std::option::Option::Some(value) => value.trim(),
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
if first_arg_value.is_empty() {
|
||||||
|
return std::result::Result::Err(format!("method '{method}' requires firstArg"));
|
||||||
|
}
|
||||||
|
let mut params = std::vec::Vec::new();
|
||||||
|
params.push(serde_json::Value::String(first_arg_value.to_string()));
|
||||||
|
if let std::option::Option::Some(config_value) = config_json {
|
||||||
|
params.push(config_value);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(params);
|
||||||
|
}
|
||||||
|
let mut params = std::vec::Vec::new();
|
||||||
|
if let std::option::Option::Some(config_value) = config_json {
|
||||||
|
params.push(config_value);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -> &'static str {
|
||||||
|
return match method_class {
|
||||||
|
kb_onchain_transport::HttpMethodClass::GeneralRpc => "GeneralRpc",
|
||||||
|
kb_onchain_transport::HttpMethodClass::SendTransaction => "SendTransaction",
|
||||||
|
kb_onchain_transport::HttpMethodClass::HeavyRead => "HeavyRead",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn raw_params_override_first_argument_and_config() {
|
||||||
|
let params = super::build_demo_http_params(
|
||||||
|
"getBalance",
|
||||||
|
std::option::Option::Some("ignored"),
|
||||||
|
std::option::Option::Some(serde_json::json!({"commitment":"confirmed"})),
|
||||||
|
std::option::Option::Some(vec![serde_json::json!("exact")]),
|
||||||
|
);
|
||||||
|
assert_eq!(params, std::result::Result::Ok(vec![serde_json::json!("exact")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_first_argument_fails_closed() {
|
||||||
|
let params = super::build_demo_http_params(
|
||||||
|
"getBalance",
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
);
|
||||||
|
assert!(params.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn method_inventory_is_unique_and_classified() {
|
||||||
|
let options = crate::build_http_method_options();
|
||||||
|
let mut names = std::collections::BTreeSet::new();
|
||||||
|
for option in &options {
|
||||||
|
assert!(names.insert(option.method.as_str()));
|
||||||
|
assert_eq!(
|
||||||
|
option.request_kind,
|
||||||
|
kb_onchain_transport::request_kind_from_method(option.method.as_str())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(!options.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/frontend_log.rs
|
// file: kb-app-demo-desktop/src/frontend_log.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Frontend logging bridge used by Tauri WebView scripts.
|
//! Frontend logging bridge used by Tauri WebView scripts.
|
||||||
|
|
||||||
@@ -49,6 +49,12 @@ fn normalize_frontend_target(target: &str) -> std::string::String {
|
|||||||
if trimmed == "kb-app-demo-desktop.frontend.splash" {
|
if trimmed == "kb-app-demo-desktop.frontend.splash" {
|
||||||
return trimmed.to_string();
|
return trimmed.to_string();
|
||||||
}
|
}
|
||||||
|
if trimmed == "kb-app-demo-desktop.frontend.backfill" {
|
||||||
|
return trimmed.to_string();
|
||||||
|
}
|
||||||
|
if trimmed == "kb-app-demo-desktop.frontend.demo_http" {
|
||||||
|
return trimmed.to_string();
|
||||||
|
}
|
||||||
return "kb-app-demo-desktop.frontend".to_string();
|
return "kb-app-demo-desktop.frontend".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,4 +75,15 @@ mod tests {
|
|||||||
"kb-app-demo-desktop.frontend.main"
|
"kb-app-demo-desktop.frontend.main"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn normalize_frontend_target_keeps_demo_targets() {
|
||||||
|
assert_eq!(
|
||||||
|
super::normalize_frontend_target("kb-app-demo-desktop.frontend.backfill"),
|
||||||
|
"kb-app-demo-desktop.frontend.backfill"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::normalize_frontend_target("kb-app-demo-desktop.frontend.demo_http"),
|
||||||
|
"kb-app-demo-desktop.frontend.demo_http"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/lib.rs
|
// file: kb-app-demo-desktop/src/lib.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
mod app_state;
|
mod app_state;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod demo_backfill;
|
mod demo_backfill;
|
||||||
|
mod demo_http;
|
||||||
mod frontend_log;
|
mod frontend_log;
|
||||||
mod main_window;
|
mod main_window;
|
||||||
mod splash;
|
mod splash;
|
||||||
@@ -40,6 +41,22 @@ pub(crate) use self::demo_backfill::build_demo_backfill_pipeline_request;
|
|||||||
pub(crate) use self::demo_backfill::build_role_options;
|
pub(crate) use self::demo_backfill::build_role_options;
|
||||||
/// Converts a pipeline summary to the UI-safe summary.
|
/// Converts a pipeline summary to the UI-safe summary.
|
||||||
pub(crate) use self::demo_backfill::demo_backfill_summary_payload;
|
pub(crate) use self::demo_backfill::demo_backfill_summary_payload;
|
||||||
|
/// HTTP demo response payload.
|
||||||
|
pub(crate) use self::demo_http::DemoHttpExecutionPayload;
|
||||||
|
/// One selectable HTTP JSON-RPC method.
|
||||||
|
pub(crate) use self::demo_http::DemoHttpMethodOption;
|
||||||
|
/// HTTP demo options payload.
|
||||||
|
pub(crate) use self::demo_http::DemoHttpOptionsPayload;
|
||||||
|
/// HTTP demo request payload.
|
||||||
|
pub(crate) use self::demo_http::DemoHttpRequest;
|
||||||
|
/// One selectable HTTP endpoint role.
|
||||||
|
pub(crate) use self::demo_http::DemoHttpRoleOption;
|
||||||
|
/// Builds the HTTP method inventory.
|
||||||
|
pub(crate) use self::demo_http::build_http_method_options;
|
||||||
|
/// Builds the HTTP role inventory from pool snapshots.
|
||||||
|
pub(crate) use self::demo_http::build_http_role_options;
|
||||||
|
/// Executes one raw HTTP JSON-RPC request.
|
||||||
|
pub(crate) use self::demo_http::demo_http_execute_request_inner;
|
||||||
/// Frontend logging payload.
|
/// Frontend logging payload.
|
||||||
pub(crate) use self::frontend_log::FrontendLogPayload;
|
pub(crate) use self::frontend_log::FrontendLogPayload;
|
||||||
/// Emits one normalized frontend log event.
|
/// Emits one normalized frontend log event.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/splash.rs
|
// file: kb-app-demo-desktop/src/splash.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Splash-window payloads and startup sequencing helpers.
|
//! Splash-window payloads and startup sequencing helpers.
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn splash_timing_is_ordered() {
|
fn splash_timing_is_ordered() {
|
||||||
assert!(crate::SPLASH_MINIMUM_MS > 0);
|
assert_eq!(crate::SPLASH_MINIMUM_MS, 3100);
|
||||||
assert!(crate::SPLASH_FADE_MS > 0);
|
assert_eq!(crate::SPLASH_FADE_MS, 3000);
|
||||||
assert!(crate::SPLASH_CLOSE_WAIT_MS >= u64::from(crate::SPLASH_FADE_MS));
|
assert!(crate::SPLASH_CLOSE_WAIT_MS >= u64::from(crate::SPLASH_FADE_MS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/tauri.rs
|
// file: kb-app-demo-desktop/src/tauri.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
//! Tauri runtime assembly and private command wrappers.
|
//! Tauri runtime assembly and private command wrappers.
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ pub fn run() -> kb_core::Result<()> {
|
|||||||
config_path = app_state.config_path(),
|
config_path = app_state.config_path(),
|
||||||
active_profile = app_state.active_profile().name.as_str(),
|
active_profile = app_state.active_profile().name.as_str(),
|
||||||
logging_routes = app_state.logging_route_count(),
|
logging_routes = app_state.logging_route_count(),
|
||||||
|
configured_profiles = app_state.app_config().profiles.len(),
|
||||||
"starting desktop demo application"
|
"starting desktop demo application"
|
||||||
);
|
);
|
||||||
let tracing_builder = tauri_plugin_tracing::Builder::new();
|
let tracing_builder = tauri_plugin_tracing::Builder::new();
|
||||||
@@ -40,6 +41,10 @@ pub fn run() -> kb_core::Result<()> {
|
|||||||
demo_backfill_options,
|
demo_backfill_options,
|
||||||
demo_backfill_execute,
|
demo_backfill_execute,
|
||||||
demo_backfill_cancel,
|
demo_backfill_cancel,
|
||||||
|
open_demo_http_window,
|
||||||
|
demo_http_list_pool_clients,
|
||||||
|
demo_http_options,
|
||||||
|
demo_http_execute_request,
|
||||||
]);
|
]);
|
||||||
builder = builder.plugin(tracing_builder.build::<tauri::Wry>());
|
builder = builder.plugin(tracing_builder.build::<tauri::Wry>());
|
||||||
builder = builder.setup(|app| {
|
builder = builder.setup(|app| {
|
||||||
@@ -63,6 +68,27 @@ pub fn run() -> kb_core::Result<()> {
|
|||||||
};
|
};
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let started_at = tokio::time::Instant::now();
|
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,
|
||||||
|
);
|
||||||
crate::emit_splash_order(
|
crate::emit_splash_order(
|
||||||
&splash_window,
|
&splash_window,
|
||||||
"fadein",
|
"fadein",
|
||||||
@@ -73,11 +99,27 @@ pub fn run() -> kb_core::Result<()> {
|
|||||||
crate::emit_splash_order(
|
crate::emit_splash_order(
|
||||||
&splash_window,
|
&splash_window,
|
||||||
"add_msg",
|
"add_msg",
|
||||||
std::option::Option::Some("Initialisation de Khadhroony Bot3..."),
|
std::option::Option::Some("Initialisation..."),
|
||||||
std::option::Option::Some("info"),
|
std::option::Option::Some("info"),
|
||||||
std::option::Option::None,
|
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;
|
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(
|
crate::emit_splash_order(
|
||||||
&splash_window,
|
&splash_window,
|
||||||
"fadeout",
|
"fadeout",
|
||||||
@@ -202,6 +244,7 @@ fn demo_backfill_options(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::question_mark_used)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn demo_backfill_execute(
|
async fn demo_backfill_execute(
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
@@ -259,3 +302,61 @@ async fn demo_backfill_execute(
|
|||||||
};
|
};
|
||||||
return std::result::Result::Ok(crate::demo_backfill_summary_payload(summary));
|
return std::result::Result::Ok(crate::demo_backfill_summary_payload(summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn open_demo_http_window(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
) -> std::result::Result<(), std::string::String> {
|
||||||
|
let existing_window = app_handle.get_webview_window("demo_http");
|
||||||
|
if let std::option::Option::Some(window) = existing_window {
|
||||||
|
if let std::result::Result::Err(error) = window.show() {
|
||||||
|
return std::result::Result::Err(error.to_string());
|
||||||
|
}
|
||||||
|
if let std::result::Result::Err(error) = window.set_focus() {
|
||||||
|
return std::result::Result::Err(error.to_string());
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
let build_result = tauri::WebviewWindowBuilder::new(
|
||||||
|
&app_handle,
|
||||||
|
"demo_http",
|
||||||
|
tauri::WebviewUrl::App("demo_http.html".into()),
|
||||||
|
)
|
||||||
|
.title("Khadhroony Bot3 - HTTP JSON-RPC")
|
||||||
|
.inner_size(1280.0, 860.0)
|
||||||
|
.min_inner_size(960.0, 620.0)
|
||||||
|
.resizable(true)
|
||||||
|
.visible(true)
|
||||||
|
.build();
|
||||||
|
return match build_result {
|
||||||
|
std::result::Result::Ok(window) => match window.set_focus() {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||||
|
},
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn demo_http_list_pool_clients(
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot> {
|
||||||
|
return state.http_pool().snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn demo_http_options(state: tauri::State<'_, crate::AppState>) -> crate::DemoHttpOptionsPayload {
|
||||||
|
return crate::DemoHttpOptionsPayload {
|
||||||
|
roles: crate::build_http_role_options(state.http_pool().snapshot()),
|
||||||
|
methods: crate::build_http_method_options(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::question_mark_used)]
|
||||||
|
#[tauri::command]
|
||||||
|
async fn demo_http_execute_request(
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
request: crate::DemoHttpRequest,
|
||||||
|
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
|
||||||
|
return crate::demo_http_execute_request_inner(state, request).await;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user