From 72783f95c538f9b86e2320040a3902d26aea27a5 Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Sun, 27 Jul 2025 19:38:41 +0100 Subject: [PATCH 01/11] chore: bumped version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae99f02..04e0229 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,7 +771,7 @@ dependencies = [ [[package]] name = "evidenceangel" -version = "1.6.0-alpha.1" +version = "1.6.0-alpha.2" dependencies = [ "angelmark", "base64", diff --git a/Cargo.toml b/Cargo.toml index 11fb106..a102e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "evidenceangel" description = "Library and executables to work with EvidenceAngel evidence packages (*.evp)." -version = "1.6.0-alpha.1" +version = "1.6.0-alpha.2" edition = "2024" license = "GPL-3.0-or-later" authors = [ From 7233f2ab72eba8f785c0fa71e348912451e36740 Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 12:08:50 +0100 Subject: [PATCH 02/11] feat: added show all checkbox on HTML output resolves #211 --- src/exporters/html.css | 17 +++++++++++++++++ src/exporters/html.js | 17 +++++++++++++++++ src/exporters/html.rs | 1 + 3 files changed, 35 insertions(+) diff --git a/src/exporters/html.css b/src/exporters/html.css index ace8949..ea88d93 100644 --- a/src/exporters/html.css +++ b/src/exporters/html.css @@ -89,6 +89,18 @@ td { } @media screen { + .show-all { + .tabs { + display: none; + } + + .tab-content { + display: block !important; + border-top: 1px solid black; + margin-top: 2rem; + } + } + .tab-content.selected { display: block; } @@ -103,12 +115,17 @@ td { } @media print { + .print-hide { + display: none; + } + .tabs { display: none; } .tab-content { border-top: 1px solid black; + margin-top: 2rem; } } diff --git a/src/exporters/html.js b/src/exporters/html.js index e363180..8e33018 100644 --- a/src/exporters/html.js +++ b/src/exporters/html.js @@ -19,5 +19,22 @@ let tabAnchorMatch = () => { } }; +window.addEventListener('DOMContentLoaded', () => { + const showAll = document.getElementById('showAll'); + const body = document.querySelector('body'); + const updateShowAll = () => { + if (showAll.checked) { + body.classList.add('show-all'); + } else { + body.classList.remove('show-all'); + } + }; + showAll.addEventListener('click', () => { + updateShowAll(); + }); + // And in case the page has some reloaded state + updateShowAll() +}); + window.addEventListener("hashchange", tabAnchorMatch); window.addEventListener("DOMContentLoaded", tabAnchorMatch); diff --git a/src/exporters/html.rs b/src/exporters/html.rs index e6dbb57..ed4c710 100644 --- a/src/exporters/html.rs +++ b/src/exporters/html.rs @@ -105,6 +105,7 @@ impl Exporter for HtmlExporter { ); test_case_elems.push(elem); } + page.add_raw(r#""#); page.add_html(tab_container); for elem in test_case_elems { page.add_html(elem); From 6e374ac7186979a8867e28a84da82df9404db246 Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 12:24:28 +0100 Subject: [PATCH 03/11] feat: added syntax highlighting in HTML output for HTTP resolves #134 --- src/exporters/html.js | 4 +++- src/exporters/html.rs | 23 ++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/exporters/html.js b/src/exporters/html.js index 8e33018..37ba96e 100644 --- a/src/exporters/html.js +++ b/src/exporters/html.js @@ -33,7 +33,9 @@ window.addEventListener('DOMContentLoaded', () => { updateShowAll(); }); // And in case the page has some reloaded state - updateShowAll() + updateShowAll(); + + hljs.highlightAll(); }); window.addEventListener("hashchange", tabAnchorMatch); diff --git a/src/exporters/html.rs b/src/exporters/html.rs index ed4c710..083c564 100644 --- a/src/exporters/html.rs +++ b/src/exporters/html.rs @@ -31,7 +31,14 @@ impl Exporter for HtmlExporter { let mut page = HtmlPage::new() .with_title(html_escape::encode_text(package.metadata().title())) .with_style(include_str!("html.css")) - .with_script_literal(include_str!("html.js")); + .with_script_literal(include_str!("html.js")) + .with_stylesheet( + "https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/default.min.css", + ) + .with_script_link("https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js") + .with_script_link( + "https://unpkg.com/@highlightjs/cdn-assets@11.11.1/languages/http.min.js", + ); let title = HtmlElement::new(HtmlTag::Heading1) .with_raw(html_escape::encode_text(package.metadata().title())); @@ -362,16 +369,22 @@ fn create_test_case_div( HtmlElement::new(HtmlTag::Div) .with_attribute("class", "http-request") .with_html( - HtmlElement::new(HtmlTag::CodeText) - .with_preformatted(html_escape::encode_text(&request)), + HtmlElement::new(HtmlTag::PreformattedText).with_html( + HtmlElement::new(HtmlTag::CodeText) + .with_attribute("class", "language-http") + .with_raw(html_escape::encode_text(&request)), + ), ), ) .with_html( HtmlElement::new(HtmlTag::Div) .with_attribute("class", "http-response") .with_html( - HtmlElement::new(HtmlTag::CodeText) - .with_preformatted(html_escape::encode_text(&response)), + HtmlElement::new(HtmlTag::PreformattedText).with_html( + HtmlElement::new(HtmlTag::CodeText) + .with_attribute("class", "language-http") + .with_raw(html_escape::encode_text(&response)), + ), ), ), ); From 765c8ee3368dce12e63e3180ba3e44a4d63f3eaa Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 20:53:55 +0100 Subject: [PATCH 04/11] feat: added branding options resolves #209 --- README.md | 8 ++++++++ src/evidenceangel-ui/app.rs | 11 +++++++++++ src/exporters/excel.rs | 6 ++++++ src/exporters/html.rs | 17 +++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/README.md b/README.md index 49471dc..2eeca1b 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,11 @@ way to go. If you prefer to install manually, you can download a suitable package from the [GitHub Releases](https://github.com/lilopkins/angelsuite-installer/releases). + +## Branding + +If you wish to apply company branding to the program, please set the +`EA_BRAND_IMAGE` environment variable to an absolute path to a brand +image. This will apply the brand in the UI and in exported files. An +image with a ratio of 4:1 (width:height) works best. You can also set +`EA_BRAND_NAME` to set image alt text to your company name. diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index c702c1b..3ebafb3 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -346,6 +346,11 @@ impl Component for AppModel { #[watch] set_visible: model.open_package.is_some(), + #[name = "nav_branding"] + adw::Bin { + set_margin_vertical: 8, + }, + #[name = "nav_metadata"] gtk::Button { add_css_class: "flat", @@ -992,6 +997,12 @@ impl Component for AppModel { root.set_visible(true); } + if let Ok(branding_img) = std::env::var("EA_BRAND_IMAGE") { + widgets + .nav_branding + .set_child(Some(>k::Picture::for_filename(branding_img))); + } + ComponentParts { model, widgets } } diff --git a/src/exporters/excel.rs b/src/exporters/excel.rs index f3d3ec8..5da7d50 100644 --- a/src/exporters/excel.rs +++ b/src/exporters/excel.rs @@ -101,6 +101,12 @@ fn create_metadata_sheet( worksheet.write_string(row, 1, description)?; } + if let Ok(branding_img) = std::env::var("EA_BRAND_IMAGE") { + row += 2; + let image = Image::new(branding_img)?; + worksheet.insert_image(row, 1, &image)?; + } + Ok(()) } diff --git a/src/exporters/html.rs b/src/exporters/html.rs index 083c564..143cef7 100644 --- a/src/exporters/html.rs +++ b/src/exporters/html.rs @@ -70,6 +70,23 @@ impl Exporter for HtmlExporter { ); } + if let Ok(branding_img) = std::env::var("EA_BRAND_IMAGE") { + let data = fs::read(branding_img).map_err(|e| { + std::io::Error::other(format!("Failed to read company brand image: {e}")) + })?; + let src = format!( + "data:application/octet-stream;base64,{}", + base64::prelude::BASE64_STANDARD_NO_PAD.encode(data) + ); + page.add_image( + src, + format!( + "{} Logo", + std::env::var("EA_BRAND_NAME").unwrap_or("Brand".to_string()) + ), + ); + } + let test_cases: Vec<&TestCase> = package.test_case_iter()?.collect(); let mut first = true; let mut test_case_elems = vec![]; From baf9ea081d4f9f89c65edcb1f38b14bfcac29553 Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 21:08:09 +0100 Subject: [PATCH 05/11] feat: retain scroll position, as a start for #203 --- src/evidenceangel-ui/app.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index 3ebafb3..4e3e91d 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -1515,6 +1515,9 @@ impl Component for AppModel { } if let Some(pkg) = self.get_package() { + let adj = widgets.nav_scrolled_window.vadjustment(); + let scroll_position = adj.value(); + let mut new_order = pkg .read() .test_case_iter() @@ -1578,6 +1581,12 @@ impl Component for AppModel { sender.input(AppInput::NavigateTo(self.open_case)); pkg.write().set_test_case_order(new_order).unwrap(); self.needs_saving = true; + + // Restore scroll position + let adj = widgets.nav_scrolled_window.vadjustment(); + tracing::debug!("Scrolling to {scroll_position}"); + adj.set_value(scroll_position); + widgets.nav_scrolled_window.set_vadjustment(Some(&adj)); } } AppInput::CreateAuthor => { From 40b08caf6e5f3ec1af1ab0d68cbbcb74e03561de Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 21:09:26 +0100 Subject: [PATCH 06/11] chore: updated dependencies --- Cargo.lock | 136 +++++++++++++++++++++++++++++++---------------------- Cargo.toml | 2 +- 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04e0229..8e8dbe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,7 +164,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -435,7 +435,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -544,7 +544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -568,7 +568,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.104", ] [[package]] @@ -579,7 +579,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -597,6 +597,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -605,7 +616,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -626,7 +637,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -636,7 +647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.104", ] [[package]] @@ -679,7 +690,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -699,9 +710,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "elasticlunr-rs" @@ -815,11 +826,12 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "d6215aee357f8c7c989ebb4b8466ca4d7dc93b3957039f2fc3ea2ade8ea5f279" dependencies = [ "bit-set", + "derivative", "regex-automata", "regex-syntax", ] @@ -914,7 +926,7 @@ dependencies = [ "ignore", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "unic-langid", ] @@ -1056,7 +1068,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1190,7 +1202,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1320,7 +1332,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1333,7 +1345,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1475,7 +1487,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1959,7 +1971,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1984,9 +1996,9 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d" +checksum = "ffb1de698f287416adf20d0925832b20f2b14026110d9ff6d4e1fad43d42d5d0" dependencies = [ "ahash", "base64", @@ -2080,9 +2092,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" dependencies = [ "bitflags 2.9.1", "libc", @@ -2156,7 +2168,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2531,7 +2543,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2584,7 +2596,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2706,7 +2718,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2813,9 +2825,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -2848,14 +2860,14 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] name = "referencing" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e" +checksum = "e53649e498ada7d1dae579c08ad8dc043278980409fd42daeb0f6665005593fb" dependencies = [ "ahash", "fluent-uri", @@ -2938,7 +2950,7 @@ checksum = "5a895a7455441a857d100ca679bd24a92f91d28b5e3df63296792ac1af2eddde" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3075,7 +3087,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.104", ] [[package]] @@ -3122,7 +3134,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3133,7 +3145,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3311,6 +3323,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -3339,7 +3362,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3430,7 +3453,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3441,7 +3464,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3619,7 +3642,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3744,7 +3767,7 @@ checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" dependencies = [ "proc-macro-hack", "quote", - "syn", + "syn 2.0.104", "unic-langid-impl", ] @@ -3899,7 +3922,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -3934,7 +3957,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4022,7 +4045,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4033,7 +4056,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4075,7 +4098,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -4096,10 +4119,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4260,7 +4284,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -4281,7 +4305,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4301,7 +4325,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -4322,7 +4346,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4355,7 +4379,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4452,7 +4476,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "zvariant_utils", ] @@ -4464,5 +4488,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index a102e86..78a0e27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ fluent-templates = { version = "0.13.0", optional = true } getset = "0.1.2" html-escape = { version = "0.2.13", optional = true } infer = "0.19.0" -jsonschema = "0.30.0" +jsonschema = "0.31.0" open = { version = "5.3.0", optional = true } parking_lot = { version = "0.12.3", optional = true } parse_datetime = { version = "0.10.0", optional = true } From a1f3eea3c8cc8d4c2fde57c179eaeff953f66c7d Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 22:10:52 +0100 Subject: [PATCH 07/11] feat: added extra fields for moving test cases part of #142 --- src/evidenceangel-ui/app.rs | 15 ++++++++++---- src/evidenceangel-ui/nav_factory.rs | 31 ++++++++++++++++++++--------- src/evidenceangel-ui/util.rs | 19 +++++++++--------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index 4e3e91d..18ba041 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -41,7 +41,7 @@ use crate::{ evidence_factory::{EvidenceFactoryInit, EvidenceFactoryModel, EvidenceFactoryOutput}, filter, lang, lang_args, nav_factory::{NavFactoryInit, NavFactoryInput, NavFactoryModel, NavFactoryOutput}, - util::{BoxedEvidenceJson, BoxedTestCaseById}, + util::{BoxedEvidenceJson, BoxedTestCase}, }; relm4::new_action_group!(MenuActionGroup, "menu"); @@ -151,6 +151,7 @@ impl AppModel { for case in pkg.test_case_iter()? { test_case_data.push_back(NavFactoryInit { + evp_path: self.open_path.clone().unwrap(), id: *case.id(), name: case.metadata().title().clone(), status: *case.metadata().passed(), @@ -373,12 +374,12 @@ impl Component for AppModel { add_controller = gtk::DropTarget { set_actions: gtk::gdk::DragAction::MOVE, - set_types: &[BoxedTestCaseById::static_type()], + set_types: &[BoxedTestCase::static_type()], connect_drop[sender] => move |_slf, val, _x, _y| { tracing::debug!("Dropped type: {:?}", val.type_()); - if let Ok(data) = val.get::() { - let dropped_case = data.inner(); + if let Ok(data) = val.get::() { + let dropped_case = *data.test_case_id(); tracing::debug!("Dropped case: {dropped_case:?}"); sender.input(AppInput::MoveTestCase { case_to_move: dropped_case, before: None, offset: None }); return true; @@ -1376,6 +1377,7 @@ impl Component for AppModel { // Add case to navigation let mut test_case_data = self.test_case_nav_factory.guard(); test_case_data.push_back(NavFactoryInit { + evp_path: self.open_path.clone().unwrap(), id: case_id, name: case.metadata().title().clone(), status: *case.metadata().passed(), @@ -1425,6 +1427,7 @@ impl Component for AppModel { // Add case to navigation let mut test_case_data = self.test_case_nav_factory.guard(); test_case_data.push_back(NavFactoryInit { + evp_path: self.open_path.clone().unwrap(), id: new_case_id, name: case.metadata().title().clone(), status: *case.metadata().passed(), @@ -1528,6 +1531,7 @@ impl Component for AppModel { new_order.remove(pos); let mut test_case_guard = self.test_case_nav_factory.guard(); let NavFactoryModel { + evp_path, id, name, status, @@ -1544,6 +1548,7 @@ impl Component for AppModel { test_case_guard.insert( other_pos, NavFactoryInit { + evp_path, id, name, status, @@ -1560,6 +1565,7 @@ impl Component for AppModel { test_case_guard.insert( new_pos, NavFactoryInit { + evp_path, id, name, status, @@ -1570,6 +1576,7 @@ impl Component for AppModel { // add to end new_order.push(case_to_move); test_case_guard.push_back(NavFactoryInit { + evp_path, id, name, status, diff --git a/src/evidenceangel-ui/nav_factory.rs b/src/evidenceangel-ui/nav_factory.rs index c24eade..c50e01b 100644 --- a/src/evidenceangel-ui/nav_factory.rs +++ b/src/evidenceangel-ui/nav_factory.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use evidenceangel::TestCasePassStatus; use gtk::prelude::*; use relm4::{ @@ -8,10 +10,11 @@ use relm4::{ }; use uuid::Uuid; -use crate::{lang, util::BoxedTestCaseById}; +use crate::{lang, util::BoxedTestCase}; pub struct NavFactoryModel { selected: bool, + pub evp_path: PathBuf, pub name: String, pub status: Option, pub id: Uuid, @@ -33,6 +36,7 @@ pub enum NavFactoryOutput { } pub struct NavFactoryInit { + pub evp_path: PathBuf, pub id: Uuid, pub name: String, pub status: Option, @@ -59,22 +63,28 @@ impl FactoryComponent for NavFactoryModel { add_controller = gtk::DragSource { set_actions: gtk::gdk::DragAction::MOVE, - connect_prepare[id] => move |_slf, _x, _y| { - let dnd_data = BoxedTestCaseById::new(id); + connect_prepare[evp_path, id] => move |_slf, _x, _y| { + let dnd_data = BoxedTestCase::new(evp_path.clone(), id); tracing::debug!("Drag case started: {dnd_data:?}"); Some(gtk::gdk::ContentProvider::for_value(&dnd_data.to_value())) } }, add_controller = gtk::DropTarget { set_actions: gtk::gdk::DragAction::MOVE, - set_types: &[BoxedTestCaseById::static_type()], + set_types: &[BoxedTestCase::static_type()], - connect_drop[sender, id] => move |_slf, val, _x, _y| { + connect_drop[sender, evp_path, id] => move |_slf, val, _x, _y| { tracing::debug!("Dropped type: {:?}", val.type_()); - if let Ok(data) = val.get::() { - let dropped_case = data.inner(); - tracing::debug!("Dropped case: {dropped_case:?}"); - sender.output(NavFactoryOutput::MoveBefore { case_to_move: dropped_case, before: id }).unwrap(); + if let Ok(data) = val.get::() { + if *data.evidence_package_path() == evp_path { + let dropped_case = *data.test_case_id(); + tracing::debug!("Dropped case from save EVP: {dropped_case:?}"); + sender.output(NavFactoryOutput::MoveBefore { case_to_move: dropped_case, before: id }).unwrap(); + } else { + let case_id = *data.test_case_id(); + tracing::debug!("Dropped case from another EVP ({}): {case_id:?}", data.evidence_package_path().display()); + // TODO Load case from other EVP + } return true; } false @@ -122,6 +132,7 @@ impl FactoryComponent for NavFactoryModel { fn init_model(init: Self::Init, _index: &DynamicIndex, _sender: FactorySender) -> Self { let Self::Init { + evp_path, name, id, status, @@ -130,6 +141,7 @@ impl FactoryComponent for NavFactoryModel { } = init; Self { selected: false, + evp_path, name, id, status, @@ -145,6 +157,7 @@ impl FactoryComponent for NavFactoryModel { sender: FactorySender, ) -> Self::Widgets { let id = self.id; + let evp_path = self.evp_path.clone(); let widgets = view_output!(); widgets } diff --git a/src/evidenceangel-ui/util.rs b/src/evidenceangel-ui/util.rs index 0f67864..c908681 100644 --- a/src/evidenceangel-ui/util.rs +++ b/src/evidenceangel-ui/util.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use evidenceangel::Evidence; use getset::Getters; use relm4::gtk::glib; @@ -21,18 +23,15 @@ impl BoxedEvidenceJson { } #[derive(Clone, Debug, PartialEq, Eq, Getters, glib::Boxed)] -#[boxed_type(name = "BoxedTestCaseById")] +#[boxed_type(name = "BoxedTestCase")] #[getset(get = "pub")] -pub struct BoxedTestCaseById { - data: Uuid, +pub struct BoxedTestCase { + evidence_package_path: PathBuf, + test_case_id: Uuid, } -impl BoxedTestCaseById { - pub fn new(data: Uuid) -> Self { - Self { data } - } - - pub fn inner(self) -> Uuid { - self.data +impl BoxedTestCase { + pub fn new(evidence_package_path: PathBuf, test_case_id: Uuid) -> Self { + Self { evidence_package_path, test_case_id } } } From 8d53c5f82bbbce2c255d35c18d5727bbe3c19a6e Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 22:27:11 +0100 Subject: [PATCH 08/11] feat: added lock release in UI resolves #200 --- src/evidenceangel-ui/app.rs | 12 +++++++++++- src/evidenceangel-ui/dialogs/error.rs | 24 +++++++++++++++++++++--- src/evidenceangel-ui/locales/en/main.ftl | 2 ++ src/evidenceangel-ui/locales/sv/main.ftl | 4 +++- src/result.rs | 2 +- src/zip_read_writer.rs | 2 +- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index 18ba041..802ec3a 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -1090,7 +1090,8 @@ impl Component for AppModel { ); } AppInput::__OpenFile(path) => { - if let Err(e) = self.open(path) { + if let Err(e) = self.open(path.clone()) { + // Show dialog with button to delete lock let error_dlg = ErrorDialogModel::builder() .launch(ErrorDialogInit { title: Box::new(lang::lookup("error-failed-open-title")), @@ -1100,6 +1101,15 @@ impl Component for AppModel { )), }) .forward(sender.input_sender(), |msg| match msg {}); + if matches!(e, evidenceangel::Error::LockNotObtained) { + // also offer to release lock + let lock_file_name = format!( + ".~lock.{}#", + path.file_name().unwrap().to_str().unwrap() + ); + let lock_file = path.clone().with_file_name(lock_file_name); + error_dlg.emit(ErrorDialogInput::OfferLockRelease { lock_file }) + } error_dlg.emit(ErrorDialogInput::Present(root.clone())); self.latest_error_dlg = Some(error_dlg); } diff --git a/src/evidenceangel-ui/dialogs/error.rs b/src/evidenceangel-ui/dialogs/error.rs index ff9bd15..5bee443 100644 --- a/src/evidenceangel-ui/dialogs/error.rs +++ b/src/evidenceangel-ui/dialogs/error.rs @@ -1,7 +1,8 @@ +use std::{fs, path::PathBuf}; + use adw::prelude::*; use relm4::{ - Component, ComponentParts, ComponentSender, - adw::{self, ApplicationWindow}, + adw::{self, ApplicationWindow}, gtk::gio::Cancellable, Component, ComponentParts, ComponentSender }; use crate::lang; @@ -9,6 +10,7 @@ use crate::lang; pub struct ErrorDialogModel { title: String, body: String, + lock_file: Option, } pub struct ErrorDialogInit { @@ -18,6 +20,9 @@ pub struct ErrorDialogInit { #[derive(Debug)] pub enum ErrorDialogInput { + OfferLockRelease { + lock_file: PathBuf, + }, Present(ApplicationWindow), } @@ -52,6 +57,7 @@ impl Component for ErrorDialogModel { let model = ErrorDialogModel { title: title.to_string(), body: body.to_string(), + lock_file: None, }; let widgets = view_output!(); ComponentParts { model, widgets } @@ -66,7 +72,19 @@ impl Component for ErrorDialogModel { ) { match message { ErrorDialogInput::Present(window) => { - root.present(Some(&window)); + let lock_file = self.lock_file.clone().unwrap(); + root.clone().choose(&window, None::<&Cancellable>, move |response| { + if response == "unlock" { + // SAFETY: unlock isn't an option without a lock file + if let Err(e) = fs::remove_file(lock_file) { + tracing::warn!("Failed to remove lock file: {e}"); + } + } + }); + } + ErrorDialogInput::OfferLockRelease { lock_file } => { + root.add_response("unlock", &lang::lookup("delete-lock")); + self.lock_file = Some(lock_file); } } self.update_view(widgets, sender); diff --git a/src/evidenceangel-ui/locales/en/main.ftl b/src/evidenceangel-ui/locales/en/main.ftl index 93fc76e..4fbfaca 100644 --- a/src/evidenceangel-ui/locales/en/main.ftl +++ b/src/evidenceangel-ui/locales/en/main.ftl @@ -16,6 +16,8 @@ header-paste-evidence = Paste Evidence header-export-package = Export Package... header-export-test-case = Export Test Case... +delete-lock = Try to delete lock + paste-evidence-failed = Failed to paste. paste-evidence-wrong-type = Cannot paste this type of data. diff --git a/src/evidenceangel-ui/locales/sv/main.ftl b/src/evidenceangel-ui/locales/sv/main.ftl index 610ad0d..c468154 100644 --- a/src/evidenceangel-ui/locales/sv/main.ftl +++ b/src/evidenceangel-ui/locales/sv/main.ftl @@ -1,5 +1,5 @@ app-name = EvidenceAngel -ok = Ok +ok = Okej cancel = Avbryt invalid-data = Ogiltiga data select = Välj @@ -16,6 +16,8 @@ header-paste-evidence = Klistra in bevis header-export-package = Exportera Paket... header-export-test-case = Exportera Testfall... +delete-lock = Försöka att ta bort låset + paste-evidence-failed = Det gick inte att klistra in. paste-evidence-wrong-type = Det går inte att klistra in den här typen av data. diff --git a/src/result.rs b/src/result.rs index 980c6e6..91e11b4 100644 --- a/src/result.rs +++ b/src/result.rs @@ -5,7 +5,7 @@ use uuid::Uuid; #[derive(Debug, Error)] pub enum Error { /// You are trying to perform an operation without a lock on the package. - #[error("The file you are working with is already open. Please close it and try again.")] + #[error("The file you are working with is already open. Please close it and try again. If you are sure no one else if working with it, you can delete the lock file and try again.")] LockNotObtained, /// An I/O error from the system. diff --git a/src/zip_read_writer.rs b/src/zip_read_writer.rs index 4708d7e..8ac3097 100644 --- a/src/zip_read_writer.rs +++ b/src/zip_read_writer.rs @@ -76,7 +76,7 @@ impl ZipReaderWriter { /// Update the locking file for this [`ZipReaderWriter`]. This will /// either obtain it (if a path is set), drop it (if a path isn't - /// set), or will return a [`crate::Error::Locking`] error. + /// set), or will return a [`crate::Error::LockNotObtained`] error. fn update_lock_file(&mut self) -> crate::Result<()> { if let Some(path) = &self.path { let mut lock_path = path.clone(); From a99b56793fcf6192c2b478727fd7e491c2e99a5e Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 22:32:39 +0100 Subject: [PATCH 09/11] ci: migrated doc publish to ssh key --- .github/workflows/publish-docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index caf864c..efbf733 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -32,7 +32,7 @@ jobs: with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} - password: ${{ secrets.SSH_PASSWORD }} + key: ${{ secrets.SSH_KEY }} source: docs/${{ steps.version.outputs.CARGO_PKG_VERSION }} target: ${{ secrets.SSH_DOCS_TARGET_PATH }} From 3a163cb8ba291608f9272e795bc2f945326eb1b4 Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 22:32:55 +0100 Subject: [PATCH 10/11] style: reformatted --- src/evidenceangel-ui/app.rs | 6 ++---- src/evidenceangel-ui/dialogs/error.rs | 23 ++++++++++++----------- src/evidenceangel-ui/util.rs | 5 ++++- src/result.rs | 4 +++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index 802ec3a..4e374bc 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -1103,10 +1103,8 @@ impl Component for AppModel { .forward(sender.input_sender(), |msg| match msg {}); if matches!(e, evidenceangel::Error::LockNotObtained) { // also offer to release lock - let lock_file_name = format!( - ".~lock.{}#", - path.file_name().unwrap().to_str().unwrap() - ); + let lock_file_name = + format!(".~lock.{}#", path.file_name().unwrap().to_str().unwrap()); let lock_file = path.clone().with_file_name(lock_file_name); error_dlg.emit(ErrorDialogInput::OfferLockRelease { lock_file }) } diff --git a/src/evidenceangel-ui/dialogs/error.rs b/src/evidenceangel-ui/dialogs/error.rs index 5bee443..d06896e 100644 --- a/src/evidenceangel-ui/dialogs/error.rs +++ b/src/evidenceangel-ui/dialogs/error.rs @@ -2,7 +2,9 @@ use std::{fs, path::PathBuf}; use adw::prelude::*; use relm4::{ - adw::{self, ApplicationWindow}, gtk::gio::Cancellable, Component, ComponentParts, ComponentSender + Component, ComponentParts, ComponentSender, + adw::{self, ApplicationWindow}, + gtk::gio::Cancellable, }; use crate::lang; @@ -20,9 +22,7 @@ pub struct ErrorDialogInit { #[derive(Debug)] pub enum ErrorDialogInput { - OfferLockRelease { - lock_file: PathBuf, - }, + OfferLockRelease { lock_file: PathBuf }, Present(ApplicationWindow), } @@ -73,14 +73,15 @@ impl Component for ErrorDialogModel { match message { ErrorDialogInput::Present(window) => { let lock_file = self.lock_file.clone().unwrap(); - root.clone().choose(&window, None::<&Cancellable>, move |response| { - if response == "unlock" { - // SAFETY: unlock isn't an option without a lock file - if let Err(e) = fs::remove_file(lock_file) { - tracing::warn!("Failed to remove lock file: {e}"); + root.clone() + .choose(&window, None::<&Cancellable>, move |response| { + if response == "unlock" { + // SAFETY: unlock isn't an option without a lock file + if let Err(e) = fs::remove_file(lock_file) { + tracing::warn!("Failed to remove lock file: {e}"); + } } - } - }); + }); } ErrorDialogInput::OfferLockRelease { lock_file } => { root.add_response("unlock", &lang::lookup("delete-lock")); diff --git a/src/evidenceangel-ui/util.rs b/src/evidenceangel-ui/util.rs index c908681..ff76df5 100644 --- a/src/evidenceangel-ui/util.rs +++ b/src/evidenceangel-ui/util.rs @@ -32,6 +32,9 @@ pub struct BoxedTestCase { impl BoxedTestCase { pub fn new(evidence_package_path: PathBuf, test_case_id: Uuid) -> Self { - Self { evidence_package_path, test_case_id } + Self { + evidence_package_path, + test_case_id, + } } } diff --git a/src/result.rs b/src/result.rs index 91e11b4..d3df935 100644 --- a/src/result.rs +++ b/src/result.rs @@ -5,7 +5,9 @@ use uuid::Uuid; #[derive(Debug, Error)] pub enum Error { /// You are trying to perform an operation without a lock on the package. - #[error("The file you are working with is already open. Please close it and try again. If you are sure no one else if working with it, you can delete the lock file and try again.")] + #[error( + "The file you are working with is already open. Please close it and try again. If you are sure no one else if working with it, you can delete the lock file and try again." + )] LockNotObtained, /// An I/O error from the system. From f2002dc3ae70fbbe154a7637852e5478c0550ffa Mon Sep 17 00:00:00 2001 From: Lily Hopkins Date: Mon, 28 Jul 2025 22:33:08 +0100 Subject: [PATCH 11/11] refactor: applied clippy suggestions --- src/evidenceangel-ui/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evidenceangel-ui/app.rs b/src/evidenceangel-ui/app.rs index 4e374bc..be3275a 100644 --- a/src/evidenceangel-ui/app.rs +++ b/src/evidenceangel-ui/app.rs @@ -1106,7 +1106,7 @@ impl Component for AppModel { let lock_file_name = format!(".~lock.{}#", path.file_name().unwrap().to_str().unwrap()); let lock_file = path.clone().with_file_name(lock_file_name); - error_dlg.emit(ErrorDialogInput::OfferLockRelease { lock_file }) + error_dlg.emit(ErrorDialogInput::OfferLockRelease { lock_file }); } error_dlg.emit(ErrorDialogInput::Present(root.clone())); self.latest_error_dlg = Some(error_dlg);