Telemetry #24
@@ -49,3 +49,13 @@ Contributions, feedback and feature requests are always welcome!
|
|||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SigNoz telemetry
|
||||||
|
|
||||||
|
After the user opts in, Gitty sends privacy-filtered product events and technical errors as OTLP/HTTP JSON logs. Every Git/Tauri command is also captured as a trace span with its command name, duration and success state, but without command arguments or results. CPU utilization and resident memory usage of the Gitty process are sampled every 30 seconds and exported as OpenTelemetry gauges. The default endpoints are `https://telemetry.cbsk-tech.de/v1/logs`, `https://telemetry.cbsk-tech.de/v1/traces`, and `https://telemetry.cbsk-tech.de/v1/metrics`.
|
||||||
|
|
||||||
|
The SigNoz reverse proxy must forward all three paths to the Collector's OTLP/HTTP receiver (normally port `4318`). For development or a separate collector hostname, set `GITTY_OTLP_LOGS_ENDPOINT`, `GITTY_OTLP_TRACES_ENDPOINT`, and `GITTY_OTLP_METRICS_ENDPOINT` to the full signal URLs before starting Gitty.
|
||||||
|
|
||||||
|
Telemetry is non-blocking, bounded, disabled until consent is stored, and never includes repository paths, remote URLs, branch names, commit messages, diffs, file names, credentials, source code, URLs, or email addresses.
|
||||||
|
|||||||
Generated
+252
-123
@@ -574,13 +574,22 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block2"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1119,6 +1128,19 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-graphics-types",
|
||||||
|
"foreign-types 0.5.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-graphics"
|
name = "core-graphics"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
@@ -1592,9 +1614,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2538,8 +2560,10 @@ dependencies = [
|
|||||||
"commit_ai",
|
"commit_ai",
|
||||||
"keyring",
|
"keyring",
|
||||||
"log",
|
"log",
|
||||||
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sysinfo 0.38.3",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-aptabase",
|
"tauri-plugin-aptabase",
|
||||||
@@ -3884,7 +3908,7 @@ dependencies = [
|
|||||||
"statrs",
|
"statrs",
|
||||||
"strum 0.27.2",
|
"strum 0.27.2",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"sysinfo",
|
"sysinfo 0.36.1",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokenizers 0.21.4",
|
"tokenizers 0.21.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -4014,10 +4038,10 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4311,6 +4335,22 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc-sys"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
|
||||||
|
dependencies = [
|
||||||
|
"objc-sys",
|
||||||
|
"objc2-encode",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2"
|
name = "objc2"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -4323,92 +4363,80 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-app-kit"
|
name = "objc2-app-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-cloud-kit"
|
name = "objc2-cloud-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-data"
|
name = "objc2-core-data"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-foundation"
|
name = "objc2-core-foundation"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-graphics"
|
name = "objc2-core-graphics"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-surface",
|
"objc2-io-surface",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-image"
|
name = "objc2-core-image"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
|
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-location"
|
name = "objc2-core-location"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
checksum = "ac0f75792558aa9d618443bbb5db7426a7a0b6fddf96903f86ef9ad02e135740"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-text"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.0",
|
|
||||||
"objc2",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4428,22 +4456,34 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-foundation"
|
name = "objc2-foundation"
|
||||||
version = "0.3.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-foundation"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"block2 0.6.2",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-io-kit"
|
name = "objc2-io-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
@@ -4451,82 +4491,106 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-io-surface"
|
name = "objc2-io-surface"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-metal"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-osa-kit"
|
name = "objc2-osa-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.3.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
"objc2-metal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-quartz-core"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-ui-kit"
|
name = "objc2-ui-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-core-image",
|
"objc2-core-image",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-core-text",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-foundation",
|
"objc2-quartz-core 0.3.1",
|
||||||
"objc2-quartz-core",
|
|
||||||
"objc2-user-notifications",
|
"objc2-user-notifications",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-user-notifications"
|
name = "objc2-user-notifications"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
checksum = "2a3f5ec77a81d9e0c5a0b32159b0cb143d7086165e79708351e02bf37dfc65cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-web-kit"
|
name = "objc2-web-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
|
checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4664,8 +4728,8 @@ dependencies = [
|
|||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"log",
|
"log",
|
||||||
"nix 0.31.3",
|
"nix 0.31.3",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"serde",
|
"serde",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
@@ -4677,8 +4741,8 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-osa-kit",
|
"objc2-osa-kit",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -5540,17 +5604,17 @@ version = "0.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"gtk-sys",
|
"gtk-sys",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
@@ -6358,24 +6422,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "softbuffer"
|
name = "softbuffer"
|
||||||
version = "0.4.8"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
|
checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"cfg_aliases",
|
||||||
|
"core-graphics 0.24.0",
|
||||||
|
"foreign-types 0.5.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"ndk",
|
"log",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-foundation",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-core-graphics",
|
"objc2-quartz-core 0.2.2",
|
||||||
"objc2-foundation",
|
|
||||||
"objc2-quartz-core",
|
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"tracing",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6772,7 +6836,21 @@ dependencies = [
|
|||||||
"ntapi",
|
"ntapi",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-kit",
|
"objc2-io-kit",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sysinfo"
|
||||||
|
version = "0.38.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d03c61d2a49c649a15c407338afe7accafde9dac869995dccb73e5f7ef7d9034"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"ntapi",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-io-kit",
|
||||||
|
"windows 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6816,9 +6894,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics",
|
"core-graphics 0.25.0",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dbus",
|
"dbus",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
@@ -6832,9 +6910,9 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"ndk",
|
"ndk",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@@ -6843,7 +6921,7 @@ dependencies = [
|
|||||||
"tao-macros",
|
"tao-macros",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
@@ -6899,9 +6977,9 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -6925,7 +7003,7 @@ dependencies = [
|
|||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"window-vibrancy",
|
"window-vibrancy",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7054,7 +7132,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"log",
|
"log",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -7127,7 +7205,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -7138,7 +7216,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7151,7 +7229,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -7163,7 +7241,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7811,11 +7889,11 @@ dependencies = [
|
|||||||
"dirs",
|
"dirs",
|
||||||
"libappindicator",
|
"libappindicator",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -8389,7 +8467,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"webview2-com-macros",
|
"webview2-com-macros",
|
||||||
"webview2-com-sys",
|
"webview2-com-sys",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
@@ -8413,7 +8491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -8476,10 +8554,10 @@ version = "0.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
@@ -8491,11 +8569,23 @@ version = "0.61.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections",
|
"windows-collections 0.2.0",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-future",
|
"windows-future 0.2.1",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-numerics",
|
"windows-numerics 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||||
|
dependencies = [
|
||||||
|
"windows-collections 0.3.2",
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-future 0.3.2",
|
||||||
|
"windows-numerics 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8507,6 +8597,15 @@ dependencies = [
|
|||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-collections"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -8541,7 +8640,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-threading",
|
"windows-threading 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-future"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
"windows-threading 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8588,6 +8698,16 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-numerics"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -8761,6 +8881,15 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-threading"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-version"
|
name = "windows-version"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -9003,7 +9132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
|
checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"block2",
|
"block2 0.6.2",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dirs",
|
"dirs",
|
||||||
@@ -9017,10 +9146,10 @@ dependencies = [
|
|||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -9034,7 +9163,7 @@ dependencies = [
|
|||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ keyring = { version = "3", features = ["apple-native", "windows-native", "async-
|
|||||||
commit_ai = { path = "crates/commit_ai" }
|
commit_ai = { path = "crates/commit_ai" }
|
||||||
tokio = "1.52.3"
|
tokio = "1.52.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||||
|
sysinfo = { version = "=0.38.3", default-features = false, features = ["system"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
mod badge;
|
mod badge;
|
||||||
mod git;
|
mod git;
|
||||||
|
mod telemetry;
|
||||||
|
|
||||||
use badge::set_sync_badge;
|
use badge::set_sync_badge;
|
||||||
use git::{
|
use git::{
|
||||||
@@ -24,6 +25,7 @@ use git::{
|
|||||||
unstage_files, update_remote,
|
unstage_files, update_remote,
|
||||||
};
|
};
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use telemetry::{emit_frontend_log, emit_frontend_span, set_telemetry_enabled};
|
||||||
|
|
||||||
struct ConsoleLogger;
|
struct ConsoleLogger;
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ fn close_splashscreen(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
init_console_logging();
|
init_console_logging();
|
||||||
|
telemetry::init();
|
||||||
if let Some(result) = run_sequence_editor_if_requested() {
|
if let Some(result) = run_sequence_editor_if_requested() {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
eprintln!("{error}");
|
eprintln!("{error}");
|
||||||
@@ -193,7 +196,10 @@ async fn main() {
|
|||||||
cred_save,
|
cred_save,
|
||||||
cred_delete,
|
cred_delete,
|
||||||
set_sync_badge,
|
set_sync_badge,
|
||||||
close_splashscreen
|
close_splashscreen,
|
||||||
|
set_telemetry_enabled,
|
||||||
|
emit_frontend_log,
|
||||||
|
emit_frontend_span
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -0,0 +1,363 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{
|
||||||
|
OnceLock,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
mpsc::{SyncSender, sync_channel},
|
||||||
|
},
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
const DEFAULT_OTLP_LOGS_ENDPOINT: &str = "https://telemetry.cbsk-tech.de/v1/logs";
|
||||||
|
const DEFAULT_OTLP_TRACES_ENDPOINT: &str = "https://telemetry.cbsk-tech.de/v1/traces";
|
||||||
|
const DEFAULT_OTLP_METRICS_ENDPOINT: &str = "https://telemetry.cbsk-tech.de/v1/metrics";
|
||||||
|
const METRICS_INTERVAL: Duration = Duration::from_secs(30);
|
||||||
|
const MAX_BODY_BYTES: usize = 2_048;
|
||||||
|
|
||||||
|
static ENABLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
static SENDER: OnceLock<SyncSender<TelemetrySignal>> = OnceLock::new();
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TelemetrySignal {
|
||||||
|
Log(TelemetryRecord),
|
||||||
|
Span(FrontendSpan),
|
||||||
|
Metrics(ProcessMetrics),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProcessMetrics {
|
||||||
|
cpu_utilization: f64,
|
||||||
|
memory_bytes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TelemetryRecord {
|
||||||
|
severity: &'static str,
|
||||||
|
severity_number: u8,
|
||||||
|
body: String,
|
||||||
|
event_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FrontendLog {
|
||||||
|
level: String,
|
||||||
|
message: String,
|
||||||
|
event_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FrontendSpan {
|
||||||
|
name: String,
|
||||||
|
trace_id: String,
|
||||||
|
span_id: String,
|
||||||
|
started_at_ms: u64,
|
||||||
|
duration_ms: f64,
|
||||||
|
success: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
let endpoint = std::env::var("GITTY_OTLP_LOGS_ENDPOINT")
|
||||||
|
.unwrap_or_else(|_| DEFAULT_OTLP_LOGS_ENDPOINT.to_string());
|
||||||
|
let traces_endpoint = std::env::var("GITTY_OTLP_TRACES_ENDPOINT")
|
||||||
|
.unwrap_or_else(|_| DEFAULT_OTLP_TRACES_ENDPOINT.to_string());
|
||||||
|
let metrics_endpoint = std::env::var("GITTY_OTLP_METRICS_ENDPOINT")
|
||||||
|
.unwrap_or_else(|_| DEFAULT_OTLP_METRICS_ENDPOINT.to_string());
|
||||||
|
let (sender, receiver) = sync_channel::<TelemetrySignal>(256);
|
||||||
|
if SENDER.set(sender.clone()).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_process_metrics(sender);
|
||||||
|
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("gitty-telemetry".into())
|
||||||
|
.spawn(move || {
|
||||||
|
let client = match reqwest::blocking::Client::builder()
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("[WARN] [gitty::telemetry] could not create OTLP client: {error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut export_warning_shown = false;
|
||||||
|
while let Ok(signal) = receiver.recv() {
|
||||||
|
let (target_endpoint, payload) = match signal {
|
||||||
|
TelemetrySignal::Log(record) => (&endpoint, log_payload(record)),
|
||||||
|
TelemetrySignal::Span(span) => (&traces_endpoint, span_payload(span)),
|
||||||
|
TelemetrySignal::Metrics(metrics) => {
|
||||||
|
(&metrics_endpoint, metrics_payload(metrics))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export(
|
||||||
|
&client,
|
||||||
|
target_endpoint,
|
||||||
|
&payload,
|
||||||
|
&mut export_warning_shown,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("failed to start telemetry worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_process_metrics(sender: SyncSender<TelemetrySignal>) {
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("gitty-process-metrics".into())
|
||||||
|
.spawn(move || {
|
||||||
|
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
|
||||||
|
|
||||||
|
let Ok(pid) = sysinfo::get_current_pid() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let refresh_kind = ProcessRefreshKind::nothing().with_cpu().with_memory();
|
||||||
|
let cpu_count = std::thread::available_parallelism()
|
||||||
|
.map(|count| count.get())
|
||||||
|
.unwrap_or(1) as f64;
|
||||||
|
let mut system = System::new();
|
||||||
|
let mut primed = false;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(METRICS_INTERVAL);
|
||||||
|
if !ENABLED.load(Ordering::Relaxed) {
|
||||||
|
primed = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
system.refresh_processes_specifics(
|
||||||
|
ProcessesToUpdate::Some(&[pid]),
|
||||||
|
true,
|
||||||
|
refresh_kind,
|
||||||
|
);
|
||||||
|
let Some(process) = system.process(pid) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !primed {
|
||||||
|
primed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let metrics = ProcessMetrics {
|
||||||
|
cpu_utilization: (f64::from(process.cpu_usage()) / (100.0 * cpu_count))
|
||||||
|
.clamp(0.0, 1.0),
|
||||||
|
memory_bytes: process.memory(),
|
||||||
|
};
|
||||||
|
let _ = sender.try_send(TelemetrySignal::Metrics(metrics));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("failed to start process metrics worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_attributes() -> serde_json::Value {
|
||||||
|
json!([
|
||||||
|
{ "key": "service.name", "value": { "stringValue": "gitty-desktop" } },
|
||||||
|
{ "key": "service.version", "value": { "stringValue": env!("CARGO_PKG_VERSION") } },
|
||||||
|
{ "key": "deployment.environment.name", "value": { "stringValue": if cfg!(debug_assertions) { "development" } else { "production" } } },
|
||||||
|
{ "key": "os.type", "value": { "stringValue": std::env::consts::OS } },
|
||||||
|
{ "key": "host.arch", "value": { "stringValue": std::env::consts::ARCH } }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_payload(record: TelemetryRecord) -> serde_json::Value {
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_nanos()
|
||||||
|
.to_string();
|
||||||
|
let mut attributes = vec![json!({
|
||||||
|
"key": "telemetry.sdk.language",
|
||||||
|
"value": { "stringValue": "rust" }
|
||||||
|
})];
|
||||||
|
if let Some(event_name) = record.event_name {
|
||||||
|
attributes.push(json!({
|
||||||
|
"key": "event.name",
|
||||||
|
"value": { "stringValue": event_name }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let payload = json!({
|
||||||
|
"resourceLogs": [{
|
||||||
|
"resource": { "attributes": resource_attributes() },
|
||||||
|
"scopeLogs": [{
|
||||||
|
"scope": { "name": "gitty.telemetry" },
|
||||||
|
"logRecords": [{
|
||||||
|
"timeUnixNano": timestamp,
|
||||||
|
"observedTimeUnixNano": timestamp,
|
||||||
|
"severityNumber": record.severity_number,
|
||||||
|
"severityText": record.severity,
|
||||||
|
"body": { "stringValue": record.body },
|
||||||
|
"attributes": attributes
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_payload(mut span: FrontendSpan) -> serde_json::Value {
|
||||||
|
if span.name.len() > 128 {
|
||||||
|
span.name.truncate(128);
|
||||||
|
}
|
||||||
|
let start = u128::from(span.started_at_ms) * 1_000_000;
|
||||||
|
let duration = (span.duration_ms.max(0.0) * 1_000_000.0) as u128;
|
||||||
|
let attributes = vec![
|
||||||
|
json!({ "key": "rpc.system", "value": { "stringValue": "tauri" } }),
|
||||||
|
json!({ "key": "rpc.method", "value": { "stringValue": span.name } }),
|
||||||
|
];
|
||||||
|
json!({
|
||||||
|
"resourceSpans": [{
|
||||||
|
"resource": { "attributes": resource_attributes() },
|
||||||
|
"scopeSpans": [{
|
||||||
|
"scope": { "name": "gitty.tauri", "version": env!("CARGO_PKG_VERSION") },
|
||||||
|
"spans": [{
|
||||||
|
"traceId": span.trace_id,
|
||||||
|
"spanId": span.span_id,
|
||||||
|
"name": format!("tauri.{}", span.name),
|
||||||
|
"kind": 1,
|
||||||
|
"startTimeUnixNano": start.to_string(),
|
||||||
|
"endTimeUnixNano": (start + duration).to_string(),
|
||||||
|
"attributes": attributes,
|
||||||
|
"status": {
|
||||||
|
"code": if span.success { 1 } else { 2 },
|
||||||
|
"message": if span.success { "" } else { "Tauri command failed" }
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metrics_payload(metrics: ProcessMetrics) -> serde_json::Value {
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_nanos()
|
||||||
|
.to_string();
|
||||||
|
json!({
|
||||||
|
"resourceMetrics": [{
|
||||||
|
"resource": { "attributes": resource_attributes() },
|
||||||
|
"scopeMetrics": [{
|
||||||
|
"scope": { "name": "gitty.process", "version": env!("CARGO_PKG_VERSION") },
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"name": "process.cpu.utilization",
|
||||||
|
"description": "Normalized CPU utilization of the Gitty process",
|
||||||
|
"unit": "1",
|
||||||
|
"gauge": { "dataPoints": [{
|
||||||
|
"timeUnixNano": timestamp,
|
||||||
|
"asDouble": metrics.cpu_utilization,
|
||||||
|
"attributes": []
|
||||||
|
}] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "process.memory.usage",
|
||||||
|
"description": "Resident memory used by the Gitty process",
|
||||||
|
"unit": "By",
|
||||||
|
"gauge": { "dataPoints": [{
|
||||||
|
"timeUnixNano": timestamp,
|
||||||
|
"asInt": metrics.memory_bytes.to_string(),
|
||||||
|
"attributes": []
|
||||||
|
}] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export(
|
||||||
|
client: &reqwest::blocking::Client,
|
||||||
|
endpoint: &str,
|
||||||
|
payload: &serde_json::Value,
|
||||||
|
export_warning_shown: &mut bool,
|
||||||
|
) {
|
||||||
|
match client.post(endpoint).json(payload).send() {
|
||||||
|
Ok(response) if response.status().is_success() => {
|
||||||
|
if response
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
.is_some_and(|value| value.starts_with("text/html"))
|
||||||
|
&& !*export_warning_shown
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[WARN] [gitty::telemetry] OTLP endpoint returned HTML; configure the collector endpoint with GITTY_OTLP_LOGS_ENDPOINT"
|
||||||
|
);
|
||||||
|
*export_warning_shown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(response) if !*export_warning_shown => {
|
||||||
|
eprintln!(
|
||||||
|
"[WARN] [gitty::telemetry] OTLP export failed with status {}",
|
||||||
|
response.status()
|
||||||
|
);
|
||||||
|
*export_warning_shown = true;
|
||||||
|
}
|
||||||
|
Err(error) if !*export_warning_shown => {
|
||||||
|
eprintln!("[WARN] [gitty::telemetry] OTLP export failed: {error}");
|
||||||
|
*export_warning_shown = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(record: TelemetryRecord) {
|
||||||
|
if !ENABLED.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(sender) = SENDER.get() {
|
||||||
|
let _ = sender.try_send(TelemetrySignal::Log(record));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn set_telemetry_enabled(enabled: bool) {
|
||||||
|
ENABLED.store(enabled, Ordering::Relaxed);
|
||||||
|
if enabled {
|
||||||
|
emit(TelemetryRecord {
|
||||||
|
severity: "INFO",
|
||||||
|
severity_number: 9,
|
||||||
|
body: "Telemetry enabled".into(),
|
||||||
|
event_name: Some("telemetry.enabled".into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn emit_frontend_log(log: FrontendLog) {
|
||||||
|
let (severity, severity_number) = match log.level.as_str() {
|
||||||
|
"error" => ("ERROR", 17),
|
||||||
|
"warn" => ("WARN", 13),
|
||||||
|
_ => ("INFO", 9),
|
||||||
|
};
|
||||||
|
let mut body = log.message;
|
||||||
|
if body.len() > MAX_BODY_BYTES {
|
||||||
|
body.truncate(MAX_BODY_BYTES);
|
||||||
|
}
|
||||||
|
emit(TelemetryRecord {
|
||||||
|
severity,
|
||||||
|
severity_number,
|
||||||
|
body,
|
||||||
|
event_name: log.event_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn emit_frontend_span(span: FrontendSpan) {
|
||||||
|
if !ENABLED.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if span.trace_id.len() != 32
|
||||||
|
|| span.span_id.len() != 16
|
||||||
|
|| !span.trace_id.bytes().all(|byte| byte.is_ascii_hexdigit())
|
||||||
|
|| !span.span_id.bytes().all(|byte| byte.is_ascii_hexdigit())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(sender) = SENDER.get() {
|
||||||
|
let _ = sender.try_send(TelemetrySignal::Span(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
-19
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount, tick } from "svelte";
|
import { onDestroy, onMount, tick } from "svelte";
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||||
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
|
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
|
||||||
import { AlertCircle, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
import { AlertCircle, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
||||||
@@ -160,6 +159,7 @@
|
|||||||
summarizeGitError,
|
summarizeGitError,
|
||||||
} from "./lib/credentials";
|
} from "./lib/credentials";
|
||||||
import { trackAnalyticsEvent, type AnalyticsEventProperties } from "./lib/analytics";
|
import { trackAnalyticsEvent, type AnalyticsEventProperties } from "./lib/analytics";
|
||||||
|
import { setTelemetryEnabled, tracedInvoke } from "./lib/telemetry";
|
||||||
|
|
||||||
type UpdateToastState = "available" | "downloading" | "installed" | "error";
|
type UpdateToastState = "available" | "downloading" | "installed" | "error";
|
||||||
type AppView = "management" | "repository";
|
type AppView = "management" | "repository";
|
||||||
@@ -191,6 +191,14 @@
|
|||||||
directoryName: string;
|
directoryName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ErrorAutoHideState {
|
||||||
|
timer?: ReturnType<typeof setTimeout>;
|
||||||
|
message: string;
|
||||||
|
remaining: number;
|
||||||
|
startedAt: number;
|
||||||
|
clearIfCurrent: (message: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const OPEN_REPOS_KEY = "gitlite.openRepos.v1";
|
const OPEN_REPOS_KEY = "gitlite.openRepos.v1";
|
||||||
const RECENT_REPOS_KEY = "gitlite.recentRepos.v1";
|
const RECENT_REPOS_KEY = "gitlite.recentRepos.v1";
|
||||||
const FAVORITE_REPOS_KEY = "gitlite.favoriteRepos.v1";
|
const FAVORITE_REPOS_KEY = "gitlite.favoriteRepos.v1";
|
||||||
@@ -364,7 +372,7 @@
|
|||||||
let updateCheckInFlight = false;
|
let updateCheckInFlight = false;
|
||||||
let updateDownloadTotal = 0;
|
let updateDownloadTotal = 0;
|
||||||
let updateDownloadedBytes = 0;
|
let updateDownloadedBytes = 0;
|
||||||
let errorAutoHideTimers: Partial<Record<string, ReturnType<typeof setTimeout>>> = {};
|
let errorAutoHideStates: Partial<Record<string, ErrorAutoHideState>> = {};
|
||||||
let commitPanelHeight = loadCommitPanelHeight();
|
let commitPanelHeight = loadCommitPanelHeight();
|
||||||
let resizingCommitPanel = false;
|
let resizingCommitPanel = false;
|
||||||
let resizeStartY = 0;
|
let resizeStartY = 0;
|
||||||
@@ -467,8 +475,8 @@
|
|||||||
if (backgroundFetchTimer) clearInterval(backgroundFetchTimer);
|
if (backgroundFetchTimer) clearInterval(backgroundFetchTimer);
|
||||||
if (commitAiPollTimer) clearInterval(commitAiPollTimer);
|
if (commitAiPollTimer) clearInterval(commitAiPollTimer);
|
||||||
if (cloneDialogErrorTimer) clearTimeout(cloneDialogErrorTimer);
|
if (cloneDialogErrorTimer) clearTimeout(cloneDialogErrorTimer);
|
||||||
Object.values(errorAutoHideTimers).forEach((timer) => {
|
Object.values(errorAutoHideStates).forEach((state) => {
|
||||||
if (timer) clearTimeout(timer);
|
if (state?.timer) clearTimeout(state.timer);
|
||||||
});
|
});
|
||||||
if (activeFileHistoryRequestId) void cancelFileHistory(activeFileHistoryRequestId);
|
if (activeFileHistoryRequestId) void cancelFileHistory(activeFileHistoryRequestId);
|
||||||
});
|
});
|
||||||
@@ -503,7 +511,7 @@
|
|||||||
|
|
||||||
async function closeStartupSplashscreen() {
|
async function closeStartupSplashscreen() {
|
||||||
try {
|
try {
|
||||||
await invoke("close_splashscreen");
|
await tracedInvoke("close_splashscreen");
|
||||||
} catch {
|
} catch {
|
||||||
// Browser preview and failed startup paths should keep working without Tauri.
|
// Browser preview and failed startup paths should keep working without Tauri.
|
||||||
}
|
}
|
||||||
@@ -815,6 +823,7 @@
|
|||||||
|
|
||||||
function initAnalytics() {
|
function initAnalytics() {
|
||||||
analyticsSettings = loadAnalyticsSettings();
|
analyticsSettings = loadAnalyticsSettings();
|
||||||
|
setTelemetryEnabled(analyticsSettings.noticeSeen && analyticsSettings.enabled);
|
||||||
if (!analyticsSettings.noticeSeen) {
|
if (!analyticsSettings.noticeSeen) {
|
||||||
analyticsNoticeOpen = true;
|
analyticsNoticeOpen = true;
|
||||||
return;
|
return;
|
||||||
@@ -831,6 +840,7 @@
|
|||||||
analyticsSettings = { enabled, noticeSeen: true };
|
analyticsSettings = { enabled, noticeSeen: true };
|
||||||
persistAnalyticsSettings(analyticsSettings);
|
persistAnalyticsSettings(analyticsSettings);
|
||||||
analyticsNoticeOpen = false;
|
analyticsNoticeOpen = false;
|
||||||
|
setTelemetryEnabled(enabled);
|
||||||
trackEvent("app_started", { first_run: 1 });
|
trackEvent("app_started", { first_run: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,6 +910,7 @@
|
|||||||
persistThemePreference(nextTheme);
|
persistThemePreference(nextTheme);
|
||||||
persistLanguagePreference(nextLanguage);
|
persistLanguagePreference(nextLanguage);
|
||||||
persistStoredBoolean(AUTO_REFRESH_ENABLED_KEY, nextAutoRefresh);
|
persistStoredBoolean(AUTO_REFRESH_ENABLED_KEY, nextAutoRefresh);
|
||||||
|
setTelemetryEnabled(next.enabled);
|
||||||
appSettingsOpen = false;
|
appSettingsOpen = false;
|
||||||
if (nextAutoRefresh && !autoRefreshWasEnabled) void autoRefreshTick();
|
if (nextAutoRefresh && !autoRefreshWasEnabled) void autoRefreshTick();
|
||||||
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage, auto_refresh: nextAutoRefresh ? 1 : 0 });
|
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage, auto_refresh: nextAutoRefresh ? 1 : 0 });
|
||||||
@@ -1107,18 +1118,39 @@
|
|||||||
message: string,
|
message: string,
|
||||||
clearIfCurrent: (message: string) => void,
|
clearIfCurrent: (message: string) => void,
|
||||||
) {
|
) {
|
||||||
const existing = errorAutoHideTimers[key];
|
const existing = errorAutoHideStates[key];
|
||||||
if (existing) {
|
if (existing?.timer) {
|
||||||
clearTimeout(existing);
|
clearTimeout(existing.timer);
|
||||||
delete errorAutoHideTimers[key];
|
|
||||||
}
|
}
|
||||||
|
delete errorAutoHideStates[key];
|
||||||
|
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
errorAutoHideTimers[key] = setTimeout(() => {
|
errorAutoHideStates[key] = {
|
||||||
clearIfCurrent(message);
|
message,
|
||||||
delete errorAutoHideTimers[key];
|
remaining: ERROR_AUTO_HIDE_MS,
|
||||||
}, ERROR_AUTO_HIDE_MS);
|
startedAt: Date.now(),
|
||||||
|
clearIfCurrent,
|
||||||
|
};
|
||||||
|
resumeAutoHideError(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseAutoHideError(key: string) {
|
||||||
|
const state = errorAutoHideStates[key];
|
||||||
|
if (!state?.timer) return;
|
||||||
|
clearTimeout(state.timer);
|
||||||
|
state.timer = undefined;
|
||||||
|
state.remaining = Math.max(0, state.remaining - (Date.now() - state.startedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeAutoHideError(key: string) {
|
||||||
|
const state = errorAutoHideStates[key];
|
||||||
|
if (!state || state.timer) return;
|
||||||
|
state.startedAt = Date.now();
|
||||||
|
state.timer = setTimeout(() => {
|
||||||
|
state.clearIfCurrent(state.message);
|
||||||
|
delete errorAutoHideStates[key];
|
||||||
|
}, state.remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
function repoKey(path: string): string {
|
function repoKey(path: string): string {
|
||||||
@@ -1746,9 +1778,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function errorToMessage(error: unknown): string {
|
function errorToMessage(error: unknown): string {
|
||||||
if (error instanceof Error) return error.message;
|
let message: string;
|
||||||
if (typeof error === "string") return error;
|
if (error instanceof Error) message = error.message;
|
||||||
try { return JSON.stringify(error) ?? "Unknown error"; } catch { return "Unknown error"; }
|
else if (typeof error === "string") message = error;
|
||||||
|
else try { message = JSON.stringify(error) ?? "Unknown error"; } catch { message = "Unknown error"; }
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNonFastForwardPushError(message: string): boolean {
|
function isNonFastForwardPushError(message: string): boolean {
|
||||||
@@ -3793,14 +3827,39 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Status notices -->
|
<!-- Global error toast -->
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
<section class="notice error" role="alert">
|
{#key errorMessage}
|
||||||
<AlertCircle size={17} aria-hidden="true" />
|
<div class="error-toast-region" aria-live="assertive" aria-atomic="true">
|
||||||
|
<section
|
||||||
|
class="error-toast"
|
||||||
|
role="alert"
|
||||||
|
onmouseenter={() => pauseAutoHideError("errorMessage")}
|
||||||
|
onmouseleave={() => resumeAutoHideError("errorMessage")}
|
||||||
|
>
|
||||||
|
<div class="error-toast-icon">
|
||||||
|
<AlertCircle size={20} aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div class="error-toast-content">
|
||||||
|
<strong>{appLanguage === "de" ? "Etwas ist schiefgelaufen" : "Something went wrong"}</strong>
|
||||||
<span>{errorMessage}</span>
|
<span>{errorMessage}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="error-toast-close"
|
||||||
|
type="button"
|
||||||
|
onclick={() => { errorMessage = ""; }}
|
||||||
|
title={appLanguage === "de" ? "Schließen" : "Close"}
|
||||||
|
aria-label={appLanguage === "de" ? "Fehlermeldung schließen" : "Dismiss error"}
|
||||||
|
>
|
||||||
|
<X size={15} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
<div class="error-toast-timeout" aria-hidden="true"></div>
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Status notices -->
|
||||||
{#if operation && operation !== "Opening repository" && !hasRepository}
|
{#if operation && operation !== "Opening repository" && !hasRepository}
|
||||||
<section class="notice busy" aria-live="polite">
|
<section class="notice busy" aria-live="polite">
|
||||||
<LoaderCircle class="spin" size={17} aria-hidden="true" />
|
<LoaderCircle class="spin" size={17} aria-hidden="true" />
|
||||||
|
|||||||
+124
@@ -1222,6 +1222,113 @@
|
|||||||
color: #eadfff;
|
color: #eadfff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Global error toast --- */
|
||||||
|
|
||||||
|
.error-toast-region {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(var(--app-titlebar-height, 40px) + 14px);
|
||||||
|
left: 50%;
|
||||||
|
z-index: 90;
|
||||||
|
width: min(560px, calc(100vw - 32px));
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
align-items: start;
|
||||||
|
gap: 11px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 13px 13px 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(232,96,96,0.46);
|
||||||
|
border-radius: 13px;
|
||||||
|
color: var(--color-ink);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(232,96,96,0.18), rgba(100,108,255,0.08)),
|
||||||
|
rgba(12,13,24,0.96);
|
||||||
|
box-shadow: 0 18px 54px rgba(0,0,0,0.46), 0 0 0 1px rgba(255,255,255,0.035) inset;
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
pointer-events: auto;
|
||||||
|
animation: error-toast-in 180ms cubic-bezier(.2,.8,.2,1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border: 1px solid rgba(232,96,96,0.28);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #ff9b9b;
|
||||||
|
background: rgba(232,96,96,0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
padding-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-content strong {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-content span {
|
||||||
|
color: var(--color-ink-muted);
|
||||||
|
font-size: 12.5px;
|
||||||
|
line-height: 1.45;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-close {
|
||||||
|
width: 28px;
|
||||||
|
min-width: 28px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
border-color: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--color-ink-dim);
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-close:hover:not(:disabled) {
|
||||||
|
border-color: rgba(255,255,255,0.2);
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast-timeout {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, #ff7878, #ef9a9a);
|
||||||
|
transform-origin: left;
|
||||||
|
animation: error-toast-timeout 6s linear both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast:hover .error-toast-timeout {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes error-toast-in {
|
||||||
|
from { opacity: 0; transform: translateY(-10px) scale(.985); }
|
||||||
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes error-toast-timeout {
|
||||||
|
from { transform: scaleX(1); }
|
||||||
|
to { transform: scaleX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Update toast --- */
|
/* --- Update toast --- */
|
||||||
|
|
||||||
.update-toast {
|
.update-toast {
|
||||||
@@ -5306,6 +5413,23 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
|
|||||||
background: rgba(210, 55, 75, 0.08);
|
background: rgba(210, 55, 75, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-theme="light"] .error-toast {
|
||||||
|
border-color: rgba(190,48,66,0.28);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(210,55,75,0.11), rgba(49,95,214,0.05)),
|
||||||
|
rgba(255,255,255,0.96);
|
||||||
|
box-shadow: 0 18px 48px rgba(28,44,74,0.18), 0 0 0 1px rgba(255,255,255,0.8) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="light"] .error-toast-icon {
|
||||||
|
color: #b33445;
|
||||||
|
background: rgba(210,55,75,0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="light"] .error-toast-content strong {
|
||||||
|
color: #24314a;
|
||||||
|
}
|
||||||
|
|
||||||
:root[data-theme="light"] .notice.busy {
|
:root[data-theme="light"] .notice.busy {
|
||||||
color: #315fd6;
|
color: #315fd6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { telemetryLog } from "./telemetry";
|
||||||
|
|
||||||
export type AnalyticsEventProperties = Record<string, string | number>;
|
export type AnalyticsEventProperties = Record<string, string | number>;
|
||||||
|
|
||||||
export function trackAnalyticsEvent(name: string, props?: AnalyticsEventProperties) {
|
export function trackAnalyticsEvent(name: string, props?: AnalyticsEventProperties) {
|
||||||
|
const details = props && Object.keys(props).length > 0 ? ` ${JSON.stringify(props)}` : "";
|
||||||
|
telemetryLog("info", `${name}${details}`, `product.${name}`);
|
||||||
void invoke("plugin:aptabase|track_event", {
|
void invoke("plugin:aptabase|track_event", {
|
||||||
name,
|
name,
|
||||||
props: props && Object.keys(props).length > 0 ? props : undefined,
|
props: props && Object.keys(props).length > 0 ? props : undefined,
|
||||||
|
|||||||
@@ -30,10 +30,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="analytics-notice-copy">
|
<div class="analytics-notice-copy">
|
||||||
<p>
|
<p>
|
||||||
Gitty can send anonymous usage events to Aptabase so crashes, rough edges, and commonly used workflows are easier to improve.
|
Gitty can send anonymous usage events and technical error logs to Aptabase and the self-hosted SigNoz service so crashes, rough edges, and commonly used workflows are easier to improve.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Events do not include repository paths, remote URLs, branch names, commit messages, diffs, file names, credentials, or source code.
|
Telemetry does not include repository paths, remote URLs, branch names, commit messages, diffs, file names, credentials, or source code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="dialog-footer analytics-notice-footer">
|
<footer class="dialog-footer analytics-notice-footer">
|
||||||
<span class="dialog-footer-info">Privacy-friendly, optional, and limited to product usage events.</span>
|
<span class="dialog-footer-info">Privacy-friendly, optional, and limited to product usage and technical errors.</span>
|
||||||
<button class="btn-primary" type="button" onclick={() => onContinue(allowAnalytics)}>
|
<button class="btn-primary" type="button" onclick={() => onContinue(allowAnalytics)}>
|
||||||
<Check size={16} aria-hidden="true" />
|
<Check size={16} aria-hidden="true" />
|
||||||
Continue
|
Continue
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
<label class="settings-toggle-row">
|
<label class="settings-toggle-row">
|
||||||
<input type="checkbox" bind:checked={analyticsEnabled} />
|
<input type="checkbox" bind:checked={analyticsEnabled} />
|
||||||
<span>
|
<span>
|
||||||
<strong>{isGerman ? "Anonyme Aptabase-Ereignisse erlauben" : "Allow anonymous Aptabase events"}</strong>
|
<strong>{isGerman ? "Anonyme Analytics und Fehlerberichte erlauben" : "Allow anonymous analytics and error reports"}</strong>
|
||||||
<small>{isGerman ? "Es werden keine Repository-Pfade, Remotes, Branches, Commit-Nachrichten, Dateinamen, Diffs, Zugangsdaten oder Code übertragen." : "No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent."}</small>
|
<small>{isGerman ? "Es werden keine Repository-Pfade, Remotes, Branches, Commit-Nachrichten, Dateinamen, Diffs, Zugangsdaten oder Code übertragen." : "No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent."}</small>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { tracedInvoke as invoke } from "./telemetry";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AiReviewResult,
|
AiReviewResult,
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
type TelemetryLevel = "info" | "warn" | "error";
|
||||||
|
|
||||||
|
const MAX_MESSAGE_LENGTH = 2_048;
|
||||||
|
let telemetryEnabled = false;
|
||||||
|
let configuration: Promise<unknown> = Promise.resolve();
|
||||||
|
|
||||||
|
// Telemetry must never contain repository contents or identifying local data.
|
||||||
|
function sanitize(message: string): string {
|
||||||
|
return message
|
||||||
|
.replace(/https?:\/\/\S+/gi, "[url]")
|
||||||
|
.replace(/(?:[A-Za-z]:\\|\/)(?:[^\s<>:"|?*]+[\\/])*[^\s<>:"|?*]*/g, "[path]")
|
||||||
|
.replace(/(token|password|secret|authorization|credential)\s*[:=]\s*\S+/gi, "$1=[redacted]")
|
||||||
|
.replace(/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/g, "[email]")
|
||||||
|
.slice(0, MAX_MESSAGE_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTelemetryEnabled(enabled: boolean) {
|
||||||
|
telemetryEnabled = enabled;
|
||||||
|
configuration = invoke("set_telemetry_enabled", { enabled }).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function telemetryLog(level: TelemetryLevel, message: string, eventName?: string) {
|
||||||
|
if (!telemetryEnabled) return;
|
||||||
|
void configuration.then(() => invoke("emit_frontend_log", {
|
||||||
|
log: {
|
||||||
|
level,
|
||||||
|
message: sanitize(message),
|
||||||
|
eventName,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.catch(() => {
|
||||||
|
// Observability must never interrupt the Git workflow.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomHex(bytes: number): string {
|
||||||
|
const values = new Uint8Array(bytes);
|
||||||
|
crypto.getRandomValues(values);
|
||||||
|
return Array.from(values, (value) => value.toString(16).padStart(2, "0")).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tracedInvoke<T>(command: string, args?: Record<string, unknown>): Promise<T> {
|
||||||
|
if (!telemetryEnabled) return invoke<T>(command, args);
|
||||||
|
const startedAtMs = Date.now();
|
||||||
|
const started = performance.now();
|
||||||
|
const traceId = randomHex(16);
|
||||||
|
const spanId = randomHex(8);
|
||||||
|
try {
|
||||||
|
const result = await invoke<T>(command, args);
|
||||||
|
void configuration.then(() => invoke("emit_frontend_span", {
|
||||||
|
span: { name: command, traceId, spanId, startedAtMs, durationMs: performance.now() - started, success: true },
|
||||||
|
})).catch(() => {});
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
void configuration.then(() => invoke("emit_frontend_span", {
|
||||||
|
span: { name: command, traceId, spanId, startedAtMs, durationMs: performance.now() - started, success: false },
|
||||||
|
})).catch(() => {});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installGlobalErrorTelemetry() {
|
||||||
|
window.addEventListener("error", (event) => {
|
||||||
|
telemetryLog("error", event.message || "Unhandled frontend error", "frontend.unhandled_error");
|
||||||
|
});
|
||||||
|
window.addEventListener("unhandledrejection", (event) => {
|
||||||
|
const message = event.reason instanceof Error ? event.reason.message : String(event.reason ?? "Unhandled promise rejection");
|
||||||
|
telemetryLog("error", message, "frontend.unhandled_rejection");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ import { mount } from "svelte";
|
|||||||
|
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
import { installGlobalErrorTelemetry } from "./lib/telemetry";
|
||||||
|
|
||||||
const THEME_KEY = "gitlite.theme.v1";
|
const THEME_KEY = "gitlite.theme.v1";
|
||||||
const target = document.getElementById("app");
|
const target = document.getElementById("app");
|
||||||
|
|
||||||
|
installGlobalErrorTelemetry();
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error("App target element was not found.");
|
throw new Error("App target element was not found.");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user