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 -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 3 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
@@ -22,6 +22,7 @@
|
||||
Démos
|
||||
</button>
|
||||
<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>
|
||||
</ul>
|
||||
</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
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
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");
|
||||
if (openDemoBackfillLink) {
|
||||
openDemoBackfillLink.addEventListener("click", event => {
|
||||
|
||||
Reference in New Issue
Block a user