v0.1.4-pre.016
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -146,10 +146,18 @@ async function loadSnapshot(): Promise<void> {
|
||||
frontendTrace("main", "Main shell status replaced", { status: "ready" });
|
||||
}
|
||||
|
||||
function bindRuntimeSnapshotRefresh(): void {
|
||||
window.addEventListener("ksp:logging-runtime-updated", () => {
|
||||
frontendDebug("main", "Application snapshot refresh requested after Logging runtime update");
|
||||
void loadSnapshot();
|
||||
});
|
||||
}
|
||||
|
||||
async function initializeMain(): Promise<void> {
|
||||
const windowLabel = getCurrentWindow().label;
|
||||
frontendInfo("main", "Config Desk main frontend loaded", { windowLabel });
|
||||
bindNavigation();
|
||||
bindRuntimeSnapshotRefresh();
|
||||
initializeDocumentsPanel();
|
||||
initializeProfilesPanel();
|
||||
initializeEnvironmentPanel();
|
||||
|
||||
Reference in New Issue
Block a user