v0.1.4-pre.017

This commit is contained in:
2026-08-16 19:49:21 +02:00
parent fd200f5335
commit 4216b6b783
18 changed files with 858 additions and 32 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts
// version: 1
// version: 2
//! Frontend logging helpers routed through the KSP Logging facade.
@@ -45,47 +45,73 @@ async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTar
targetId,
message,
};
try {
await invoke("emit_frontend_log", { payload });
} catch (caughtError) {
originalConsole.error("KSP frontend logging bridge failed", caughtError);
}
await invoke("emit_frontend_log", { payload });
}
function writeOriginalConsole(level: FrontendLogLevel, message: string): void {
return level === "trace"
? originalConsole.trace(message)
: level === "debug"
? originalConsole.debug(message)
: level === "info"
? originalConsole.info(message)
: level === "warn"
? originalConsole.warn(message)
: originalConsole.error(message);
}
/// Emits one frontend event and resolves only after the Rust bridge accepted it.
export async function emitFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise<void> {
writeOriginalConsole(level, message);
await sendFrontendLog(level, targetId, message);
}
export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.trace(message);
void sendFrontendLog("trace", targetId, message);
void sendFrontendLog("trace", targetId, message).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
}
export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.debug(message);
void sendFrontendLog("debug", targetId, message);
void sendFrontendLog("debug", targetId, message).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
}
export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.info(message);
void sendFrontendLog("info", targetId, message);
void sendFrontendLog("info", targetId, message).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
}
export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.warn(message);
void sendFrontendLog("warn", targetId, message);
void sendFrontendLog("warn", targetId, message).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
}
export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.error(message);
void sendFrontendLog("error", targetId, message);
void sendFrontendLog("error", targetId, message).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
}
function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod {
return (...items: unknown[]) => {
original(...items);
void sendFrontendLog(level, targetId, formatMessage(items));
void sendFrontendLog(level, targetId, formatMessage(items)).catch(caughtError =>
originalConsole.error("KSP frontend logging bridge failed", caughtError),
);
};
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/frontend/ts/logging.ts
// version: 5
// version: 6
//! Typed Logging editor backed by ConfigManagement persistence and KSP-owned runtime hot reload.
@@ -13,6 +13,7 @@ import type { LoggingTargetFilterDto } from "./bindings/ksp_app_config_desk/logg
import type { LoggingRuntimeStatusDto } from "./bindings/ksp_app_config_desk/logging_runtime/LoggingRuntimeStatusDto";
import { frontendDebug, frontendTrace } from "./frontend_log";
import { invokeKsp } from "./invoke";
import { initializeLoggingTestPanel } from "./logging_test";
const LEVELS = ["off", "error", "warn", "info", "debug", "trace"] as const;
const ROTATIONS = ["never", "hourly", "daily"] as const;
@@ -772,6 +773,7 @@ export function initializeLoggingPanel(): void {
document.querySelector<HTMLButtonElement>("#loggingDiscardDraftConfirm")?.addEventListener("click", confirmReload);
document.querySelector<HTMLButtonElement>("#addLoggingFile")?.addEventListener("click", addFile);
document.querySelector<HTMLButtonElement>("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter);
initializeLoggingTestPanel();
frontendTrace("main", "Logging editor mutation handlers installed");
void refreshLoggingDocument("startup");
}

View File

@@ -0,0 +1,122 @@
// file: crates/ksp-app-config-desk/frontend/ts/logging_test.ts
// version: 1
//! Controlled Logging test panel for proving backend routing and the frontend bridge.
import type { LoggingTestRequestDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestRequestDto";
import type { LoggingTestResultDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestResultDto";
import { emitFrontendLog, frontendTrace, type FrontendLogLevel } from "./frontend_log";
import { invokeKsp } from "./invoke";
const FRONTEND_BRIDGE_TARGET = "ksp-app-config-desk.frontend.main";
const FRONTEND_BRIDGE_DOMAIN = "frontend";
function inputValue(selector: string): string {
return document.querySelector<HTMLInputElement>(selector)?.value ?? "";
}
function selectedValue(selector: string): string {
return document.querySelector<HTMLSelectElement>(selector)?.value ?? "";
}
function setStatus(message: string, tone: "secondary" | "success" | "warning" | "danger" = "secondary"): void {
const status = document.querySelector<HTMLElement>("#loggingTestStatus");
if (!status) {
return;
}
status.className = `alert alert-${tone} mb-0`;
status.textContent = message;
}
function updateDomainControls(): void {
const mode = selectedValue("#loggingTestDomainMode");
const known = document.querySelector<HTMLSelectElement>("#loggingTestKnownDomain");
const custom = document.querySelector<HTMLInputElement>("#loggingTestCustomDomain");
if (known) {
known.disabled = mode !== "known";
}
if (custom) {
custom.disabled = mode !== "custom";
}
}
function effectiveDomain(): string {
const mode = selectedValue("#loggingTestDomainMode");
if (mode === "known") {
return selectedValue("#loggingTestKnownDomain");
}
if (mode === "custom") {
return inputValue("#loggingTestCustomDomain");
}
return "";
}
function buildRequest(): LoggingTestRequestDto {
return {
level: selectedValue("#loggingTestLevel"),
targetId: selectedValue("#loggingTestTarget"),
domainMode: selectedValue("#loggingTestDomainMode"),
domain: effectiveDomain(),
message: inputValue("#loggingTestMessage"),
};
}
async function emitBackendTest(): Promise<void> {
const request = buildRequest();
setStatus("Émission backend en cours...");
try {
const result = await invokeKsp<LoggingTestResultDto>("main", "emit_logging_test", { request });
const domain = result.domain ?? "<absent>";
const summary = `${result.emittedEventCount} événement(s) backend émis · niveau=${result.requestedLevel} · target=${result.target}`
+ ` · domain=${domain} · génération=${result.loggingGeneration}.`;
setStatus(summary, "success");
} catch {
setStatus("Émission backend refusée. Vérifie le message, le target et le domain sélectionnés.", "danger");
}
}
async function emitFrontendLevel(level: FrontendLogLevel, message: string): Promise<void> {
await emitFrontendLog(level, "main", message);
}
async function emitFrontendBridgeTest(): Promise<void> {
const message = inputValue("#loggingTestMessage").trim();
if (message.length === 0 || message.length > 500) {
setStatus("Le message de test frontend doit contenir entre 1 et 500 caractères.", "danger");
return;
}
const level = selectedValue("#loggingTestLevel");
setStatus("Émission via bridge frontend en cours...");
try {
if (level === "all") {
for (const item of ["trace", "debug", "info", "warn", "error"] as const) {
await emitFrontendLevel(item, `[logging-test frontend/${item}] ${message}`);
}
setStatus(
`5 événements acceptés par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`,
"success",
);
return;
}
if (level === "trace" || level === "debug" || level === "info" || level === "warn" || level === "error") {
await emitFrontendLevel(level, `[logging-test frontend/${level}] ${message}`);
setStatus(
`1 événement accepté par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`,
"success",
);
return;
}
setStatus("Le niveau de test frontend sélectionné n'est pas supporté.", "danger");
} catch {
setStatus("Le bridge frontend n'a pas accepté l'événement de test.", "danger");
}
}
/// Initializes the controlled Logging test surface.
export function initializeLoggingTestPanel(): void {
document.querySelector<HTMLSelectElement>("#loggingTestDomainMode")?.addEventListener("change", updateDomainControls);
document.querySelector<HTMLButtonElement>("#emitLoggingTestBackend")?.addEventListener("click", () => void emitBackendTest());
document.querySelector<HTMLButtonElement>("#emitLoggingTestFrontend")?.addEventListener("click", () => void emitFrontendBridgeTest());
updateDomainControls();
frontendTrace("main", "Logging test panel handlers installed");
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
// version: 11
// version: 12
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
@@ -38,7 +38,7 @@ const viewCopy: Record<ViewId, { title: string; description: string }> = {
},
logging: {
title: "Logging",
description: "Édition typée et persistence atomique des profils, sinks et filtres Logging ; le hot reload runtime reste dans la tranche suivante.",
description: "Édition typée, hot reload runtime, profils actifs et panneau de test du routing Logging KSP.",
},
};