// file: crates/ksp-app-config-desk/frontend/ts/logging.ts // version: 1 //! Read-only typed Logging editor backed exclusively by ConfigManagement. import type { LoggingDocumentDto } from "./bindings/ksp_app_config_desk/logging/LoggingDocumentDto"; import type { LoggingOutputFilterDto } from "./bindings/ksp_app_config_desk/logging/LoggingOutputFilterDto"; import type { LoggingProfileDto } from "./bindings/ksp_app_config_desk/logging/LoggingProfileDto"; import { frontendDebug, frontendTrace } from "./frontend_log"; import { invokeKsp } from "./invoke"; let loggingDocument: LoggingDocumentDto | null = null; function setLoggingStatus(message: string, tone: "primary" | "success" | "danger" = "primary"): void { const status = document.querySelector("#loggingStatus"); if (!status) { return; } status.className = `alert alert-${tone} mb-0`; status.textContent = message; frontendTrace("main", "Logging editor status replaced", { tone }); } function setText(selector: string, value: string): void { const element = document.querySelector(selector); if (element) { element.textContent = value; } } function boolLabel(value: boolean): string { return value ? "oui" : "non"; } function selectorsLabel(values: string[]): string { return values.length > 0 ? values.join(", ") : "—"; } function filterSummary(filter: LoggingOutputFilterDto): { level: string; targets: string; domains: string } { return { level: filter.level, targets: selectorsLabel(filter.targets), domains: selectorsLabel(filter.domains) }; } function appendCell(row: HTMLTableRowElement, value: string, className?: string): void { const cell = document.createElement("td"); cell.textContent = value; if (className) { cell.className = className; } row.append(cell); } function renderProfileSelect(documentDto: LoggingDocumentDto): void { const select = document.querySelector("#loggingProfileSelect"); if (!select) { return; } const previous = select.value; select.replaceChildren(); for (const profile of documentDto.profiles) { const option = document.createElement("option"); option.value = profile.profileId; option.textContent = profile.profileId === documentDto.defaultProfile ? `${profile.profileId} (default)` : profile.profileId; select.append(option); } const previousExists = documentDto.profiles.some(profile => profile.profileId === previous); select.value = previousExists ? previous : documentDto.defaultProfile; if (!documentDto.profiles.some(profile => profile.profileId === select.value) && documentDto.profiles.length > 0) { select.value = documentDto.profiles[0].profileId; } select.disabled = documentDto.profiles.length === 0; frontendTrace("main", "Logging editor profile select replaced", { profileCount: documentDto.profiles.length }); } function renderConsole(profile: LoggingProfileDto): void { const summary = filterSummary(profile.console.filter); setText("#loggingConsoleEnabled", boolLabel(profile.console.enabled)); setText("#loggingConsoleOutput", profile.console.output); setText("#loggingConsoleAnsi", boolLabel(profile.console.ansi)); setText("#loggingConsoleFormat", profile.console.format); setText("#loggingConsoleLevel", summary.level); setText("#loggingConsoleTargets", summary.targets); setText("#loggingConsoleDomains", summary.domains); } function renderFiles(profile: LoggingProfileDto): void { const body = document.querySelector("#loggingFilesTable tbody"); if (!body) { return; } body.replaceChildren(); for (const file of profile.files) { const summary = filterSummary(file.filter); const row = document.createElement("tr"); appendCell(row, file.outputId, "font-monospace"); appendCell(row, boolLabel(file.enabled)); appendCell(row, file.path, "font-monospace text-break"); appendCell(row, file.rotation); appendCell(row, file.format); appendCell(row, boolLabel(file.ansi)); appendCell(row, summary.level); appendCell(row, summary.targets, "font-monospace text-break"); appendCell(row, summary.domains, "font-monospace text-break"); body.append(row); } frontendTrace("main", "Logging file sink table DOM replaced", { fileCount: profile.files.length }); } function renderTargetFilters(profile: LoggingProfileDto): void { const body = document.querySelector("#loggingTargetFiltersTable tbody"); if (!body) { return; } body.replaceChildren(); for (const filter of profile.targetFilters) { const row = document.createElement("tr"); appendCell(row, filter.targetPrefix, "font-monospace"); appendCell(row, filter.level); body.append(row); } frontendTrace("main", "Logging target override table DOM replaced", { targetFilterCount: profile.targetFilters.length }); } function renderSelectedProfile(source: "load" | "selection"): void { const documentDto = loggingDocument; const select = document.querySelector("#loggingProfileSelect"); if (!documentDto || !select) { return; } const profile = documentDto.profiles.find(candidate => candidate.profileId === select.value); if (!profile) { setLoggingStatus("Le profil Logging sélectionné n'existe plus dans le document chargé.", "danger"); return; } setText("#loggingProfileDefaultFilter", profile.defaultFilter); setText("#loggingProfileSpanEvents", profile.spanEvents); renderConsole(profile); renderFiles(profile); renderTargetFilters(profile); frontendTrace("main", "Logging profile read-only view rendered", { profileId: profile.profileId, source, fileCount: profile.files.length, targetFilterCount: profile.targetFilters.length, }); } function renderDocument(documentDto: LoggingDocumentDto): void { loggingDocument = documentDto; setText("#loggingFileId", documentDto.fileId); setText("#loggingPath", documentDto.path); setText("#loggingFormatVersion", documentDto.formatVersion.toString()); setText("#loggingLogsDirectory", documentDto.logsDirectory); setText("#loggingDefaultProfile", documentDto.defaultProfile); renderProfileSelect(documentDto); renderSelectedProfile("load"); frontendTrace("main", "Logging document read-only view rendered", { profileCount: documentDto.profiles.length }); } async function refreshLoggingDocument(source: "startup" | "user"): Promise { if (source === "user") { frontendDebug("main", "Logging document refresh requested"); } setLoggingStatus("Chargement du document Logging typé..."); try { const documentDto = await invokeKsp("main", "get_logging_document"); renderDocument(documentDto); setLoggingStatus(`${documentDto.profiles.length} profil(s) Logging chargé(s) en lecture seule.`, "success"); frontendDebug("main", "Logging document refresh completed", { profileCount: documentDto.profiles.length, defaultProfile: documentDto.defaultProfile, }); } catch { loggingDocument = null; setLoggingStatus("Le document Logging typé n'a pas pu être chargé. Consulte les diagnostics backend.", "danger"); } } /// Initializes the read-only Logging editor surface. export function initializeLoggingPanel(): void { document.querySelector("#refreshLoggingDocument")?.addEventListener("click", () => { void refreshLoggingDocument("user"); }); document.querySelector("#loggingProfileSelect")?.addEventListener("change", event => { const profileId = (event.currentTarget as HTMLSelectElement).value; frontendDebug("main", "Logging read-only profile selection changed", { profileId }); renderSelectedProfile("selection"); }); frontendTrace("main", "Logging editor handlers installed"); void refreshLoggingDocument("startup"); }