v0.3.8-pre.007-fix.002
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-store-desk/frontend/main.html -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -197,7 +197,13 @@
|
||||
</dl>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 mb-2">
|
||||
<h3 class="h6 mb-0">Payload preview hex</h3>
|
||||
<span id="transactionDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button id="transactionDetailPayloadPreviewCopy" class="btn btn-sm btn-outline-secondary" type="button" title="Copier le preview hex affiché" aria-label="Copier le preview hex affiché" hidden>
|
||||
<i class="fa-regular fa-copy" aria-hidden="true"></i>
|
||||
<span>Copier</span>
|
||||
</button>
|
||||
<span id="transactionDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="transactionDetailPayloadPreview" class="small bg-body-tertiary border rounded p-3 overflow-auto text-break mb-0">—</pre>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-store-desk/frontend/ts/main.ts
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
import DataTable from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
@@ -133,6 +133,7 @@ async function copyLongText(button: HTMLButtonElement, value: string, fieldId: s
|
||||
}
|
||||
const icon = button.querySelector<HTMLElement>("i");
|
||||
const previousClassName = icon?.className ?? "";
|
||||
const previousTitle = button.title;
|
||||
if (icon) {
|
||||
icon.className = "fa-solid fa-check";
|
||||
}
|
||||
@@ -142,7 +143,7 @@ async function copyLongText(button: HTMLButtonElement, value: string, fieldId: s
|
||||
if (icon) {
|
||||
icon.className = previousClassName;
|
||||
}
|
||||
button.title = "Copier la valeur complète";
|
||||
button.title = previousTitle;
|
||||
}, COPY_FEEDBACK_MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -428,6 +429,12 @@ function clearTransactionDetail(): void {
|
||||
}
|
||||
setVisible("transactionDetailError", false);
|
||||
setVisible("transactionDetailPreviewTruncated", false);
|
||||
const payloadPreviewCopy = document.querySelector<HTMLButtonElement>("#transactionDetailPayloadPreviewCopy");
|
||||
if (payloadPreviewCopy) {
|
||||
delete payloadPreviewCopy.dataset.copyLongText;
|
||||
delete payloadPreviewCopy.dataset.copyField;
|
||||
payloadPreviewCopy.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderTransactionDetail(detail: StoreTransactionDetailDto): void {
|
||||
@@ -438,7 +445,18 @@ function renderTransactionDetail(detail: StoreTransactionDetailDto): void {
|
||||
setCopyableLongText("transactionDetailContentHash", detail.contentHash, "transaction-detail-content-hash");
|
||||
setText("transactionDetailRetention", detail.retentionState);
|
||||
setText("transactionDetailPayloadSize", detail.payloadSizeDecimal ?? "—");
|
||||
setText("transactionDetailPayloadPreview", detail.payloadPreviewHex ?? "Payload non disponible dans cet état de rétention.");
|
||||
const payloadPreview = detail.payloadPreviewHex;
|
||||
setText("transactionDetailPayloadPreview", payloadPreview ?? "Payload non disponible dans cet état de rétention.");
|
||||
const payloadPreviewCopy = document.querySelector<HTMLButtonElement>("#transactionDetailPayloadPreviewCopy");
|
||||
if (payloadPreviewCopy && payloadPreview) {
|
||||
payloadPreviewCopy.dataset.copyLongText = payloadPreview;
|
||||
payloadPreviewCopy.dataset.copyField = "transaction-payload-preview";
|
||||
payloadPreviewCopy.hidden = false;
|
||||
} else if (payloadPreviewCopy) {
|
||||
delete payloadPreviewCopy.dataset.copyLongText;
|
||||
delete payloadPreviewCopy.dataset.copyField;
|
||||
payloadPreviewCopy.hidden = true;
|
||||
}
|
||||
setVisible("transactionDetailPreviewTruncated", detail.payloadPreviewTruncated);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-store-desk/tests/desktop_contract.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Desktop template and frontend contract tests for the Store Desk scaffold.
|
||||
|
||||
@@ -88,7 +88,8 @@ fn pre_007_shell_exposes_transaction_filters_detail_and_keeps_account_skeleton_u
|
||||
}
|
||||
assert!(html.contains("id=\"rawTransactionsTable\""));
|
||||
assert!(html.contains("id=\"rawAccountsTable\""));
|
||||
for overview_id in ["refreshOverview", "overviewStoreProfile", "overviewStoreTarget", "overviewStoreHealth", "overviewStorePool", "overviewStoreMigration"] {
|
||||
for overview_id in ["refreshOverview", "overviewStoreProfile", "overviewStoreTarget", "overviewStoreHealth", "overviewStorePool", "overviewStoreMigration"]
|
||||
{
|
||||
assert!(html.contains(&format!("id=\"{overview_id}\"")), "missing Store Overview field: {overview_id}");
|
||||
}
|
||||
for transaction_id in ["refreshTransactions", "transactionSlotMin", "transactionSlotMax", "transactionDirection", "transactionDetailModal"] {
|
||||
@@ -202,14 +203,31 @@ fn pre_007_fix_001_long_transaction_identifiers_are_truncated_tooltipped_and_cop
|
||||
let frontend = include_str!("../frontend/ts/main.ts");
|
||||
let sass = include_str!("../frontend/sass/_app.scss");
|
||||
assert!(frontend.contains("truncateLongText"));
|
||||
assert!(frontend.contains("renderCopyableLongText(String(data), "transaction-signature")"));
|
||||
assert!(frontend.contains("renderCopyableLongText(String(data), "transaction-content-hash")"));
|
||||
assert!(frontend.contains("setCopyableLongText("transactionDetailSignature""));
|
||||
assert!(frontend.contains("setCopyableLongText("transactionDetailContentHash""));
|
||||
assert!(frontend.contains("title="${escapedValue}""));
|
||||
assert!(frontend.contains(r#"renderCopyableLongText(String(data), "transaction-signature")"#));
|
||||
assert!(frontend.contains(r#"renderCopyableLongText(String(data), "transaction-content-hash")"#));
|
||||
assert!(frontend.contains(r#"setCopyableLongText("transactionDetailSignature""#));
|
||||
assert!(frontend.contains(r#"setCopyableLongText("transactionDetailContentHash""#));
|
||||
assert!(frontend.contains(r#"title="${escapedValue}""#));
|
||||
assert!(frontend.contains("data-copy-long-text"));
|
||||
assert!(frontend.contains("navigator.clipboard.writeText"));
|
||||
assert!(frontend.contains("document.execCommand("copy")"));
|
||||
assert!(frontend.contains(r#"document.execCommand("copy")"#));
|
||||
assert!(sass.contains(".app-copyable-long-text__value"));
|
||||
assert!(sass.contains("text-overflow: ellipsis"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_007_fix_002_payload_preview_remains_bounded_and_is_explicitly_copyable() {
|
||||
let frontend = include_str!("../frontend/ts/main.ts");
|
||||
let html = include_str!("../frontend/main.html");
|
||||
let runtime = include_str!("../src/store_runtime.rs");
|
||||
assert!(runtime.contains("const TRANSACTION_DETAIL_PREVIEW_BYTES: usize = 512;"));
|
||||
assert!(html.contains("transactionDetailPayloadPreviewCopy"));
|
||||
assert!(html.contains("Copier le preview hex affiché"));
|
||||
assert!(html.contains("Preview tronquée à 512 bytes"));
|
||||
assert!(frontend.contains(r#"payloadPreviewCopy.dataset.copyField = "transaction-payload-preview""#));
|
||||
assert!(frontend.contains("payloadPreviewCopy.dataset.copyLongText = payloadPreview"));
|
||||
assert!(frontend.contains("payloadPreviewCopy.hidden = false"));
|
||||
assert!(frontend.contains("payloadPreviewCopy.hidden = true"));
|
||||
assert!(frontend.contains("const previousTitle = button.title"));
|
||||
assert!(frontend.contains("button.title = previousTitle"));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-store-desk/tests/desktop_security.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Security boundary tests for the Store Desk scaffold.
|
||||
|
||||
@@ -137,13 +137,22 @@ fn pre_007_fix_001_copy_tracing_never_logs_long_identifier_values() {
|
||||
assert!(frontend.contains("Store Desk long text copy failed"));
|
||||
assert!(frontend.contains("{ fieldId }"));
|
||||
for forbidden in [
|
||||
"copy requested", { value",
|
||||
"copy completed", { value",
|
||||
"copy failed", { value",
|
||||
"copy requested", { signature",
|
||||
"copy completed", { signature",
|
||||
"copy failed", { signature",
|
||||
r#"{ fieldId, value }"#,
|
||||
r#"{ value, fieldId }"#,
|
||||
r#"{ value }"#,
|
||||
r#"{ signature }"#,
|
||||
r#"{ fieldId, signature }"#,
|
||||
r#"{ signature, fieldId }"#,
|
||||
] {
|
||||
assert!(!frontend.contains(forbidden), "long identifier value leaked into copy tracing: {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_007_fix_002_payload_preview_copy_never_logs_preview_material() {
|
||||
let frontend = include_str!("../frontend/ts/main.ts");
|
||||
assert!(frontend.contains(r#"payloadPreviewCopy.dataset.copyField = "transaction-payload-preview""#));
|
||||
assert!(!frontend.contains(r#"frontendDebug("main", "Store Desk long text copy requested", { fieldId, value })"#));
|
||||
assert!(!frontend.contains(r#"frontendDebug("main", "Store Desk long text copy completed", { fieldId, value })"#));
|
||||
assert!(!frontend.contains(r#"frontendError("main", "Store Desk long text copy failed", { fieldId, value })"#));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user