207 lines
7.8 KiB
TypeScript
207 lines
7.8 KiB
TypeScript
// file: crates/ksp-app-config-desk/frontend/ts/secret_reveal.ts
|
|
// version: 1
|
|
|
|
import { Modal } from "bootstrap";
|
|
import type { SecretRevealRequestDto } from "./bindings/ksp_app_config_desk/secrets/SecretRevealRequestDto";
|
|
import type { SecretRevealResponseDto } from "./bindings/ksp_app_config_desk/secrets/SecretRevealResponseDto";
|
|
import { frontendDebug, frontendTrace } from "./frontend_log";
|
|
import { invokeKsp } from "./invoke";
|
|
|
|
export type SecretRevealSource = "effective" | "dotenv";
|
|
|
|
let pendingRequest: SecretRevealRequestDto | null = null;
|
|
let revealInFlight = false;
|
|
let revealGeneration = 0;
|
|
|
|
function sourceLabel(source: SecretRevealSource): string {
|
|
return source === "effective" ? "effective (process prioritaire sur .env)" : ".env persisté";
|
|
}
|
|
|
|
function modalElement(): HTMLElement | null {
|
|
return document.querySelector<HTMLElement>("#secretRevealModal");
|
|
}
|
|
|
|
function clearSecretValue(): void {
|
|
const input = document.querySelector<HTMLInputElement>("#secretRevealValue");
|
|
const absent = document.querySelector<HTMLElement>("#secretRevealValueAbsent");
|
|
const result = document.querySelector<HTMLElement>("#secretRevealResultPanel");
|
|
const confirmation = document.querySelector<HTMLElement>("#secretRevealConfirmPanel");
|
|
const confirm = document.querySelector<HTMLButtonElement>("#secretRevealConfirmAction");
|
|
const toggle = document.querySelector<HTMLButtonElement>("#secretRevealToggleVisibility");
|
|
if (input) {
|
|
input.value = "";
|
|
input.type = "password";
|
|
}
|
|
if (absent) {
|
|
absent.hidden = true;
|
|
}
|
|
if (result) {
|
|
result.hidden = true;
|
|
}
|
|
if (confirmation) {
|
|
confirmation.hidden = false;
|
|
}
|
|
if (confirm) {
|
|
confirm.hidden = false;
|
|
confirm.disabled = false;
|
|
}
|
|
if (toggle) {
|
|
toggle.hidden = true;
|
|
toggle.textContent = "Afficher";
|
|
}
|
|
}
|
|
|
|
function renderRevealResponse(response: SecretRevealResponseDto): void {
|
|
const input = document.querySelector<HTMLInputElement>("#secretRevealValue");
|
|
const absent = document.querySelector<HTMLElement>("#secretRevealValueAbsent");
|
|
const result = document.querySelector<HTMLElement>("#secretRevealResultPanel");
|
|
const confirmation = document.querySelector<HTMLElement>("#secretRevealConfirmPanel");
|
|
const confirm = document.querySelector<HTMLButtonElement>("#secretRevealConfirmAction");
|
|
const toggle = document.querySelector<HTMLButtonElement>("#secretRevealToggleVisibility");
|
|
if (confirmation) {
|
|
confirmation.hidden = true;
|
|
}
|
|
if (confirm) {
|
|
confirm.hidden = true;
|
|
}
|
|
if (result) {
|
|
result.hidden = false;
|
|
}
|
|
if (response.value === null) {
|
|
if (input) {
|
|
input.value = "";
|
|
input.hidden = true;
|
|
}
|
|
if (absent) {
|
|
absent.hidden = false;
|
|
}
|
|
if (toggle) {
|
|
toggle.hidden = true;
|
|
}
|
|
} else {
|
|
if (input) {
|
|
input.hidden = false;
|
|
input.type = "password";
|
|
input.value = response.value;
|
|
}
|
|
if (absent) {
|
|
absent.hidden = true;
|
|
}
|
|
if (toggle) {
|
|
toggle.hidden = false;
|
|
toggle.textContent = "Afficher";
|
|
}
|
|
}
|
|
frontendDebug("main", "Privileged Secret reveal rendered", {
|
|
variableName: response.variableName,
|
|
source: response.source,
|
|
valuePresent: response.value !== null,
|
|
});
|
|
}
|
|
|
|
async function executeReveal(): Promise<void> {
|
|
if (!pendingRequest || revealInFlight) {
|
|
return;
|
|
}
|
|
const request = pendingRequest;
|
|
const requestGeneration = revealGeneration;
|
|
const confirm = document.querySelector<HTMLButtonElement>("#secretRevealConfirmAction");
|
|
const status = document.querySelector<HTMLElement>("#secretRevealStatus");
|
|
revealInFlight = true;
|
|
if (confirm) {
|
|
confirm.disabled = true;
|
|
}
|
|
if (status) {
|
|
status.textContent = "Reveal privilégié en cours via ksp-config-lib...";
|
|
}
|
|
frontendDebug("main", "Privileged Secret reveal requested", { variableName: request.variableName, source: request.source });
|
|
try {
|
|
const response = await invokeKsp<SecretRevealResponseDto>("main", "reveal_environment_value", { request });
|
|
if (requestGeneration !== revealGeneration || !pendingRequest) {
|
|
frontendDebug("main", "Privileged Secret reveal response discarded after modal state changed", {
|
|
variableName: request.variableName,
|
|
source: request.source,
|
|
});
|
|
return;
|
|
}
|
|
renderRevealResponse(response);
|
|
if (status) {
|
|
status.textContent = response.value === null ? "Aucune valeur n'existe dans la source demandée." : "Valeur révélée temporairement. Elle sera effacée à la fermeture.";
|
|
}
|
|
} catch {
|
|
if (status) {
|
|
status.textContent = "Le reveal a été refusé ou a échoué.";
|
|
}
|
|
if (confirm) {
|
|
confirm.disabled = false;
|
|
}
|
|
} finally {
|
|
if (requestGeneration === revealGeneration) {
|
|
revealInFlight = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleRevealVisibility(): void {
|
|
const input = document.querySelector<HTMLInputElement>("#secretRevealValue");
|
|
const toggle = document.querySelector<HTMLButtonElement>("#secretRevealToggleVisibility");
|
|
if (!input || !toggle || input.hidden) {
|
|
return;
|
|
}
|
|
const show = input.type === "password";
|
|
input.type = show ? "text" : "password";
|
|
toggle.textContent = show ? "Masquer" : "Afficher";
|
|
frontendDebug("main", "Transient Secret reveal visibility changed", { visible: show });
|
|
}
|
|
|
|
export function requestSecretReveal(variableName: string, source: SecretRevealSource): void {
|
|
const modal = modalElement();
|
|
const variable = document.querySelector<HTMLElement>("#secretRevealVariable");
|
|
const sourceElement = document.querySelector<HTMLElement>("#secretRevealSource");
|
|
const status = document.querySelector<HTMLElement>("#secretRevealStatus");
|
|
if (!modal || !variable || !sourceElement || !status) {
|
|
frontendDebug("main", "Privileged Secret reveal modal is unavailable", { variableName, source });
|
|
return;
|
|
}
|
|
revealGeneration += 1;
|
|
clearSecretValue();
|
|
pendingRequest = { variableName, source };
|
|
variable.textContent = variableName;
|
|
sourceElement.textContent = sourceLabel(source);
|
|
status.textContent = "Confirmation explicite requise avant tout accès à la valeur réelle.";
|
|
frontendDebug("main", "Privileged Secret reveal confirmation opened", { variableName, source });
|
|
Modal.getOrCreateInstance(modal).show();
|
|
}
|
|
|
|
export function clearTransientSecretReveal(): void {
|
|
const modal = modalElement();
|
|
const variableName = pendingRequest?.variableName ?? null;
|
|
revealGeneration += 1;
|
|
clearSecretValue();
|
|
pendingRequest = null;
|
|
revealInFlight = false;
|
|
if (modal) {
|
|
Modal.getOrCreateInstance(modal).hide();
|
|
}
|
|
frontendTrace("main", "Transient Secret reveal state cleared", { variableName });
|
|
}
|
|
|
|
export function initializeSecretReveal(): void {
|
|
const modal = modalElement();
|
|
document.querySelector<HTMLButtonElement>("#secretRevealConfirmAction")?.addEventListener("click", () => {
|
|
void executeReveal();
|
|
});
|
|
document.querySelector<HTMLButtonElement>("#secretRevealToggleVisibility")?.addEventListener("click", () => {
|
|
toggleRevealVisibility();
|
|
});
|
|
modal?.addEventListener("hidden.bs.modal", () => {
|
|
const variableName = pendingRequest?.variableName ?? null;
|
|
revealGeneration += 1;
|
|
clearSecretValue();
|
|
pendingRequest = null;
|
|
revealInFlight = false;
|
|
frontendDebug("main", "Privileged Secret reveal closed and value cleared", { variableName });
|
|
});
|
|
frontendTrace("main", "Privileged Secret reveal handlers installed");
|
|
}
|