// file: crates/ksp-app-config-desk/frontend/ts/profiles.ts // version: 1 import type { ConfigProfileDetailDto } from "./bindings/ksp_app_config_desk/profiles/ConfigProfileDetailDto.ts"; import type { ConfigProfileDocumentDto } from "./bindings/ksp_app_config_desk/profiles/ConfigProfileDocumentDto.ts"; import { frontendDebug, frontendTrace } from "./frontend_log"; import { invokeKsp } from "./invoke"; let profileDocuments: ConfigProfileDocumentDto[] = []; let activeDetail: ConfigProfileDetailDto | null = null; function setProfilesStatus(message: string, tone: "primary" | "success" | "warning" | "danger" = "primary"): void { const status = document.querySelector("#profilesStatus"); if (!status) { return; } status.className = `alert alert-${tone} mb-0`; status.textContent = message; frontendTrace("main", "Profiles panel status replaced", { tone }); } function setText(selector: string, value: string): void { const element = document.querySelector(selector); if (element) { element.textContent = value; } } function setCode(selector: string, value: string): void { const element = document.querySelector(selector); if (element) { element.textContent = value; } } function sourceLabel(source: string): string { if (source === "default_profile") { return "default_profile"; } if (source === "explicit") { return "sélection explicite"; } if (source === "composite") { return "composite"; } return source; } function sensitivityLabel(value: string): string { if (value === "public") { return "Public"; } if (value === "internal") { return "Internal"; } if (value === "secret") { return "Secret"; } return value; } function populateDocumentSelect(documents: ConfigProfileDocumentDto[]): void { const select = document.querySelector("#profileDocumentSelect"); if (!select) { return; } select.replaceChildren(); for (const item of documents) { const option = document.createElement("option"); option.value = item.fileId; option.textContent = `${item.fileId} — ${item.defaultProfile}`; select.append(option); } select.disabled = documents.length === 0; frontendTrace("main", "Profile document select DOM replaced", { documentCount: documents.length }); } function populateProfileSelect(detail: ConfigProfileDetailDto): void { const select = document.querySelector("#profileIdSelect"); if (!select) { return; } select.replaceChildren(); for (const profileId of detail.profileIds) { const option = document.createElement("option"); option.value = profileId; option.textContent = profileId === detail.defaultProfile ? `${profileId} (défaut)` : profileId; option.selected = profileId === detail.selectedProfile; select.append(option); } select.disabled = detail.profileIds.length === 0; frontendTrace("main", "Profile id select DOM replaced", { profileCount: detail.profileIds.length }); } function renderValueOrigins(detail: ConfigProfileDetailDto): void { const body = document.querySelector("#profileOriginsTable tbody"); if (!body) { return; } body.replaceChildren(); for (const entry of detail.valueOrigins) { const row = document.createElement("tr"); const key = document.createElement("td"); const origin = document.createElement("td"); key.textContent = entry.key; origin.textContent = entry.origin === "global" ? "global" : entry.origin === "profile" ? "profil" : entry.origin; row.append(key, origin); body.append(row); } frontendTrace("main", "Profile top-level provenance table DOM replaced", { entryCount: detail.valueOrigins.length }); } function renderEnvironmentProvenance(detail: ConfigProfileDetailDto): void { const body = document.querySelector("#profileEnvironmentProvenanceTable tbody"); if (!body) { return; } body.replaceChildren(); for (const entry of detail.environmentProvenance) { const row = document.createElement("tr"); for (const value of [entry.jsonPointer, entry.variableName, entry.source, sensitivityLabel(entry.sensitivity)]) { const cell = document.createElement("td"); cell.textContent = value; row.append(cell); } body.append(row); } if (detail.environmentProvenance.length === 0) { const row = document.createElement("tr"); const cell = document.createElement("td"); cell.colSpan = 4; cell.className = "text-body-secondary text-center"; cell.textContent = "Aucune substitution d'environnement dans l'effective sélectionnée."; row.append(cell); body.append(row); } frontendTrace("main", "Profile environment provenance table DOM replaced", { entryCount: detail.environmentProvenance.length }); } function renderDetail(detail: ConfigProfileDetailDto): void { activeDetail = detail; populateProfileSelect(detail); setText("#profileDetailFileId", detail.fileId); setText("#profileDetailPath", detail.path); setText("#profileDetailDefault", detail.defaultProfile); setText("#profileDetailSelected", detail.selectedProfile); setText("#profileDetailSelectionSource", sourceLabel(detail.selectionSource)); setText("#profileDetailSensitivity", sensitivityLabel(detail.effectiveSensitivity)); setCode("#profileGlobalsJson", detail.globalsJson); setCode("#profileSourceJson", detail.profileJson); setCode("#profileEffectiveJson", detail.effectiveSafeJson); renderValueOrigins(detail); renderEnvironmentProvenance(detail); const detailPanel = document.querySelector("#profileDetailPanel"); if (detailPanel) { detailPanel.hidden = false; } frontendTrace("main", "Config profile detail rendered", { fileId: detail.fileId, selectedProfile: detail.selectedProfile, selectionSource: detail.selectionSource, effectiveSensitivity: detail.effectiveSensitivity, }); } function activeDocumentId(): string | null { const select = document.querySelector("#profileDocumentSelect"); if (!select || select.value.length === 0) { return null; } return select.value; } async function loadProfile(fileId: string, profileId: string | null, source: "default" | "explicit" | "reload"): Promise { frontendDebug("main", "Config profile inspection requested", { fileId, profileId, source }); setProfilesStatus(`Résolution du profil ${profileId ?? "par défaut"}...`, "primary"); try { const detail = await invokeKsp("main", "get_config_profile_detail", { fileId, profileId, }); renderDetail(detail); setProfilesStatus( `${detail.fileId} / ${detail.selectedProfile} résolu via ${sourceLabel(detail.selectionSource)}; effective sûre affichée.`, "success", ); frontendDebug("main", "Config profile inspection completed", { fileId: detail.fileId, selectedProfile: detail.selectedProfile, selectionSource: detail.selectionSource, }); } catch { setProfilesStatus("Le profil Config n'a pas pu être résolu par le backend.", "danger"); } } async function refreshProfileDocuments(): Promise { frontendDebug("main", "Config profile document inventory refresh requested"); try { profileDocuments = await invokeKsp("main", "get_config_profile_documents"); populateDocumentSelect(profileDocuments); if (profileDocuments.length === 0) { activeDetail = null; const detailPanel = document.querySelector("#profileDetailPanel"); if (detailPanel) { detailPanel.hidden = true; } setProfilesStatus("Aucun document Config avec profils standard n'est enregistré.", "warning"); return; } await loadProfile(profileDocuments[0].fileId, null, "default"); } catch { setProfilesStatus("L'inventaire des profils Config n'a pas pu être chargé.", "danger"); } } function bindProfileActions(): void { document.querySelector("#refreshProfiles")?.addEventListener("click", () => { frontendTrace("main", "Profiles refresh button clicked"); void refreshProfileDocuments(); }); document.querySelector("#profileDocumentSelect")?.addEventListener("change", event => { const select = event.currentTarget; if (!(select instanceof HTMLSelectElement) || select.value.length === 0) { return; } frontendTrace("main", "Profile document selection changed", { fileId: select.value }); void loadProfile(select.value, null, "default"); }); document.querySelector("#profileIdSelect")?.addEventListener("change", event => { const select = event.currentTarget; const fileId = activeDocumentId(); if (!(select instanceof HTMLSelectElement) || !fileId || select.value.length === 0) { return; } frontendTrace("main", "Explicit profile selection changed", { fileId, profileId: select.value }); void loadProfile(fileId, select.value, "explicit"); }); document.querySelector("#inspectDefaultProfile")?.addEventListener("click", () => { const fileId = activeDocumentId(); if (!fileId) { return; } frontendTrace("main", "Default profile inspection button clicked", { fileId }); void loadProfile(fileId, null, "default"); }); document.querySelector("#reloadProfileDetail")?.addEventListener("click", () => { if (!activeDetail) { return; } const explicit = activeDetail.selectionSource === "explicit" ? activeDetail.selectedProfile : null; frontendTrace("main", "Profile detail reload button clicked", { fileId: activeDetail.fileId, explicitProfile: explicit }); void loadProfile(activeDetail.fileId, explicit, "reload"); }); frontendTrace("main", "Profiles panel handlers installed"); } export function initializeProfilesPanel(): void { bindProfileActions(); void refreshProfileDocuments(); }