v0.1.4-pre.012
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/environment.ts
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Safe environment report panel backed exclusively by ConfigManagement::environment_report().
|
||||
//! Safe environment report and `.env` management panel backed exclusively by ConfigManagement.
|
||||
|
||||
import DataTable from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
import type { ConfigEnvironmentChangeDto } from "./bindings/ksp_app_config_desk/environment/ConfigEnvironmentChangeDto";
|
||||
import type { ConfigEnvironmentReportDto } from "./bindings/ksp_app_config_desk/environment/ConfigEnvironmentReportDto";
|
||||
import { frontendDebug, frontendTrace } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
@@ -14,6 +15,7 @@ interface EnvironmentDataTable {
|
||||
}
|
||||
|
||||
let environmentTable: EnvironmentDataTable | null = null;
|
||||
let currentReports: ConfigEnvironmentReportDto[] = [];
|
||||
|
||||
function setEnvironmentStatus(message: string, tone: "primary" | "success" | "warning" | "danger" = "primary"): void {
|
||||
const status = document.querySelector<HTMLElement>("#environmentStatus");
|
||||
@@ -25,6 +27,16 @@ function setEnvironmentStatus(message: string, tone: "primary" | "success" | "wa
|
||||
frontendTrace("main", "Environment panel status replaced", { tone });
|
||||
}
|
||||
|
||||
function setMutationStatus(message: string, tone: "secondary" | "success" | "warning" | "danger" = "secondary"): void {
|
||||
const status = document.querySelector<HTMLElement>("#environmentMutationStatus");
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
status.className = `alert alert-${tone} mb-0`;
|
||||
status.textContent = message;
|
||||
frontendTrace("main", "Environment mutation status replaced", { tone });
|
||||
}
|
||||
|
||||
function sensitivityLabel(value: string): string {
|
||||
if (value === "public") {
|
||||
return "Public";
|
||||
@@ -72,6 +84,7 @@ function populateTableRows(reports: ConfigEnvironmentReportDto[]): void {
|
||||
body.replaceChildren();
|
||||
for (const report of reports) {
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.variableName = report.variableName;
|
||||
appendCell(row, report.variableName, "font-monospace");
|
||||
appendCell(row, report.namespace);
|
||||
appendCell(row, sensitivityLabel(report.sensitivity));
|
||||
@@ -84,11 +97,29 @@ function populateTableRows(reports: ConfigEnvironmentReportDto[]): void {
|
||||
frontendTrace("main", "Environment report table DOM replaced", { variableCount: reports.length });
|
||||
}
|
||||
|
||||
function initializeDataTable(): void {
|
||||
function populateVariableSuggestions(reports: ConfigEnvironmentReportDto[]): void {
|
||||
const list = document.querySelector<HTMLDataListElement>("#environmentVariableSuggestions");
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
list.replaceChildren();
|
||||
for (const report of reports) {
|
||||
const option = document.createElement("option");
|
||||
option.value = report.variableName;
|
||||
list.append(option);
|
||||
}
|
||||
frontendTrace("main", "Environment variable suggestions replaced", { variableCount: reports.length });
|
||||
}
|
||||
|
||||
function destroyEnvironmentDataTable(): void {
|
||||
if (environmentTable) {
|
||||
environmentTable.destroy();
|
||||
environmentTable = null;
|
||||
frontendTrace("main", "DataTables Environment destroyed before report replacement");
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataTable(): void {
|
||||
environmentTable = new DataTable("#environmentReportTable", {
|
||||
order: [[0, "asc"]],
|
||||
pageLength: 10,
|
||||
@@ -104,12 +135,101 @@ function initializeDataTable(): void {
|
||||
frontendTrace("main", "DataTables Environment initialized");
|
||||
}
|
||||
|
||||
function classifyEditorSensitivity(variableName: string): "public" | "internal" | "secret" | "invalid" {
|
||||
if (variableName.startsWith("KSP_SECRET_") || variableName.startsWith("KSPB_SECRET_")) {
|
||||
return "secret";
|
||||
}
|
||||
if (variableName.startsWith("KSP_PUBLIC_") || variableName.startsWith("KSPB_PUBLIC_")) {
|
||||
return "public";
|
||||
}
|
||||
if (variableName.startsWith("KSP_") || variableName.startsWith("KSPB_")) {
|
||||
return "internal";
|
||||
}
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
function updateEditorSensitivity(): void {
|
||||
const variable = document.querySelector<HTMLInputElement>("#environmentVariableName");
|
||||
const value = document.querySelector<HTMLInputElement>("#environmentVariableValue");
|
||||
const note = document.querySelector<HTMLElement>("#environmentEditorSensitivity");
|
||||
if (!variable || !value || !note) {
|
||||
return;
|
||||
}
|
||||
const sensitivity = classifyEditorSensitivity(variable.value.trim());
|
||||
value.type = sensitivity === "secret" ? "password" : "text";
|
||||
note.textContent = sensitivity === "secret"
|
||||
? "Secret : la nouvelle saisie est masquée et l'ancienne valeur réelle n'est jamais préchargée."
|
||||
: sensitivity === "invalid"
|
||||
? "Saisis un nom KSP_* ou KSPB_* ; Config reste l'autorité de validation."
|
||||
: `${sensitivityLabel(sensitivity)} : la valeur saisie n'est jamais préchargée depuis .env.`;
|
||||
frontendTrace("main", "Environment mutation editor sensitivity updated", { sensitivity });
|
||||
}
|
||||
|
||||
function selectVariableForMutation(variableName: string, source: "table" | "input"): void {
|
||||
const variable = document.querySelector<HTMLInputElement>("#environmentVariableName");
|
||||
const value = document.querySelector<HTMLInputElement>("#environmentVariableValue");
|
||||
if (!variable || !value) {
|
||||
return;
|
||||
}
|
||||
variable.value = variableName;
|
||||
value.value = "";
|
||||
updateEditorSensitivity();
|
||||
frontendDebug("main", "Environment mutation variable selected", { variableName, source });
|
||||
}
|
||||
|
||||
function renderMutationResult(result: ConfigEnvironmentChangeDto): void {
|
||||
const operation = document.querySelector<HTMLElement>("#environmentMutationOperation");
|
||||
const variable = document.querySelector<HTMLElement>("#environmentMutationVariable");
|
||||
const sourceChanged = document.querySelector<HTMLElement>("#environmentMutationSourceChanged");
|
||||
const effectiveChanged = document.querySelector<HTMLElement>("#environmentMutationEffectiveChanged");
|
||||
const shadowed = document.querySelector<HTMLElement>("#environmentMutationShadowed");
|
||||
const reload = document.querySelector<HTMLElement>("#environmentMutationReload");
|
||||
const panel = document.querySelector<HTMLElement>("#environmentMutationResult");
|
||||
if (operation) {
|
||||
operation.textContent = result.operation === "remove" ? "suppression" : "création / modification";
|
||||
}
|
||||
if (variable) {
|
||||
variable.textContent = result.variableName;
|
||||
}
|
||||
if (sourceChanged) {
|
||||
sourceChanged.textContent = result.sourceChanged ? "true" : "false";
|
||||
}
|
||||
if (effectiveChanged) {
|
||||
effectiveChanged.textContent = result.effectiveChanged ? "true" : "false";
|
||||
}
|
||||
if (shadowed) {
|
||||
shadowed.textContent = result.shadowedByProcessEnvironment ? "true" : "false";
|
||||
}
|
||||
if (reload) {
|
||||
reload.textContent = result.reloadRequired ? "true" : "false";
|
||||
}
|
||||
if (panel) {
|
||||
panel.hidden = false;
|
||||
}
|
||||
frontendTrace("main", "Environment mutation result rendered", {
|
||||
operation: result.operation,
|
||||
variableName: result.variableName,
|
||||
sourceChanged: result.sourceChanged,
|
||||
effectiveChanged: result.effectiveChanged,
|
||||
shadowedByProcessEnvironment: result.shadowedByProcessEnvironment,
|
||||
reloadRequired: result.reloadRequired,
|
||||
});
|
||||
}
|
||||
|
||||
function setMutationControlsDisabled(disabled: boolean): void {
|
||||
document.querySelector<HTMLButtonElement>("#setEnvironmentValue")?.toggleAttribute("disabled", disabled);
|
||||
document.querySelector<HTMLButtonElement>("#removeEnvironmentValue")?.toggleAttribute("disabled", disabled);
|
||||
}
|
||||
|
||||
async function refreshEnvironmentReport(): Promise<void> {
|
||||
frontendDebug("main", "Config environment report refresh requested");
|
||||
setEnvironmentStatus("Chargement du rapport environnement sûr...", "primary");
|
||||
try {
|
||||
const reports = await invokeKsp<ConfigEnvironmentReportDto[]>("main", "get_environment_report");
|
||||
currentReports = reports;
|
||||
destroyEnvironmentDataTable();
|
||||
populateTableRows(reports);
|
||||
populateVariableSuggestions(reports);
|
||||
initializeDataTable();
|
||||
const shadowedCount = reports.filter(report => report.shadowedByProcessEnvironment).length;
|
||||
setEnvironmentStatus(`${reports.length} variable(s) KSP/KSPB évaluée(s) par Config ; ${shadowedCount} valeur(s) .env shadowed.`, "success");
|
||||
@@ -122,15 +242,131 @@ async function refreshEnvironmentReport(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function setEnvironmentValue(): Promise<void> {
|
||||
const variable = document.querySelector<HTMLInputElement>("#environmentVariableName");
|
||||
const value = document.querySelector<HTMLInputElement>("#environmentVariableValue");
|
||||
if (!variable || !value) {
|
||||
return;
|
||||
}
|
||||
const variableName = variable.value.trim();
|
||||
if (!variableName) {
|
||||
setMutationStatus("Saisis un nom de variable KSP_* ou KSPB_*.", "warning");
|
||||
return;
|
||||
}
|
||||
const sensitivity = classifyEditorSensitivity(variableName);
|
||||
frontendDebug("main", "Environment .env mutation requested", { operation: "set", variableName, sensitivity });
|
||||
setMutationControlsDisabled(true);
|
||||
setMutationStatus("Validation et persistence atomique par Config...", "secondary");
|
||||
try {
|
||||
const result = await invokeKsp<ConfigEnvironmentChangeDto>("main", "set_environment_value", { variableName, value: value.value });
|
||||
renderMutationResult(result);
|
||||
value.value = "";
|
||||
setMutationStatus("Mutation .env acceptée par Config ; rapport rechargé depuis la source persistée.", "success");
|
||||
await refreshEnvironmentReport();
|
||||
frontendDebug("main", "Environment .env mutation completed", {
|
||||
operation: result.operation,
|
||||
variableName: result.variableName,
|
||||
sourceChanged: result.sourceChanged,
|
||||
effectiveChanged: result.effectiveChanged,
|
||||
shadowedByProcessEnvironment: result.shadowedByProcessEnvironment,
|
||||
reloadRequired: result.reloadRequired,
|
||||
});
|
||||
} catch {
|
||||
setMutationStatus("La mutation .env a été refusée ou n'a pas pu être persistée par Config.", "danger");
|
||||
} finally {
|
||||
setMutationControlsDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeEnvironmentValue(): Promise<void> {
|
||||
const variable = document.querySelector<HTMLInputElement>("#environmentVariableName");
|
||||
const value = document.querySelector<HTMLInputElement>("#environmentVariableValue");
|
||||
if (!variable || !value) {
|
||||
return;
|
||||
}
|
||||
const variableName = variable.value.trim();
|
||||
if (!variableName) {
|
||||
setMutationStatus("Saisis ou sélectionne la variable .env à supprimer.", "warning");
|
||||
return;
|
||||
}
|
||||
const confirmed = window.confirm(`Supprimer l'entrée .env ${variableName} ? L'environnement process hérité ne sera pas modifié.`);
|
||||
frontendTrace("main", "Environment .env removal confirmation answered", { variableName, confirmed });
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const sensitivity = classifyEditorSensitivity(variableName);
|
||||
frontendDebug("main", "Environment .env mutation requested", { operation: "remove", variableName, sensitivity });
|
||||
setMutationControlsDisabled(true);
|
||||
setMutationStatus("Suppression atomique par Config...", "secondary");
|
||||
try {
|
||||
const result = await invokeKsp<ConfigEnvironmentChangeDto>("main", "remove_environment_value", { variableName });
|
||||
renderMutationResult(result);
|
||||
value.value = "";
|
||||
setMutationStatus("Suppression .env traitée par Config ; rapport rechargé depuis la source persistée.", "success");
|
||||
await refreshEnvironmentReport();
|
||||
frontendDebug("main", "Environment .env mutation completed", {
|
||||
operation: result.operation,
|
||||
variableName: result.variableName,
|
||||
sourceChanged: result.sourceChanged,
|
||||
effectiveChanged: result.effectiveChanged,
|
||||
shadowedByProcessEnvironment: result.shadowedByProcessEnvironment,
|
||||
reloadRequired: result.reloadRequired,
|
||||
});
|
||||
} catch {
|
||||
setMutationStatus("La suppression .env a été refusée ou n'a pas pu être persistée par Config.", "danger");
|
||||
} finally {
|
||||
setMutationControlsDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
function bindEnvironmentActions(): void {
|
||||
document.querySelector<HTMLButtonElement>("#refreshEnvironment")?.addEventListener("click", () => {
|
||||
frontendTrace("main", "Environment refresh button clicked");
|
||||
void refreshEnvironmentReport();
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#environmentVariableName")?.addEventListener("input", () => {
|
||||
updateEditorSensitivity();
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#environmentVariableName")?.addEventListener("change", event => {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
const exact = currentReports.find(report => report.variableName === input.value.trim());
|
||||
if (exact) {
|
||||
selectVariableForMutation(exact.variableName, "input");
|
||||
}
|
||||
});
|
||||
document.querySelector<HTMLTableElement>("#environmentReportTable")?.addEventListener("click", event => {
|
||||
const target = event.target as HTMLElement;
|
||||
const row = target.closest<HTMLTableRowElement>("tbody tr");
|
||||
const variableName = row?.dataset.variableName;
|
||||
if (variableName) {
|
||||
selectVariableForMutation(variableName, "table");
|
||||
}
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#setEnvironmentValue")?.addEventListener("click", () => {
|
||||
frontendTrace("main", "Environment set value button clicked");
|
||||
void setEnvironmentValue();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#removeEnvironmentValue")?.addEventListener("click", () => {
|
||||
frontendTrace("main", "Environment remove value button clicked");
|
||||
void removeEnvironmentValue();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#clearEnvironmentEditor")?.addEventListener("click", () => {
|
||||
const variable = document.querySelector<HTMLInputElement>("#environmentVariableName");
|
||||
const value = document.querySelector<HTMLInputElement>("#environmentVariableValue");
|
||||
if (variable) {
|
||||
variable.value = "";
|
||||
}
|
||||
if (value) {
|
||||
value.value = "";
|
||||
}
|
||||
updateEditorSensitivity();
|
||||
frontendTrace("main", "Environment mutation editor cleared");
|
||||
});
|
||||
frontendTrace("main", "Environment panel handlers installed");
|
||||
}
|
||||
|
||||
export function initializeEnvironmentPanel(): void {
|
||||
bindEnvironmentActions();
|
||||
updateEditorSensitivity();
|
||||
void refreshEnvironmentReport();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user