v0.1.4-pre.014
This commit is contained in:
190
crates/ksp-app-config-desk/frontend/ts/logging.ts
Normal file
190
crates/ksp-app-config-desk/frontend/ts/logging.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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");
|
||||
}
|
||||
Reference in New Issue
Block a user