v0.1.4-pre.012
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 79
|
# version: 80
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.4-pre.11.fix.1"
|
version = "0.1.4-pre.12"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||||
<!-- version: 11 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# `ksp-app-config-desk`
|
# `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Le gabarit desktop actif fournit maintenant :
|
|||||||
- DataTables 3 + Select 4 pour l’inventaire interactif des documents Config ;
|
- DataTables 3 + Select 4 pour l’inventaire interactif des documents Config ;
|
||||||
- le panneau Documents générique alimenté par le registre `ksp-config-lib`, avec diagnostics backend et réparation validée ;
|
- le panneau Documents générique alimenté par le registre `ksp-config-lib`, avec diagnostics backend et réparation validée ;
|
||||||
- le panneau Profils générique pour `default_profile`, sélection explicite, vues global/profil/effective sûre et provenance ;
|
- le panneau Profils générique pour `default_profile`, sélection explicite, vues global/profil/effective sûre et provenance ;
|
||||||
- le panneau Environnement en lecture seule, alimenté uniquement par `ConfigManagement::environment_report()`, avec desired/effective sûres, source, sensibilité et shadowing ;
|
- le panneau Environnement alimenté uniquement par `ConfigManagement`, avec rapport desired/effective sûr, source/shadowing et management atomique `.env` create/update/remove ;
|
||||||
- `tauri-plugin-tracing` côté Rust et `@fltsci/tauri-plugin-tracing` côté frontend ;
|
- `tauri-plugin-tracing` côté Rust et `@fltsci/tauri-plugin-tracing` côté frontend ;
|
||||||
- les ports dédiés `1430` pour Vite HTTP et `1431` pour le WebSocket de développement ;
|
- les ports dédiés `1430` pour Vite HTTP et `1431` pour le WebSocket de développement ;
|
||||||
- la destination frontend externe commune à Tauri et Vite;
|
- la destination frontend externe commune à Tauri et Vite;
|
||||||
@@ -149,6 +149,8 @@ Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre,
|
|||||||
|
|
||||||
## Panneau Environnement / `.env`
|
## Panneau Environnement / `.env`
|
||||||
|
|
||||||
La vue **Environnement / `.env`** expose la projection sûre de `ConfigManagement::environment_report()`. Elle ne lit jamais directement le process environment ni le fichier `.env`. Pour chaque variable KSP/KSPB actuellement présente dans le process ou `.env`, le frontend reçoit uniquement : nom, namespace, sensibilité, valeur desired sûre, valeur effective sûre, source effective et indicateur de shadowing.
|
La vue **Environnement / `.env`** expose la projection sûre de `ConfigManagement::environment_report()` puis une zone de management qui appelle exclusivement `ConfigManagement::set_dotenv_value()` et `ConfigManagement::remove_dotenv_value()`. Elle ne lit jamais directement le process environment ni le fichier `.env`. Pour chaque variable KSP/KSPB actuellement présente dans le process ou `.env`, le frontend reçoit uniquement : nom, namespace, sensibilité, valeur desired sûre, valeur effective sûre, source effective et indicateur de shadowing.
|
||||||
|
|
||||||
Les valeurs `KSP_SECRET_*` / `KSPB_SECRET_*` restent redacted par `ksp-config-lib` avant projection applicative. La table est triable/filtrable/paginée via DataTables, et les logs frontend/backend n'enregistrent que des comptes et métadonnées, jamais les valeurs. La création, modification et suppression de valeurs `.env` restent volontairement hors de cette tranche et arrivent en `pre.012`.
|
Les mutations acceptent un nom existant ou nouveau sous `KSP_*` / `KSPB_*`, présentent explicitement `source_changed`, `effective_changed`, `shadowed_by_process_environment` et `reload_required`, puis rechargent le rapport depuis Config. Une ligne du tableau ne précharge que le nom de variable : aucune ancienne valeur réelle n'est injectée dans le formulaire. Pour un namespace Secret, la nouvelle saisie est masquée ; la consultation d'une valeur réelle reste réservée au flux privilégié de `pre.013`.
|
||||||
|
|
||||||
|
Les valeurs `KSP_SECRET_*` / `KSPB_SECRET_*` restent redacted par `ksp-config-lib` dans le rapport. La priorité `process > .env > fallback` reste visible : une mutation `.env` shadowed peut changer le desired sans modifier l'effective. Les logs frontend/backend n'enregistrent jamais la valeur soumise, seulement le nom de variable, l'opération et les booléens du rapport de changement.
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
||||||
<!-- version: 10 -->
|
<!-- version: 11 -->
|
||||||
|
|
||||||
# TODO `ksp-app-config-desk`
|
# TODO `ksp-app-config-desk`
|
||||||
|
|
||||||
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et le rapport Environnement sûr sont maintenant présents.
|
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et Environnement avec management `.env` sont maintenant présents.
|
||||||
|
|
||||||
Les prochaines tranches doivent encore :
|
Les prochaines tranches doivent encore :
|
||||||
|
|
||||||
- développer le management `.env`, puis les surfaces Secrets et Logging ;
|
- développer les surfaces Secrets privilégiées puis Logging ;
|
||||||
- faire évoluer le runtime Logging vers des fichiers applicatifs uniques par lancement (`app-name.<start-timestamp>.<ext>`) ;
|
- faire évoluer le runtime Logging vers des fichiers applicatifs uniques par lancement (`app-name.<start-timestamp>.<ext>`) ;
|
||||||
- ramener le target applicatif de `debug` à `info` ou `warn` avant clôture de la release ;
|
- ramener le target applicatif de `debug` à `info` ou `warn` avant clôture de la release ;
|
||||||
- démontrer le hot reload Logging et le panneau de test de routage ;
|
- démontrer le hot reload Logging et le panneau de test de routage ;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||||
<!-- version: 11 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-config-desk`
|
# Utilisation de `ksp-app-config-desk`
|
||||||
|
|
||||||
## État actuel
|
## État actuel
|
||||||
|
|
||||||
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et le rapport Environnement/`.env` sont fonctionnels ; les mutations `.env`, Secrets et Logging restent à compléter.
|
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et Environnement/`.env` sont fonctionnels, y compris les mutations `.env` create/update/remove ; les reveals Secrets et Logging restent à compléter.
|
||||||
|
|
||||||
La fenêtre `splash` est visible au démarrage. Après readiness du frontend et temporisation résolue par Config, l'application effectue le fade-out, affiche/focalise `main` puis détruit `splash`. La fenêtre principale expose immédiatement la navigation monofenêtre de référence.
|
La fenêtre `splash` est visible au démarrage. Après readiness du frontend et temporisation résolue par Config, l'application effectue le fade-out, affiche/focalise `main` puis détruit `splash`. La fenêtre principale expose immédiatement la navigation monofenêtre de référence.
|
||||||
|
|
||||||
@@ -181,4 +181,17 @@ La vue **Environnement / `.env`** charge `get_environment_report`, qui projette
|
|||||||
- source effective ;
|
- source effective ;
|
||||||
- indicateur `shadowed` lorsqu'une valeur `.env` existe mais que le process hérité gagne.
|
- indicateur `shadowed` lorsqu'une valeur `.env` existe mais que le process hérité gagne.
|
||||||
|
|
||||||
Une valeur Secret doit apparaître redacted (`********`) dans les colonnes desired/effective. Le bouton **Rafraîchir** relit le rapport Config. Cette tranche est strictement en lecture seule ; les opérations de création/update/remove `.env` sont ajoutées en `pre.012`.
|
Une valeur Secret doit apparaître redacted (`********`) dans les colonnes desired/effective. Le bouton **Rafraîchir** relit le rapport Config.
|
||||||
|
|
||||||
|
La zone **Management / test .env** accepte une variable existante ou un nouveau nom `KSP_*` / `KSPB_*`. **Créer / Modifier** appelle `ConfigManagement::set_dotenv_value()` et **Supprimer** appelle `ConfigManagement::remove_dotenv_value()` ; aucune écriture `.env` n'est implémentée dans le frontend ou dans la couche Tauri. Après chaque mutation, le rapport est rechargé et les quatre indicateurs Config sont affichés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
source_changed
|
||||||
|
effective_changed
|
||||||
|
shadowed_by_process_environment
|
||||||
|
reload_required
|
||||||
|
```
|
||||||
|
|
||||||
|
Cliquer une ligne du rapport remplit uniquement le nom de variable. L'ancienne valeur n'est jamais préchargée dans le formulaire. Une saisie `KSP_SECRET_*` / `KSPB_SECRET_*` est masquée ; le reveal de l'ancienne valeur réelle reste hors de cette tranche.
|
||||||
|
|
||||||
|
Pour valider le shadowing, lancer ponctuellement l'application avec `KSP_LOGS_DIRECTORY` fourni par le process puis modifier la même variable dans `.env` depuis le panneau. Le résultat attendu est `source_changed=true`, `effective_changed=false`, `shadowed_by_process_environment=true`, `reload_required=false`. Le process hérité reste prioritaire et n'est jamais modifié par Config Desk.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-between mb-3">
|
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="h5 mb-1">Rapport Environnement / .env</h2>
|
<h2 class="h5 mb-1">Rapport Environnement / .env</h2>
|
||||||
<p class="text-body-secondary small mb-0">Valeurs desired/effective sûres, provenance externe et shadowing calculés exclusivement par ksp-config-lib.</p>
|
<p class="text-body-secondary small mb-0">Valeurs desired/effective sûres, provenance externe, shadowing et mutations persistées exclusivement par ksp-config-lib.</p>
|
||||||
</div>
|
</div>
|
||||||
<button id="refreshEnvironment" class="btn btn-outline-primary btn-sm" type="button">
|
<button id="refreshEnvironment" class="btn btn-outline-primary btn-sm" type="button">
|
||||||
<i class="fa-solid fa-rotate me-1" aria-hidden="true"></i>
|
<i class="fa-solid fa-rotate me-1" aria-hidden="true"></i>
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-light border small" role="note">
|
<div class="alert alert-light border small" role="note">
|
||||||
Les valeurs Secret restent redacted. Cette tranche est en lecture seule : création, modification et suppression de valeurs `.env` arrivent en pre.012.
|
Priorité effective : <code>process > .env > fallback</code>. Une mutation ne modifie jamais l'environnement process déjà hérité. Les valeurs Secret restent redacted dans le rapport.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive mb-4">
|
<div class="table-responsive mb-4">
|
||||||
@@ -237,7 +237,57 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="environmentStatus" class="alert alert-primary mb-0" role="status" aria-live="polite">Chargement du rapport environnement...</div>
|
<div id="environmentStatus" class="alert alert-primary mb-4" role="status" aria-live="polite">Chargement du rapport environnement...</div>
|
||||||
|
|
||||||
|
<section class="border rounded p-3 bg-body-tertiary" aria-label="Management .env">
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="h5 mb-1">Management / test .env</h2>
|
||||||
|
<p class="text-body-secondary small mb-0">Cliquer une ligne du rapport sélectionne son nom uniquement. Aucune ancienne valeur réelle n'est préchargée dans le formulaire.</p>
|
||||||
|
</div>
|
||||||
|
<button id="clearEnvironmentEditor" class="btn btn-outline-secondary btn-sm" type="button">Vider le formulaire</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 align-items-end mb-3">
|
||||||
|
<div class="col-xl-5">
|
||||||
|
<label class="form-label" for="environmentVariableName">Variable KSP/KSPB</label>
|
||||||
|
<input id="environmentVariableName" class="form-control font-monospace" type="text" list="environmentVariableSuggestions" autocomplete="off" placeholder="KSP_... ou KSPB_...">
|
||||||
|
<datalist id="environmentVariableSuggestions"></datalist>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-5">
|
||||||
|
<label class="form-label" for="environmentVariableValue">Nouvelle valeur</label>
|
||||||
|
<input id="environmentVariableValue" class="form-control font-monospace" type="text" autocomplete="new-password" spellcheck="false">
|
||||||
|
<div id="environmentEditorSensitivity" class="form-text">La valeur saisie n'est jamais préchargée depuis .env.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-2 d-grid gap-2">
|
||||||
|
<button id="setEnvironmentValue" class="btn btn-primary btn-sm" type="button">Créer / Modifier</button>
|
||||||
|
<button id="removeEnvironmentValue" class="btn btn-outline-danger btn-sm" type="button">Supprimer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="environmentMutationStatus" class="alert alert-secondary mb-3" role="status" aria-live="polite">
|
||||||
|
Aucune mutation effectuée pendant ce lancement.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="environmentMutationResult" hidden>
|
||||||
|
<h3 class="h6">Dernier résultat ConfigEnvironmentChangeReport</h3>
|
||||||
|
<dl class="row small mb-0 app-profile-metadata">
|
||||||
|
<dt class="col-md-4">Opération</dt>
|
||||||
|
<dd id="environmentMutationOperation" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Variable</dt>
|
||||||
|
<dd id="environmentMutationVariable" class="col-md-8 font-monospace">—</dd>
|
||||||
|
<dt class="col-md-4">source_changed</dt>
|
||||||
|
<dd id="environmentMutationSourceChanged" class="col-md-8 font-monospace">—</dd>
|
||||||
|
<dt class="col-md-4">effective_changed</dt>
|
||||||
|
<dd id="environmentMutationEffectiveChanged" class="col-md-8 font-monospace">—</dd>
|
||||||
|
<dt class="col-md-4">shadowed_by_process</dt>
|
||||||
|
<dd id="environmentMutationShadowed" class="col-md-8 font-monospace">—</dd>
|
||||||
|
<dt class="col-md-4 mb-0">reload_required</dt>
|
||||||
|
<dd id="environmentMutationReload" class="col-md-8 mb-0 font-monospace">—</dd>
|
||||||
|
</dl>
|
||||||
|
<div class="form-text mt-2">Le rapport est rechargé après chaque mutation. Un consumer qui conserve son propre snapshot doit respecter <code>reload_required</code>.</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="placeholderPanel" hidden aria-live="polite">
|
<section id="placeholderPanel" hidden aria-live="polite">
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/environment.ts
|
// 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 DataTable from "datatables.net-bs5";
|
||||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
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 type { ConfigEnvironmentReportDto } from "./bindings/ksp_app_config_desk/environment/ConfigEnvironmentReportDto";
|
||||||
import { frontendDebug, frontendTrace } from "./frontend_log";
|
import { frontendDebug, frontendTrace } from "./frontend_log";
|
||||||
import { invokeKsp } from "./invoke";
|
import { invokeKsp } from "./invoke";
|
||||||
@@ -14,6 +15,7 @@ interface EnvironmentDataTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let environmentTable: EnvironmentDataTable | null = null;
|
let environmentTable: EnvironmentDataTable | null = null;
|
||||||
|
let currentReports: ConfigEnvironmentReportDto[] = [];
|
||||||
|
|
||||||
function setEnvironmentStatus(message: string, tone: "primary" | "success" | "warning" | "danger" = "primary"): void {
|
function setEnvironmentStatus(message: string, tone: "primary" | "success" | "warning" | "danger" = "primary"): void {
|
||||||
const status = document.querySelector<HTMLElement>("#environmentStatus");
|
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 });
|
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 {
|
function sensitivityLabel(value: string): string {
|
||||||
if (value === "public") {
|
if (value === "public") {
|
||||||
return "Public";
|
return "Public";
|
||||||
@@ -72,6 +84,7 @@ function populateTableRows(reports: ConfigEnvironmentReportDto[]): void {
|
|||||||
body.replaceChildren();
|
body.replaceChildren();
|
||||||
for (const report of reports) {
|
for (const report of reports) {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
|
row.dataset.variableName = report.variableName;
|
||||||
appendCell(row, report.variableName, "font-monospace");
|
appendCell(row, report.variableName, "font-monospace");
|
||||||
appendCell(row, report.namespace);
|
appendCell(row, report.namespace);
|
||||||
appendCell(row, sensitivityLabel(report.sensitivity));
|
appendCell(row, sensitivityLabel(report.sensitivity));
|
||||||
@@ -84,11 +97,29 @@ function populateTableRows(reports: ConfigEnvironmentReportDto[]): void {
|
|||||||
frontendTrace("main", "Environment report table DOM replaced", { variableCount: reports.length });
|
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) {
|
if (environmentTable) {
|
||||||
environmentTable.destroy();
|
environmentTable.destroy();
|
||||||
environmentTable = null;
|
environmentTable = null;
|
||||||
|
frontendTrace("main", "DataTables Environment destroyed before report replacement");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeDataTable(): void {
|
||||||
environmentTable = new DataTable("#environmentReportTable", {
|
environmentTable = new DataTable("#environmentReportTable", {
|
||||||
order: [[0, "asc"]],
|
order: [[0, "asc"]],
|
||||||
pageLength: 10,
|
pageLength: 10,
|
||||||
@@ -104,12 +135,101 @@ function initializeDataTable(): void {
|
|||||||
frontendTrace("main", "DataTables Environment initialized");
|
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> {
|
async function refreshEnvironmentReport(): Promise<void> {
|
||||||
frontendDebug("main", "Config environment report refresh requested");
|
frontendDebug("main", "Config environment report refresh requested");
|
||||||
setEnvironmentStatus("Chargement du rapport environnement sûr...", "primary");
|
setEnvironmentStatus("Chargement du rapport environnement sûr...", "primary");
|
||||||
try {
|
try {
|
||||||
const reports = await invokeKsp<ConfigEnvironmentReportDto[]>("main", "get_environment_report");
|
const reports = await invokeKsp<ConfigEnvironmentReportDto[]>("main", "get_environment_report");
|
||||||
|
currentReports = reports;
|
||||||
|
destroyEnvironmentDataTable();
|
||||||
populateTableRows(reports);
|
populateTableRows(reports);
|
||||||
|
populateVariableSuggestions(reports);
|
||||||
initializeDataTable();
|
initializeDataTable();
|
||||||
const shadowedCount = reports.filter(report => report.shadowedByProcessEnvironment).length;
|
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");
|
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 {
|
function bindEnvironmentActions(): void {
|
||||||
document.querySelector<HTMLButtonElement>("#refreshEnvironment")?.addEventListener("click", () => {
|
document.querySelector<HTMLButtonElement>("#refreshEnvironment")?.addEventListener("click", () => {
|
||||||
frontendTrace("main", "Environment refresh button clicked");
|
frontendTrace("main", "Environment refresh button clicked");
|
||||||
void refreshEnvironmentReport();
|
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");
|
frontendTrace("main", "Environment panel handlers installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initializeEnvironmentPanel(): void {
|
export function initializeEnvironmentPanel(): void {
|
||||||
bindEnvironmentActions();
|
bindEnvironmentActions();
|
||||||
|
updateEditorSensitivity();
|
||||||
void refreshEnvironmentReport();
|
void refreshEnvironmentReport();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import ResizeObserver from "resize-observer-polyfill";
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
@@ -32,7 +32,7 @@ const viewCopy: Record<ViewId, { title: string; description: string }> = {
|
|||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
title: "Environnement / .env",
|
title: "Environnement / .env",
|
||||||
description: "Rapport sûr desired/effective/source/shadow des variables KSP/KSPB ; mutations .env prévues en pre.012.",
|
description: "Rapport sûr desired/effective/source/shadow et management atomique des variables .env KSP/KSPB.",
|
||||||
},
|
},
|
||||||
logging: {
|
logging: {
|
||||||
title: "Logging",
|
title: "Logging",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/environment.rs
|
// file: crates/ksp-app-config-desk/src/environment.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Safe Config environment report projections for Config Desk.
|
//! Safe Config environment reports and `.env` management projections for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: derive-import
|
||||||
|
|
||||||
@@ -26,11 +26,57 @@ pub(crate) struct ConfigEnvironmentReportDto {
|
|||||||
pub(crate) shadowed_by_process_environment: bool,
|
pub(crate) shadowed_by_process_environment: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Safe result metadata for one `.env` mutation.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/environment/ConfigEnvironmentChangeDto.ts")]
|
||||||
|
pub(crate) struct ConfigEnvironmentChangeDto {
|
||||||
|
/// Variable targeted by the operation; never its value.
|
||||||
|
pub(crate) variable_name: String,
|
||||||
|
/// Stable operation label: `set` or `remove`.
|
||||||
|
pub(crate) operation: String,
|
||||||
|
/// Sensitivity classified by Config from the variable namespace.
|
||||||
|
pub(crate) sensitivity: String,
|
||||||
|
/// Whether the persisted `.env` source bytes changed.
|
||||||
|
pub(crate) source_changed: bool,
|
||||||
|
/// Whether the effective process-or-`.env` value changed for the running process.
|
||||||
|
pub(crate) effective_changed: bool,
|
||||||
|
/// Whether the resulting `.env` entry is shadowed by inherited process environment.
|
||||||
|
pub(crate) shadowed_by_process_environment: bool,
|
||||||
|
/// Whether consumers holding an environment snapshot must reload to observe the effective change.
|
||||||
|
pub(crate) reload_required: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum EnvironmentMutationOperation {
|
||||||
|
Set,
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnvironmentMutationOperation {
|
||||||
|
const fn label(self) -> &'static str {
|
||||||
|
return match self {
|
||||||
|
Self::Set => "set",
|
||||||
|
Self::Remove => "remove",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the safe environment report owned by Config.
|
/// Returns the safe environment report owned by Config.
|
||||||
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||||
return report_from_management(state.config_management());
|
return report_from_management(state.config_management());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
||||||
|
pub(crate) fn set_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
|
return set_value_from_management(state.config_management(), variable_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes one `.env` entry exclusively through ConfigManagement.
|
||||||
|
pub(crate) fn remove_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
|
return remove_value_from_management(state.config_management(), variable_name);
|
||||||
|
}
|
||||||
|
|
||||||
fn report_from_management(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
fn report_from_management(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||||
let reports = management.environment_report();
|
let reports = management.environment_report();
|
||||||
let reports = match reports {
|
let reports = match reports {
|
||||||
@@ -78,6 +124,72 @@ fn report_from_management(management: &ksp_config_lib::ConfigManagement) -> ksp_
|
|||||||
return std::result::Result::Ok(result);
|
return std::result::Result::Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_value_from_management(
|
||||||
|
management: &ksp_config_lib::ConfigManagement,
|
||||||
|
variable_name: &str,
|
||||||
|
value: &str,
|
||||||
|
) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
|
let sensitivity = mutation_sensitivity(variable_name);
|
||||||
|
let sensitivity = match sensitivity {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let report = management.set_dotenv_value(variable_name, value);
|
||||||
|
let report = match report {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(mutation_result(variable_name, EnvironmentMutationOperation::Set, sensitivity, report));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_value_from_management(management: &ksp_config_lib::ConfigManagement, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
|
let sensitivity = mutation_sensitivity(variable_name);
|
||||||
|
let sensitivity = match sensitivity {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let report = management.remove_dotenv_value(variable_name);
|
||||||
|
let report = match report {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(mutation_result(variable_name, EnvironmentMutationOperation::Remove, sensitivity, report));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mutation_result(
|
||||||
|
variable_name: &str,
|
||||||
|
operation: EnvironmentMutationOperation,
|
||||||
|
sensitivity: ksp_config_lib::ConfigSensitivity,
|
||||||
|
report: ksp_config_lib::ConfigEnvironmentChangeReport,
|
||||||
|
) -> ConfigEnvironmentChangeDto {
|
||||||
|
let result = ConfigEnvironmentChangeDto {
|
||||||
|
variable_name: variable_name.to_owned(),
|
||||||
|
operation: operation.label().to_owned(),
|
||||||
|
sensitivity: sensitivity_label(sensitivity).to_owned(),
|
||||||
|
source_changed: report.source_changed(),
|
||||||
|
effective_changed: report.effective_changed(),
|
||||||
|
shadowed_by_process_environment: report.shadowed_by_process_environment(),
|
||||||
|
reload_required: report.reload_required(),
|
||||||
|
};
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_ENVIRONMENT,
|
||||||
|
variable_name = result.variable_name.as_str(),
|
||||||
|
operation = result.operation.as_str(),
|
||||||
|
sensitivity = result.sensitivity.as_str(),
|
||||||
|
source_changed = result.source_changed,
|
||||||
|
effective_changed = result.effective_changed,
|
||||||
|
shadowed_by_process_environment = result.shadowed_by_process_environment,
|
||||||
|
reload_required = result.reload_required,
|
||||||
|
"Config .env mutation completed"
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mutation_sensitivity(variable_name: &str) -> ksp_core_lib::Result<ksp_config_lib::ConfigSensitivity> {
|
||||||
|
return ksp_config_lib::ConfigSensitivity::from_variable_name(variable_name);
|
||||||
|
}
|
||||||
|
|
||||||
const fn environment_source_label(source: std::option::Option<ksp_config_lib::ConfigEnvironmentSource>) -> &'static str {
|
const fn environment_source_label(source: std::option::Option<ksp_config_lib::ConfigEnvironmentSource>) -> &'static str {
|
||||||
return match source {
|
return match source {
|
||||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Process) => "process",
|
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Process) => "process",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
//! Tauri desktop application for managing and validating KSP configuration.
|
//! Tauri desktop application for managing and validating KSP configuration.
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
|||||||
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
||||||
pub(crate) use self::dto_common::AppSnapshotDto;
|
pub(crate) use self::dto_common::AppSnapshotDto;
|
||||||
pub(crate) use self::dto_common::CommandErrorDto;
|
pub(crate) use self::dto_common::CommandErrorDto;
|
||||||
|
pub(crate) use self::environment::ConfigEnvironmentChangeDto;
|
||||||
pub(crate) use self::environment::ConfigEnvironmentReportDto;
|
pub(crate) use self::environment::ConfigEnvironmentReportDto;
|
||||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||||
// version: 9
|
// version: 10
|
||||||
|
|
||||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||||
|
|
||||||
@@ -45,6 +45,8 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
|||||||
get_config_profile_documents,
|
get_config_profile_documents,
|
||||||
get_config_profile_detail,
|
get_config_profile_detail,
|
||||||
get_environment_report,
|
get_environment_report,
|
||||||
|
set_environment_value,
|
||||||
|
remove_environment_value,
|
||||||
emit_frontend_log,
|
emit_frontend_log,
|
||||||
splash_frontend_ready
|
splash_frontend_ready
|
||||||
]);
|
]);
|
||||||
@@ -130,6 +132,31 @@ fn get_environment_report(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn set_environment_value(
|
||||||
|
variable_name: String,
|
||||||
|
value: String,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||||
|
let result = crate::environment::set_value(&state, variable_name.as_str(), value.as_str());
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn remove_environment_value(
|
||||||
|
variable_name: String,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||||
|
let result = crate::environment::remove_value(&state, variable_name.as_str());
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
let result = crate::emit_frontend_log_event(payload);
|
let result = crate::emit_frontend_log_event(payload);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
||||||
@@ -22,6 +22,32 @@ fn environment_labels_cover_config_sources_and_sensitivities() {
|
|||||||
assert_eq!(super::sensitivity_label(ksp_config_lib::ConfigSensitivity::Secret), "secret");
|
assert_eq!(super::sensitivity_label(ksp_config_lib::ConfigSensitivity::Secret), "secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mutation_sensitivity_is_owned_by_config_namespace_rules() {
|
||||||
|
let public = super::mutation_sensitivity("KSP_PUBLIC_TEST_VALUE");
|
||||||
|
assert!(public.is_ok());
|
||||||
|
if let std::result::Result::Ok(value) = public {
|
||||||
|
assert_eq!(value, ksp_config_lib::ConfigSensitivity::Public);
|
||||||
|
}
|
||||||
|
let secret = super::mutation_sensitivity("KSP_SECRET_TEST_VALUE");
|
||||||
|
assert!(secret.is_ok());
|
||||||
|
if let std::result::Result::Ok(value) = secret {
|
||||||
|
assert_eq!(value, ksp_config_lib::ConfigSensitivity::Secret);
|
||||||
|
}
|
||||||
|
let internal = super::mutation_sensitivity("KSPB_TEST_VALUE");
|
||||||
|
assert!(internal.is_ok());
|
||||||
|
if let std::result::Result::Ok(value) = internal {
|
||||||
|
assert_eq!(value, ksp_config_lib::ConfigSensitivity::Internal);
|
||||||
|
}
|
||||||
|
assert!(super::mutation_sensitivity("EXTERNAL_TEST_VALUE").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mutation_operation_labels_are_stable() {
|
||||||
|
assert_eq!(super::EnvironmentMutationOperation::Set.label(), "set");
|
||||||
|
assert_eq!(super::EnvironmentMutationOperation::Remove.label(), "remove");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn committed_environment_report_is_safe_and_deterministic_when_present() {
|
fn committed_environment_report_is_safe_and_deterministic_when_present() {
|
||||||
let management = committed_management();
|
let management = committed_management();
|
||||||
|
|||||||
118
deltas/0.1.4/pre.012.md
Normal file
118
deltas/0.1.4/pre.012.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!-- file: deltas/0.1.4/pre.012.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.1.4-pre.012` — management/test `.env`
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
Tranche livrée pour validation locale.
|
||||||
|
|
||||||
|
`0.1.4-pre.011-fix.001` est considérée validée : `fmt`, `check`, `clippy`, les tests Config Desk, les tests Config, les cinq audits ownership et les tests d'API publique sont propres ; le runtime Environnement retourne correctement les trois variables `.env` actuellement configurées.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Rendre fonctionnelle la zone de management/test `.env` du panneau **Environnement / `.env`**, exclusivement via `ConfigManagement`, afin de démontrer les créations/modifications/suppressions, le rapport de changement et la priorité `process > .env` sans lecture/écriture directe du fichier par l'application.
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- ajout du DTO TS-RS `ConfigEnvironmentChangeDto` ;
|
||||||
|
- ajout des services applicatifs :
|
||||||
|
- `environment::set_value()` -> `ConfigManagement::set_dotenv_value()` ;
|
||||||
|
- `environment::remove_value()` -> `ConfigManagement::remove_dotenv_value()` ;
|
||||||
|
- projection sûre de `ConfigEnvironmentChangeReport` :
|
||||||
|
- `source_changed` ;
|
||||||
|
- `effective_changed` ;
|
||||||
|
- `shadowed_by_process_environment` ;
|
||||||
|
- `reload_required` ;
|
||||||
|
- ajout des commandes Tauri centralisées :
|
||||||
|
- `set_environment_value` ;
|
||||||
|
- `remove_environment_value` ;
|
||||||
|
- classification de sensibilité déléguée à `ConfigSensitivity::from_variable_name()` ;
|
||||||
|
- logging `debug` limité au nom de variable, à l'opération, à la sensibilité et aux booléens du rapport ;
|
||||||
|
- aucune valeur soumise n'est journalisée.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- transformation de la vue Environnement en surface de management/test complète ;
|
||||||
|
- formulaire permettant :
|
||||||
|
- sélection d'une variable existante via la table/datalist ;
|
||||||
|
- saisie d'un nouveau nom `KSP_*` / `KSPB_*` ;
|
||||||
|
- création/modification ;
|
||||||
|
- suppression avec confirmation ;
|
||||||
|
- aucune ancienne valeur n'est préchargée dans le champ d'édition ;
|
||||||
|
- les nouvelles saisies Secret sont masquées ;
|
||||||
|
- affichage explicite du dernier `ConfigEnvironmentChangeReport` ;
|
||||||
|
- rechargement du rapport Config après chaque mutation ;
|
||||||
|
- destruction/réinitialisation propre de DataTables avant remplacement des lignes ;
|
||||||
|
- clic sur une ligne du rapport = sélection du nom uniquement ;
|
||||||
|
- instrumentation `debug`/`trace` des actions et transitions sans contenu de valeur.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- classification de sensibilité de mutation par le contrat Config ;
|
||||||
|
- stabilité des labels d'opération `set` / `remove` ;
|
||||||
|
- conservation des tests de projection sûre/redacted du rapport Environnement.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `pre.011` marquée réalisée dans le plan ;
|
||||||
|
- `pre.012` marquée en cours ;
|
||||||
|
- README/USAGE/TODO alignés sur la nouvelle surface de mutation `.env` ;
|
||||||
|
- scénario de validation du shadowing documenté.
|
||||||
|
|
||||||
|
## Sécurité / ownership
|
||||||
|
|
||||||
|
- l'application ne lit ni ne parse `.env` directement ;
|
||||||
|
- l'application ne modifie jamais l'environnement process hérité ;
|
||||||
|
- Config reste seul responsable de la validation des noms, de l'encodage `.env`, de la persistence atomique et du calcul desired/effective ;
|
||||||
|
- le rapport ordinaire continue à redacter les Secrets ;
|
||||||
|
- le flux reveal de valeurs réelles reste hors périmètre et réservé à `pre.013`.
|
||||||
|
|
||||||
|
## Scénarios de validation
|
||||||
|
|
||||||
|
### Mutation normale
|
||||||
|
|
||||||
|
1. créer une variable KSP/KSPB de test depuis le formulaire ;
|
||||||
|
2. vérifier `source_changed=true` et `effective_changed=true` lorsqu'aucune valeur process ne la masque ;
|
||||||
|
3. vérifier que le rapport rechargé affiche la variable avec `source=dotenv` ;
|
||||||
|
4. supprimer la variable ;
|
||||||
|
5. vérifier qu'elle disparaît du rapport si elle n'existe pas dans le process.
|
||||||
|
|
||||||
|
### Shadowing `process > .env`
|
||||||
|
|
||||||
|
Lancer ponctuellement Config Desk avec `KSP_LOGS_DIRECTORY` défini dans le process, puis modifier la même variable dans `.env` via le panneau.
|
||||||
|
|
||||||
|
Résultat attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
source_changed = true
|
||||||
|
effective_changed = false
|
||||||
|
shadowed_by_process_environment = true
|
||||||
|
reload_required = false
|
||||||
|
```
|
||||||
|
|
||||||
|
Le desired `.env` doit changer alors que l'effective reste issue du process.
|
||||||
|
|
||||||
|
### Placeholder Logging
|
||||||
|
|
||||||
|
Sans override process, modifier `KSP_LOGS_DIRECTORY` depuis le panneau puis recharger le panneau Profils. `/logs_directory` doit refléter la nouvelle valeur/provenance `.env` dans l'effective sûre. Le runtime Logging déjà installé n'est pas hot-reloadé dans cette tranche ; cette responsabilité reste prévue en `pre.016`.
|
||||||
|
|
||||||
|
## Version technique
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = "0.1.4-pre.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation locale demandée
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-app-config-desk
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo tree -p ksp-app-config-desk
|
||||||
|
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||||
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
||||||
<!-- version: 12 -->
|
<!-- version: 13 -->
|
||||||
|
|
||||||
# Plan `0.1.4` — `ksp-app-config-desk`
|
# Plan `0.1.4` — `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -1211,14 +1211,14 @@ pre.010 Profils + provenance [réalisé]
|
|||||||
- global/profile/effective
|
- global/profile/effective
|
||||||
- provenance sûre
|
- provenance sûre
|
||||||
|
|
||||||
pre.011 Environnement — rapports [en cours]
|
pre.011 Environnement — rapports [réalisé]
|
||||||
- desired/effective/source/shadow
|
- desired/effective/source/shadow
|
||||||
- sensibilité + safe values
|
- sensibilité + safe values
|
||||||
- table issue uniquement de Config
|
- table issue uniquement de Config
|
||||||
- DataTables si tri/filtre/sélection/checkboxes sont requis à ce stade
|
- DataTables si tri/filtre/sélection/checkboxes sont requis à ce stade
|
||||||
- refresh/reload du rapport
|
- refresh/reload du rapport
|
||||||
|
|
||||||
pre.012 Environnement — management/test .env
|
pre.012 Environnement — management/test .env [en cours]
|
||||||
- create/update via set_dotenv_value
|
- create/update via set_dotenv_value
|
||||||
- remove via remove_dotenv_value
|
- remove via remove_dotenv_value
|
||||||
- résultats source/effective/shadow/reload
|
- résultats source/effective/shadow/reload
|
||||||
|
|||||||
Reference in New Issue
Block a user