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