262 lines
10 KiB
TypeScript
262 lines
10 KiB
TypeScript
// 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<HTMLElement>("#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<HTMLElement>(selector);
|
|
if (element) {
|
|
element.textContent = value;
|
|
}
|
|
}
|
|
|
|
function setCode(selector: string, value: string): void {
|
|
const element = document.querySelector<HTMLElement>(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<HTMLSelectElement>("#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<HTMLSelectElement>("#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<HTMLTableSectionElement>("#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<HTMLTableSectionElement>("#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<HTMLElement>("#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<HTMLSelectElement>("#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<void> {
|
|
frontendDebug("main", "Config profile inspection requested", { fileId, profileId, source });
|
|
setProfilesStatus(`Résolution du profil ${profileId ?? "par défaut"}...`, "primary");
|
|
try {
|
|
const detail = await invokeKsp<ConfigProfileDetailDto>("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<void> {
|
|
frontendDebug("main", "Config profile document inventory refresh requested");
|
|
try {
|
|
profileDocuments = await invokeKsp<ConfigProfileDocumentDto[]>("main", "get_config_profile_documents");
|
|
populateDocumentSelect(profileDocuments);
|
|
if (profileDocuments.length === 0) {
|
|
activeDetail = null;
|
|
const detailPanel = document.querySelector<HTMLElement>("#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<HTMLButtonElement>("#refreshProfiles")?.addEventListener("click", () => {
|
|
frontendTrace("main", "Profiles refresh button clicked");
|
|
void refreshProfileDocuments();
|
|
});
|
|
document.querySelector<HTMLSelectElement>("#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<HTMLSelectElement>("#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<HTMLButtonElement>("#inspectDefaultProfile")?.addEventListener("click", () => {
|
|
const fileId = activeDocumentId();
|
|
if (!fileId) {
|
|
return;
|
|
}
|
|
frontendTrace("main", "Default profile inspection button clicked", { fileId });
|
|
void loadProfile(fileId, null, "default");
|
|
});
|
|
document.querySelector<HTMLButtonElement>("#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();
|
|
}
|