From 7b47efd66dd62a2874c5359782633f8f52b367a3 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 7 Sep 2024 17:31:06 -0600 Subject: [PATCH] Some updates --- Cargo.lock | 212 +++++++++++++++++++---------------------- Cargo.toml | 4 +- css/main.scss | 5 +- js/graph.ts | 87 +++++++++++------ src/handlers/js.rs | 8 +- src/handlers/mod.rs | 57 ++++++++++- src/handlers/report.rs | 107 ++++++++++++++------- src/main.rs | 12 ++- src/util.rs | 50 ++++++++++ templates/report.html | 16 ++-- templates/treemap.svg | 2 +- 11 files changed, 361 insertions(+), 199 deletions(-) create mode 100644 src/util.rs diff --git a/Cargo.lock b/Cargo.lock index b6ac895..fe7d3de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "approx" @@ -185,7 +185,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -235,13 +235,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "a93fe60e2fc87b6ba2c117f67ae14f66e3fc7d6a1e612a25adb238cc980eadb3" dependencies = [ "jobserver", "libc", @@ -591,18 +591,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -725,9 +725,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -855,7 +855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -947,7 +947,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "treemap", + "url", "zip", "zstd", ] @@ -980,7 +980,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1003,7 +1003,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1147,9 +1147,9 @@ checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1292,7 +1292,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1391,7 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" dependencies = [ "codemap", - "indexmap 2.4.0", + "indexmap 2.5.0", "lasso", "once_cell", "phf 0.11.2", @@ -1705,9 +1705,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -1741,7 +1741,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1917,7 +1917,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall", ] [[package]] @@ -2092,7 +2092,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2120,6 +2120,7 @@ dependencies = [ "memo-map", "self_cell", "serde", + "serde_json", ] [[package]] @@ -2307,7 +2308,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2527,7 +2528,7 @@ checksum = "807868208f9a594a88f6714dae60bc8bed4ccb87a5d3fd33ed946f6fc8a216de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2657,7 +2658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd200ec63c6bcb0251af23608e741449add80be84c925f2ba2bbc66ac614e7a6" dependencies = [ "assert-unchecked", - "indexmap 2.4.0", + "indexmap 2.5.0", "itertools 0.13.0", "oxc_allocator", "oxc_ast", @@ -2722,7 +2723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0bed15e5c31df9b299da2d24fda74f6954ffcb7d77940872e279e7fb0c6258" dependencies = [ "dashmap 6.1.0", - "indexmap 2.4.0", + "indexmap 2.5.0", "oxc-browserslist", "oxc_allocator", "oxc_ast", @@ -2774,7 +2775,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2831,7 +2832,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -2902,7 +2903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.5.0", ] [[package]] @@ -2964,7 +2965,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -3008,7 +3009,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -3091,7 +3092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -3119,14 +3120,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "prost" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" dependencies = [ "bytes", "prost-derive", @@ -3134,9 +3135,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck", @@ -3149,28 +3150,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.76", + "syn 2.0.77", "tempfile", ] [[package]] name = "prost-derive" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "prost-types" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" dependencies = [ "prost", ] @@ -3348,15 +3349,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.3" @@ -3548,9 +3540,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.6.0", "errno", @@ -3731,9 +3723,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -3751,13 +3743,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -3768,14 +3760,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -3820,7 +3812,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "itoa", "ryu", "serde", @@ -4003,7 +3995,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -4037,9 +4029,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", @@ -4047,9 +4039,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4060,9 +4052,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "byteorder", @@ -4080,7 +4072,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.4.0", + "indexmap 2.5.0", "log", "memchr", "once_cell", @@ -4100,22 +4092,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "sqlx-macros-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", @@ -4131,7 +4123,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.76", + "syn 2.0.77", "tempfile", "tokio", "url", @@ -4139,9 +4131,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", @@ -4182,9 +4174,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", @@ -4221,9 +4213,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "chrono", @@ -4309,7 +4301,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -4341,9 +4333,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -4434,7 +4426,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -4580,7 +4572,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -4596,9 +4588,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -4607,9 +4599,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -4645,7 +4637,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -4736,7 +4728,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -4778,12 +4770,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "treemap" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1571f89da27a5e1aa83304ee1ab9519ea8c6432b4c8903aaaa6c9a9eecb6f36" - [[package]] name = "triomphe" version = "0.1.11" @@ -4817,7 +4803,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -5091,7 +5077,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -5113,7 +5099,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5142,11 +5128,11 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", ] @@ -5446,7 +5432,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -5466,7 +5452,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.4.0", + "indexmap 2.5.0", "memchr", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 67c2159..e4af10a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ grass = "0.13" image = "0.25" lightningcss = "1.0.0-alpha" mime = "0.3" -minijinja = { version = "2.2", features = ["loader"] } +minijinja = { version = "2.2", features = ["loader", "json"] } minijinja-autoreload = "2.2" moka = { version = "0.12", features = ["future"] } objdiff-core = { git = "https://github.com/encounter/objdiff", tag = "v2.0.0-beta.6", features = ["bindings"] } @@ -36,6 +36,6 @@ tower = { version = "0.4", features = ["full"] } tower-http = { version = "0.5", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -treemap = "0.3" +url = { version = "2.5", features = ["serde"] } zip = { version = "2.2", default-features = false, features = ["deflate-flate2"] } zstd = "0.13" diff --git a/css/main.scss b/css/main.scss index e725fa9..1e37c4c 100644 --- a/css/main.scss +++ b/css/main.scss @@ -255,7 +255,8 @@ $unit-size: 0.75rem; margin-bottom: var(--pico-spacing); touch-action: none; // Accessed via JS - --font-weight: 400; - --font-size: 1rem; + --font-weight: normal; + --font-size: 0.875rem; --font-family: var(--pico-font-family); + --tooltip-background: var(--pico-tooltip-background-color); } diff --git a/js/graph.ts b/js/graph.ts index 4170734..6630855 100644 --- a/js/graph.ts +++ b/js/graph.ts @@ -10,48 +10,75 @@ type Unit = { const unitBounds = (unit: Unit, width: number, height: number) => { return { - x: (unit.x / 100) * width, - y: (unit.y / 100) * height, - w: (unit.w / 100) * width, - h: (unit.h / 100) * height, + x: unit.x * width, + y: unit.y * height, + w: unit.w * width, + h: unit.h * height, }; } -const TOOLTIP_PADDING = 10; -const TOOLTIP_MARGIN = 10; +const BORDER_RADIUS = 5; +const PADDING_W = 10; +const PADDING_H = 5; +const MARGIN = 5; const drawTooltip = (ctx: CanvasRenderingContext2D, unit: Unit, width: number, height: number) => { + const style = getComputedStyle(ctx.canvas); + const fontWeight = style.getPropertyValue('--font-weight') || 'normal'; + const fontSize = style.getPropertyValue('--font-size') || '16px'; + const fontFamily = style.getPropertyValue('--font-family') || 'sans-serif'; + const tooltipBackground = style.getPropertyValue('--tooltip-background') || "#fff" + ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`; + ctx.textBaseline = "middle"; + const {x, y, w, h} = unitBounds(unit, width, height); const text = `${unit.name} • ${unit.fuzzy_match_percent.toFixed(2)}%`; const m = ctx.measureText(text); - const hw = m.actualBoundingBoxRight + m.actualBoundingBoxLeft + TOOLTIP_PADDING * 2; - const bh = m.actualBoundingBoxAscent + m.actualBoundingBoxDescent + TOOLTIP_PADDING * 2; - let bx = x + (w - hw) / 2 + TOOLTIP_PADDING; - let by = y - TOOLTIP_MARGIN; + const bw = m.actualBoundingBoxRight + m.actualBoundingBoxLeft + PADDING_W * 2; + const bh = m.fontBoundingBoxAscent + m.fontBoundingBoxDescent + PADDING_H * 2; + const margin = isTouch ? MARGIN * 2 : MARGIN; + let bx = x + (w - bw) / 2 + PADDING_W; + let by = y - bh - margin; + let invY = false; + if (bx + bw > width) { + bx = width - bw; + } if (bx < 0) { bx = 0; } - if (bx + hw > width) { - bx = width - hw; + if (by < 0) { + by = y + h + margin; + invY = true; } - if (by - bh < 0) { - by = y + h + bh + TOOLTIP_MARGIN; + ctx.fillStyle = tooltipBackground; + ctx.beginPath(); + ctx.roundRect(bx, by, bw, bh, BORDER_RADIUS); + // Arrow + const ax = x + w / 2; + if (invY) { + ctx.moveTo(ax, y + h); + ctx.lineTo(ax + margin, y + h + margin); + ctx.lineTo(ax - margin, y + h + margin); + } else { + ctx.moveTo(ax, y); + ctx.lineTo(ax + margin, y - margin); + ctx.lineTo(ax - margin, y - margin); } - ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; - ctx.fillRect(bx, by - m.actualBoundingBoxAscent - TOOLTIP_PADDING * 2, hw, bh); + ctx.fill(); ctx.fillStyle = "#000"; - ctx.fillText(text, bx + TOOLTIP_PADDING, by - TOOLTIP_PADDING); + ctx.fillText(text, bx + PADDING_W, by + bh / 2); }; let hovered = null; let dirty = false; +let isTouch = false; const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { if (!canvas.getContext) { return; } - const ratio = window.devicePixelRatio; const {width, height} = canvas.getBoundingClientRect(); + const ratio = window.devicePixelRatio; const renderWidth = width * ratio; const renderHeight = height * ratio; if (!dirty && canvas.width === renderWidth && canvas.height === renderHeight) { @@ -59,25 +86,23 @@ const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { return; } dirty = false; + // High DPI support if (canvas.width !== renderWidth || canvas.height !== renderHeight) { canvas.width = renderWidth; canvas.height = renderHeight; } - const style = getComputedStyle(canvas); - const fontWeight = style.getPropertyValue('--font-weight') || 'normal'; - const fontSize = style.getPropertyValue('--font-size') || '16px'; - const fontFamily = style.getPropertyValue('--font-family') || 'sans-serif'; const ctx = canvas.getContext("2d"); - ctx.setTransform(ratio, 0, 0, ratio, 0, 0); + ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // Scale to device pixel ratio ctx.clearRect(0, 0, width, height); - ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`; ctx.lineWidth = 1; ctx.strokeStyle = "#000"; for (const unit of units) { const {x, y, w, h} = unitBounds(unit, width, height); ctx.fillStyle = unit.color; - ctx.fillRect(x, y, w, h); - ctx.strokeRect(x, y, w, h); + ctx.beginPath(); + ctx.rect(x, y, w, h); + ctx.fill(); + ctx.stroke(); } if (hovered) { const {x, y, w, h} = unitBounds(hovered, width, height); @@ -123,9 +148,15 @@ const drawGraph = (id: string, units: Unit[]) => { dirty = true; queueDraw(); }; - canvas.addEventListener("mousemove", handleHover); + canvas.addEventListener("mousemove", (e) => { + isTouch = false; + handleHover(e); + }); canvas.addEventListener("mouseleave", handleLeave); - canvas.addEventListener("touchmove", (e) => handleHover(e.touches[0])); + canvas.addEventListener("touchmove", (e) => { + isTouch = true; + handleHover(e.touches[0]); + }); canvas.addEventListener("touchend", handleLeave); draw(canvas, units); }; diff --git a/src/handlers/js.rs b/src/handlers/js.rs index 51bbcf7..87a4da5 100644 --- a/src/handlers/js.rs +++ b/src/handlers/js.rs @@ -73,11 +73,11 @@ fn transform( ) -> Result { let source_type = SourceType::from_path(path)?; let allocator = Allocator::default(); - let parsed = Parser::new(&allocator, &source_text, source_type).parse(); + let parsed = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(parsed.program); - let (symbols, scopes) = SemanticBuilder::new(&source_text, source_type) - .build(&program) + let (symbols, scopes) = SemanticBuilder::new(source_text, source_type) + .build(program) .semantic .into_symbol_table_and_scope_tree(); @@ -91,7 +91,7 @@ fn transform( &allocator, path, source_type, - &source_text, + source_text, parsed.trivias.clone(), transform_options, ) diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index cf518d0..42cdec3 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,10 +1,12 @@ -use std::{str::FromStr, sync::Arc}; +use std::{convert::Infallible, net::SocketAddr, str::FromStr, sync::Arc}; use axum::{ - http::{header, HeaderMap, StatusCode}, + async_trait, + extract::{ConnectInfo, FromRequestParts, OriginalUri}, + http::{header, request::Parts, HeaderMap, StatusCode, Uri}, response::{IntoResponse, Response}, routing::get, - Router, + Extension, Router, }; use bytes::BytesMut; use mime::Mime; @@ -14,9 +16,9 @@ use crate::AppState; mod css; mod graph; +mod js; mod project; mod report; -mod js; pub fn build_router() -> Router { Router::new() @@ -76,3 +78,50 @@ impl IntoResponse for Protobuf { ([(header::CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes.freeze()).into_response() } } + +/// Extractor for the full URI of the request, including the scheme and authority. +/// Uses the `x-forwarded-proto` and `x-forwarded-host` headers if present. +pub struct FullUri(pub Uri); + +#[async_trait] +impl FromRequestParts for FullUri +where S: Send + Sync +{ + type Rejection = Infallible; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let uri = Extension::::from_request_parts(parts, state) + .await + .map_or_else(|_| parts.uri.clone(), |Extension(OriginalUri(uri))| uri); + let mut builder = Uri::builder(); + if let Some(scheme) = + parts.headers.get("x-forwarded-proto").and_then(|value| value.to_str().ok()) + { + builder = builder.scheme(scheme); + } else if let Some(scheme) = uri.scheme().cloned() { + builder = builder.scheme(scheme); + } else { + // TODO: native https? + builder = builder.scheme("http"); + } + if let Some(host) = + parts.headers.get("x-forwarded-host").and_then(|value| value.to_str().ok()) + { + builder = builder.authority(host); + } else if let Some(host) = + parts.headers.get(header::HOST).and_then(|value| value.to_str().ok()) + { + builder = builder.authority(host); + } else if let Some(authority) = uri.authority().cloned() { + builder = builder.authority(authority); + } else if let Ok(ConnectInfo(socket_addr)) = + ConnectInfo::::from_request_parts(parts, state).await + { + builder = builder.authority(socket_addr.to_string()); + } + if let Some(path_and_query) = uri.path_and_query().cloned() { + builder = builder.path_and_query(path_and_query); + } + Ok(FullUri(builder.build().unwrap_or(uri))) + } +} diff --git a/src/handlers/report.rs b/src/handlers/report.rs index 7af0703..c369835 100644 --- a/src/handlers/report.rs +++ b/src/handlers/report.rs @@ -2,21 +2,23 @@ use std::{iter, str::FromStr, time::Instant}; use anyhow::{Context, Result}; use axum::{ - extract::{Path, Query, State}, - http::{header, HeaderMap, StatusCode}, + extract::{ Path, Query, State}, + http::{header, HeaderMap, StatusCode, Uri}, response::{Html, IntoResponse, Response}, Json, }; use image::ImageFormat; use mime::Mime; -use objdiff_core::bindings::report::Measures; +use objdiff_core::bindings::report::{Measures, ReportCategory}; use serde::{Deserialize, Serialize}; +use url::Url; -use super::{graph::layout_units, parse_accept, AppError, Protobuf, PROTOBUF}; +use super::{graph::layout_units, parse_accept, AppError, FullUri, Protobuf, PROTOBUF}; use crate::{ handlers::graph::{render_image, render_svg, unit_color}, models::{Project, ProjectInfo, ReportFile}, templates::render, + util::UrlExt, AppState, }; @@ -55,12 +57,14 @@ struct ReportTemplateContext<'a> { version: &'a str, measures: TemplateMeasures, units: &'a [ReportTemplateUnit<'a>], - versions: &'a [String], + versions: &'a [ReportTemplateVersion<'a>], prev_commit: Option<&'a str>, next_commit: Option<&'a str>, categories: &'a [ReportCategoryItem<'a>], current_category: &'a ReportCategoryItem<'a>, - units_json: String, + canonical_path: &'a str, + canonical_url: &'a str, + image_url: &'a str, } #[derive(Serialize)] @@ -74,14 +78,17 @@ pub struct ReportTemplateUnit<'a> { h: f32, } -#[derive(Serialize, Copy, Clone)] +#[derive(Serialize, Clone)] struct ReportCategoryItem<'a> { id: &'a str, name: &'a str, + path: String, } -impl Default for ReportCategoryItem<'static> { - fn default() -> Self { Self { id: "all", name: "All" } } +#[derive(Serialize, Clone)] +struct ReportTemplateVersion<'a> { + id: &'a str, + path: String, } /// Duplicate of Measures to avoid omitting empty fields @@ -159,13 +166,14 @@ fn extract_extension(params: ReportParams) -> (ReportParams, Option) { } else if let Some((repo, ext)) = params.repo.rsplit_once('.') { return (ReportParams { repo: repo.to_string(), ..params }, Some(ext.to_string())); } - return (params, None); + (params, None) } pub async fn get_report( Path(params): Path, Query(query): Query, headers: HeaderMap, + FullUri(uri): FullUri, State(state): State, ) -> Result { let start = Instant::now(); @@ -225,9 +233,10 @@ pub async fn get_report( &report, &project_info, measures, - ¤t_category, + current_category, &units, &state, + uri, )?; let elapsed = start.elapsed(); rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis())); @@ -274,15 +283,15 @@ const EMPTY_MEASURES: Measures = Measures { fn apply_category<'a>( report: &'a ReportFile, query: &ReportQuery, -) -> Result<(&'a Measures, ReportCategoryItem<'a>, Vec>)> { +) -> Result<(&'a Measures, Option<&'a ReportCategory>, Vec>)> { let mut measures = report.report.measures.as_ref().unwrap_or(&EMPTY_MEASURES); - let mut current_category = ReportCategoryItem::default(); + let mut current_category = None; let mut category_id_filter = None; if let Some(category) = query.category.as_ref().and_then(|id| report.report.categories.iter().find(|c| c.id == *id)) { measures = category.measures.as_ref().unwrap_or(&EMPTY_MEASURES); - current_category = ReportCategoryItem { id: &category.id, name: &category.name }; + current_category = Some(category); category_id_filter = Some(category.id.clone()); } let (w, h) = query.size(); @@ -313,10 +322,10 @@ fn apply_category<'a>( name: &unit.name, fuzzy_match_percent: match_percent, color: unit_color(match_percent), - x: bounds.x * 100.0, - y: bounds.y * 100.0, - w: bounds.w * 100.0, - h: bounds.h * 100.0, + x: bounds.x, + y: bounds.y, + w: bounds.w, + h: bounds.h, } }) .collect(); @@ -327,35 +336,67 @@ fn render_template( report: &ReportFile, project_info: &ProjectInfo, measures: &Measures, - current_category: &ReportCategoryItem, + current_category: Option<&ReportCategory>, units: &[ReportTemplateUnit], state: &AppState, + uri: Uri, ) -> Result { - let categories = iter::once(ReportCategoryItem::default()) - .chain( - report - .report - .categories - .iter() - .map(|c| ReportCategoryItem { id: &c.id, name: &c.name }), - ) + let request_url = Url::parse(&uri.to_string()).context("Failed to parse URI")?; + let project_base_path = + format!("/{}/{}", project_info.project.owner, project_info.project.repo); + let canonical_url = request_url.with_path(&format!( + "/{}/{}/{}/{}", + project_info.project.owner, project_info.project.repo, report.version, report.commit.sha + )); + let image_url = canonical_url.with_path(&format!("{}.png", canonical_url.path())); + + let versions = project_info + .report_versions + .iter() + .map(|version| { + let version_url = request_url.with_path(&format!( + "/{}/{}/{}/{}", + project_info.project.owner, project_info.project.repo, version, report.commit.sha + )); + ReportTemplateVersion { id: version, path: version_url.path_and_query().to_string() } + }) .collect::>(); - let units_json = serde_json::to_string(&units).context("Failed to serialize units")?; + + let all_url = canonical_url.set_query("category", None); + let all_category = + ReportCategoryItem { id: "all", name: "All", path: all_url.path_and_query().to_string() }; + let current_category = current_category + .map(|c| { + let path = + canonical_url.set_query("category", Some(&c.id)).path_and_query().to_string(); + ReportCategoryItem { id: &c.id, name: &c.name, path } + }) + .unwrap_or_else(|| all_category.clone()); + let categories = iter::once(all_category) + .chain(report.report.categories.iter().map(|c| { + let path = + canonical_url.set_query("category", Some(&c.id)).path_and_query().to_string(); + ReportCategoryItem { id: &c.id, name: &c.name, path } + })) + .collect::>(); + render(&state.templates, "report.html", ReportTemplateContext { project: &report.project, project_name: &report.project.name(), - project_short_name: &report.project.short_name(), + project_short_name: report.project.short_name(), project_url: &report.project.repo_url(), - project_path: &format!("/{}/{}", report.project.owner, report.project.repo), + project_path: &project_base_path, commit: &report.commit.sha, version: &report.version, measures: TemplateMeasures::from(measures), units, - versions: &project_info.report_versions, + versions: &versions, prev_commit: project_info.prev_commit.as_deref(), next_commit: project_info.next_commit.as_deref(), categories: &categories, - current_category, - units_json, + current_category: ¤t_category, + canonical_path: canonical_url.path_and_query(), + canonical_url: canonical_url.as_ref(), + image_url: image_url.as_ref(), }) } diff --git a/src/main.rs b/src/main.rs index e5008eb..bc18db3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod github; mod handlers; mod models; mod templates; +mod util; use std::{ fs::File, @@ -51,10 +52,13 @@ async fn main() { // Run our service let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, state.config.server.port)); tracing::info!("Listening on {}", addr); - axum::serve(TcpListener::bind(addr).await.expect("bind error"), app(state).into_make_service()) - .with_graceful_shutdown(shutdown_signal()) - .await - .expect("server error"); + axum::serve( + TcpListener::bind(addr).await.expect("bind error"), + app(state).into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await + .expect("server error"); scheduler.shutdown().await.expect("Failed to shut down scheduler"); db.close().await; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..2375c49 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,50 @@ +use url::Url; + +pub trait UrlExt { + fn set_query(&self, key: &str, value: Option<&str>) -> Url; + fn with_path(&self, path: &str) -> Url; + fn path_and_query(&self) -> &str; +} + +impl UrlExt for Url { + #[inline] + fn set_query(&self, key: &str, value: Option<&str>) -> Url { + let mut out = self.clone(); + let mut pairs = out.query_pairs_mut(); + pairs.clear(); + let mut updated = false; + for (k, v) in self.query_pairs() { + if k == key { + if let Some(value) = value { + if value.is_empty() { + pairs.append_key_only(&k); + } else { + pairs.append_pair(&k, value); + } + } + updated = true; + } else if v.is_empty() { + pairs.append_key_only(&k); + } else { + pairs.append_pair(&k, &v); + } + } + if !updated { + if let Some(value) = value { + pairs.append_pair(key, value); + } + } + drop(pairs); + out + } + + #[inline] + fn with_path(&self, path: &str) -> Url { + let mut out = self.clone(); + out.set_path(path); + out + } + + #[inline] + fn path_and_query(&self) -> &str { &self[url::Position::BeforePath..] } +} diff --git a/templates/report.html b/templates/report.html index 1547fad..8d818f1 100644 --- a/templates/report.html +++ b/templates/report.html @@ -7,15 +7,15 @@ {{ project_short_name }}{% if current_category.id != "all" %} ({{ current_category.name }}){% endif %} • Progress Report - + - - + - + +
@@ -37,7 +37,7 @@ {{ version }} @@ -94,17 +94,17 @@ {{ current_category.name }} {% endif %}
Links
diff --git a/templates/treemap.svg b/templates/treemap.svg index fea7da3..fcd27c8 100644 --- a/templates/treemap.svg +++ b/templates/treemap.svg @@ -2,6 +2,6 @@ {% for unit in units %} - + {% endfor %}