v0.1.4-pre.011
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# `ksp-app-config-desk`
|
||||
|
||||
@@ -28,6 +28,7 @@ Le gabarit desktop actif fournit maintenant :
|
||||
- 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 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 ;
|
||||
- `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 ;
|
||||
- la destination frontend externe commune à Tauri et Vite;
|
||||
@@ -49,6 +50,7 @@ frontend/
|
||||
│ ├── main.scss
|
||||
│ └── splash.scss
|
||||
├── ts/
|
||||
│ ├── environment.ts
|
||||
│ ├── frontend_log.ts
|
||||
│ ├── invoke.ts
|
||||
│ ├── main.ts
|
||||
@@ -143,3 +145,10 @@ Les actions utilisateur significatives et changements d'état sont journalisés
|
||||
## Police du splash
|
||||
|
||||
Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre, via le `@font-face` déclaré dans `frontend/sass/splash.scss`. Le répertoire `frontend/fonts/` documente la provenance et l'empreinte de l'asset de référence. Le lifecycle du splash est désormais actif et utilise cette police pendant la transition vers la fenêtre principale.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
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`.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# 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`, le panneau Documents et le panneau Profils/provenance 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 le rapport Environnement sûr sont maintenant présents.
|
||||
|
||||
Les prochaines tranches doivent encore :
|
||||
|
||||
- développer les panneaux Environnement/`.env`, Secrets et Logging ;
|
||||
- développer le management `.env`, puis les surfaces Secrets et Logging ;
|
||||
- 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 ;
|
||||
- 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 -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# Utilisation de `ksp-app-config-desk`
|
||||
|
||||
## É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 et Profils sont fonctionnels ; les panneaux Environnement/`.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 le rapport Environnement/`.env` sont fonctionnels ; les mutations `.env`, 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.
|
||||
|
||||
@@ -131,7 +131,7 @@ Environnement / .env
|
||||
Logging
|
||||
```
|
||||
|
||||
Les vues d'ensemble, Documents et Profils sont fonctionnelles ; Environnement / `.env` et Logging restent des placeholders jusqu'à leurs tranches fonctionnelles. Le header affiche `Config Desk — <vue active>` : le logo fournit déjà l'identité KSP. Les commandes principales peu nombreuses utilisent des pills/tabs alignées à droite ; une application plus chargée devra préférer un dropdown. Chaque clic de tab est tracé en `trace`, l'activation utilisateur significative reste tracée en `debug`, et chaque remplacement/rendu de section en `trace`. Les appels Tauri partagés utilisent `frontend/ts/invoke.ts`, qui journalise le début et la fin d'une commande sans journaliser ses arguments.
|
||||
Les vues d'ensemble, Documents, Profils et Environnement / `.env` sont fonctionnelles ; Logging reste un placeholder jusqu'à sa tranche fonctionnelle. Le header affiche `Config Desk — <vue active>` : le logo fournit déjà l'identité KSP. Les commandes principales peu nombreuses utilisent des pills/tabs alignées à droite ; une application plus chargée devra préférer un dropdown. Chaque clic de tab est tracé en `trace`, l'activation utilisateur significative reste tracée en `debug`, et chaque remplacement/rendu de section en `trace`. Les appels Tauri partagés utilisent `frontend/ts/invoke.ts`, qui journalise le début et la fin d'une commande sans journaliser ses arguments.
|
||||
|
||||
## Asset font du splash
|
||||
|
||||
@@ -167,3 +167,18 @@ La vue **Profils** charge l'inventaire des documents Config validés qui possèd
|
||||
- **Provenance environnement** indique JSON Pointer, variable, source et sensibilité sans valeur réelle.
|
||||
|
||||
Avec le `std.logging.json` actuel et `KSP_LOGS_DIRECTORY` défini dans `.env`, la provenance attendue pour `/logs_directory` est `KSP_LOGS_DIRECTORY`, source `dotenv`, sensibilité `Internal`. Le contenu des vues JSON n'est pas recopié dans les logs ; seuls les identifiants de document/profil et métadonnées de résolution sont tracés.
|
||||
|
||||
|
||||
## Panneau Environnement / `.env`
|
||||
|
||||
La vue **Environnement / `.env`** charge `get_environment_report`, qui projette exclusivement `ConfigManagement::environment_report()`. La table affiche :
|
||||
|
||||
- nom de variable ;
|
||||
- namespace KSP/KSPB ;
|
||||
- sensibilité ;
|
||||
- valeur persisted `.env` sûre (`desired`) ;
|
||||
- valeur effective process-ou-`.env` sûre ;
|
||||
- source effective ;
|
||||
- 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`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -204,6 +204,42 @@
|
||||
<div id="profilesStatus" class="alert alert-primary mb-0" role="status" aria-live="polite">Chargement des profils Config...</div>
|
||||
</section>
|
||||
|
||||
<section id="environmentPanel" hidden aria-label="Environnement Config">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
<button id="refreshEnvironment" class="btn btn-outline-primary btn-sm" type="button">
|
||||
<i class="fa-solid fa-rotate me-1" aria-hidden="true"></i>
|
||||
Rafraîchir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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.
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mb-4">
|
||||
<table id="environmentReportTable" class="table table-striped table-hover align-middle w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Variable</th>
|
||||
<th scope="col">Namespace</th>
|
||||
<th scope="col">Sensibilité</th>
|
||||
<th scope="col">Desired .env (safe)</th>
|
||||
<th scope="col">Effective (safe)</th>
|
||||
<th scope="col">Source</th>
|
||||
<th scope="col">Shadowed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="environmentStatus" class="alert alert-primary mb-0" role="status" aria-live="polite">Chargement du rapport environnement...</div>
|
||||
</section>
|
||||
|
||||
<section id="placeholderPanel" hidden aria-live="polite">
|
||||
<div class="alert alert-secondary mb-4" role="status">
|
||||
Cette route du shell est prête ; son contenu fonctionnel sera ajouté dans la prerelease prévue par le plan 0.1.4.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
$app-header-height: 72px;
|
||||
$app-footer-height: 42px;
|
||||
@@ -111,3 +111,10 @@ body {
|
||||
background: var(--bs-body-bg);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
#environmentReportTable td:first-child,
|
||||
#environmentReportTable td:nth-child(4),
|
||||
#environmentReportTable td:nth-child(5) {
|
||||
font-family: var(--bs-font-monospace);
|
||||
}
|
||||
|
||||
136
crates/ksp-app-config-desk/frontend/ts/environment.ts
Normal file
136
crates/ksp-app-config-desk/frontend/ts/environment.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/environment.ts
|
||||
// version: 1
|
||||
|
||||
//! Safe environment report panel backed exclusively by ConfigManagement::environment_report().
|
||||
|
||||
import DataTable from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
import type { ConfigEnvironmentReportDto } from "./bindings/ksp_app_config_desk/environment/ConfigEnvironmentReportDto";
|
||||
import { frontendDebug, frontendTrace } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
|
||||
interface EnvironmentDataTable {
|
||||
destroy(): unknown;
|
||||
}
|
||||
|
||||
let environmentTable: EnvironmentDataTable | null = null;
|
||||
|
||||
function setEnvironmentStatus(message: string, tone: "primary" | "success" | "warning" | "danger" = "primary"): void {
|
||||
const status = document.querySelector<HTMLElement>("#environmentStatus");
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
status.className = `alert alert-${tone} mb-0`;
|
||||
status.textContent = message;
|
||||
frontendTrace("main", "Environment panel status replaced", { tone });
|
||||
}
|
||||
|
||||
function sensitivityLabel(value: string): string {
|
||||
if (value === "public") {
|
||||
return "Public";
|
||||
}
|
||||
if (value === "internal") {
|
||||
return "Internal";
|
||||
}
|
||||
if (value === "secret") {
|
||||
return "Secret";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sourceLabel(value: string): string {
|
||||
if (value === "process") {
|
||||
return "process";
|
||||
}
|
||||
if (value === "dotenv") {
|
||||
return "dotenv";
|
||||
}
|
||||
if (value === "fallback") {
|
||||
return "fallback";
|
||||
}
|
||||
return "—";
|
||||
}
|
||||
|
||||
function safeValue(value: string | null): string {
|
||||
return value ?? "—";
|
||||
}
|
||||
|
||||
function appendCell(row: HTMLTableRowElement, text: string, className?: string): void {
|
||||
const cell = document.createElement("td");
|
||||
cell.textContent = text;
|
||||
if (className) {
|
||||
cell.className = className;
|
||||
}
|
||||
row.append(cell);
|
||||
}
|
||||
|
||||
function populateTableRows(reports: ConfigEnvironmentReportDto[]): void {
|
||||
const body = document.querySelector<HTMLTableSectionElement>("#environmentReportTable tbody");
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
body.replaceChildren();
|
||||
for (const report of reports) {
|
||||
const row = document.createElement("tr");
|
||||
appendCell(row, report.variableName, "font-monospace");
|
||||
appendCell(row, report.namespace);
|
||||
appendCell(row, sensitivityLabel(report.sensitivity));
|
||||
appendCell(row, safeValue(report.desiredSafeValue), "font-monospace text-break");
|
||||
appendCell(row, safeValue(report.effectiveSafeValue), "font-monospace text-break");
|
||||
appendCell(row, sourceLabel(report.effectiveSource));
|
||||
appendCell(row, report.shadowedByProcessEnvironment ? "oui" : "non");
|
||||
body.append(row);
|
||||
}
|
||||
frontendTrace("main", "Environment report table DOM replaced", { variableCount: reports.length });
|
||||
}
|
||||
|
||||
function initializeDataTable(): void {
|
||||
if (environmentTable) {
|
||||
environmentTable.destroy();
|
||||
environmentTable = null;
|
||||
}
|
||||
environmentTable = new DataTable("#environmentReportTable", {
|
||||
order: [[0, "asc"]],
|
||||
pageLength: 10,
|
||||
language: {
|
||||
emptyTable: "Aucune variable KSP/KSPB présente dans process ou .env.",
|
||||
info: "_START_ à _END_ sur _TOTAL_ variable(s)",
|
||||
infoEmpty: "0 variable",
|
||||
lengthMenu: "Afficher _MENU_",
|
||||
search: "Filtrer :",
|
||||
zeroRecords: "Aucune variable correspondante.",
|
||||
},
|
||||
}) as unknown as EnvironmentDataTable;
|
||||
frontendTrace("main", "DataTables Environment initialized");
|
||||
}
|
||||
|
||||
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");
|
||||
populateTableRows(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");
|
||||
frontendDebug("main", "Config environment report refresh completed", {
|
||||
variableCount: reports.length,
|
||||
shadowedCount,
|
||||
});
|
||||
} catch {
|
||||
setEnvironmentStatus("Le rapport environnement n'a pas pu être chargé par Config.", "danger");
|
||||
}
|
||||
}
|
||||
|
||||
function bindEnvironmentActions(): void {
|
||||
document.querySelector<HTMLButtonElement>("#refreshEnvironment")?.addEventListener("click", () => {
|
||||
frontendTrace("main", "Environment refresh button clicked");
|
||||
void refreshEnvironmentReport();
|
||||
});
|
||||
frontendTrace("main", "Environment panel handlers installed");
|
||||
}
|
||||
|
||||
export function initializeEnvironmentPanel(): void {
|
||||
bindEnvironmentActions();
|
||||
void refreshEnvironmentReport();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -8,6 +8,7 @@ import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { AppSnapshotDto } from "./bindings/ksp_app_config_desk/dto_common/AppSnapshotDto.ts";
|
||||
import { frontendDebug, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { initializeDocumentsPanel } from "./documents";
|
||||
import { initializeEnvironmentPanel } from "./environment";
|
||||
import { initializeProfilesPanel } from "./profiles";
|
||||
import { invokeKsp } from "./invoke";
|
||||
|
||||
@@ -31,7 +32,7 @@ const viewCopy: Record<ViewId, { title: string; description: string }> = {
|
||||
},
|
||||
environment: {
|
||||
title: "Environnement / .env",
|
||||
description: "Rapports et management des variables KSP/KSPB — surfaces fonctionnelles prévues en pre.011/pre.012.",
|
||||
description: "Rapport sûr desired/effective/source/shadow des variables KSP/KSPB ; mutations .env prévues en pre.012.",
|
||||
},
|
||||
logging: {
|
||||
title: "Logging",
|
||||
@@ -79,9 +80,13 @@ function activateView(viewId: ViewId, source: "startup" | "user"): void {
|
||||
if (profiles) {
|
||||
profiles.hidden = viewId !== "profiles";
|
||||
}
|
||||
const environment = document.querySelector<HTMLElement>("#environmentPanel");
|
||||
if (environment) {
|
||||
environment.hidden = viewId !== "environment";
|
||||
}
|
||||
const placeholder = document.querySelector<HTMLElement>("#placeholderPanel");
|
||||
if (placeholder) {
|
||||
placeholder.hidden = viewId === "overview" || viewId === "documents" || viewId === "profiles";
|
||||
placeholder.hidden = viewId !== "logging";
|
||||
}
|
||||
frontendTrace("main", "Main view DOM updated", { viewId, source });
|
||||
}
|
||||
@@ -142,6 +147,7 @@ async function initializeMain(): Promise<void> {
|
||||
bindNavigation();
|
||||
initializeDocumentsPanel();
|
||||
initializeProfilesPanel();
|
||||
initializeEnvironmentPanel();
|
||||
activateView("overview", "startup");
|
||||
try {
|
||||
await loadSnapshot();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/constants.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Application-owned tracing targets and domains.
|
||||
|
||||
@@ -21,3 +21,5 @@ pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
pub(crate) const TRACING_DOMAIN_DOCUMENTS: &str = "config.documents";
|
||||
/// Structured domain for Config profile selection and provenance inspection.
|
||||
pub(crate) const TRACING_DOMAIN_PROFILES: &str = "config.profiles";
|
||||
/// Structured domain for safe Config environment reports.
|
||||
pub(crate) const TRACING_DOMAIN_ENVIRONMENT: &str = "config.environment";
|
||||
|
||||
119
crates/ksp-app-config-desk/src/environment.rs
Normal file
119
crates/ksp-app-config-desk/src/environment.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// file: crates/ksp-app-config-desk/src/environment.rs
|
||||
// version: 1
|
||||
|
||||
//! Safe Config environment report projections for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/environment/ConfigEnvironmentReportDto.ts")]
|
||||
pub(crate) struct ConfigEnvironmentReportDto {
|
||||
/// Supported KSP/KSPB variable name.
|
||||
pub(crate) variable_name: String,
|
||||
/// Namespace family derived from the variable name without inspecting its value.
|
||||
pub(crate) namespace: String,
|
||||
/// `public`, `internal` or `secret`.
|
||||
pub(crate) sensitivity: String,
|
||||
/// Persisted `.env` value using Config's safe/redacted representation.
|
||||
pub(crate) desired_safe_value: std::option::Option<String>,
|
||||
/// Currently effective process-or-`.env` value using Config's safe/redacted representation.
|
||||
pub(crate) effective_safe_value: std::option::Option<String>,
|
||||
/// Winning external source: `process`, `dotenv`, or `none`.
|
||||
pub(crate) effective_source: String,
|
||||
/// Whether the persisted `.env` value is shadowed by the inherited process environment.
|
||||
pub(crate) shadowed_by_process_environment: bool,
|
||||
}
|
||||
|
||||
/// Returns the safe environment report owned by Config.
|
||||
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||
return report_from_management(state.config_management());
|
||||
}
|
||||
|
||||
fn report_from_management(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||
let reports = management.environment_report();
|
||||
let reports = match reports {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut result = std::vec::Vec::<ConfigEnvironmentReportDto>::with_capacity(reports.len());
|
||||
let mut process_count = 0usize;
|
||||
let mut dotenv_count = 0usize;
|
||||
let mut shadowed_count = 0usize;
|
||||
let mut secret_count = 0usize;
|
||||
for report in reports {
|
||||
let source = report.effective_source();
|
||||
match source {
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Process) => process_count += 1,
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::DotEnv) => dotenv_count += 1,
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Fallback) | std::option::Option::None => {},
|
||||
}
|
||||
if report.shadowed_by_process_environment() {
|
||||
shadowed_count += 1;
|
||||
}
|
||||
if report.sensitivity() == ksp_config_lib::ConfigSensitivity::Secret {
|
||||
secret_count += 1;
|
||||
}
|
||||
result.push(ConfigEnvironmentReportDto {
|
||||
variable_name: report.variable_name().to_owned(),
|
||||
namespace: namespace_label(report.variable_name()).to_owned(),
|
||||
sensitivity: sensitivity_label(report.sensitivity()).to_owned(),
|
||||
desired_safe_value: report.desired_safe_value().map(str::to_owned),
|
||||
effective_safe_value: report.effective_safe_value().map(str::to_owned),
|
||||
effective_source: environment_source_label(source).to_owned(),
|
||||
shadowed_by_process_environment: report.shadowed_by_process_environment(),
|
||||
});
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_ENVIRONMENT,
|
||||
variable_count = result.len(),
|
||||
process_count,
|
||||
dotenv_count,
|
||||
shadowed_count,
|
||||
secret_count,
|
||||
"Config environment report evaluated"
|
||||
);
|
||||
return std::result::Result::Ok(result);
|
||||
}
|
||||
|
||||
const fn environment_source_label(source: std::option::Option<ksp_config_lib::ConfigEnvironmentSource>) -> &'static str {
|
||||
return match source {
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Process) => "process",
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::DotEnv) => "dotenv",
|
||||
std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Fallback) => "fallback",
|
||||
std::option::Option::None => "none",
|
||||
};
|
||||
}
|
||||
|
||||
const fn sensitivity_label(sensitivity: ksp_config_lib::ConfigSensitivity) -> &'static str {
|
||||
return match sensitivity {
|
||||
ksp_config_lib::ConfigSensitivity::Public => "public",
|
||||
ksp_config_lib::ConfigSensitivity::Internal => "internal",
|
||||
ksp_config_lib::ConfigSensitivity::Secret => "secret",
|
||||
};
|
||||
}
|
||||
|
||||
fn namespace_label(variable_name: &str) -> &'static str {
|
||||
if variable_name.starts_with("KSPB_SECRET_") {
|
||||
return "KSPB_SECRET";
|
||||
}
|
||||
if variable_name.starts_with("KSPB_PUBLIC_") {
|
||||
return "KSPB_PUBLIC";
|
||||
}
|
||||
if variable_name.starts_with("KSPB_") {
|
||||
return "KSPB";
|
||||
}
|
||||
if variable_name.starts_with("KSP_SECRET_") {
|
||||
return "KSP_SECRET";
|
||||
}
|
||||
if variable_name.starts_with("KSP_PUBLIC_") {
|
||||
return "KSP_PUBLIC";
|
||||
}
|
||||
return "KSP";
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/environment.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Tauri desktop application for managing and validating KSP configuration.
|
||||
|
||||
@@ -12,6 +12,7 @@ mod bootstrap;
|
||||
mod constants;
|
||||
mod documents;
|
||||
mod dto_common;
|
||||
mod environment;
|
||||
mod errors;
|
||||
mod frontend_logging;
|
||||
mod profiles;
|
||||
@@ -28,6 +29,7 @@ pub(crate) use self::bootstrap::config_management;
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_DOCUMENTS;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||
@@ -41,6 +43,7 @@ pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
||||
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
||||
pub(crate) use self::dto_common::AppSnapshotDto;
|
||||
pub(crate) use self::dto_common::CommandErrorDto;
|
||||
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_LOCK_FAILED;
|
||||
pub(crate) use self::errors::ERROR_CODE_DOCUMENT_KIND_INVALID;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||
|
||||
@@ -44,6 +44,7 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
||||
save_config_document_source,
|
||||
get_config_profile_documents,
|
||||
get_config_profile_detail,
|
||||
get_environment_report,
|
||||
emit_frontend_log,
|
||||
splash_frontend_ready
|
||||
]);
|
||||
@@ -118,6 +119,17 @@ fn get_config_profile_detail(
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_environment_report(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<std::vec::Vec<crate::ConfigEnvironmentReportDto>, crate::CommandErrorDto> {
|
||||
let result = crate::environment::report(&state);
|
||||
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 emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||
let result = crate::emit_frontend_log_event(payload);
|
||||
|
||||
65
crates/ksp-app-config-desk/unit_tests/environment.rs
Normal file
65
crates/ksp-app-config-desk/unit_tests/environment.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
||||
assert_eq!(super::namespace_label("KSP_LOGS_DIRECTORY"), "KSP");
|
||||
assert_eq!(super::namespace_label("KSP_PUBLIC_RPC_URL"), "KSP_PUBLIC");
|
||||
assert_eq!(super::namespace_label("KSP_SECRET_API_KEY"), "KSP_SECRET");
|
||||
assert_eq!(super::namespace_label("KSPB_WORKER_MODE"), "KSPB");
|
||||
assert_eq!(super::namespace_label("KSPB_PUBLIC_ENDPOINT"), "KSPB_PUBLIC");
|
||||
assert_eq!(super::namespace_label("KSPB_SECRET_TOKEN"), "KSPB_SECRET");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn environment_labels_cover_config_sources_and_sensitivities() {
|
||||
assert_eq!(super::environment_source_label(std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Process)), "process");
|
||||
assert_eq!(super::environment_source_label(std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::DotEnv)), "dotenv");
|
||||
assert_eq!(super::environment_source_label(std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Fallback)), "fallback");
|
||||
assert_eq!(super::environment_source_label(std::option::Option::None), "none");
|
||||
assert_eq!(super::sensitivity_label(ksp_config_lib::ConfigSensitivity::Public), "public");
|
||||
assert_eq!(super::sensitivity_label(ksp_config_lib::ConfigSensitivity::Internal), "internal");
|
||||
assert_eq!(super::sensitivity_label(ksp_config_lib::ConfigSensitivity::Secret), "secret");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_environment_report_is_safe_and_deterministic_when_present() {
|
||||
let management = committed_management();
|
||||
assert!(management.is_ok(), "committed management should construct: {management:?}");
|
||||
if let std::result::Result::Ok(management) = management {
|
||||
let report = super::report_from_management(&management);
|
||||
assert!(report.is_ok(), "environment report should project safely: {report:?}");
|
||||
if let std::result::Result::Ok(report) = report {
|
||||
let mut previous = std::option::Option::<&str>::None;
|
||||
for entry in &report {
|
||||
if let std::option::Option::Some(previous) = previous {
|
||||
assert!(previous <= entry.variable_name.as_str());
|
||||
}
|
||||
if entry.sensitivity == "secret" {
|
||||
if let std::option::Option::Some(value) = entry.desired_safe_value.as_deref() {
|
||||
assert_eq!(value, ksp_config_lib::REDACTED_CONFIG_VALUE);
|
||||
}
|
||||
if let std::option::Option::Some(value) = entry.effective_safe_value.as_deref() {
|
||||
assert_eq!(value, ksp_config_lib::REDACTED_CONFIG_VALUE);
|
||||
}
|
||||
}
|
||||
previous = std::option::Option::Some(entry.variable_name.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn committed_management() -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
|
||||
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
|
||||
let registry = match registry {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry)));
|
||||
}
|
||||
Reference in New Issue
Block a user