v0.1.4-pre.016

This commit is contained in:
2026-08-16 19:00:49 +02:00
parent d92eb01bc2
commit 5aa538ed5d
29 changed files with 994 additions and 73 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/frontend/ts/logging.ts
// version: 4
// version: 5
//! Typed Logging editor backed by ConfigManagement persistence and KSP-owned runtime hot reload.
@@ -10,6 +10,7 @@ import type { LoggingDocumentSaveResultDto } from "./bindings/ksp_app_config_des
import type { LoggingFileDto } from "./bindings/ksp_app_config_desk/logging/LoggingFileDto";
import type { LoggingProfileDto } from "./bindings/ksp_app_config_desk/logging/LoggingProfileDto";
import type { LoggingTargetFilterDto } from "./bindings/ksp_app_config_desk/logging/LoggingTargetFilterDto";
import type { LoggingRuntimeStatusDto } from "./bindings/ksp_app_config_desk/logging_runtime/LoggingRuntimeStatusDto";
import { frontendDebug, frontendTrace } from "./frontend_log";
import { invokeKsp } from "./invoke";
@@ -18,6 +19,7 @@ const ROTATIONS = ["never", "hourly", "daily"] as const;
const FORMATS = ["human", "compact", "pretty", "json"] as const;
let draft: LoggingDocumentCandidateDto | null = null;
let persistedDocument: LoggingDocumentDto | null = null;
let selectedProfileId = "";
let dirty = false;
@@ -104,6 +106,7 @@ function markDirty(reason: string): void {
saveButton.disabled = false;
}
setLoggingStatus("Brouillon modifié. Sauvegarde nécessaire pour persister std.logging.json.", "warning");
updateRuntimeActionState();
frontendTrace("main", "Logging draft changed", { reason, selectedProfileId });
}
@@ -295,7 +298,130 @@ function renderSelectedProfile(source: "load" | "selection" | "mutation"): void
});
}
function updateRuntimeActionState(): void {
const select = document.querySelector<HTMLSelectElement>("#loggingRuntimeProfileSelect");
const apply = document.querySelector<HTMLButtonElement>("#applyLoggingRuntimeProfile");
if (select) {
select.disabled = persistedDocument === null || dirty;
}
if (apply) {
apply.disabled = persistedDocument === null || dirty || !select || select.value.length === 0;
}
}
function renderRuntimeProfileSelect(): void {
const select = document.querySelector<HTMLSelectElement>("#loggingRuntimeProfileSelect");
if (!select || !persistedDocument) {
updateRuntimeActionState();
return;
}
const previous = select.value;
select.replaceChildren();
for (const profile of persistedDocument.profiles) {
const option = document.createElement("option");
option.value = profile.profileId;
option.textContent = profile.profileId === persistedDocument.defaultProfile ? `${profile.profileId} (default)` : profile.profileId;
select.append(option);
}
if (persistedDocument.profiles.some(profile => profile.profileId === previous)) {
select.value = previous;
} else {
select.value = persistedDocument.defaultProfile;
}
updateRuntimeActionState();
}
function renderRuntimeFiles(status: LoggingRuntimeStatusDto): void {
const body = document.querySelector<HTMLTableSectionElement>("#loggingRuntimeFilesTable tbody");
if (!body) {
return;
}
body.replaceChildren();
for (const file of status.files) {
const row = document.createElement("tr");
const values: Array<{ value: string; monospace: boolean }> = [
{ value: file.outputId, monospace: true },
{ value: file.directory, monospace: true },
{ value: file.fileNamePrefix, monospace: true },
{ value: file.rotation, monospace: false },
];
for (const item of values) {
const cell = document.createElement("td");
cell.textContent = item.value;
if (item.monospace) {
cell.classList.add("font-monospace");
}
row.append(cell);
}
body.append(row);
}
}
function renderRuntimeStatus(status: LoggingRuntimeStatusDto): void {
setText("#loggingRuntimeActiveProfile", status.activeProfile ?? "fallback transitoire");
setText("#loggingRuntimeSelectionSource", status.selectionSource);
setText("#loggingRuntimeGeneration", status.generation.toString());
setText("#loggingRuntimeFallback", status.fallbackActive ? "oui" : "non");
setText("#loggingRuntimeApplicationId", status.applicationId);
setText("#loggingRuntimeLaunchTimestamp", status.launchTimestamp);
setText("#loggingRuntimeConsole", status.consoleEnabled ? "active" : "désactivée");
setText("#loggingRuntimeDropped", `${status.droppedTotalLines} total · console=${status.droppedConsoleLines} · fichiers=${status.droppedFileLines}`);
renderRuntimeFiles(status);
const select = document.querySelector<HTMLSelectElement>("#loggingRuntimeProfileSelect");
if (select && status.activeProfile && persistedDocument?.profiles.some(profile => profile.profileId === status.activeProfile)) {
select.value = status.activeProfile;
}
updateRuntimeActionState();
frontendTrace("main", "Logging runtime metadata rendered", {
generation: status.generation,
selectionSource: status.selectionSource,
fileCount: status.files.length,
consoleEnabled: status.consoleEnabled,
});
}
async function refreshRuntimeStatus(source: "startup" | "save" | "explicit" | "user"): Promise<void> {
if (source === "user") {
frontendDebug("main", "Logging runtime metadata refresh requested");
}
try {
const status = await invokeKsp<LoggingRuntimeStatusDto>("main", "get_logging_runtime_status");
renderRuntimeStatus(status);
frontendDebug("main", "Logging runtime metadata refresh completed", { generation: status.generation, activeProfile: status.activeProfile });
} catch {
setText("#loggingRuntimeActiveProfile", "indisponible");
}
}
async function applyRuntimeProfile(): Promise<void> {
if (dirty) {
setLoggingStatus("Sauvegarde ou abandonne d'abord le brouillon avant d'appliquer un profil runtime explicite.", "warning");
return;
}
const profileId = selectedValue("#loggingRuntimeProfileSelect");
if (profileId.length === 0) {
return;
}
const apply = document.querySelector<HTMLButtonElement>("#applyLoggingRuntimeProfile");
if (apply) {
apply.disabled = true;
}
setLoggingStatus(`Application explicite du profil runtime ${profileId}...`);
frontendDebug("main", "Explicit Logging runtime profile apply requested", { profileId });
try {
const status = await invokeKsp<LoggingRuntimeStatusDto>("main", "apply_logging_profile", { profileId });
renderRuntimeStatus(status);
window.dispatchEvent(new CustomEvent("ksp:logging-runtime-updated"));
setLoggingStatus(`Profil ${profileId} appliqué au runtime sans modifier default_profile. Génération ${status.generation}.`, "success");
frontendDebug("main", "Explicit Logging runtime profile apply completed", { profileId, generation: status.generation });
} catch {
updateRuntimeActionState();
setLoggingStatus("Application explicite refusée. Le runtime précédent et sa génération sont conservés.", "danger");
}
}
function renderDocument(documentDto: LoggingDocumentDto): void {
persistedDocument = structuredClone(documentDto);
draft = {
logsDirectory: documentDto.logsDirectory,
defaultProfile: documentDto.defaultProfile,
@@ -311,6 +437,7 @@ function renderDocument(documentDto: LoggingDocumentDto): void {
setText("#loggingFormatVersion", documentDto.formatVersion.toString());
setInputValue("#loggingLogsDirectoryInput", draft.logsDirectory);
renderProfileSelects();
renderRuntimeProfileSelect();
renderSelectedProfile("load");
setText("#loggingSaveReport", "Aucune sauvegarde effectuée depuis le dernier chargement.");
frontendTrace("main", "Logging document editor rendered", { profileCount: documentDto.profiles.length });
@@ -512,6 +639,8 @@ async function saveLoggingDocument(): Promise<void> {
try {
const result = await invokeKsp<LoggingDocumentSaveResultDto>("main", "save_logging_document", { candidate: draft });
renderDocument(result.document);
await refreshRuntimeStatus("save");
window.dispatchEvent(new CustomEvent("ksp:logging-runtime-updated"));
setText(
"#loggingSaveReport",
`source_changed=${result.sourceChanged} · reload_required=${result.reloadRequired} · runtime_applied=${result.runtimeApplied} · generation=${result.loggingGeneration} · active_profile=${result.activeProfile}`,
@@ -576,9 +705,12 @@ async function refreshLoggingDocument(source: "startup" | "user"): Promise<void>
profileCount: documentDto.profiles.length,
defaultProfile: documentDto.defaultProfile,
});
await refreshRuntimeStatus(source === "startup" ? "startup" : "user");
} catch {
draft = null;
persistedDocument = null;
setEditorEnabled(false);
updateRuntimeActionState();
setLoggingStatus("Le document Logging typé n'a pas pu être chargé. Consulte les diagnostics backend.", "danger");
}
}
@@ -623,6 +755,9 @@ export function initializeLoggingPanel(): void {
bindStaticDraftFields();
document.querySelector<HTMLButtonElement>("#refreshLoggingDocument")?.addEventListener("click", requestReload);
document.querySelector<HTMLButtonElement>("#saveLoggingDocument")?.addEventListener("click", () => void saveLoggingDocument());
document.querySelector<HTMLButtonElement>("#refreshLoggingRuntime")?.addEventListener("click", () => void refreshRuntimeStatus("user"));
document.querySelector<HTMLButtonElement>("#applyLoggingRuntimeProfile")?.addEventListener("click", () => void applyRuntimeProfile());
document.querySelector<HTMLSelectElement>("#loggingRuntimeProfileSelect")?.addEventListener("change", updateRuntimeActionState);
document.querySelector<HTMLSelectElement>("#loggingProfileSelect")?.addEventListener("change", event => {
syncStaticProfileFields();
selectedProfileId = (event.currentTarget as HTMLSelectElement).value;