v0.1.0-pre.061

This commit is contained in:
2026-07-28 18:41:30 +02:00
parent c7bad46f50
commit f40fb78817
527 changed files with 4598 additions and 4114 deletions

View File

@@ -252,19 +252,17 @@ function renderBackfillSummary(value: unknown): void {
}
container.textContent = "";
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("data", JSON.stringify(value ?? { status: "Aucun résultat" }, null, 2));
viewer.setAttribute("data", JSON.stringify(value ?? { status: "idle", message: "Aucune campagne exécutée." }, null, 2));
container.appendChild(viewer);
}
async function copyText(value: string): Promise<void> {
await navigator.clipboard.writeText(value);
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.backfill");
@@ -298,9 +296,5 @@ document.addEventListener("DOMContentLoaded", () => {
frontendError("kb-app-demo-desktop.frontend.backfill", `Options loading failed: ${message}`);
});
document.querySelector<HTMLButtonElement>("#copyBackfillSummaryButton")?.addEventListener("click", async () => {
const value = document.querySelector("#backfillSummaryOutput andypf-json-viewer")?.getAttribute("data") ?? "";
await copyText(value);
});
renderBackfillSummary(null);
});

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
// version: 5
// version: 6
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -143,6 +143,13 @@ async function loadOptions(): Promise<void> {
element<HTMLElement>("#coreExtractionVersionBadge").textContent = `Version ${options.processorVersion}`;
element<HTMLInputElement>("#coreExtractionLimitInput").value = String(options.defaultLimit);
element<HTMLInputElement>("#coreExtractionConcurrencyInput").value = String(options.defaultMaxConcurrentExtractions);
const programOptions = element<HTMLDataListElement>("#coreExtractionProgramIdOptions");
programOptions.replaceChildren(...options.programs.map(program => {
const option = document.createElement("option");
option.value = program.programId;
option.label = program.code;
return option;
}));
setRunning(options.running);
}

View File

@@ -1,10 +1,12 @@
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
// version: 6
// version: 7
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import DataTable, { type Api } from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import type { DemoDecodeDiagnosticsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts";
@@ -224,7 +226,11 @@ function shortened(value: string, visible: number): string {
return value.length > visible ? `${value.slice(0, visible)}` : value;
}
let annotationJournalTable: Api<DemoTransactionAnnotationRow> | null = null;
function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
annotationJournalTable?.destroy();
annotationJournalTable = null;
const body = element<HTMLTableSectionElement>("#annotationJournalBody");
body.replaceChildren();
for (const row of rows) {
@@ -241,6 +247,21 @@ function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
);
body.append(tableRow);
}
annotationJournalTable = new DataTable<DemoTransactionAnnotationRow>("#annotationJournalTable", {
autoWidth: false,
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
order: [[0, "desc"]],
scrollX: true,
language: {
emptyTable: "Aucune annotation.",
info: "_START_ à _END_ sur _TOTAL_ annotations",
infoEmpty: "0 annotation",
lengthMenu: "Afficher _MENU_ annotations",
search: "Filtrer :",
zeroRecords: "Aucune annotation correspondante.",
},
});
element<HTMLElement>("#annotationJournalStatus").textContent = `${rows.length} annotation(s) committed chargée(s).`;
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 3
// version: 4
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -25,9 +25,9 @@ import type { DemoSplAtaDerivationPayload } from "./bindings/kb_app_demo_desktop
import type { DemoSplAtaDerivationRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts";
import type { DemoSplAtaJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts";
import type { DemoSplAtaJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
@@ -37,7 +37,7 @@ const logLines: string[] = [];
const maximumLogLines = 1200;
let running = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
let token2022Fixture: DemoSplToken2022FixturePayload | null = null;
let token_2022Fixture: DemoSplToken2022FixturePayload | null = null;
function element<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
@@ -297,10 +297,18 @@ async function executeAta(submit: boolean): Promise<void> {
if (running) {
return;
}
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
const message = "La confirmation opérateur est obligatoire avant signature ATA.";
appendLog({
timestamp: new Date().toISOString(),
level: "error",
stage: "ata_validation",
message,
});
frontendError("kb-app-demo-desktop.frontend.execution_spl", message);
return;
}
try {
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
throw new Error("La confirmation opérateur est obligatoire avant signature ATA.");
}
const derivation = await deriveAta();
const request: DemoExecutionSplAtaRequest = {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
@@ -558,86 +566,86 @@ function renderTokenJournal(rows: DemoSplTokenJournalRow[]): void {
function token2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token2022_mint_to_checked") {
function token_2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token_2022_mint_to_checked") {
return "1";
}
if (scenarioId === "token2022_approve_checked") {
if (scenarioId === "token_2022_approve_checked") {
return "2";
}
if (scenarioId === "token2022_burn_checked") {
if (scenarioId === "token_2022_burn_checked") {
return "1";
}
return fixture.defaultAmountRaw;
}
function applyToken2022Fixture(): void {
if (!token2022Fixture) {
if (!token_2022Fixture) {
return;
}
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
element<HTMLInputElement>("#token2022SourceInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.closeAccount
: token2022Fixture.source;
element<HTMLInputElement>("#token2022MintInput").value = token2022Fixture.mint;
element<HTMLInputElement>("#token2022DestinationInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.authority
: token2022Fixture.destination;
element<HTMLInputElement>("#token2022DelegateInput").value = token2022Fixture.delegate;
element<HTMLInputElement>("#token2022AuthorityInput").value = token2022Fixture.authority;
element<HTMLInputElement>("#token2022FreezeAuthorityInput").value = token2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token2022AmountInput").value = token2022ScenarioAmount(token2022Fixture, scenarioId);
element<HTMLInputElement>("#token2022DecimalsInput").value = String(token2022Fixture.decimals);
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
element<HTMLInputElement>("#token_2022SourceInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.closeAccount
: token_2022Fixture.source;
element<HTMLInputElement>("#token_2022MintInput").value = token_2022Fixture.mint;
element<HTMLInputElement>("#token_2022DestinationInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.authority
: token_2022Fixture.destination;
element<HTMLInputElement>("#token_2022DelegateInput").value = token_2022Fixture.delegate;
element<HTMLInputElement>("#token_2022AuthorityInput").value = token_2022Fixture.authority;
element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value = token_2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token_2022AmountInput").value = token_2022ScenarioAmount(token_2022Fixture, scenarioId);
element<HTMLInputElement>("#token_2022DecimalsInput").value = String(token_2022Fixture.decimals);
}
async function loadToken2022Fixture(): Promise<void> {
try {
token2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token2022_fixture", {
token_2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token_2022_fixture", {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
});
applyToken2022Fixture();
element<HTMLElement>("#token2022FixtureHelp").textContent = `Fixture: ${token2022Fixture.fixturePath} — program=${token2022Fixture.programId}`;
element<HTMLElement>("#token_2022FixtureHelp").textContent = `Fixture: ${token_2022Fixture.fixturePath} — program=${token_2022Fixture.programId}`;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "token2022_fixture",
message: `fixture chargée: mint=${token2022Fixture.mint}, source=${token2022Fixture.source}, destination=${token2022Fixture.destination}`,
stage: "token_2022_fixture",
message: `fixture chargée: mint=${token_2022Fixture.mint}, source=${token_2022Fixture.source}, destination=${token_2022Fixture.destination}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
token2022Fixture = null;
element<HTMLElement>("#token2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_fixture", message });
token_2022Fixture = null;
element<HTMLElement>("#token_2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_fixture", message });
}
}
function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Request {
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token2022FreezeAuthorityInput").value.trim();
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token_2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token_2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token_2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token_2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token_2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value.trim();
if (source.length === 0) {
throw new Error("Le compte source ou cible Token-2022 est obligatoire.");
}
if (scenarioId !== "token2022_revoke" && scenarioId !== "token2022_close_destination" && mint.length === 0) {
if (scenarioId !== "token_2022_revoke" && scenarioId !== "token_2022_close_destination" && mint.length === 0) {
throw new Error("Le mint Token-2022 est obligatoire pour ce scénario.");
}
if (scenarioId === "token2022_transfer_checked" && destination.length === 0) {
if (scenarioId === "token_2022_transfer_checked" && destination.length === 0) {
throw new Error("Le compte destination Token-2022 est obligatoire.");
}
if (scenarioId === "token2022_close_destination" && destination.length === 0) {
if (scenarioId === "token_2022_close_destination" && destination.length === 0) {
throw new Error("La destination lamports est obligatoire pour CloseAccount.");
}
if (scenarioId === "token2022_approve_checked" && delegate.length === 0) {
if (scenarioId === "token_2022_approve_checked" && delegate.length === 0) {
throw new Error("Le delegate est obligatoire pour ApproveChecked.");
}
if (["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
if (["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
throw new Error("La freeze authority est obligatoire pour FreezeAccount et ThawAccount.");
}
if (!["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
if (!["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
throw new Error("Lautorité Token-2022 est obligatoire.");
}
return {
@@ -649,8 +657,8 @@ function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Reques
delegate,
authority,
freezeAuthority,
amountRaw: element<HTMLInputElement>("#token2022AmountInput").value.trim(),
decimals: integerValue("#token2022DecimalsInput"),
amountRaw: element<HTMLInputElement>("#token_2022AmountInput").value.trim(),
decimals: integerValue("#token_2022DecimalsInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
@@ -692,12 +700,12 @@ async function executeToken2022(submit: boolean): Promise<void> {
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur est obligatoire avant signature Token-2022.";
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
setRunning(true);
@@ -705,23 +713,23 @@ async function executeToken2022(submit: boolean): Promise<void> {
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "token2022_submit" : "token2022_simulation",
stage: submit ? "token_2022_submit" : "token_2022_simulation",
message: `${request.scenarioId}, source=${request.source}`,
});
try {
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token2022_execute", { request });
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token_2022_execute", { request });
displayToken2022Summary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "token2022_completed",
stage: "token_2022_completed",
message: `scenario=${summary.scenarioId}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token-2022 execution failed: ${message}`);
} finally {
setRunning(false);
@@ -841,7 +849,7 @@ document.addEventListener("DOMContentLoaded", () => {
element<HTMLButtonElement>("#loadToken2022FixtureButton").addEventListener("click", () => {
void loadToken2022Fixture();
});
element<HTMLSelectElement>("#token2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLSelectElement>("#token_2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLButtonElement>("#simulateToken2022Button").addEventListener("click", () => {
void executeToken2022(false);
});

View File

@@ -1,11 +1,12 @@
// file: kb-app-demo-desktop/frontend/ts/demo_http.ts
// version: 5
// version: 7
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 { renderJsonViewer } from "./json_viewer.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";
@@ -17,35 +18,27 @@ import type { DemoHttpRoleOption } from "./bindings/kb_app_demo_desktop/demo_htt
let roleOptions: DemoHttpRoleOption[] = [];
let methodOptions: DemoHttpMethodOption[] = [];
let httpResultBuffer = "Aucune requête exécutée.";
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 parseJsonValue(value: string): unknown {
try {
return JSON.parse(value) as unknown;
} catch {
return { raw: value };
}
}
function writeHttpResult(value: string): void {
httpResultBuffer = value;
writeTextarea("#httpResultOutput", httpResultBuffer);
function writeHttpResult(value: unknown): void {
renderJsonViewer("#httpResultOutput", value);
}
function clearHttpResult(): void {
httpResultBuffer = "";
writeTextarea("#httpResultOutput", httpResultBuffer);
}
function jsonText(value: unknown): string {
if (typeof value === "string") {
return value;
}
return JSON.stringify(value, null, 2);
writeHttpResult({ status: "idle", message: "Aucune requête exécutée." });
}
function roleSupportsMethod(role: DemoHttpRoleOption, method: DemoHttpMethodOption): boolean {
@@ -99,8 +92,8 @@ function refreshMethodFields(): void {
}
}
function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): string {
const header = {
function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): unknown {
return {
endpointName: payload.endpointName,
provider: payload.provider,
endpointUrl: payload.endpointUrl,
@@ -108,16 +101,8 @@ function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): string {
method: payload.method,
requestKind: payload.requestKind,
methodClass: payload.methodClass,
response: parseJsonValue(payload.responseJson),
};
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> {
@@ -132,7 +117,7 @@ async function refreshHttpOptions(): Promise<void> {
refreshMethodList();
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeHttpResult(`Erreur options HTTP : ${message}`);
writeHttpResult({ status: "error", stage: "options", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP options loading failed: ${message}`);
}
}
@@ -140,10 +125,10 @@ async function refreshHttpOptions(): Promise<void> {
async function refreshHttpPool(): Promise<void> {
try {
const snapshots = await invoke("demo_http_list_pool_clients");
writeTextarea("#httpPoolOutput", jsonText(snapshots));
renderJsonViewer("#httpPoolOutput", snapshots);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#httpPoolOutput", `Erreur : ${message}`);
renderJsonViewer("#httpPoolOutput", { status: "error", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP pool refresh failed: ${message}`);
}
}
@@ -156,14 +141,14 @@ async function executeHttpRequest(): Promise<void> {
configJson: textInputValue("#httpConfigInput") || null,
paramsJson: textInputValue("#httpParamsInput") || null,
};
writeHttpResult("Exécution en cours...");
writeHttpResult({ status: "running", message: "Exécution HTTP en cours." });
try {
const response = await invoke<DemoHttpExecutionPayload>("demo_http_execute_request", { request });
writeHttpResult(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);
writeHttpResult(`Erreur : ${message}`);
writeHttpResult({ status: "error", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP request failed: ${message}`);
}
}
@@ -171,6 +156,8 @@ async function executeHttpRequest(): Promise<void> {
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_http");
frontendDebug("kb-app-demo-desktop.frontend.demo_http", "HTTP demo window loaded");
renderJsonViewer("#httpPoolOutput", { status: "loading", message: "Chargement des endpoints HTTP." });
clearHttpResult();
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLSelectElement>("#httpRoleSelect")?.addEventListener("change", refreshMethodList);
@@ -181,12 +168,6 @@ document.addEventListener("DOMContentLoaded", () => {
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");
});
document.querySelector<HTMLButtonElement>("#clearHttpResultButton")?.addEventListener("click", clearHttpResult);
void refreshHttpOptions();
void refreshHttpPool();

View File

@@ -26,7 +26,7 @@ async function refreshDiagnostics(): Promise<void> {
setText("#sqlDiagHealth", payload.healthStatus);
setText("#sqlDiagMigration", payload.migrationStatus);
setText("#sqlDiagStatus", "OK");
renderTables("#sqlDiagTableBody", payload.tables);
renderTables("#sqlDiagTable", "#sqlDiagTableBody", payload.tables);
latestPayload = payload;
renderJsonViewer("#sqlDiagJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_diag", "SQL diagnostics refreshed");

View File

@@ -19,7 +19,7 @@ async function refreshCore(): Promise<void> {
setText("#sqlCoreProfile", payload.activeProfileName);
setText("#sqlCoreDsn", payload.maskedDsn);
setText("#sqlCoreStatus", "OK");
renderTables("#sqlCoreTableBody", payload.tables);
renderTables("#sqlCoreTable", "#sqlCoreTableBody", payload.tables);
renderJsonViewer("#sqlCoreJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_pg_core", "PostgreSQL core diagnostics refreshed");
} catch (caughtError) {

View File

@@ -19,7 +19,7 @@ async function refreshRaw(): Promise<void> {
setText("#sqlRawProfile", payload.activeProfileName);
setText("#sqlRawDsn", payload.maskedDsn);
setText("#sqlRawStatus", "OK");
renderTables("#sqlRawTableBody", payload.tables);
renderTables("#sqlRawTable", "#sqlRawTableBody", payload.tables);
renderJsonViewer("#sqlRawJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_pg_raw", "PostgreSQL raw diagnostics refreshed");
} catch (caughtError) {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_replay_candidates.ts
// version: 7
// version: 8
import * as bootstrap from "bootstrap";
import "simplebar";

View File

@@ -1,9 +1,13 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_tables.ts
// version: 2
// version: 4
import "@andypf/json-viewer";
import DataTable, { type Api } from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
import { jsonText, renderJsonViewer } from "./json_viewer.ts";
import type { DemoSqlTableSnapshot } from "./bindings/kb_app_demo_desktop/demo_sql/DemoSqlTableSnapshot";
const tableInstances = new Map<string, Api>();
export function setText(selector: string, value: string): void {
const element = document.querySelector<HTMLElement>(selector);
if (element) {
@@ -11,8 +15,13 @@ export function setText(selector: string, value: string): void {
}
}
export function renderTables(selector: string, tables: DemoSqlTableSnapshot[]): void {
const tbody = document.querySelector<HTMLTableSectionElement>(selector);
export function renderTables(tableSelector: string, bodySelector: string, tables: DemoSqlTableSnapshot[]): void {
const existing = tableInstances.get(tableSelector);
if (existing) {
existing.destroy();
tableInstances.delete(tableSelector);
}
const tbody = document.querySelector<HTMLTableSectionElement>(bodySelector);
if (!tbody) {
return;
}
@@ -29,32 +38,28 @@ export function renderTables(selector: string, tables: DemoSqlTableSnapshot[]):
row.appendChild(cell(table.latestCreatedAt ?? "—", false));
tbody.appendChild(row);
}
}
export function jsonText(value: unknown): string {
return JSON.stringify(value, null, 2);
}
export function renderJsonViewer(selector: string, value: unknown): void {
const container = document.querySelector<HTMLElement>(selector);
if (!container) {
const tableElement = document.querySelector<HTMLTableElement>(tableSelector);
if (!tableElement) {
return;
}
const viewer = document.createElement("andypf-json-viewer");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("expand-icon-type", "arrow");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("show-size", "true");
viewer.setAttribute("expand-empty", "false");
viewer.setAttribute("data", jsonText(value));
container.textContent = "";
container.appendChild(viewer);
tableInstances.set(tableSelector, new DataTable(tableElement, {
scrollX: true,
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
order: [[0, "asc"]],
language: {
emptyTable: "Aucune table disponible",
info: "Tables _START_ à _END_ sur _TOTAL_",
infoEmpty: "Aucune table",
lengthMenu: "Afficher _MENU_ tables",
search: "Rechercher :",
zeroRecords: "Aucune table correspondante",
},
}));
}
export { jsonText, renderJsonViewer };
function cell(value: string, code: boolean): HTMLTableCellElement {
const td = document.createElement("td");
if (code) {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_ws.ts
// version: 7
// version: 8
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -7,6 +7,7 @@ import ResizeObserver from "resize-observer-polyfill";
import {invoke} from "@tauri-apps/api/core";
import {listen} from "@tauri-apps/api/event";
import {frontendDebug, frontendError, installFrontendConsoleBridge} from "./frontend_log.ts";
import {renderJsonViewer} from "./json_viewer.ts";
import type {DemoWsExecutionPayload} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsExecutionPayload.ts";
import type {DemoWsMessagePayload} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsMessagePayload.ts";
import type {DemoWsMethodOption} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsMethodOption.ts";
@@ -81,13 +82,6 @@ function clearWsMessages(): void {
writeWsMessages("");
}
function jsonText(value: unknown): string {
if (typeof value === "string") {
return value;
}
return JSON.stringify(value, null, 2);
}
function roleSupportsMethod(role: DemoWsRoleOption, method: DemoWsMethodOption): boolean {
return role.requestKinds.includes("*") || role.requestKinds.includes(method.requestKind);
}
@@ -176,7 +170,7 @@ function setConnectedUi(status: DemoWsStatusPayload): void {
disconnectButton.disabled = !status.connected;
}
refreshSubscriptionSelect(status);
writeTextarea("#wsStatusOutput", jsonText(status));
renderJsonViewer("#wsStatusOutput", status);
}
function formatWsExecutionPayload(payload: DemoWsExecutionPayload): string {
@@ -221,10 +215,10 @@ async function refreshWsOptions(): Promise<void> {
async function refreshWsPool(): Promise<void> {
try {
const snapshots = await invoke("demo_ws_list_pool_clients");
writeTextarea("#wsPoolOutput", jsonText(snapshots));
renderJsonViewer("#wsPoolOutput", snapshots);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#wsPoolOutput", `Erreur : ${message}`);
renderJsonViewer("#wsPoolOutput", {status: "error", message});
frontendError("kb-app-demo-desktop.frontend.demo_ws", `WebSocket pool refresh failed: ${message}`);
}
}
@@ -235,7 +229,7 @@ async function refreshWsStatus(): Promise<void> {
setConnectedUi(status);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#wsStatusOutput", `Erreur statut WebSocket : ${message}`);
renderJsonViewer("#wsStatusOutput", {status: "error", message});
frontendError("kb-app-demo-desktop.frontend.demo_ws", `WebSocket status failed: ${message}`);
}
}
@@ -330,9 +324,6 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#refreshWsPoolButton")?.addEventListener("click", () => {
void refreshWsPool();
});
document.querySelector<HTMLButtonElement>("#copyWsPoolButton")?.addEventListener("click", () => {
void copyTextarea("#wsPoolOutput");
});
document.querySelector<HTMLButtonElement>("#copyWsResultButton")?.addEventListener("click", () => {
void copyTextarea("#wsResultOutput");
});

View File

@@ -98,6 +98,7 @@ function upgradeJsonTextareas(): void {
const container = document.createElement("div");
container.className = "app-json-viewer";
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
@@ -108,7 +109,7 @@ function upgradeJsonTextareas(): void {
let currentValue = textarea.value;
const render = (value: string): void => {
currentValue = value;
let parsed: unknown = value;
let parsed: unknown;
try {
parsed = JSON.parse(value) as unknown;
} catch {

View File

@@ -0,0 +1,28 @@
// file: kb-app-demo-desktop/frontend/ts/json_viewer.ts
// version: 2
import "@andypf/json-viewer";
export function jsonText(value: unknown): string {
return JSON.stringify(value, null, 2);
}
export function renderJsonViewer(selector: string, value: unknown): void {
const container = document.querySelector<HTMLElement>(selector);
if (!container) {
return;
}
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("expand-icon-type", "arrow");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("show-size", "true");
viewer.setAttribute("expand-empty", "false");
viewer.setAttribute("data", jsonText(value));
container.replaceChildren(viewer);
}