Files
khadhroony-solana-project/crates/ksp-app-config-desk/frontend/ts/logging.ts
2026-08-16 17:03:07 +02:00

191 lines
8.1 KiB
TypeScript

// 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<HTMLElement>("#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<HTMLElement>(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<HTMLSelectElement>("#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<HTMLTableSectionElement>("#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<HTMLTableSectionElement>("#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<HTMLSelectElement>("#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<void> {
if (source === "user") {
frontendDebug("main", "Logging document refresh requested");
}
setLoggingStatus("Chargement du document Logging typé...");
try {
const documentDto = await invokeKsp<LoggingDocumentDto>("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<HTMLButtonElement>("#refreshLoggingDocument")?.addEventListener("click", () => {
void refreshLoggingDocument("user");
});
document.querySelector<HTMLSelectElement>("#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");
}