v0.1.4-pre.017
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||
<!-- version: 14 -->
|
||||
<!-- version: 15 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -360,6 +360,66 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="border rounded p-3 mb-4" aria-label="Test Logging">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<h3 class="h6 mb-1">Test Logging — routing contrôlé</h3>
|
||||
<p class="text-body-secondary small mb-0">Émet des événements volontairement journalisés pour vérifier niveaux, targets, domains, sinks et hot reload. Ne colle jamais de Secret dans le message de test.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-xl-6">
|
||||
<label class="form-label" for="loggingTestMessage">Message</label>
|
||||
<input id="loggingTestMessage" class="form-control" type="text" maxlength="500" autocomplete="off" value="Config Desk Logging routing test">
|
||||
</div>
|
||||
<div class="col-md-3 col-xl-2">
|
||||
<label class="form-label" for="loggingTestLevel">Niveau</label>
|
||||
<select id="loggingTestLevel" class="form-select">
|
||||
<option value="trace">trace</option><option value="debug">debug</option><option value="info" selected>info</option><option value="warn">warn</option><option value="error">error</option><option value="all">tous</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5 col-xl-4">
|
||||
<label class="form-label" for="loggingTestTarget">Target backend contrôlé</label>
|
||||
<select id="loggingTestTarget" class="form-select font-monospace">
|
||||
<option value="app">ksp-app-config-desk</option>
|
||||
<option value="logging_test" selected>ksp-app-config-desk.logging-test</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="loggingTestDomainMode">Domain</label>
|
||||
<select id="loggingTestDomainMode" class="form-select">
|
||||
<option value="absent">absent</option>
|
||||
<option value="known" selected>connu</option>
|
||||
<option value="custom">personnalisé</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="loggingTestKnownDomain">Domain connu</label>
|
||||
<select id="loggingTestKnownDomain" class="form-select font-monospace">
|
||||
<option value="config.logging_test">config.logging_test</option>
|
||||
<option value="config.logging_runtime">config.logging_runtime</option>
|
||||
<option value="frontend">frontend</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label" for="loggingTestCustomDomain">Domain personnalisé</label>
|
||||
<input id="loggingTestCustomDomain" class="form-control font-monospace" type="text" maxlength="80" autocomplete="off" placeholder="custom.routing" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<button id="emitLoggingTestBackend" class="btn btn-outline-primary" type="button">
|
||||
<i class="fa-solid fa-terminal me-1" aria-hidden="true"></i>
|
||||
Log backend
|
||||
</button>
|
||||
<button id="emitLoggingTestFrontend" class="btn btn-outline-secondary" type="button">
|
||||
<i class="fa-solid fa-code me-1" aria-hidden="true"></i>
|
||||
Log via bridge frontend
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text mb-3">Le test backend respecte le target/domain choisis. Le bridge frontend conserve volontairement son contrat fixe <code>target=ksp-app-config-desk.frontend.main</code>, <code>domain=frontend</code> et réutilise uniquement le message/niveau.</div>
|
||||
<div id="loggingTestStatus" class="alert alert-secondary mb-0" role="status" aria-live="polite">Aucun événement de test émis pendant ce lancement.</div>
|
||||
</section>
|
||||
|
||||
<section class="border rounded p-3 mb-4 bg-body-tertiary" aria-label="Globals Logging">
|
||||
<h3 class="h6 mb-3">Globals</h3>
|
||||
<div class="row g-3">
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
122
crates/ksp-app-config-desk/frontend/ts/logging_test.ts
Normal file
122
crates/ksp-app-config-desk/frontend/ts/logging_test.ts
Normal 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");
|
||||
}
|
||||
@@ -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.",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user