feat: Add hyperdeck command

This commit is contained in:
Baud 2024-05-20 23:04:47 +01:00
parent ee375e34a8
commit f9cac35a29
14 changed files with 1122 additions and 174 deletions

602
Cargo.lock generated
View File

@ -23,10 +23,6 @@ name = "accesskit"
version = "0.12.3" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b"
dependencies = [
"enumn",
"serde",
]
[[package]] [[package]]
name = "accesskit_consumer" name = "accesskit_consumer"
@ -117,7 +113,6 @@ dependencies = [
"cfg-if", "cfg-if",
"getrandom", "getrandom",
"once_cell", "once_cell",
"serde",
"version_check", "version_check",
"zerocopy", "zerocopy",
] ]
@ -131,6 +126,27 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.5.2" version = "0.5.2"
@ -202,6 +218,22 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-compression"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498"
dependencies = [
"brotli",
"flate2",
"futures-core",
"memchr",
"pin-project-lite",
"tokio",
"zstd",
"zstd-safe",
]
[[package]] [[package]]
name = "async-executor" name = "async-executor"
version = "1.11.0" version = "1.11.0"
@ -415,6 +447,71 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"base64",
"bitflags 1.3.2",
"bytes",
"futures-util",
"http 0.2.12",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sha1",
"sync_wrapper",
"tokio",
"tokio-tungstenite 0.20.1",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 0.2.12",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-macros"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.71" version = "0.3.71"
@ -447,9 +544,6 @@ name = "bitflags"
version = "2.5.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "block" name = "block"
@ -527,6 +621,27 @@ dependencies = [
"piper", "piper",
] ]
[[package]]
name = "brotli"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@ -827,27 +942,6 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@ -885,7 +979,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde",
] ]
[[package]] [[package]]
@ -896,7 +989,6 @@ checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"cocoa", "cocoa",
"directories-next",
"document-features", "document-features",
"egui", "egui",
"egui-winit", "egui-winit",
@ -912,8 +1004,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"raw-window-handle 0.6.1", "raw-window-handle 0.6.1",
"ron",
"serde",
"static_assertions", "static_assertions",
"thiserror", "thiserror",
"wasm-bindgen", "wasm-bindgen",
@ -935,8 +1025,6 @@ dependencies = [
"epaint", "epaint",
"log", "log",
"nohash-hasher", "nohash-hasher",
"ron",
"serde",
] ]
[[package]] [[package]]
@ -950,7 +1038,6 @@ dependencies = [
"egui", "egui",
"log", "log",
"raw-window-handle 0.6.1", "raw-window-handle 0.6.1",
"serde",
"smithay-clipboard", "smithay-clipboard",
"web-time", "web-time",
"webbrowser", "webbrowser",
@ -979,7 +1066,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde",
] ]
[[package]] [[package]]
@ -1003,17 +1089,6 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "enumn"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.2" version = "0.10.2"
@ -1041,7 +1116,6 @@ dependencies = [
"log", "log",
"nohash-hasher", "nohash-hasher",
"parking_lot 0.12.2", "parking_lot 0.12.2",
"serde",
] ]
[[package]] [[package]]
@ -1125,6 +1199,21 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "ewebsock"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6177769715c6ec5a324acee995183b22721ea23c58e49af14a828eadec85d120"
dependencies = [
"document-features",
"js-sys",
"log",
"tungstenite 0.21.0",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.12" version = "0.6.12"
@ -1453,11 +1542,37 @@ dependencies = [
"gl_generator", "gl_generator",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hdrhistogram"
version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [
"byteorder",
"num-traits",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -1486,6 +1601,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a80870ee775a6b862265eb04a8d61d1b1ef8e4c423154a52ba6a1e3ff24836c" checksum = "2a80870ee775a6b862265eb04a8d61d1b1ef8e4c423154a52ba6a1e3ff24836c"
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.1.0"
@ -1497,32 +1623,84 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.8.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 0.2.12",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.7",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyperdeck-monitor" name = "hyperdeck-monitor"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum",
"color-eyre", "color-eyre",
"futures",
"futures-util", "futures-util",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tokio-tungstenite", "tokio-stream",
"tokio-tungstenite 0.21.0",
"tokio-util", "tokio-util",
"tower",
"tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"url", "url",
"uuid",
] ]
[[package]] [[package]]
@ -1532,9 +1710,11 @@ dependencies = [
"eframe", "eframe",
"egui", "egui",
"env_logger", "env_logger",
"ewebsock",
"hrtime", "hrtime",
"log", "log",
"serde", "serde",
"serde_json",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-timer", "wasm-timer",
] ]
@ -1579,6 +1759,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@ -1586,7 +1776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@ -1609,6 +1799,16 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "iri-string"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f5f6c2df22c009ac44f6f1499308e7a3ac7ba42cd2378475cc691510e1eef1b"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.12" version = "0.4.12"
@ -1705,16 +1905,6 @@ dependencies = [
"redox_syscall 0.4.1", "redox_syscall 0.4.1",
] ]
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"libc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.3.8"
@ -1767,6 +1957,12 @@ dependencies = [
"regex-automata 0.1.10", "regex-automata 0.1.10",
] ]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.2" version = "2.7.2"
@ -1800,6 +1996,22 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.2" version = "0.7.2"
@ -2047,7 +2259,7 @@ version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
dependencies = [ dependencies = [
"libredox 0.0.2", "libredox",
] ]
[[package]] [[package]]
@ -2147,6 +2359,26 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.14" version = "0.2.14"
@ -2350,17 +2582,6 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
] ]
[[package]]
name = "redox_users"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox 0.1.3",
"thiserror",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.4"
@ -2405,18 +2626,6 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "ron"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64",
"bitflags 2.5.0",
"serde",
"serde_derive",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -2450,6 +2659,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@ -2499,15 +2714,25 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.116" version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
dependencies = [
"itoa",
"serde",
]
[[package]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.19" version = "0.1.19"
@ -2519,6 +2744,18 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -2671,6 +2908,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.1" version = "3.10.1"
@ -2767,6 +3010,30 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "tokio-stream"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
"tokio-util",
]
[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite 0.20.1",
]
[[package]] [[package]]
name = "tokio-tungstenite" name = "tokio-tungstenite"
version = "0.21.0" version = "0.21.0"
@ -2776,19 +3043,23 @@ dependencies = [
"futures-util", "futures-util",
"log", "log",
"tokio", "tokio",
"tungstenite", "tungstenite 0.21.0",
] ]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.10" version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-io",
"futures-sink", "futures-sink",
"futures-util",
"hashbrown 0.14.5",
"pin-project-lite", "pin-project-lite",
"slab",
"tokio", "tokio",
] ]
@ -2804,7 +3075,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow", "winnow",
] ]
@ -2815,17 +3086,81 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow", "winnow",
] ]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"hdrhistogram",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"async-compression",
"base64",
"bitflags 2.5.0",
"bytes",
"futures-core",
"futures-util",
"http 0.2.12",
"http-body",
"http-range-header",
"httpdate",
"iri-string",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower",
"tower-layer",
"tower-service",
"tracing",
"uuid",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.40" version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",
@ -2891,12 +3226,37 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.20.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
[[package]]
name = "tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 0.2.12",
"httparse",
"log",
"rand",
"sha1",
"thiserror",
"url",
"utf-8",
]
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.21.0" version = "0.21.0"
@ -2906,7 +3266,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding", "data-encoding",
"http", "http 1.1.0",
"httparse", "httparse",
"log", "log",
"rand", "rand",
@ -2933,6 +3293,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.15" version = "0.3.15"
@ -2977,6 +3346,16 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
"serde",
]
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"
@ -3005,6 +3384,15 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -3723,6 +4111,34 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "zstd"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.10+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
dependencies = [
"cc",
"pkg-config",
]
[[package]] [[package]]
name = "zvariant" name = "zvariant"
version = "3.15.2" version = "3.15.2"

View File

@ -1,7 +1,7 @@
[workspace] [workspace]
members = [ members = [
"monitor", "monitor",
"frontend" "frontend",
] ]
resolver = "2" resolver = "2"

3
frontend/.gitignore vendored
View File

@ -1,3 +1,6 @@
# Copied during build (🤮)
src/websocket.rs
# trunk output folder # trunk output folder
dist dist

View File

@ -9,12 +9,13 @@ eframe = { version = "0.27.0", default-features = false, features = [
"accesskit", # Make egui comptaible with screen readers. "accesskit", # Make egui comptaible with screen readers.
"default_fonts", # Embed the default egui fonts. "default_fonts", # Embed the default egui fonts.
"glow", # Use the glow rendering backend. Alternative: "wgpu". "glow", # Use the glow rendering backend. Alternative: "wgpu".
"persistence", # Enable restoring app state when restarting the app.
] } ] }
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
hrtime = "0.2.0" hrtime = "0.2.0"
wasm-timer = "0.2.5" wasm-timer = "0.2.5"
ewebsock = "0.5.0"
serde_json = "1.0.117"
# native: # native:

7
frontend/build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
// Sorry, I know it's horrible but I don't have the spoons to make it right.
const API_DEFINITION_FILE: &str = "../monitor/src/api/message.rs";
println!("cargo:rerun-if-changed={API_DEFINITION_FILE}");
std::fs::copy(API_DEFINITION_FILE, "./src/websocket.rs")
.expect("Failed to copy API definition");
}

View File

@ -1,35 +1,38 @@
use std::{ use std::{
collections::VecDeque,
fmt::Display, fmt::Display,
mem,
net::{IpAddr, Ipv4Addr}, net::{IpAddr, Ipv4Addr},
}; };
use egui::{Color32, RichText, Sense, Stroke, Vec2}; use egui::{Button, Color32, RichText, Sense, Stroke, Vec2};
use ewebsock::{Options, WsReceiver, WsSender};
use wasm_timer::Instant; use wasm_timer::Instant;
#[derive(serde::Deserialize, serde::Serialize)] use crate::websocket::{AddHyperdeckRequest, ClientRequest};
#[serde(default)]
pub struct HyperdeckMonitorApp { pub struct HyperdeckMonitorApp {
#[serde(skip)]
blink: bool, blink: bool,
#[serde(skip)]
last_blink_change: Instant, last_blink_change: Instant,
#[serde(skip)]
new_hyperdeck_ip: String, new_hyperdeck_ip: String,
#[serde(skip)]
new_hyperdeck_name: String, new_hyperdeck_name: String,
new_hyperdeck_port: String,
#[serde(skip)]
hyperdecks: Vec<Hyperdeck>, hyperdecks: Vec<Hyperdeck>,
websocket_message_queue: VecDeque<ClientRequest>,
ws_sender: WsSender,
ws_receiver: WsReceiver,
} }
impl Default for HyperdeckMonitorApp { impl Default for HyperdeckMonitorApp {
fn default() -> Self { fn default() -> Self {
let (ws_sender, ws_receiver) =
ewebsock::connect("ws://127.0.0.1:9681/ws", Options::default()).unwrap();
Self { Self {
blink: false, blink: false,
last_blink_change: Instant::now(), last_blink_change: Instant::now(),
new_hyperdeck_ip: "".to_owned(), new_hyperdeck_ip: "".to_owned(),
new_hyperdeck_name: "".to_owned(), new_hyperdeck_name: "".to_owned(),
new_hyperdeck_port: 9993.to_string(),
hyperdecks: vec![ hyperdecks: vec![
Hyperdeck { Hyperdeck {
name: "Test Hyperdeck 1".to_string(), name: "Test Hyperdeck 1".to_string(),
@ -52,27 +55,20 @@ impl Default for HyperdeckMonitorApp {
}], }],
}, },
], ],
websocket_message_queue: VecDeque::new(),
ws_sender,
ws_receiver,
} }
} }
} }
impl HyperdeckMonitorApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// Load previous app state (if any)
if let Some(storage) = cc.storage {
return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
}
Default::default()
}
}
impl eframe::App for HyperdeckMonitorApp { impl eframe::App for HyperdeckMonitorApp {
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if let Some(message) = self.websocket_message_queue.pop_front() {
self.ws_sender.send(ewebsock::WsMessage::Text(
serde_json::to_string(&message).expect("Could not serialize message"),
));
}
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| { egui::menu::bar(ui, |ui| {
egui::widgets::global_dark_light_mode_buttons(ui); egui::widgets::global_dark_light_mode_buttons(ui);
@ -80,7 +76,13 @@ impl eframe::App for HyperdeckMonitorApp {
}); });
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
add_hyperdeck_panel(ui, &mut self.new_hyperdeck_ip, &mut self.new_hyperdeck_name); add_hyperdeck_panel(
ui,
&mut self.new_hyperdeck_name,
&mut self.new_hyperdeck_ip,
&mut self.new_hyperdeck_port,
&mut self.websocket_message_queue,
);
ui.separator(); ui.separator();
ui.vertical(|ui| { ui.vertical(|ui| {
@ -105,8 +107,10 @@ impl eframe::App for HyperdeckMonitorApp {
fn add_hyperdeck_panel( fn add_hyperdeck_panel(
ui: &mut egui::Ui, ui: &mut egui::Ui,
new_hyperdeck_ip: &mut String,
new_hyperdeck_name: &mut String, new_hyperdeck_name: &mut String,
new_hyperdeck_ip: &mut String,
new_hyperdeck_port: &mut String,
message_queue: &mut VecDeque<ClientRequest>,
) { ) {
ui.heading("Add hyperdeck"); ui.heading("Add hyperdeck");
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -114,8 +118,19 @@ fn add_hyperdeck_panel(
ui.text_edit_singleline(new_hyperdeck_name); ui.text_edit_singleline(new_hyperdeck_name);
ui.label("IP"); ui.label("IP");
ui.text_edit_singleline(new_hyperdeck_ip); ui.text_edit_singleline(new_hyperdeck_ip);
if ui.button("Add").clicked() { ui.label("Port");
// Do Something ui.text_edit_singleline(new_hyperdeck_port);
let button_enabled = new_hyperdeck_ip.parse::<IpAddr>().is_ok()
&& !new_hyperdeck_name.is_empty()
&& new_hyperdeck_port.parse::<u16>().is_ok();
if ui.add_enabled(button_enabled, Button::new("Add")).clicked() {
message_queue.push_back(ClientRequest::AddHyperdeck(AddHyperdeckRequest {
name: mem::take(new_hyperdeck_name),
ip: mem::take(new_hyperdeck_ip),
port: mem::replace(new_hyperdeck_port, "9993".to_string())
.parse::<u16>()
.unwrap(),
}));
} }
}); });
} }

View File

@ -1,2 +1,4 @@
mod app; mod app;
mod websocket;
pub use app::HyperdeckMonitorApp; pub use app::HyperdeckMonitorApp;

View File

@ -11,7 +11,7 @@ fn main() -> eframe::Result<()> {
eframe::run_native( eframe::run_native(
"eframe template", "eframe template",
native_options, native_options,
Box::new(|cc| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::new(cc))), Box::new(|_| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::default())),
) )
} }
@ -26,7 +26,7 @@ fn main() {
.start( .start(
"the_canvas_id", // Where to draw to. "the_canvas_id", // Where to draw to.
web_options, web_options,
Box::new(|cc| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::new(cc))), Box::new(|_| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::default())),
) )
.await .await
.expect("failed to start eframe"); .expect("failed to start eframe");

View File

@ -6,13 +6,19 @@ edition = "2021"
default-run = "hyperdeck-monitor" default-run = "hyperdeck-monitor"
[dependencies] [dependencies]
axum = { version = "0.6.10", features = ["macros", "ws"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
futures = { version = "0.3.25" }
futures-util = "0.3.30" futures-util = "0.3.30"
serde = { version = "1.0.199", features = ["derive"] } serde = { version = "1.0.199", features = ["derive"] }
serde_json = "1.0.116" serde_json = "1.0.116"
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
tokio-stream = { version = "0.1.12", features = ["sync"] }
tokio-tungstenite = "0.21.0" tokio-tungstenite = "0.21.0"
tokio-util = "0.7.10" tokio-util = { version = "0.7.11", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
tower-http = { version = "0.4.0", features = ["full"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = "2.5.0" url = "2.5.0"
uuid = { version = "1.2.2", features = ["serde", "v4"] }

View File

@ -1,10 +1,35 @@
import { Hyperdeck, Commands } from 'hyperdeck-connection';
import WebSocket from 'ws'; import WebSocket from 'ws';
interface WrappedHyperdeck {
ip: String,
port: number,
hyperdeck: Hyperdeck
}
const hyperdecks: Map<string, WrappedHyperdeck> = new Map()
enum WebSocketMessageType {
AddHyperdeck = "add_hyperdeck"
}
type WebSocketMessage = {
type: WebSocketMessageType.AddHyperdeck,
id: string,
ip: string,
port: number
}
const wss = new WebSocket.Server({ port: 7867 }); const wss = new WebSocket.Server({ port: 7867 });
wss.on('connection', function connection(ws) { wss.on('connection', function connection(ws) {
ws.on('message', function message(data) { ws.on('message', function message(data) {
console.log('received: %s', data); try {
const message = JSON.parse(data.toString()) as Partial<WebSocketMessage>;
handle_message(message)
} catch (_err) {
return;
}
}); });
ws.send(JSON.stringify({ ws.send(JSON.stringify({
@ -12,3 +37,55 @@ wss.on('connection', function connection(ws) {
message: "Hello" message: "Hello"
})); }));
}); });
function exhaustiveMatch(_never: never) {
return;
}
function handle_message(message: Partial<WebSocketMessage>) {
console.log(JSON.stringify(message));
if (message.type === undefined) return;
switch (message.type) {
case WebSocketMessageType.AddHyperdeck:
if (message.id === undefined) return;
if (message.ip === undefined) return;
if (message.port === undefined) return;
if (isNaN(message.port)) return;
if (message.port <= 0) return;
console.log("Adding hyperdeck");
const newHyperdeck = new Hyperdeck()
// hyperdecks.set(message.id, {
// ip: message.ip,
// port: message.port,
// hyperdeck: newHyperdeck
// });
newHyperdeck.on('connected', (info) => {
console.log(JSON.stringify(info))
newHyperdeck.sendCommand(new Commands.TransportInfoCommand()).then((transportInfo) => {
console.log(JSON.stringify(transportInfo))
})
})
newHyperdeck.on('notify.slot', function (state) {
console.log(JSON.stringify(state)) // catch the slot state change.
})
newHyperdeck.on('notify.transport', function (state) {
console.log(JSON.stringify(state)) // catch the transport state change.
})
newHyperdeck.on('error', (err) => {
console.log('Hyperdeck error', JSON.stringify(err))
})
newHyperdeck.connect(message.ip, message.port)
break;
default:
exhaustiveMatch(message.type)
}
}

172
monitor/src/api.rs Normal file
View File

@ -0,0 +1,172 @@
use axum::extract::ws::Message;
use axum::extract::{State, WebSocketUpgrade};
use axum::response::Html;
use axum::{
body::Bytes,
extract::Path,
http::{header, HeaderValue, Method},
response::IntoResponse,
routing::get,
Router,
};
use message::{ClientRequest, HyperdeckMonitorState, ServerEvent};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
net::{Ipv4Addr, SocketAddr},
sync::Arc,
time::Duration,
};
use tokio::sync::{Mutex, RwLock};
use tower::ServiceBuilder;
use tower_http::timeout::TimeoutLayer;
use tower_http::ServiceBuilderExt;
use tower_http::{
cors::{Any, CorsLayer},
trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
LatencyUnit,
};
use tracing::info;
use uuid::Uuid;
pub mod message;
mod ws;
#[derive(Debug, Clone)]
pub struct Client {
pub sender: Option<tokio::sync::broadcast::Sender<Message>>,
}
type Clients = Arc<Mutex<HashMap<Uuid, Client>>>;
pub async fn initialize_api(
mut state_rx: tokio::sync::broadcast::Receiver<HyperdeckMonitorState>,
client_request_tx: tokio::sync::mpsc::UnboundedSender<ClientRequest>,
) {
info!("Initializing API");
let clients: Clients = Default::default();
let state = Arc::new(RwLock::new(state_rx.recv().await.unwrap()));
let state_clients = clients.clone();
let state_loop = state.clone();
tokio::spawn(async move {
loop {
if let Ok(hyperdeck_monitor_state) = state_rx.recv().await {
let mut state = state_loop.write().await;
*state = hyperdeck_monitor_state.clone();
let clients = state_clients.lock().await;
let state_json = serde_json::to_string(&ServerEvent::HyperdeckMonitorState(
hyperdeck_monitor_state.into(),
))
.unwrap();
for (_, client) in clients.iter() {
if let Some(sender) = &client.sender {
let message: Message = Message::Text(state_json.clone());
let _ = sender.send(message);
}
}
}
}
});
let app_state = AppState {
state,
client_request_tx,
clients,
port: 9681,
};
let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, app_state.port));
info!("Listening on {}", addr);
// TODO: This could fail, need to figure out how to get a result from this
let _ = axum::Server::bind(&addr)
.serve(app(app_state).into_make_service())
.await;
}
#[derive(Clone)]
struct AppState {
state: Arc<RwLock<HyperdeckMonitorState>>,
client_request_tx: tokio::sync::mpsc::UnboundedSender<ClientRequest>,
clients: Clients,
port: u16,
}
fn app(state: AppState) -> Router {
let sensitive_headers: Arc<[_]> = vec![header::AUTHORIZATION, header::COOKIE].into();
let middleware = ServiceBuilder::new()
// Mark the `Authorization` and `Cookie` headers as sensitive so it doesn't show in logs
.sensitive_request_headers(sensitive_headers.clone())
// Add high level tracing/logging to all requests
.layer(
TraceLayer::new_for_http()
.on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| {
tracing::trace!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk")
})
.make_span_with(DefaultMakeSpan::new().include_headers(true))
.on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)),
)
.sensitive_response_headers(sensitive_headers)
// Set a timeout
.layer(TimeoutLayer::new(Duration::from_secs(10)))
// Box the response body so it implements `Default` which is required by axum
.map_response_body(axum::body::boxed)
// Compress responses
.compression()
// Set a `Content-Type` if there isn't one already.
.insert_response_header_if_not_present(
header::CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
let cors = CorsLayer::new()
.allow_methods(vec![
Method::GET,
Method::POST,
Method::PUT,
Method::DELETE,
Method::OPTIONS,
])
.allow_headers(Any)
.allow_origin(Any)
.allow_credentials(false);
Router::new()
.route("/", get(get_index))
.route("/ws", get(upgrade_ws))
.layer(middleware)
.layer(cors)
.with_state(state)
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WebSocketUpgradeRequest {}
async fn get_index() -> Html<String> {
Html(format!("Hello!"))
}
#[axum::debug_handler]
async fn upgrade_ws(state: State<AppState>, ws: WebSocketUpgrade) -> impl IntoResponse {
info!("New client websocket connection");
let client_id = uuid::Uuid::new_v4();
state
.clients
.lock()
.await
.insert(client_id.clone(), Client { sender: None });
let client = state.clients.lock().await.get(&client_id).cloned().unwrap();
ws.on_upgrade(move |socket| {
ws::client_connection(
state.client_request_tx.clone(),
socket,
client_id,
state.state.clone(),
state.clients.clone(),
client,
)
})
}

View File

@ -0,0 +1,36 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum ClientRequest {
AddHyperdeck(AddHyperdeckRequest),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddHyperdeckRequest {
pub name: String,
pub ip: String,
pub port: u16,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum ServerEvent {
HyperdeckMonitorState(HyperdeckMonitorState),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HyperdeckMonitorState {
pub hyperdecks: HashMap<String, HyperdeckState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperdeckState {
pub name: String,
pub ip: String,
pub port: u16,
}

84
monitor/src/api/ws.rs Normal file
View File

@ -0,0 +1,84 @@
use std::{future, sync::Arc};
use super::message::{ClientRequest, HyperdeckMonitorState};
use crate::api::ServerEvent;
use axum::extract::ws::{Message, WebSocket};
use futures::StreamExt;
use tokio::sync::RwLock;
use tokio_stream::wrappers::BroadcastStream;
use tracing::{debug, error, log::info};
use uuid::Uuid;
use super::{Client, Clients};
pub async fn client_connection(
client_request_tx: tokio::sync::mpsc::UnboundedSender<ClientRequest>,
ws: WebSocket,
id: Uuid,
state: Arc<RwLock<HyperdeckMonitorState>>,
clients: Clients,
mut client: Client,
) {
let (client_ws_sender, mut client_ws_rcv) = ws.split();
let (client_sender, client_rcv) = tokio::sync::broadcast::channel::<Message>(10);
let client_rcv = BroadcastStream::new(client_rcv);
tokio::task::spawn(
client_rcv
.filter(|msg| future::ready(msg.is_ok()))
.map(|msg| Ok(msg.unwrap()))
.forward(client_ws_sender),
);
let current_state = state.read().await.clone();
let state_json =
serde_json::to_string(&ServerEvent::HyperdeckMonitorState(current_state.into())).unwrap();
client_sender.send(Message::Text(state_json.clone())).ok();
client.sender = Some(client_sender);
clients.lock().await.insert(id, client);
info!("{} connected", id);
while let Some(result) = client_ws_rcv.next().await {
let msg = match result {
Ok(msg) => msg,
Err(e) => {
error!("error resolving ws message for id: {}: {}", id.clone(), e);
break;
}
};
client_msg(client_request_tx.clone(), &id, msg).await;
}
clients.lock().await.remove(&id);
info!("{} disconnected", id);
}
async fn client_msg(
client_request_tx: tokio::sync::mpsc::UnboundedSender<ClientRequest>,
id: &Uuid,
msg: Message,
) {
debug!("received message from {}: {:?}", id, msg);
let message = match msg.into_text() {
Ok(v) => v,
Err(err) => {
error!("error: {:?}", err);
return;
}
};
if message == "ping" || message == "ping\n" {
return;
}
let client_request: super::message::ClientRequest = match serde_json::from_str(&message) {
Ok(v) => v,
Err(_) => {
return;
}
};
let _ = client_request_tx.send(client_request);
}

View File

@ -1,5 +1,6 @@
use std::{net::IpAddr, time::Duration}; use std::{process::Stdio, time::Duration};
use api::message::{AddHyperdeckRequest, ClientRequest, HyperdeckMonitorState, HyperdeckState};
use color_eyre::Report; use color_eyre::Report;
use futures_util::{ use futures_util::{
pin_mut, select, pin_mut, select,
@ -9,9 +10,14 @@ use futures_util::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
use tokio_util::sync::CancellationToken; use tokio_util::{
codec::{FramedRead, LinesCodec},
sync::CancellationToken,
};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
mod api;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
setup_logging().expect("Failed to setup logging"); setup_logging().expect("Failed to setup logging");
@ -20,35 +26,169 @@ async fn main() {
let cancel = CancellationToken::new(); let cancel = CancellationToken::new();
let node_process = run_node_process(cancel.clone()).fuse(); let node_process = run_node_process(cancel.clone()).fuse();
let (ws_message_tx, ws_message_rx) = tokio::sync::mpsc::unbounded_channel(); let (node_ws_message_tx, node_ws_message_rx) = tokio::sync::mpsc::unbounded_channel();
let (commands_tx, commands_rx) = tokio::sync::mpsc::unbounded_channel(); let (node_commands_tx, node_commands_rx) = tokio::sync::mpsc::unbounded_channel();
let state = AppState::default(); let state = AppState::default();
let ws_process = talk_to_node_ws(state, ws_message_tx, commands_rx, cancel.clone()).fuse(); let node_ws_communication =
talk_to_node_ws(state, node_ws_message_tx, node_commands_rx, cancel.clone()).fuse();
let (state_tx, state_rx) = tokio::sync::broadcast::channel(1);
let (client_request_tx, client_request_rx) = tokio::sync::mpsc::unbounded_channel();
let api = api::initialize_api(state_rx, client_request_tx).fuse();
let hyperdeck_monitor = run(
node_commands_tx,
node_ws_message_rx,
state_tx,
client_request_rx,
cancel.clone(),
)
.fuse();
pin_mut!(node_process); pin_mut!(node_process);
pin_mut!(ws_process); pin_mut!(node_ws_communication);
pin_mut!(api);
pin_mut!(hyperdeck_monitor);
select! { select! {
_ = node_process => {}, _ = node_process => {},
_ = ws_process => {}, _ = node_ws_communication => {},
_ = api => {},
_ = hyperdeck_monitor => {},
_ = cancel.cancelled().fuse() => {} _ = cancel.cancelled().fuse() => {}
}; };
cancel.cancel(); cancel.cancel();
} }
async fn run(
mut node_commands_tx: tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
mut node_ws_message_rx: tokio::sync::mpsc::UnboundedReceiver<NodeWsMessageReceived>,
mut state_tx: tokio::sync::broadcast::Sender<HyperdeckMonitorState>,
mut client_request_rx: tokio::sync::mpsc::UnboundedReceiver<ClientRequest>,
cancel: CancellationToken,
) {
let mut state = HyperdeckMonitorState::default();
let _ = state_tx.send(state.clone());
let mut ping_interval = tokio::time::interval(Duration::from_millis(500));
ping_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
while !cancel.is_cancelled() {
let state_modified = select! {
_ = ping_interval.tick().fuse() => {
// TODO: Ping node, check it's still alive
false
},
message_from_node = node_ws_message_rx.recv().fuse() => {
if let Some(msg) = message_from_node {
handle_message_from_node(msg, &mut node_commands_tx, &mut state).await
} else {
false
}
},
message_from_client = client_request_rx.recv().fuse() => {
if let Some(msg) = message_from_client {
handle_message_from_client(msg, &mut node_commands_tx, &mut state).await
} else {
false
}
}
};
if state_modified {
let _ = state_tx.send(state.clone());
}
}
}
async fn handle_message_from_node(
msg: NodeWsMessageReceived,
node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
state: &mut HyperdeckMonitorState,
) -> bool {
match msg {
NodeWsMessageReceived::Log { message } => {
tracing::info!("[NODE] {message}");
false
}
}
}
async fn handle_message_from_client(
msg: ClientRequest,
node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender<NodeWsCommand>,
state: &mut HyperdeckMonitorState,
) -> bool {
match msg {
ClientRequest::AddHyperdeck(AddHyperdeckRequest { name, ip, port }) => {
tracing::info!("Adding hyperdeck");
let id = uuid::Uuid::new_v4();
state.hyperdecks.insert(
id.to_string(),
HyperdeckState {
name,
ip: ip.clone(),
port,
},
);
let _ = node_commands_tx.send(NodeWsCommand::AddHyperdeck(AddHyperdeckCommand {
id: id.to_string(),
ip,
port,
}));
true
}
}
}
async fn run_node_process(cancel: CancellationToken) { async fn run_node_process(cancel: CancellationToken) {
while !cancel.is_cancelled() { while !cancel.is_cancelled() {
// Back-off in case we are immediately crashing in a loop.
tokio::time::sleep(Duration::from_secs(1)).await;
let result = tokio::process::Command::new("node") let result = tokio::process::Command::new("node")
.arg("monitor/index.js") .arg("monitor/index.js")
.output() .stdin(Stdio::piped())
.await; .stdout(Stdio::piped())
if let Ok(output) = result { .stderr(Stdio::piped())
if !output.status.success() { .spawn();
let err = String::from_utf8(output.stderr).unwrap_or("Unknown".to_string()); match result {
tracing::error!("Node process exited with error: {}", err); Ok(mut child_process) => {
// Back-off in case we are immediately crashing in a loop. let Some(raw_stdout) = child_process.stdout.take() else {
tokio::time::sleep(Duration::from_secs(1)).await; let _ = child_process.kill().await;
continue;
};
let Some(raw_stderr) = child_process.stderr.take() else {
let _ = child_process.kill().await;
continue;
};
let mut stdout = FramedRead::new(raw_stdout, LinesCodec::new())
.map(|data| data.expect("Could not read stdout"));
let mut stderr = FramedRead::new(raw_stderr, LinesCodec::new())
.map(|data| data.expect("Could not read stderr"));
while !cancel.is_cancelled() {
select! {
line = stdout.next().fuse() => {
if let Some(line) = line {
tracing::info!("[NODE] {line}");
}
}
line = stderr.next().fuse() => {
if let Some(line) = line {
tracing::error!("[NODE] {line}");
}
}
}
}
let _ = child_process.kill().await;
}
Err(err) => {
tracing::error!("Error running Node child process: {err}");
} }
} }
} }
@ -56,9 +196,15 @@ async fn run_node_process(cancel: CancellationToken) {
#[derive(Default)] #[derive(Default)]
struct AppState {} struct AppState {}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum NodeWsCommand { enum NodeWsCommand {
#[serde(rename = "ping")]
Ping, Ping,
#[serde(rename = "add_hyperdeck")]
AddHyperdeck(AddHyperdeckCommand), AddHyperdeck(AddHyperdeckCommand),
#[serde(rename = "remove_hyperdeck")]
RemoveHyperdeck(RemoveHyperdeckCommand), RemoveHyperdeck(RemoveHyperdeckCommand),
} }
@ -69,16 +215,18 @@ enum NodeWsMessageReceived {
Log { message: String }, Log { message: String },
} }
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct AddHyperdeckCommand { struct AddHyperdeckCommand {
ip: IpAddr, id: String,
ip: String,
port: u16,
} }
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct RemoveHyperdeckCommand { struct RemoveHyperdeckCommand {
ip: IpAddr, id: String,
} }
async fn talk_to_node_ws( async fn talk_to_node_ws(
@ -126,28 +274,13 @@ async fn handle_outbound_messages(
mut socket_tx: SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>, mut socket_tx: SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
) { ) {
while let Some(command) = commands_rx.recv().await { while let Some(command) = commands_rx.recv().await {
match command { if let Err(err) = socket_tx
NodeWsCommand::Ping => { .send(tokio_tungstenite::tungstenite::Message::Text(
let _ = socket_tx serde_json::to_string(&command).expect("Could not serialize command"),
.send(tokio_tungstenite::tungstenite::Message::Ping(vec![])) ))
.await; .await
} {
NodeWsCommand::AddHyperdeck(command) => { tracing::error!("Error sending command to Node proccess: {err}");
let _ = socket_tx
.send(tokio_tungstenite::tungstenite::Message::Text(
serde_json::to_string(&command)
.expect("Could not serialize AddHyperdeck command"),
))
.await;
}
NodeWsCommand::RemoveHyperdeck(command) => {
let _ = socket_tx
.send(tokio_tungstenite::tungstenite::Message::Text(
serde_json::to_string(&command)
.expect("Could not serialize RemoveHyperdeck command"),
))
.await;
}
} }
} }
} }
@ -161,11 +294,7 @@ async fn handle_inbound_messages(
match message { match message {
Ok(tokio_tungstenite::tungstenite::Message::Text(text)) => { Ok(tokio_tungstenite::tungstenite::Message::Text(text)) => {
if let Ok(received) = serde_json::from_str::<NodeWsMessageReceived>(&text) { if let Ok(received) = serde_json::from_str::<NodeWsMessageReceived>(&text) {
match received { let _ = ws_message_tx.send(received);
NodeWsMessageReceived::Log { message } => {
tracing::info!("Message from Node process: {message}");
}
}
} }
} }
Ok(tokio_tungstenite::tungstenite::Message::Pong(_)) => {} Ok(tokio_tungstenite::tungstenite::Message::Pong(_)) => {}