From 2ef21b3e8fd991266df4cc0569382fd6d1f4b9a6 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Thu, 27 Mar 2025 10:36:20 -0400 Subject: [PATCH 01/26] notebook proxy url impl --- config/notebook.toml | 8 +++++--- src/config/mod.rs | 3 ++- src/kube/models.rs | 9 ++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/config/notebook.toml b/config/notebook.toml index 5b9c3214..4b0765a6 100644 --- a/config/notebook.toml +++ b/config/notebook.toml @@ -1,10 +1,10 @@ [open_ad_workbench] name = "OpenAD Workbench" -url = "quay.io/ibmdpdev/openad_workbench_stage:latest" +url = "quay.io/ibmdpdev/openad_workbench_prod:latest" pull_policy = "Always" working_dir = "/opt/app-root/src" volume_mnt_path = "/opt/app-root/src" -env = [ +notebook_env = [ "--ServerApp.token=''", "--ServerApp.password=''", "--ServerApp.notebook_dir='/opt/app-root/src'", @@ -17,6 +17,8 @@ env = [ secret = "ibmdpdev-openad-pull-secret" start_up_url = "lab/tree/start_menu.ipynb" max_idle_time = 86400 +[open_ad_workbench.env] +PROXY_URL = "https://open.accelerate.science/proxy" [datascience_notebook] name = "Data Science Notebook" @@ -24,7 +26,7 @@ url = "jupyter/base-notebook:latest" pull_policy = "IfNotPresent" working_dir = "/home/jovyan" volume_mnt_path = "/home/jovyan/" -env = [ +notebook_env = [ "--ServerApp.token=''", "--ServerApp.password=''", "--ServerApp.notebook_dir='/home/jovyan'", diff --git a/src/config/mod.rs b/src/config/mod.rs index bdefe24a..d6634e90 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -54,7 +54,8 @@ pub struct Notebook { pub pull_policy: String, pub working_dir: Option, pub volume_mnt_path: Option, - pub env: Option>, + pub notebook_env: Option>, + pub env: Option>, pub secret: Option, pub command: Option>, pub args: Option>, diff --git a/src/kube/models.rs b/src/kube/models.rs index 2726ce45..8546045d 100644 --- a/src/kube/models.rs +++ b/src/kube/models.rs @@ -29,7 +29,7 @@ impl NotebookSpec { env_to_add: Vec<(String, String)>, ) -> Self { let notebook_image = CONFIG.notebooks.get(notebook_image_name).unwrap(); - let mut notebook_env = notebook_image.env.clone().unwrap_or_default(); + let mut notebook_env = notebook_image.notebook_env.clone().unwrap_or_default(); notebook_env.push(format!( "--ServerApp.base_url='notebook/{}/{}'", @@ -47,6 +47,13 @@ impl NotebookSpec { }); } + // TODO: look into removing this clone + if let Some(envs) = notebook_image.env.clone() { + envs.into_iter().for_each(|(name, value)| { + env.push(EnvVar { name, value }); + }); + } + *notebook_start_url = notebook_image.start_up_url.clone(); *max_idle_time = notebook_image.max_idle_time; From f1fb4dc76a4b904ad88dcccc88cc099e7869c90d Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 6 May 2025 14:03:18 -0400 Subject: [PATCH 02/26] config update --- config/notebook.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/notebook.toml b/config/notebook.toml index 4b0765a6..a31e44c4 100644 --- a/config/notebook.toml +++ b/config/notebook.toml @@ -1,6 +1,6 @@ [open_ad_workbench] name = "OpenAD Workbench" -url = "quay.io/ibmdpdev/openad_workbench_prod:latest" +url = "quay.io/ibmdpdev/openad_workbench_stage:latest" pull_policy = "Always" working_dir = "/opt/app-root/src" volume_mnt_path = "/opt/app-root/src" From eb445aa12a017cc3131184557cea7afc47d4b905 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 6 May 2025 14:32:41 -0400 Subject: [PATCH 03/26] fmt --- src/config/mod.rs | 5 +---- src/db/models.rs | 1 - src/web/bridge_middleware/security_cache_header.rs | 6 ++++-- src/web/mod.rs | 9 ++++++--- src/web/route/proxy/services.rs | 9 ++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index d6634e90..4a77f98d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -121,10 +121,7 @@ pub fn init_once() -> Configuration { validation.leeway = 0; let (config_location_str, database_location_str) = if cfg!(debug_assertions) { - ( - "config/configurations_sample.toml", - "config/database_sample.toml", - ) + ("config/configurations.toml", "config/database_sample.toml") } else { ("config/configurations.toml", "config/database.toml") }; diff --git a/src/db/models.rs b/src/db/models.rs index 21c44beb..6f03b076 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -130,7 +130,6 @@ pub struct Group { pub last_updated_by: String, } - #[derive(Debug, Deserialize, Serialize)] pub struct GroupPortalRep { pub _id: String, diff --git a/src/web/bridge_middleware/security_cache_header.rs b/src/web/bridge_middleware/security_cache_header.rs index a15d2869..81ab6e9d 100644 --- a/src/web/bridge_middleware/security_cache_header.rs +++ b/src/web/bridge_middleware/security_cache_header.rs @@ -49,8 +49,10 @@ where let mut res = response.await?; let header = res.headers_mut(); header.insert( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_str("default-src 'self'; img-src *; style-src 'self'; script-src 'self';")?, + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_str( + "default-src 'self'; img-src *; style-src 'self'; script-src 'self';", + )?, ); header.insert(CACHE_CONTROL, HeaderValue::from_str("no-cache")?); Ok(res) diff --git a/src/web/mod.rs b/src/web/mod.rs index 9381cc2d..7dac74aa 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -19,10 +19,13 @@ use crate::kube::{LifecycleStream, Medium, notebook_lifecycle}; use futures::future::select; use crate::{ - auth::openid, config::CONFIG, db::{ - keydb::{CacheDB, CACHEDB}, + auth::openid, + config::CONFIG, + db::{ + keydb::{CACHEDB, CacheDB}, mongo::{DB, DBCONN, DBNAME}, - }, logger, templating + }, + logger, templating, }; mod bridge_middleware; diff --git a/src/web/route/proxy/services.rs b/src/web/route/proxy/services.rs index 2ea6125a..8af6bdef 100644 --- a/src/web/route/proxy/services.rs +++ b/src/web/route/proxy/services.rs @@ -16,10 +16,8 @@ pub static CATALOG: LazyLock = LazyLock::new(|| { }; Catalog( - toml::from_str( - &read_to_string(PathBuf::from_str(service_config).unwrap()).unwrap(), - ) - .unwrap(), + toml::from_str(&read_to_string(PathBuf::from_str(service_config).unwrap()).unwrap()) + .unwrap(), ) }); pub static CATALOG_URLS: LazyLock> = @@ -205,7 +203,8 @@ mod test { #[test] fn test_get_details() { let catalog = &CATALOG; - let Value::Boolean(b) = *catalog.get_details("resources", "example", "show").unwrap() else { + let Value::Boolean(b) = *catalog.get_details("resources", "example", "show").unwrap() + else { panic!("show not found"); }; assert!(b); From 95eec8bc348092b7d605f66db7a98d2bb10cd7f3 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 7 May 2025 17:05:48 -0400 Subject: [PATCH 04/26] dep update and remove copy button when no token has been generated --- Cargo.lock | 247 ++++++++++++++++---------------- templates/components/token.html | 4 +- 2 files changed, 130 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 677ea80a..ce2f46f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -226,7 +226,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -380,7 +380,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -391,7 +391,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -504,7 +504,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.100", + "syn 2.0.101", "which", ] @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.20" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "jobserver", "libc", @@ -667,9 +667,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -915,7 +915,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -939,7 +939,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -950,7 +950,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -988,18 +988,18 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "derive-where" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2364b9aa47e460ce9bca6ac1777d14c98eef7e274eb077beed49f3adc94183ed" +checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1012,7 +1012,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1032,15 +1032,15 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "unicode-xid", ] [[package]] name = "deunicode" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "digest" @@ -1062,7 +1062,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1124,7 +1124,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1175,7 +1175,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1195,7 +1195,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1371,7 +1371,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1522,9 +1522,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -1547,9 +1547,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1756,7 +1756,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.9", + "h2 0.4.10", "http 1.3.1", "http-body", "httparse", @@ -1798,13 +1798,13 @@ dependencies = [ "hyper", "hyper-util", "log", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", "tower-service", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -1995,7 +1995,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2065,7 +2065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -2263,7 +2263,7 @@ dependencies = [ "k8s-openapi", "kube-core", "pem 3.0.5", - "rustls 0.23.26", + "rustls 0.23.27", "secrecy", "serde", "serde_json", @@ -2305,7 +2305,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2321,7 +2321,7 @@ dependencies = [ "backon", "educe", "futures", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "hostname", "json-patch", "k8s-openapi", @@ -2375,9 +2375,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" @@ -2464,7 +2464,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2478,7 +2478,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2489,7 +2489,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2500,7 +2500,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2629,7 +2629,7 @@ dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2796,7 +2796,7 @@ dependencies = [ "regex", "reqwest", "rust-argon2", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-pemfile 2.2.0", "rustls-pki-types", "schemars", @@ -2874,7 +2874,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2885,9 +2885,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -3046,7 +3046,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3115,7 +3115,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3179,7 +3179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3212,7 +3212,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.26", + "rustls 0.23.27", "socket2", "thiserror 2.0.12", "tokio", @@ -3231,7 +3231,7 @@ dependencies = [ "rand 0.9.1", "ring 0.17.14", "rustc-hash 2.1.1", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3242,9 +3242,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", @@ -3359,9 +3359,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -3412,7 +3412,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.9", + "h2 0.4.10", "http 1.3.1", "http-body", "http-body-util", @@ -3429,7 +3429,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -3448,18 +3448,15 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", "windows-registry", ] [[package]] name = "resolv-conf" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48375394603e3dd4b2d64371f7148fd8c7baa2680e28741f2cb8d23b59e3d4c4" -dependencies = [ - "hostname", -] +checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" [[package]] name = "rfc6979" @@ -3583,9 +3580,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.0", "errno", @@ -3608,16 +3605,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.2", "subtle", "zeroize", ] @@ -3667,11 +3664,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] @@ -3686,9 +3684,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "aws-lc-rs", "ring 0.17.14", @@ -3747,7 +3745,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3868,7 +3866,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3879,7 +3877,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3962,7 +3960,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4018,9 +4016,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -4178,9 +4176,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -4198,13 +4196,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4249,7 +4247,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -4301,7 +4299,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4312,7 +4310,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4392,9 +4390,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -4416,7 +4414,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4445,7 +4443,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.26", + "rustls 0.23.27", "tokio", ] @@ -4469,7 +4467,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -4494,9 +4492,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -4515,9 +4513,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.25" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap 2.9.0", "serde", @@ -4529,9 +4527,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tower" @@ -4552,9 +4550,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +checksum = "a1cfca9ae570b2a6efc764a88e914c29b3dfaa1fafe5f495812ae97ec9bc4d53" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", @@ -4600,7 +4598,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4656,7 +4654,7 @@ dependencies = [ "httparse", "log", "rand 0.9.1", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-pki-types", "sha1", "thiserror 2.0.12", @@ -4835,7 +4833,7 @@ name = "utils" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4930,7 +4928,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -4965,7 +4963,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5020,9 +5018,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -5097,7 +5104,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5108,7 +5115,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5369,9 +5376,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -5445,7 +5452,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] @@ -5475,7 +5482,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5486,7 +5493,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5506,7 +5513,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] @@ -5527,7 +5534,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5549,7 +5556,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] diff --git a/templates/components/token.html b/templates/components/token.html index e39e1449..c625ca43 100644 --- a/templates/components/token.html +++ b/templates/components/token.html @@ -1,5 +1,6 @@
-

Your API Token

+

Your API Token

+ {%if token %}
From de46a0a894c0c8078bd745cc4f6fe117a259ab8c Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 7 May 2025 23:25:43 -0400 Subject: [PATCH 05/26] https redirect --- Dockerfile | 1 + src/web/mod.rs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5cd13b0a..8053f95f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,5 +45,6 @@ RUN chgrp -R 0 /app && \ USER 1001 EXPOSE 8080 +EXPOSE 8000 CMD ["./openbridge"] diff --git a/src/web/mod.rs b/src/web/mod.rs index 7dac74aa..df841da9 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -41,7 +41,7 @@ pub use helper::utils; pub use route::notebook::notebook_helper; pub use route::proxy::services; -use self::helper::maintenance_watch; +use self::{bridge_middleware::HttpRedirect, helper::maintenance_watch}; // One hour timeout for client requests // TODO: Make this configurable @@ -170,6 +170,18 @@ pub async fn start_server(with_tls: bool) -> Result<()> { }); if with_tls { + // Application level https redirect, but only in release mode + let redirect_handle = if !cfg!(debug_assertions) { + Some(tokio::spawn( + HttpServer::new(move || App::new().wrap(HttpRedirect)) + .workers(1) + .bind(("0.0.0.0", 8000))? + .run(), + )) + } else { + None + }; + server .bind_rustls_0_23( ("0.0.0.0", 8080), @@ -177,6 +189,10 @@ pub async fn start_server(with_tls: bool) -> Result<()> { )? .run() .await?; + + if let Some(handler) = redirect_handle { + handler.await??; + } } else { server.bind(("0.0.0.0", 8080))?.run().await?; } From 134632540d0bd5df75b82da48c38b20f92581e47 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Thu, 8 May 2025 14:12:01 -0400 Subject: [PATCH 06/26] hsts header added --- src/web/bridge_middleware/security_cache_header.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/web/bridge_middleware/security_cache_header.rs b/src/web/bridge_middleware/security_cache_header.rs index 81ab6e9d..044b253b 100644 --- a/src/web/bridge_middleware/security_cache_header.rs +++ b/src/web/bridge_middleware/security_cache_header.rs @@ -55,6 +55,11 @@ where )?, ); header.insert(CACHE_CONTROL, HeaderValue::from_str("no-cache")?); + // add HSTS header + header.insert( + header::STRICT_TRANSPORT_SECURITY, + HeaderValue::from_str("max-age=31536000; includeSubDomains")?, + ); Ok(res) }) } From 4139df23634bf4eea428454fc92f3c017b4dd137 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Sun, 11 May 2025 00:58:00 -0400 Subject: [PATCH 07/26] fmt for observability --- src/logger/observability.rs | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/logger/observability.rs b/src/logger/observability.rs index 7e48887b..30a38d58 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -4,7 +4,13 @@ use std::io::Write; use reqwest::Client; use tracing::level_filters::LevelFilter; -use tracing_subscriber::{Layer, fmt::MakeWriter}; +use tracing_subscriber::{ + Layer, Registry, filter, + fmt::{self, MakeWriter, format}, + layer, +}; + +use crate::errors::Result; pub struct Observe { client: Client, @@ -13,38 +19,28 @@ pub struct Observe { // sender: Sender, } -type LayerAlias = tracing_subscriber::filter::Filtered< - tracing_subscriber::fmt::Layer< - tracing_subscriber::layer::Layered< - tracing_subscriber::filter::Filtered< - tracing_subscriber::fmt::Layer< - tracing_subscriber::Registry, - tracing_subscriber::fmt::format::DefaultFields, - tracing_subscriber::fmt::format::Format< - tracing_subscriber::fmt::format::Compact, - >, - >, +type LayerAlias = filter::Filtered< + fmt::Layer< + layer::Layered< + filter::Filtered< + fmt::Layer>, LevelFilter, - tracing_subscriber::Registry, + Registry, >, - tracing_subscriber::Registry, + Registry, >, - tracing_subscriber::fmt::format::DefaultFields, - tracing_subscriber::fmt::format::Format, + format::DefaultFields, + format::Format, Observe, >, LevelFilter, - tracing_subscriber::layer::Layered< - tracing_subscriber::filter::Filtered< - tracing_subscriber::fmt::Layer< - tracing_subscriber::Registry, - tracing_subscriber::fmt::format::DefaultFields, - tracing_subscriber::fmt::format::Format, - >, + layer::Layered< + filter::Filtered< + fmt::Layer>, LevelFilter, - tracing_subscriber::Registry, + Registry, >, - tracing_subscriber::Registry, + Registry, >, >; @@ -60,13 +56,17 @@ impl Observe { } pub fn wrap_layer(self, level: LevelFilter) -> LayerAlias { - tracing_subscriber::fmt::layer() + fmt::layer() .compact() .with_file(true) .with_line_number(true) .with_writer(self) .with_filter(level) } + + pub fn send_message(&self, _msg: T) -> Result<()> { + Ok(()) + } } impl Write for &Observe { From e7e3bd3954bffe7508fbf8b8b7415bcdfbbd2462 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 13 May 2025 00:28:21 -0400 Subject: [PATCH 08/26] overflow txt fix --- Cargo.lock | 264 +++++++++++--------------- Cargo.toml | 4 +- config/services_sample.toml | 6 + src/web/mod.rs | 8 +- static/css/output.css | 2 +- templates/components/group_view.html | 2 +- templates/components/member_view.html | 10 +- templates/pages/pending_user.html | 2 +- 8 files changed, 130 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce2f46f3..162b3abd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa882656b67966045e4152c634051e70346939fced7117d5f0b52146a7c74c9" +checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" dependencies = [ "actix-codec", "actix-rt", @@ -119,9 +119,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6398974fd4284f4768af07965701efbbb5fdc0616bff20cade1bb14b77675e24" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.10.2" +version = "4.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" dependencies = [ "actix-codec", "actix-http", @@ -275,15 +275,15 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.16", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -331,12 +331,6 @@ dependencies = [ "libc", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - [[package]] name = "arrayref" version = "0.3.9" @@ -408,9 +402,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -418,9 +412,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen", "cc", @@ -554,9 +548,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -565,9 +559,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -583,7 +577,7 @@ dependencies = [ "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.2", + "getrandom 0.3.3", "hex", "indexmap 2.9.0", "js-sys", @@ -635,9 +629,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -1430,9 +1424,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -1882,21 +1876,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1905,31 +1900,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1937,67 +1912,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2017,9 +1979,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2117,7 +2079,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -2365,12 +2327,12 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -2409,9 +2371,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "local-channel" @@ -2455,6 +2417,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "macro_magic" version = "0.5.1" @@ -3157,6 +3125,15 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3169,7 +3146,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.25", + "zerocopy", ] [[package]] @@ -3202,9 +3179,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -3222,12 +3199,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", + "lru-slab", "rand 0.9.1", "ring 0.17.14", "rustc-hash 2.1.1", @@ -3331,16 +3309,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] name = "redis" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "438a4e5f8e9aa246d6f3666d6978441bf1b37d5f417b50c4dd220be09f5fcc17" +checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" dependencies = [ - "arc-swap", "bytes", "cfg-if", "combine", @@ -3614,7 +3591,7 @@ dependencies = [ "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.103.2", + "rustls-webpki 0.103.3", "subtle", "zeroize", ] @@ -3684,9 +3661,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.2" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring 0.17.14", @@ -4240,12 +4217,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.7", "windows-sys 0.59.0", @@ -4365,9 +4342,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -4550,9 +4527,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1cfca9ae570b2a6efc764a88e914c29b3dfaa1fafe5f495812ae97ec9bc4d53" +checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", @@ -4816,12 +4793,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4842,7 +4813,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -5402,17 +5373,11 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -5434,9 +5399,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -5446,9 +5411,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -5456,33 +5421,13 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive 0.8.25", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "zerocopy-derive", ] [[package]] @@ -5537,11 +5482,22 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -5550,9 +5506,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0f420e9c..2506c3d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ time = { version = "0.3.36", features = ["serde"] } parking_lot = "~0.12" num-bigint = "~0.4" # Web deps -actix-web = { version = "~4.10", features = ["rustls-0_23", "cookies"] } +actix-web = { version = "~4.11", features = ["rustls-0_23", "cookies"] } actix-web-httpauth = "0.8" actix-files = "0.6.6" tera = { version = "1.20.0", features= ["builtins"] } @@ -58,7 +58,7 @@ regex = "~1.11" thiserror = "2" # DB mongodb = "~3.2" -redis = { version = "0.30", features = ["tokio-comp", "num-bigint"] } +redis = { version = "0.31", features = ["tokio-comp", "num-bigint"] } #macro utils = { path = "utils" } # Kubernetes diff --git a/config/services_sample.toml b/config/services_sample.toml index 5a55336c..c91dcd35 100644 --- a/config/services_sample.toml +++ b/config/services_sample.toml @@ -18,3 +18,9 @@ readiness = "/" check = true show = true + + [resources.reddit] + url = "https://www.reddit.com" + readiness = "/" + check = true + show = false diff --git a/src/web/mod.rs b/src/web/mod.rs index df841da9..9c16071e 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -66,8 +66,7 @@ const SIGTERM_FREQ: Duration = Duration::from_secs(5); /// } /// ``` pub async fn start_server(with_tls: bool) -> Result<()> { - // Not configurable by the caller - // Either DEBUG or INGO based on release mode + // Logger and this is not configurable by the caller if cfg!(debug_assertions) { logger::start_logger(LevelFilter::DEBUG); } else { @@ -76,7 +75,7 @@ pub async fn start_server(with_tls: bool) -> Result<()> { rustls::crypto::ring::default_provider() .install_default() - .expect("Cannot install default provider"); + .expect("Cannot install default provider with ring"); // Singletons openid::init_once().await; @@ -98,6 +97,7 @@ pub async fn start_server(with_tls: bool) -> Result<()> { #[cfg(feature = "notebook")] kube::init_once().await; + // Maintainence window impl let _ = maintenance_watch(); // Lifecycle with "advisory lock" @@ -171,7 +171,7 @@ pub async fn start_server(with_tls: bool) -> Result<()> { if with_tls { // Application level https redirect, but only in release mode - let redirect_handle = if !cfg!(debug_assertions) { + let redirect_handle = if cfg!(not(debug_assertions)) { Some(tokio::spawn( HttpServer::new(move || App::new().wrap(HttpRedirect)) .workers(1) diff --git a/static/css/output.css b/static/css/output.css index 0363b138..e7bfbec8 100644 --- a/static/css/output.css +++ b/static/css/output.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-bold:700;--leading-tight:1.25;--radius-md:.375rem;--drop-shadow-md:0 3px 3px #0000001f;--ease-in:cubic-bezier(.4,0,1,1);--animate-spin:spin 1s linear infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.-inset-0\.5{inset:calc(var(--spacing)*-.5)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-12{top:calc(var(--spacing)*12)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-5{margin-inline:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-\[42px\]{height:42px}.h-\[50px\]{height:50px}.h-full{height:100%}.max-h-80{max-height:calc(var(--spacing)*80)}.min-h-60{min-height:calc(var(--spacing)*60)}.min-h-96{min-height:calc(var(--spacing)*96)}.min-h-\[calc\(100vh-3rem\)\]{min-height:calc(100vh - 3rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-\[220px\]{width:220px}.w-\[384px\]{width:384px}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.min-w-52{min-width:calc(var(--spacing)*52)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#4589ff\]{border-color:#4589ff}.border-\[\#e0e0e0\]{border-color:#e0e0e0}.border-blue-500{border-color:var(--color-blue-500)}.bg-\[\#f4f4f4\]{background-color:#f4f4f4}.bg-\[\#f4f4f4\]\/70{background-color:oklab(96.7153% -5.96046e-8 5.96046e-8/.7)}.bg-\[\#ffffff\]{background-color:#fff}.bg-inherit{background-color:inherit}.bg-slate-300{background-color:var(--color-slate-300)}.bg-white{background-color:var(--color-white)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-10{padding-top:calc(var(--spacing)*10)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-10{padding-bottom:calc(var(--spacing)*10)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-all{word-break:break-all}.text-black{color:var(--color-black)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-green-600{color:var(--color-green-600)}.text-green-900{color:var(--color-green-900)}.text-red-600{color:var(--color-red-600)}.text-slate-900{color:var(--color-slate-900)}.capitalize{text-transform:capitalize}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-\[\#c6c6c6\]:hover{background-color:#c6c6c6}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media (min-width:40rem){.sm\:items-stretch{align-items:stretch}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:w-\[384px\]{width:384px}.md\:justify-center{justify-content:center}.md\:justify-start{justify-content:flex-start}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pb-0{padding-bottom:calc(var(--spacing)*0)}.md\:pl-56{padding-left:calc(var(--spacing)*56)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}@media (min-width:64rem){.lg\:w-\[300px\]{width:300px}.lg\:w-\[500px\]{width:500px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-2{padding-block:calc(var(--spacing)*2)}}@media (min-width:80rem){.xl\:my-0{margin-block:calc(var(--spacing)*0)}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-\[\#4d5358\]{border-color:#4d5358}.dark\:bg-\[\#343a3f\]{background-color:#343a3f}.dark\:bg-\[\#21272a\]{background-color:#21272a}.dark\:bg-\[\#42484e\]{background-color:#42484e}.dark\:bg-\[\#121619\]{background-color:#121619}.dark\:bg-\[\#121619\]\/70{background-color:oklab(19.7278% -.00427963 -.00752458/.7)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-700{color:var(--color-green-700)}.dark\:text-red-500{color:var(--color-red-500)}.dark\:text-slate-200{color:var(--color-slate-200)}@media (hover:hover){.dark\:hover\:bg-\[\#4d5358\]:hover{background-color:#4d5358}}}}.menu_selected{border-left-style:var(--tw-border-style);background-color:#c6c6c6;border-color:#4589ff;border-left-width:2px}@media (prefers-color-scheme:dark){.menu_selected{background-color:#4d5358}}.mobile_menu_selected{border-left-style:var(--tw-border-style);border-color:#4589ff;border-left-width:4px}.cb_button{color:var(--color-slate-200);background-color:#0050e6}@media (hover:hover){.cb_button:hover{background-color:var(--color-blue-700)}}.cb_button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button:active{background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button:focus{outline-offset:2px;outline:2px solid #0000}}}.cb_button_secondary{color:var(--color-slate-900);background-color:#fff}@media (hover:hover){.cb_button_secondary:hover{background-color:var(--color-blue-700);color:var(--color-slate-200)}}.cb_button_secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button_secondary:active{color:#fff;background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button_secondary:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button_secondary:focus{outline-offset:2px;outline:2px solid #0000}}}.htmx-indicator{opacity:0}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1;transition:opacity .2s ease-in}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@keyframes spin{to{transform:rotate(360deg)}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-bold:700;--leading-tight:1.25;--radius-md:.375rem;--drop-shadow-md:0 3px 3px #0000001f;--ease-in:cubic-bezier(.4,0,1,1);--animate-spin:spin 1s linear infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.-inset-0{inset:calc(var(--spacing)*0)}.-inset-0\.5{inset:calc(var(--spacing)*-.5)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-12{top:calc(var(--spacing)*12)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-5{margin-inline:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-\[42px\]{height:42px}.h-\[50px\]{height:50px}.h-full{height:100%}.max-h-80{max-height:calc(var(--spacing)*80)}.min-h-60{min-height:calc(var(--spacing)*60)}.min-h-96{min-height:calc(var(--spacing)*96)}.min-h-\[calc\(100vh-3rem\)\]{min-height:calc(100vh - 3rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-\[220px\]{width:220px}.w-\[384px\]{width:384px}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.min-w-52{min-width:calc(var(--spacing)*52)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#4589ff\]{border-color:#4589ff}.border-\[\#e0e0e0\]{border-color:#e0e0e0}.border-blue-500{border-color:var(--color-blue-500)}.bg-\[\#f4f4f4\]{background-color:#f4f4f4}.bg-\[\#f4f4f4\]\/70{background-color:oklab(96.7153% -5.96046e-8 5.96046e-8/.7)}.bg-\[\#ffffff\]{background-color:#fff}.bg-inherit{background-color:inherit}.bg-slate-300{background-color:var(--color-slate-300)}.bg-white{background-color:var(--color-white)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-10{padding-top:calc(var(--spacing)*10)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-0{padding-bottom:calc(var(--spacing)*0)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-10{padding-bottom:calc(var(--spacing)*10)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.text-black{color:var(--color-black)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-green-600{color:var(--color-green-600)}.text-green-900{color:var(--color-green-900)}.text-red-600{color:var(--color-red-600)}.text-slate-900{color:var(--color-slate-900)}.capitalize{text-transform:capitalize}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-\[\#c6c6c6\]:hover{background-color:#c6c6c6}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media not all and (min-width:64rem){.max-lg\:hidden{display:none}}@media not all and (min-width:40rem){.max-sm\:hidden{display:none}}@media (min-width:40rem){.sm\:block{display:block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table{display:table}.sm\:items-stretch{align-items:stretch}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:w-\[384px\]{width:384px}.md\:justify-center{justify-content:center}.md\:justify-start{justify-content:flex-start}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pb-0{padding-bottom:calc(var(--spacing)*0)}.md\:pl-56{padding-left:calc(var(--spacing)*56)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}@media (min-width:64rem){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:table{display:table}.lg\:w-\[300px\]{width:300px}.lg\:w-\[500px\]{width:500px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-2{padding-block:calc(var(--spacing)*2)}}@media (min-width:80rem){.xl\:my-0{margin-block:calc(var(--spacing)*0)}.xl\:block{display:block}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-\[\#4d5358\]{border-color:#4d5358}.dark\:bg-\[\#343a3f\]{background-color:#343a3f}.dark\:bg-\[\#21272a\]{background-color:#21272a}.dark\:bg-\[\#42484e\]{background-color:#42484e}.dark\:bg-\[\#121619\]{background-color:#121619}.dark\:bg-\[\#121619\]\/70{background-color:oklab(19.7278% -.00427963 -.00752458/.7)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-700{color:var(--color-green-700)}.dark\:text-red-500{color:var(--color-red-500)}.dark\:text-slate-200{color:var(--color-slate-200)}@media (hover:hover){.dark\:hover\:bg-\[\#4d5358\]:hover{background-color:#4d5358}}}}.menu_selected{border-left-style:var(--tw-border-style);background-color:#c6c6c6;border-color:#4589ff;border-left-width:2px}@media (prefers-color-scheme:dark){.menu_selected{background-color:#4d5358}}.mobile_menu_selected{border-left-style:var(--tw-border-style);border-color:#4589ff;border-left-width:4px}.cb_button{color:var(--color-slate-200);background-color:#0050e6}@media (hover:hover){.cb_button:hover{background-color:var(--color-blue-700)}}.cb_button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button:active{background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button:focus{outline-offset:2px;outline:2px solid #0000}}}.cb_button_secondary{color:var(--color-slate-900);background-color:#fff}@media (hover:hover){.cb_button_secondary:hover{background-color:var(--color-blue-700);color:var(--color-slate-200)}}.cb_button_secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button_secondary:active{color:#fff;background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button_secondary:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button_secondary:focus{outline-offset:2px;outline:2px solid #0000}}}.htmx-indicator{opacity:0}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1;transition:opacity .2s ease-in}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} \ No newline at end of file diff --git a/templates/components/group_view.html b/templates/components/group_view.html index 27aaf9fd..8850afbe 100644 --- a/templates/components/group_view.html +++ b/templates/components/group_view.html @@ -28,7 +28,7 @@ {% if groups %} {% for group in groups %} - + {{ group.name }} diff --git a/templates/components/member_view.html b/templates/components/member_view.html index fb93606f..c9e45cb3 100644 --- a/templates/components/member_view.html +++ b/templates/components/member_view.html @@ -22,13 +22,13 @@ - - {% if group_members %} {% for user in group_members %} - - + -
+ Name Email + Updated @@ -39,14 +39,14 @@
+
{{ user.user_name }} {{ user.email }} + {{ user.updated_at | truncate(length=16) }} diff --git a/templates/pages/pending_user.html b/templates/pages/pending_user.html index ba2dd4e8..02e4d4b6 100644 --- a/templates/pages/pending_user.html +++ b/templates/pages/pending_user.html @@ -3,7 +3,7 @@ {% block title %}Pending Account{% endblock title %} {% block main %} -
+

{{ name }},

Thank you for registering! Your account is currently in the pending state. An admin will need to approve and pull you into a group. From 96a70bdf6d7a6817f91ab9d108535f67b893989f Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 13 May 2025 13:24:17 -0400 Subject: [PATCH 09/26] resource impl --- static/css/output.css | 2 +- static/js/main.js | 120 +++++++++++++++++++++++++++++++++- static/ts/main.ts | 7 +- templates/pages/nav_base.html | 35 ++++++++-- tsconfig.json | 6 +- 5 files changed, 162 insertions(+), 8 deletions(-) diff --git a/static/css/output.css b/static/css/output.css index e7bfbec8..a29c48ea 100644 --- a/static/css/output.css +++ b/static/css/output.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-bold:700;--leading-tight:1.25;--radius-md:.375rem;--drop-shadow-md:0 3px 3px #0000001f;--ease-in:cubic-bezier(.4,0,1,1);--animate-spin:spin 1s linear infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.-inset-0{inset:calc(var(--spacing)*0)}.-inset-0\.5{inset:calc(var(--spacing)*-.5)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-12{top:calc(var(--spacing)*12)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-5{margin-inline:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-\[42px\]{height:42px}.h-\[50px\]{height:50px}.h-full{height:100%}.max-h-80{max-height:calc(var(--spacing)*80)}.min-h-60{min-height:calc(var(--spacing)*60)}.min-h-96{min-height:calc(var(--spacing)*96)}.min-h-\[calc\(100vh-3rem\)\]{min-height:calc(100vh - 3rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-\[220px\]{width:220px}.w-\[384px\]{width:384px}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.min-w-52{min-width:calc(var(--spacing)*52)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#4589ff\]{border-color:#4589ff}.border-\[\#e0e0e0\]{border-color:#e0e0e0}.border-blue-500{border-color:var(--color-blue-500)}.bg-\[\#f4f4f4\]{background-color:#f4f4f4}.bg-\[\#f4f4f4\]\/70{background-color:oklab(96.7153% -5.96046e-8 5.96046e-8/.7)}.bg-\[\#ffffff\]{background-color:#fff}.bg-inherit{background-color:inherit}.bg-slate-300{background-color:var(--color-slate-300)}.bg-white{background-color:var(--color-white)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-10{padding-top:calc(var(--spacing)*10)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-0{padding-bottom:calc(var(--spacing)*0)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-10{padding-bottom:calc(var(--spacing)*10)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.text-black{color:var(--color-black)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-green-600{color:var(--color-green-600)}.text-green-900{color:var(--color-green-900)}.text-red-600{color:var(--color-red-600)}.text-slate-900{color:var(--color-slate-900)}.capitalize{text-transform:capitalize}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-\[\#c6c6c6\]:hover{background-color:#c6c6c6}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media not all and (min-width:64rem){.max-lg\:hidden{display:none}}@media not all and (min-width:40rem){.max-sm\:hidden{display:none}}@media (min-width:40rem){.sm\:block{display:block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:table{display:table}.sm\:items-stretch{align-items:stretch}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:w-\[384px\]{width:384px}.md\:justify-center{justify-content:center}.md\:justify-start{justify-content:flex-start}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pb-0{padding-bottom:calc(var(--spacing)*0)}.md\:pl-56{padding-left:calc(var(--spacing)*56)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}@media (min-width:64rem){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:table{display:table}.lg\:w-\[300px\]{width:300px}.lg\:w-\[500px\]{width:500px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-2{padding-block:calc(var(--spacing)*2)}}@media (min-width:80rem){.xl\:my-0{margin-block:calc(var(--spacing)*0)}.xl\:block{display:block}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-\[\#4d5358\]{border-color:#4d5358}.dark\:bg-\[\#343a3f\]{background-color:#343a3f}.dark\:bg-\[\#21272a\]{background-color:#21272a}.dark\:bg-\[\#42484e\]{background-color:#42484e}.dark\:bg-\[\#121619\]{background-color:#121619}.dark\:bg-\[\#121619\]\/70{background-color:oklab(19.7278% -.00427963 -.00752458/.7)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-700{color:var(--color-green-700)}.dark\:text-red-500{color:var(--color-red-500)}.dark\:text-slate-200{color:var(--color-slate-200)}@media (hover:hover){.dark\:hover\:bg-\[\#4d5358\]:hover{background-color:#4d5358}}}}.menu_selected{border-left-style:var(--tw-border-style);background-color:#c6c6c6;border-color:#4589ff;border-left-width:2px}@media (prefers-color-scheme:dark){.menu_selected{background-color:#4d5358}}.mobile_menu_selected{border-left-style:var(--tw-border-style);border-color:#4589ff;border-left-width:4px}.cb_button{color:var(--color-slate-200);background-color:#0050e6}@media (hover:hover){.cb_button:hover{background-color:var(--color-blue-700)}}.cb_button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button:active{background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button:focus{outline-offset:2px;outline:2px solid #0000}}}.cb_button_secondary{color:var(--color-slate-900);background-color:#fff}@media (hover:hover){.cb_button_secondary:hover{background-color:var(--color-blue-700);color:var(--color-slate-200)}}.cb_button_secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button_secondary:active{color:#fff;background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button_secondary:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button_secondary:focus{outline-offset:2px;outline:2px solid #0000}}}.htmx-indicator{opacity:0}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1;transition:opacity .2s ease-in}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-bold:700;--leading-tight:1.25;--radius-md:.375rem;--drop-shadow-md:0 3px 3px #0000001f;--ease-in:cubic-bezier(.4,0,1,1);--animate-spin:spin 1s linear infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.-inset-0\.5{inset:calc(var(--spacing)*-.5)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-12{top:calc(var(--spacing)*12)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-5{margin-inline:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-\[42px\]{height:42px}.h-\[50px\]{height:50px}.h-full{height:100%}.max-h-80{max-height:calc(var(--spacing)*80)}.min-h-60{min-height:calc(var(--spacing)*60)}.min-h-96{min-height:calc(var(--spacing)*96)}.min-h-\[calc\(100vh-3rem\)\]{min-height:calc(100vh - 3rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-\[220px\]{width:220px}.w-\[384px\]{width:384px}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.min-w-52{min-width:calc(var(--spacing)*52)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#4589ff\]{border-color:#4589ff}.border-\[\#e0e0e0\]{border-color:#e0e0e0}.border-blue-500{border-color:var(--color-blue-500)}.bg-\[\#f4f4f4\]{background-color:#f4f4f4}.bg-\[\#f4f4f4\]\/70{background-color:oklab(96.7153% -5.96046e-8 5.96046e-8/.7)}.bg-\[\#ffffff\]{background-color:#fff}.bg-inherit{background-color:inherit}.bg-slate-300{background-color:var(--color-slate-300)}.bg-white{background-color:var(--color-white)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-10{padding-top:calc(var(--spacing)*10)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-10{padding-bottom:calc(var(--spacing)*10)}.pb-20{padding-bottom:calc(var(--spacing)*20)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-all{word-break:break-all}.text-black{color:var(--color-black)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-green-600{color:var(--color-green-600)}.text-green-900{color:var(--color-green-900)}.text-red-600{color:var(--color-red-600)}.text-slate-900{color:var(--color-slate-900)}.capitalize{text-transform:capitalize}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.focus-within\:border-transparent:focus-within{border-color:#0000}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-\[\#c6c6c6\]:hover{background-color:#c6c6c6}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media not all and (min-width:64rem){.max-lg\:hidden{display:none}}@media not all and (min-width:40rem){.max-sm\:hidden{display:none}}@media (min-width:40rem){.sm\:items-stretch{align-items:stretch}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:w-\[384px\]{width:384px}.md\:justify-center{justify-content:center}.md\:justify-start{justify-content:flex-start}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pb-0{padding-bottom:calc(var(--spacing)*0)}.md\:pl-56{padding-left:calc(var(--spacing)*56)}.md\:pl-64{padding-left:calc(var(--spacing)*64)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}@media (min-width:64rem){.lg\:w-\[300px\]{width:300px}.lg\:w-\[500px\]{width:500px}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:py-2{padding-block:calc(var(--spacing)*2)}}@media (min-width:80rem){.xl\:my-0{margin-block:calc(var(--spacing)*0)}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-\[\#4d5358\]{border-color:#4d5358}.dark\:bg-\[\#343a3f\]{background-color:#343a3f}.dark\:bg-\[\#21272a\]{background-color:#21272a}.dark\:bg-\[\#42484e\]{background-color:#42484e}.dark\:bg-\[\#121619\]{background-color:#121619}.dark\:bg-\[\#121619\]\/70{background-color:oklab(19.7278% -.00427963 -.00752458/.7)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-700{color:var(--color-green-700)}.dark\:text-red-500{color:var(--color-red-500)}.dark\:text-slate-200{color:var(--color-slate-200)}@media (hover:hover){.dark\:hover\:bg-\[\#4d5358\]:hover{background-color:#4d5358}}}}.menu_selected{border-left-style:var(--tw-border-style);background-color:#c6c6c6;border-color:#4589ff;border-left-width:2px}@media (prefers-color-scheme:dark){.menu_selected{background-color:#4d5358}}.mobile_menu_selected{border-left-style:var(--tw-border-style);border-color:#4589ff;border-left-width:4px}.cb_button{color:var(--color-slate-200);background-color:#0050e6}@media (hover:hover){.cb_button:hover{background-color:var(--color-blue-700)}}.cb_button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button:active{background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button:focus{outline-offset:2px;outline:2px solid #0000}}}.cb_button_secondary{color:var(--color-slate-900);background-color:#fff}@media (hover:hover){.cb_button_secondary:hover{background-color:var(--color-blue-700);color:var(--color-slate-200)}}.cb_button_secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:#0050e6}.cb_button_secondary:active{color:#fff;background-color:#002d9c}@media (prefers-color-scheme:dark){.cb_button_secondary:focus{--tw-ring-color:var(--color-white);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.cb_button_secondary:focus{outline-offset:2px;outline:2px solid #0000}}}.htmx-indicator{opacity:0}.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1;transition:opacity .2s ease-in}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index d3464164..1583e5c9 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1 +1,119 @@ -var currentPath=window.location.pathname,Menu=function(t,e){function s(e,n){e instanceof HTMLElement&&(e=e.id.split(n)[1],(n=document.getElementById(e))instanceof HTMLElement)&&n.classList.remove("hidden")}function i(){c.main.forEach(function(e){e instanceof HTMLElement&&e.classList.add("hidden")})}var c=this;this.menu_button=document.getElementById(t.button),this.menu_big=document.getElementById(t.big).childNodes,this.menu=document.getElementById(t.menu),this.menu_mobile=document.getElementById(t.menu).childNodes,this.menu_open=document.getElementById(t.open),this.menu_close=document.getElementById(t.close),this.main=document.getElementById(e).childNodes,this.menu_big.forEach(function(n){n.addEventListener("click",function(){var e;i(),s(n,"_"),c.menu_big.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.menu_selected)}),c.menu_mobile.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.mobile_menu_selected)}),n instanceof HTMLElement&&(n.classList.add(t.menu_selected),e=n.id.replace("big_","mobile_"),document.getElementById(e).classList.add(t.mobile_menu_selected))})}),this.menu_mobile.forEach(function(n){n.addEventListener("click",function(){var e;i(),s(n,"_"),c.menu_mobile.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.mobile_menu_selected)}),c.menu_big.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.menu_selected)}),n instanceof HTMLElement&&(n.classList.add(t.mobile_menu_selected),e=n.id.replace("mobile_","big_"),document.getElementById(e).classList.add(t.menu_selected))})}),this.menu_button.addEventListener("click",function(){c.menu.classList.contains("hidden")?(c.menu.classList.add("flex"),c.menu.classList.remove("hidden"),c.menu_open.classList.add("hidden"),c.menu_close.classList.remove("hidden")):(c.menu.classList.add("hidden"),c.menu.classList.remove("flex"),c.menu_open.classList.remove("hidden"),c.menu_close.classList.add("hidden"))}),document.addEventListener("click",function(e){e.target instanceof HTMLElement&&!c.menu_button.contains(e.target)&&c.menu.classList.contains("flex")&&(c.menu.classList.add("hidden"),c.menu.classList.remove("flex"),c.menu_open.classList.remove("hidden"),c.menu_close.classList.add("hidden"))})};"/"!==currentPath&&window.addEventListener("DOMContentLoaded",function(){new Menu({button:"menu_button",big:"menu_big",menu:"menu",open:"menu_open",close:"menu_close",menu_selected:"menu_selected",mobile_menu_selected:"mobile_menu_selected"},"main")}),htmx.config.includeIndicatorStyles=!1; \ No newline at end of file +var currentPath = window.location.pathname; +var Menu = /** @class */ (function () { + function Menu(menu_labels, main) { + var _this = this; + this.menu_button = document.getElementById(menu_labels.button); + this.menu_big = document.getElementById(menu_labels.big).childNodes; + this.menu = document.getElementById(menu_labels.menu); + this.menu_mobile = document.getElementById(menu_labels.menu).childNodes; + this.menu_open = document.getElementById(menu_labels.open); + this.menu_close = document.getElementById(menu_labels.close); + this.main = document.getElementById(main).childNodes; + var showById = function (node, delimiter) { + if (node instanceof HTMLElement) { + var element = node.id; + var id = element.split(delimiter)[1]; + var target = document.getElementById(id); + if (target instanceof HTMLElement) { + target.classList.remove("hidden"); + } + } + }; + var hideAllMain = function () { + _this.main.forEach(function (node) { + if (node instanceof HTMLElement) { + node.classList.add("hidden"); + } + }); + }; + // Big menu item selection styling + this.menu_big.forEach(function (node) { + node.addEventListener("click", function () { + // if the node name starts with "ignore" skip it + if (node instanceof HTMLElement && node.id.startsWith("ignore")) { + return; + } + hideAllMain(); + showById(node, "_"); + _this.menu_big.forEach(function (node) { + if (node instanceof HTMLElement) { + node.classList.remove(menu_labels.menu_selected); + } + }); + _this.menu_mobile.forEach(function (node) { + if (node instanceof HTMLElement) { + node.classList.remove(menu_labels.mobile_menu_selected); + } + }); + if (node instanceof HTMLElement) { + node.classList.add(menu_labels.menu_selected); + var mobile_target = node.id.replace("big_", "mobile_"); + document.getElementById(mobile_target).classList.add(menu_labels.mobile_menu_selected); + } + }); + }); + // mobile menu item selection styling + this.menu_mobile.forEach(function (node) { + node.addEventListener("click", function () { + hideAllMain(); + showById(node, "_"); + _this.menu_mobile.forEach(function (node) { + if (node instanceof HTMLElement) { + node.classList.remove(menu_labels.mobile_menu_selected); + } + }); + _this.menu_big.forEach(function (node) { + if (node instanceof HTMLElement) { + node.classList.remove(menu_labels.menu_selected); + } + }); + if (node instanceof HTMLElement) { + node.classList.add(menu_labels.mobile_menu_selected); + var big_target = node.id.replace("mobile_", "big_"); + document.getElementById(big_target).classList.add(menu_labels.menu_selected); + } + }); + }); + // Mobile menu + this.menu_button.addEventListener("click", function () { + if (_this.menu.classList.contains("hidden")) { + _this.menu.classList.add("flex"); + _this.menu.classList.remove("hidden"); + _this.menu_open.classList.add("hidden"); + _this.menu_close.classList.remove("hidden"); + } + else { + _this.menu.classList.add("hidden"); + _this.menu.classList.remove("flex"); + _this.menu_open.classList.remove("hidden"); + _this.menu_close.classList.add("hidden"); + } + }); + // Mobile when close when clicking outside the menu + document.addEventListener("click", function (e) { + if (e.target instanceof HTMLElement && !_this.menu_button.contains(e.target) && _this.menu.classList.contains("flex")) { + _this.menu.classList.add("hidden"); + _this.menu.classList.remove("flex"); + _this.menu_open.classList.remove("hidden"); + _this.menu_close.classList.add("hidden"); + } + }); + } + return Menu; +}()); +if (currentPath !== "/") { + window.addEventListener("DOMContentLoaded", function () { + var menu_labels = { + button: "menu_button", + big: "menu_big", + menu: "menu", + open: "menu_open", + close: "menu_close", + menu_selected: "menu_selected", + mobile_menu_selected: "mobile_menu_selected" + }; + new Menu(menu_labels, "main"); + }); +} +// @ts-ignore +htmx.config.includeIndicatorStyles = false; diff --git a/static/ts/main.ts b/static/ts/main.ts index 24500bee..ea760228 100644 --- a/static/ts/main.ts +++ b/static/ts/main.ts @@ -31,7 +31,7 @@ class Menu { const showById = (node: ChildNode, delimiter: string) => { if (node instanceof HTMLElement) { const element = node.id; - const id = element.split(delimiter)[1]; + const id = element.split(delimiter)[1]; const target = document.getElementById(id); if (target instanceof HTMLElement) { target.classList.remove("hidden"); @@ -51,6 +51,11 @@ class Menu { this.menu_big.forEach((node) => { node.addEventListener("click", () => { + // if the node name starts with "ignore" skip it + if (node instanceof HTMLElement && node.id.startsWith("ignore")) { + return; + } + hideAllMain(); showById(node, "_"); diff --git a/templates/pages/nav_base.html b/templates/pages/nav_base.html index 89a2293d..5e942a89 100644 --- a/templates/pages/nav_base.html +++ b/templates/pages/nav_base.html @@ -112,12 +112,12 @@ {% if notebook %}

@@ -157,6 +157,25 @@ {% endif %} {% endif %} + {% if resources %} + {% for resource in resources %} + {% if resource.1 %} + +
+ +

{{ resource.0 }}

+
+
+ {% endif %} + {% endfor %} + {% endif %}

Powered by {{ application }} {{ application_version }}

@@ -194,6 +213,14 @@ Management {% endif %} {% endif %} + {% if resources %} + {% for resource in resources %} + {% if resource.1 %} + {{ resource.0 }} + {% endif %} + {% endfor %} + {% endif %}

Powered by {{ application }} {{ application_version }}

diff --git a/tsconfig.json b/tsconfig.json index 2076fbe8..810707bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "module": "ESNext", - "outDir": "./static/js/" + "outDir": "./static/js/", + "lib": [ + "ESNext", + "DOM" + ] }, "include": ["./static/ts/**/*"] } From 3dd86f37fc753c4a95f7db63acd625f7f7d6453e Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 13 May 2025 13:30:40 -0400 Subject: [PATCH 10/26] minify --- static/js/main.js | 120 +--------------------------------- static/ts/main.ts | 5 ++ templates/pages/nav_base.html | 2 +- 3 files changed, 7 insertions(+), 120 deletions(-) diff --git a/static/js/main.js b/static/js/main.js index 1583e5c9..ccbdfc28 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,119 +1 @@ -var currentPath = window.location.pathname; -var Menu = /** @class */ (function () { - function Menu(menu_labels, main) { - var _this = this; - this.menu_button = document.getElementById(menu_labels.button); - this.menu_big = document.getElementById(menu_labels.big).childNodes; - this.menu = document.getElementById(menu_labels.menu); - this.menu_mobile = document.getElementById(menu_labels.menu).childNodes; - this.menu_open = document.getElementById(menu_labels.open); - this.menu_close = document.getElementById(menu_labels.close); - this.main = document.getElementById(main).childNodes; - var showById = function (node, delimiter) { - if (node instanceof HTMLElement) { - var element = node.id; - var id = element.split(delimiter)[1]; - var target = document.getElementById(id); - if (target instanceof HTMLElement) { - target.classList.remove("hidden"); - } - } - }; - var hideAllMain = function () { - _this.main.forEach(function (node) { - if (node instanceof HTMLElement) { - node.classList.add("hidden"); - } - }); - }; - // Big menu item selection styling - this.menu_big.forEach(function (node) { - node.addEventListener("click", function () { - // if the node name starts with "ignore" skip it - if (node instanceof HTMLElement && node.id.startsWith("ignore")) { - return; - } - hideAllMain(); - showById(node, "_"); - _this.menu_big.forEach(function (node) { - if (node instanceof HTMLElement) { - node.classList.remove(menu_labels.menu_selected); - } - }); - _this.menu_mobile.forEach(function (node) { - if (node instanceof HTMLElement) { - node.classList.remove(menu_labels.mobile_menu_selected); - } - }); - if (node instanceof HTMLElement) { - node.classList.add(menu_labels.menu_selected); - var mobile_target = node.id.replace("big_", "mobile_"); - document.getElementById(mobile_target).classList.add(menu_labels.mobile_menu_selected); - } - }); - }); - // mobile menu item selection styling - this.menu_mobile.forEach(function (node) { - node.addEventListener("click", function () { - hideAllMain(); - showById(node, "_"); - _this.menu_mobile.forEach(function (node) { - if (node instanceof HTMLElement) { - node.classList.remove(menu_labels.mobile_menu_selected); - } - }); - _this.menu_big.forEach(function (node) { - if (node instanceof HTMLElement) { - node.classList.remove(menu_labels.menu_selected); - } - }); - if (node instanceof HTMLElement) { - node.classList.add(menu_labels.mobile_menu_selected); - var big_target = node.id.replace("mobile_", "big_"); - document.getElementById(big_target).classList.add(menu_labels.menu_selected); - } - }); - }); - // Mobile menu - this.menu_button.addEventListener("click", function () { - if (_this.menu.classList.contains("hidden")) { - _this.menu.classList.add("flex"); - _this.menu.classList.remove("hidden"); - _this.menu_open.classList.add("hidden"); - _this.menu_close.classList.remove("hidden"); - } - else { - _this.menu.classList.add("hidden"); - _this.menu.classList.remove("flex"); - _this.menu_open.classList.remove("hidden"); - _this.menu_close.classList.add("hidden"); - } - }); - // Mobile when close when clicking outside the menu - document.addEventListener("click", function (e) { - if (e.target instanceof HTMLElement && !_this.menu_button.contains(e.target) && _this.menu.classList.contains("flex")) { - _this.menu.classList.add("hidden"); - _this.menu.classList.remove("flex"); - _this.menu_open.classList.remove("hidden"); - _this.menu_close.classList.add("hidden"); - } - }); - } - return Menu; -}()); -if (currentPath !== "/") { - window.addEventListener("DOMContentLoaded", function () { - var menu_labels = { - button: "menu_button", - big: "menu_big", - menu: "menu", - open: "menu_open", - close: "menu_close", - menu_selected: "menu_selected", - mobile_menu_selected: "mobile_menu_selected" - }; - new Menu(menu_labels, "main"); - }); -} -// @ts-ignore -htmx.config.includeIndicatorStyles = false; +var currentPath=window.location.pathname,Menu=function(t,e){function s(e,n){e instanceof HTMLElement&&(e=e.id.split(n)[1],(n=document.getElementById(e))instanceof HTMLElement)&&n.classList.remove("hidden")}function i(){c.main.forEach(function(e){e instanceof HTMLElement&&e.classList.add("hidden")})}var c=this;this.menu_button=document.getElementById(t.button),this.menu_big=document.getElementById(t.big).childNodes,this.menu=document.getElementById(t.menu),this.menu_mobile=document.getElementById(t.menu).childNodes,this.menu_open=document.getElementById(t.open),this.menu_close=document.getElementById(t.close),this.main=document.getElementById(e).childNodes,this.menu_big.forEach(function(n){n.addEventListener("click",function(){var e;n instanceof HTMLElement&&n.id.startsWith("ignore")||(i(),s(n,"_"),c.menu_big.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.menu_selected)}),c.menu_mobile.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.mobile_menu_selected)}),n instanceof HTMLElement&&(n.classList.add(t.menu_selected),e=n.id.replace("big_","mobile_"),document.getElementById(e).classList.add(t.mobile_menu_selected)))})}),this.menu_mobile.forEach(function(n){n.addEventListener("click",function(){var e;n instanceof HTMLElement&&n.id.startsWith("ignore")||(i(),s(n,"_"),c.menu_mobile.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.mobile_menu_selected)}),c.menu_big.forEach(function(e){e instanceof HTMLElement&&e.classList.remove(t.menu_selected)}),n instanceof HTMLElement&&(n.classList.add(t.mobile_menu_selected),e=n.id.replace("mobile_","big_"),document.getElementById(e).classList.add(t.menu_selected)))})}),this.menu_button.addEventListener("click",function(){c.menu.classList.contains("hidden")?(c.menu.classList.add("flex"),c.menu.classList.remove("hidden"),c.menu_open.classList.add("hidden"),c.menu_close.classList.remove("hidden")):(c.menu.classList.add("hidden"),c.menu.classList.remove("flex"),c.menu_open.classList.remove("hidden"),c.menu_close.classList.add("hidden"))}),document.addEventListener("click",function(e){e.target instanceof HTMLElement&&!c.menu_button.contains(e.target)&&c.menu.classList.contains("flex")&&(c.menu.classList.add("hidden"),c.menu.classList.remove("flex"),c.menu_open.classList.remove("hidden"),c.menu_close.classList.add("hidden"))})};"/"!==currentPath&&window.addEventListener("DOMContentLoaded",function(){new Menu({button:"menu_button",big:"menu_big",menu:"menu",open:"menu_open",close:"menu_close",menu_selected:"menu_selected",mobile_menu_selected:"mobile_menu_selected"},"main")}),htmx.config.includeIndicatorStyles=!1; \ No newline at end of file diff --git a/static/ts/main.ts b/static/ts/main.ts index ea760228..64baf283 100644 --- a/static/ts/main.ts +++ b/static/ts/main.ts @@ -81,6 +81,11 @@ class Menu { this.menu_mobile.forEach((node) => { node.addEventListener("click", () => { + // if the node name starts with "ignore" skip it + if (node instanceof HTMLElement && node.id.startsWith("ignore")) { + return; + } + hideAllMain(); showById(node, "_"); diff --git a/templates/pages/nav_base.html b/templates/pages/nav_base.html index 5e942a89..cb4e2e3d 100644 --- a/templates/pages/nav_base.html +++ b/templates/pages/nav_base.html @@ -216,7 +216,7 @@ {% if resources %} {% for resource in resources %} {% if resource.1 %} - {{ resource.0 }} {% endif %} {% endfor %} From 1c12babaa2c94968c5b1cdaeecf4eb11dd6ee714 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 13 May 2025 20:04:19 -0400 Subject: [PATCH 11/26] config fix --- src/web/mod.rs | 4 +++- src/web/route/mod.rs | 10 ++-------- src/web/route/portal/mod.rs | 1 - templates/pages/login.html | 4 +++- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/web/mod.rs b/src/web/mod.rs index 9c16071e..d43b1edc 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -131,6 +131,8 @@ pub async fn start_server(with_tls: bool) -> Result<()> { context.insert("application", "OpenBridge"); context.insert("application_version", "v0.1.0"); context.insert("app_name", &CONFIG.app_name); + context.insert("company", &CONFIG.company); + context.insert("description", &CONFIG.app_discription); let context = Data::new(context); let client_data = Data::new( @@ -185,7 +187,7 @@ pub async fn start_server(with_tls: bool) -> Result<()> { server .bind_rustls_0_23( ("0.0.0.0", 8080), - tls::load_certs("certs/fullchain.cer", "certs/open.accelerator.cafe.key"), + tls::load_certs("certs/fullchain.cer", "certs/open.accelerate.science.key"), )? .run() .await?; diff --git a/src/web/route/mod.rs b/src/web/route/mod.rs index 41169790..58c2a0fb 100644 --- a/src/web/route/mod.rs +++ b/src/web/route/mod.rs @@ -7,7 +7,6 @@ use tera::{Context, Tera}; use crate::{ auth::COOKIE_NAME, - config::CONFIG, errors::Result, web::helper::{self}, }; @@ -21,10 +20,8 @@ pub mod portal; pub mod proxy; pub mod resource; -static APP_VERSION: &str = env!("CARGO_PKG_VERSION"); - #[get("")] -async fn index(data: Data, req: HttpRequest) -> Result { +async fn index(data: Data, ctx: Data, req: HttpRequest) -> Result { // if cookie exists, redirect to portal if req.cookie(COOKIE_NAME).is_some() { return Ok(HttpResponse::SeeOther() @@ -32,10 +29,7 @@ async fn index(data: Data, req: HttpRequest) -> Result { .finish()); } - let mut ctx = Context::new(); - ctx.insert("version", APP_VERSION); - ctx.insert("app_name", &CONFIG.app_name); - ctx.insert("description", &CONFIG.app_discription); + let ctx = (**ctx).clone(); let rendered = helper::log_with_level!(data.render("pages/login.html", &ctx), error)?; Ok(HttpResponse::Ok().body(rendered)) diff --git a/src/web/route/portal/mod.rs b/src/web/route/portal/mod.rs index 2815d2b8..f4229235 100644 --- a/src/web/route/portal/mod.rs +++ b/src/web/route/portal/mod.rs @@ -188,7 +188,6 @@ async fn search_by_email( #[post("logout")] async fn logout() -> HttpResponse { - println!("Logging out user"); // clear all the cookie let mut cookie_remove = Cookie::build(COOKIE_NAME, "") .same_site(SameSite::Strict) diff --git a/templates/pages/login.html b/templates/pages/login.html index c521950f..fe98d083 100644 --- a/templates/pages/login.html +++ b/templates/pages/login.html @@ -4,7 +4,7 @@
-

{{ app_name }} +

{{ company }}

{{ description }}

@@ -24,6 +24,7 @@ Continue with IBMID + {# Continue with W3 + #}
From f45bc96a40e751f49a6a004c2aa215481377ee31 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 14 May 2025 13:48:01 -0400 Subject: [PATCH 12/26] error page redirects --- config/notebook.toml | 6 +++--- templates/pages/400.html | 11 ++++++++++- templates/pages/403.html | 11 ++++++++++- templates/pages/404.html | 14 +++++++++++++- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/config/notebook.toml b/config/notebook.toml index a31e44c4..bcd611dd 100644 --- a/config/notebook.toml +++ b/config/notebook.toml @@ -1,6 +1,6 @@ [open_ad_workbench] name = "OpenAD Workbench" -url = "quay.io/ibmdpdev/openad_workbench_stage:latest" +url = "quay.io/ibmdpdev/openad_workbench_bi:latest" pull_policy = "Always" working_dir = "/opt/app-root/src" volume_mnt_path = "/opt/app-root/src" @@ -15,10 +15,10 @@ notebook_env = [ # base-url is taken care by bridge ] secret = "ibmdpdev-openad-pull-secret" -start_up_url = "lab/tree/start_menu.ipynb" +start_up_url = "lab/tree/ranking-ensemble-model-examples.ipynb" max_idle_time = 86400 [open_ad_workbench.env] -PROXY_URL = "https://open.accelerate.science/proxy" +PROXY_URL = "https://bi.open.accelerate.science/proxy" [datascience_notebook] name = "Data Science Notebook" diff --git a/templates/pages/400.html b/templates/pages/400.html index 21f5da26..744b3875 100644 --- a/templates/pages/400.html +++ b/templates/pages/400.html @@ -1 +1,10 @@ -{% extends 'base.html' %} +{% extends "base.html" %} +{% block title %}400: Bad Request{% endblock title %} + +{% block body %} +
+

Error 400

+

Bad Request

+

You are now being redirected to the login page.

+
+{% endblock body %} diff --git a/templates/pages/403.html b/templates/pages/403.html index 21f5da26..1ec2a820 100644 --- a/templates/pages/403.html +++ b/templates/pages/403.html @@ -1 +1,10 @@ -{% extends 'base.html' %} +{% extends "base.html" %} +{% block title %}403: Forbidden{% endblock title %} + +{% block body %} +
+

Error 403

+

Forbidden

+

You are now being redirected to the login page.

+
+{% endblock body %} diff --git a/templates/pages/404.html b/templates/pages/404.html index 21f5da26..cb8e6f09 100644 --- a/templates/pages/404.html +++ b/templates/pages/404.html @@ -1 +1,13 @@ -{% extends 'base.html' %} +{% extends "base.html" %} +{% block title %}404: Not Found{% endblock title %} +{% block head %} + +{% endblock head %} + +{% block body %} +
+

Error 404

+

Page not found

+

You are now being redirected to the login page.

+
+{% endblock body %} From 2978d967dc4fa1a101eae3bbbb962f1837548438 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 14 May 2025 13:51:12 -0400 Subject: [PATCH 13/26] error page finish --- templates/pages/404.html | 5 ++--- templates/pages/500.html | 14 +++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/templates/pages/404.html b/templates/pages/404.html index cb8e6f09..b9fbe007 100644 --- a/templates/pages/404.html +++ b/templates/pages/404.html @@ -1,13 +1,12 @@ {% extends "base.html" %} {% block title %}404: Not Found{% endblock title %} {% block head %} - + {% endblock head %} - {% block body %}

Error 404

Page not found

-

You are now being redirected to the login page.

+

You are now being redirected to the main page.

{% endblock body %} diff --git a/templates/pages/500.html b/templates/pages/500.html index 21f5da26..73060701 100644 --- a/templates/pages/500.html +++ b/templates/pages/500.html @@ -1 +1,13 @@ -{% extends 'base.html' %} +{% extends "base.html" %} +{% block title %}500: Internal Server Error{% endblock title %} +{% block head %} + +{% endblock head %} +{% block body %} +
+

Error 500

+

Internal Server Error

+

You are now being redirected to the main page.

+
+{% endblock body %} + From d472c3e8841ec0532afa24bc12a374884084d014 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 14 May 2025 22:00:31 -0400 Subject: [PATCH 14/26] dep update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 162b3abd..0f5e660e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3431,9 +3431,9 @@ dependencies = [ [[package]] name = "resolv-conf" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "rfc6979" From 17f74dd0f32d69bdc8caf54b10c22f449e6a0bb4 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Sat, 17 May 2025 01:19:05 -0400 Subject: [PATCH 15/26] dep update --- Cargo.lock | 81 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 4 +-- Dockerfile | 2 +- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f5e660e..59f9ef04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "derive_more 0.99.20", "futures-core", @@ -54,7 +54,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "brotli", "bytes", "bytestring", @@ -485,7 +485,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.12.1", @@ -510,9 +510,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -1200,9 +1200,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1467,7 +1467,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "ignore", "walkdir", ] @@ -2177,22 +2177,21 @@ dependencies = [ [[package]] name = "k8s-openapi" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c75b990324f09bef15e791606b7b7a296d02fc88a344f6eba9390970a870ad5" +checksum = "aa60a41b57ae1a0a071af77dbcf89fc9819cfe66edaf2beeb204c34459dcf0b2" dependencies = [ "base64 0.22.1", "chrono", "serde", - "serde-value", "serde_json", ] [[package]] name = "kube" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4eb20010536b48abe97fec37d23d43069bcbe9686adcf9932202327bc5ca6e" +checksum = "1b49c39074089233c2bb7b1791d1b6c06c84dbab26757491fad9d233db0d432f" dependencies = [ "k8s-openapi", "kube-client", @@ -2203,9 +2202,9 @@ dependencies = [ [[package]] name = "kube-client" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2ed952042df20d15ac2fe9614d0ec14b6118eab89633985d4b36e688dccf1" +checksum = "e199797b1b08865041c9c698f0d11a91de0a8143e808b71e250cd4a1d7ce2b9f" dependencies = [ "base64 0.22.1", "bytes", @@ -2240,11 +2239,12 @@ dependencies = [ [[package]] name = "kube-core" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0d0793db58e70ca6d689489183816cb3aa481673e7433dc618cf7e8007c675" +checksum = "1bdefbba89dea2d99ea822a1d7cd6945535efbfb10b790056ee9284bf9e698e7" dependencies = [ "chrono", + "derive_more 2.0.1", "form_urlencoded", "http 1.3.1", "json-patch", @@ -2258,9 +2258,9 @@ dependencies = [ [[package]] name = "kube-derive" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562f58dc9f7ca5feac8a6ee5850ca221edd6f04ce0dd2ee873202a88cd494c9" +checksum = "8e609a3633689a50869352a3c16e01d863b6137863c80eeb038383d5ab9f83bf" dependencies = [ "darling", "proc-macro2", @@ -2272,14 +2272,13 @@ dependencies = [ [[package]] name = "kube-runtime" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" +checksum = "1d4bd8a4554786f8f9a87bfa977fb7dbaa1d7f102a30477338b044b65de29d8e" dependencies = [ "ahash", "async-broadcast", "async-stream", - "async-trait", "backon", "educe", "futures", @@ -2825,7 +2824,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -3340,7 +3339,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -3548,7 +3547,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3561,7 +3560,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -3771,7 +3770,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -3784,7 +3783,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.10.0", "core-foundation-sys", "libc", @@ -4188,7 +4187,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4532,7 +4531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" dependencies = [ "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "http 1.3.1", "http-body", @@ -5056,15 +5055,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.0", + "windows-strings 0.4.1", ] [[package]] @@ -5108,9 +5107,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" dependencies = [ "windows-link", ] @@ -5126,9 +5125,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" dependencies = [ "windows-link", ] @@ -5370,7 +5369,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2506c3d1..7eb75833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,8 +62,8 @@ redis = { version = "0.31", features = ["tokio-comp", "num-bigint"] } #macro utils = { path = "utils" } # Kubernetes -kube = { version = "0.99", features = ["runtime", "derive"], optional = true } -k8s-openapi = { version = "0.24", features = ["latest"], optional = true } +kube = { version = "1.0", features = ["runtime", "derive"], optional = true } +k8s-openapi = { version = "0.25", features = ["latest"], optional = true } schemars = { version = "0.8.21", optional = true } either = { version = "1.13.0", optional = true } # memory allocator diff --git a/Dockerfile b/Dockerfile index 8053f95f..5044ac54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1 build -FROM rust:1.86.0 AS builder +FROM rust:1.87.0 AS builder WORKDIR /app From 1f83d4ba260290b964a2dacc8e614fba8b6a9754 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Thu, 22 May 2025 17:24:40 -0400 Subject: [PATCH 16/26] Observability impl --- Cargo.lock | 34 +++++++++++++++++----------------- Cargo.toml | 1 + config/notebook.toml | 8 +++++--- src/logger/mod.rs | 34 ++++++++++++++++++++-------------- src/logger/observability.rs | 27 ++++++++++++++------------- src/web/mod.rs | 17 +++++++++-------- 6 files changed, 66 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59f9ef04..a2cb3f88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "bson" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8113ff51309e2779e8785a246c10fb783e8c2452f134d6257fd71cc03ccd6c" +checksum = "7969a9ba84b0ff843813e7249eed1678d9b6607ce5a3b8f0a47af3fcf7978e6e" dependencies = [ "ahash", "base64 0.22.1", @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -1923,9 +1923,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -1939,9 +1939,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -3672,9 +3672,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -5055,15 +5055,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.1", + "windows-strings 0.4.2", ] [[package]] @@ -5107,9 +5107,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -5125,9 +5125,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] diff --git a/Cargo.toml b/Cargo.toml index 7eb75833..21b9076f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = ["utils"] [features] notebook = ["dep:kube", "dep:k8s-openapi", "dep:schemars", "dep:either"] lifecycle = ["notebook"] +observe = [] [dependencies] # Async dep diff --git a/config/notebook.toml b/config/notebook.toml index bcd611dd..38cc09da 100644 --- a/config/notebook.toml +++ b/config/notebook.toml @@ -1,6 +1,6 @@ [open_ad_workbench] name = "OpenAD Workbench" -url = "quay.io/ibmdpdev/openad_workbench_bi:latest" +url = "quay.io/ibmdpdev/openad_workbench_prod:latest" pull_policy = "Always" working_dir = "/opt/app-root/src" volume_mnt_path = "/opt/app-root/src" @@ -15,10 +15,10 @@ notebook_env = [ # base-url is taken care by bridge ] secret = "ibmdpdev-openad-pull-secret" -start_up_url = "lab/tree/ranking-ensemble-model-examples.ipynb" +start_up_url = "lab/tree/start_menu.ipynb" max_idle_time = 86400 [open_ad_workbench.env] -PROXY_URL = "https://bi.open.accelerate.science/proxy" +PROXY_URL = "https://open.accelerate.science/proxy" [datascience_notebook] name = "Data Science Notebook" @@ -36,3 +36,5 @@ notebook_env = [ # base-url is taken care by bridge ] max_idle_time = 86400 +[datascience_notebook.env] +PROXY_URL = "" diff --git a/src/logger/mod.rs b/src/logger/mod.rs index bc6239e4..1d5a648d 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -1,21 +1,27 @@ +use reqwest::Client; use tracing_subscriber::{filter::LevelFilter, prelude::*}; mod observability; -pub fn start_logger(level: LevelFilter) { +pub fn start_logger(level: LevelFilter, client: Client) { // let file = std::fs::File::create("./log").unwrap(); - // let writer = observability::Observe::new("".to_string(), "".to_string()); - tracing_subscriber::registry() - .with( - tracing_subscriber::fmt::layer() - .compact() - .with_file(true) - .with_line_number(true) - .with_thread_ids(true) - // .with_writer(Arc::new(file)) - .with_filter(level), - ) - // .with(writer.wrap_layer(level)) - .init() + let ts = tracing_subscriber::registry().with( + tracing_subscriber::fmt::layer() + .compact() + .with_file(true) + .with_line_number(true) + .with_thread_ids(true) + // .with_writer(Arc::new(file)) + .with_filter(level), + ); + + #[cfg(feature = "observe")] + let ts = { + let writer = observability::Observe::new("".to_string(), "".to_string(), client) + .expect("Failed to create observability for logger"); + ts.with(writer.wrap_layer(level)) + }; + + ts.init() } diff --git a/src/logger/observability.rs b/src/logger/observability.rs index 30a38d58..2bf95069 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -9,13 +9,16 @@ use tracing_subscriber::{ fmt::{self, MakeWriter, format}, layer, }; +use url::Url; -use crate::errors::Result; +use crate::errors::{BridgeError, Result}; + +pub const MESSAGE_DELIMITER: &str = "*~*~*"; pub struct Observe { client: Client, api_key: String, - endpoint: String, + endpoint: Url, // sender: Sender, } @@ -45,14 +48,14 @@ type LayerAlias = filter::Filtered< >; impl Observe { - pub fn new(api_key: String, endpoint: String) -> Self { - let client = Client::new(); - Observe { + pub fn new(api_key: String, endpoint: String, client: Client) -> Result { + Ok(Observe { client, api_key, - endpoint, - // sender, - } + // validate and sanitize that the endpoint is a proper url + endpoint: Url::parse(&endpoint) + .map_err(|_| BridgeError::GeneralError("Invalid URL".to_string()))?, + }) } pub fn wrap_layer(self, level: LevelFilter) -> LayerAlias { @@ -72,11 +75,9 @@ impl Observe { impl Write for &Observe { fn write(&mut self, buf: &[u8]) -> std::io::Result { let message = String::from_utf8(buf.to_vec()).unwrap_or_default(); - println!("!!!message: {}", message); - // if let Err(e) = self.sender.send(message) { - // eprintln!("Failed to send message: {}", e); - // } - // let mut reference of stdout + if let Some(message) = message.split(MESSAGE_DELIMITER).nth(1) { + println!("!!!message: {}", message); + } Ok(buf.len()) } diff --git a/src/web/mod.rs b/src/web/mod.rs index d43b1edc..7d8bf885 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -66,11 +66,16 @@ const SIGTERM_FREQ: Duration = Duration::from_secs(5); /// } /// ``` pub async fn start_server(with_tls: bool) -> Result<()> { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(TIMEOUT)) + .build() + .expect("Cannot create reqwest client"); + // Logger and this is not configurable by the caller if cfg!(debug_assertions) { - logger::start_logger(LevelFilter::DEBUG); + logger::start_logger(LevelFilter::DEBUG, client.clone()); } else { - logger::start_logger(LevelFilter::INFO); + logger::start_logger(LevelFilter::INFO, client.clone()); } rustls::crypto::ring::default_provider() @@ -135,12 +140,8 @@ pub async fn start_server(with_tls: bool) -> Result<()> { context.insert("description", &CONFIG.app_discription); let context = Data::new(context); - let client_data = Data::new( - reqwest::Client::builder() - .timeout(Duration::from_secs(TIMEOUT)) - .build() - .expect("Cannot create reqwest client"), - ); + // clone needed due to HttpServer::new impl Fn trait and not FnOnce + let client_data = Data::new(client.clone()); let db = Data::new(db); let cache = Data::new(CACHEDB.get()); From af275124dc912b59dc54696651edd4022ab6cc96 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Fri, 23 May 2025 14:04:50 -0400 Subject: [PATCH 17/26] dep update and mcp impl --- Cargo.lock | 15 +++--- Cargo.toml | 3 +- Dockerfile | 8 +++ config/services_sample.toml | 8 +++ justfile | 9 ++++ src/errors/mod.rs | 5 ++ src/logger/mod.rs | 4 +- src/web/bridge_middleware/authenicate.rs | 4 +- src/web/mod.rs | 11 ++-- src/web/route/mcp/mod.rs | 65 ++++++++++++++++++++++++ src/web/route/mod.rs | 2 + src/web/route/proxy/mod.rs | 2 +- src/web/route/proxy/services.rs | 22 ++++++++ 13 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 src/web/route/mcp/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a2cb3f88..645ef096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.23" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ "jobserver", "libc", @@ -1783,11 +1783,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" dependencies = [ - "futures-util", "http 1.3.1", "hyper", "hyper-util", @@ -1798,7 +1797,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "tower-service", - "webpki-roots 0.26.11", + "webpki-roots 1.0.0", ] [[package]] @@ -4808,9 +4807,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 21b9076f..e12b02bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = ["utils"] notebook = ["dep:kube", "dep:k8s-openapi", "dep:schemars", "dep:either"] lifecycle = ["notebook"] observe = [] +mcp = [] [dependencies] # Async dep @@ -72,7 +73,7 @@ mimalloc = "~0.1" # security base64 = "~0.22.1" rand = "~0.9" -uuid = { version = "~1.16", features = ["v4"] } +uuid = { version = "~1.17", features = ["v4"] } [dev-dependencies] jsonwebkey = { version = "~0.3", features = ["jwt-convert"] } diff --git a/Dockerfile b/Dockerfile index 5044ac54..239b44a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,8 @@ COPY . ./ ARG NOTEBOOK=false ARG LIFECYCLE=false +ARG OBSERVE=false +ARG MCP=false RUN < StatusCode::BAD_REQUEST, BridgeError::RecordSearchError(_) => StatusCode::BAD_REQUEST, BridgeError::IntrospectionError(_) => StatusCode::BAD_REQUEST, + #[cfg(feature = "mcp")] + BridgeError::MCPParseIssue => StatusCode::BAD_REQUEST, BridgeError::JsonWebTokenError(e) => { match e.kind() { diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 1d5a648d..043685b7 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -3,7 +3,7 @@ use tracing_subscriber::{filter::LevelFilter, prelude::*}; mod observability; -pub fn start_logger(level: LevelFilter, client: Client) { +pub fn start_logger(level: LevelFilter, _client: Client) { // let file = std::fs::File::create("./log").unwrap(); let ts = tracing_subscriber::registry().with( @@ -18,7 +18,7 @@ pub fn start_logger(level: LevelFilter, client: Client) { #[cfg(feature = "observe")] let ts = { - let writer = observability::Observe::new("".to_string(), "".to_string(), client) + let writer = observability::Observe::new("".to_string(), "".to_string(), _client) .expect("Failed to create observability for logger"); ts.with(writer.wrap_layer(level)) }; diff --git a/src/web/bridge_middleware/authenicate.rs b/src/web/bridge_middleware/authenicate.rs index f5691a9e..6d6afb0b 100644 --- a/src/web/bridge_middleware/authenicate.rs +++ b/src/web/bridge_middleware/authenicate.rs @@ -10,7 +10,7 @@ pub async fn validator( req: ServiceRequest, credentials: BearerAuth, ) -> Result { - let token = credentials.token().to_string(); + let token = credentials.token(); let config = req .app_data::() @@ -19,7 +19,7 @@ pub async fn validator( .realm("proxy"); let error = AuthenticationError::from(config); - match validate_token(&token, &CONFIG.decoder, &CONFIG.validation) { + match validate_token(token, &CONFIG.decoder, &CONFIG.validation) { Ok(claims) => { if let Some(r) = req.headers().get(INFERENCE_HEADER) { // TODO: handle unwrap_or better diff --git a/src/web/mod.rs b/src/web/mod.rs index 7d8bf885..6d259bb7 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -159,8 +159,8 @@ pub async fn start_server(with_tls: bool) -> Result<()> { .service(actix_files::Files::new("/static", "static")); #[cfg(feature = "notebook")] let app = app.configure(route::notebook::config_notebook); - app.service( - web::scope("") + app.service({ + let scope = web::scope("") .wrap(bridge_middleware::SecurityCacheHeader) .configure(route::auth::config_auth) .configure(route::health::config_status) @@ -168,8 +168,11 @@ pub async fn start_server(with_tls: bool) -> Result<()> { .configure(route::config_index) .configure(route::portal::config_portal) .configure(route::resource::config_resource) - .configure(route::foo::config_foo), - ) + .configure(route::foo::config_foo); + #[cfg(feature = "mcp")] + let scope = scope.configure(route::mcp::config_mcp); + scope + }) }); if with_tls { diff --git a/src/web/route/mcp/mod.rs b/src/web/route/mcp/mod.rs new file mode 100644 index 00000000..6c9ca766 --- /dev/null +++ b/src/web/route/mcp/mod.rs @@ -0,0 +1,65 @@ +use actix_web::{HttpRequest, HttpResponse, dev::PeerAddr, http::Method, web}; +use actix_web_httpauth::extractors::bearer::BearerAuth; +use tracing::{instrument, warn}; + +use crate::{ + auth::jwt::validate_token, + config::CONFIG, + errors::{BridgeError, Result}, + web::{helper, services::CATALOG}, +}; + +const MCP_PREFIX: &str = "/mcp/"; + +#[instrument(skip(payload))] +async fn forward( + req: HttpRequest, + payload: web::Payload, + credentials: BearerAuth, + method: Method, + peer_addr: Option, + client: web::Data, +) -> Result { + let token = credentials.token(); + let path = req + .uri() + .path() + .strip_prefix(MCP_PREFIX) + .unwrap_or(req.uri().path()); + + if let Some(mcp) = path.split('/').next() { + let path = path.strip_prefix(mcp).unwrap_or(path); + + if !CATALOG.is_servise_mcp(mcp)? { + return Err(BridgeError::ServiceDoesNotExist(mcp.to_string())); + } + + if let Ok(claims) = validate_token(token, &CONFIG.decoder, &CONFIG.validation) { + // ugh TODO: Remove this extra string clone + if !claims.scp.contains(&mcp.to_string()) { + return Err(BridgeError::Unauthorized(format!( + "User does not have access to {mcp}" + ))); + } + } else { + return Err(BridgeError::Unauthorized( + "JWT token is invalid".to_string(), + )); + } + + let mut new_url = helper::log_with_level!(CATALOG.get_service(mcp), error)?; + new_url.set_path(path); + new_url.set_query(req.uri().query()); + + helper::forwarding::forward(req, payload, method, peer_addr, client, new_url, None).await + } else { + warn!("MCP service not found in url request"); + Err(BridgeError::MCPParseIssue) + } + + // get header for which infernce service to forward to +} + +pub fn config_mcp(cfg: &mut web::ServiceConfig) { + cfg.service(web::scope("/mcp").default_service(web::to(forward))); +} diff --git a/src/web/route/mod.rs b/src/web/route/mod.rs index 58c2a0fb..1fae8bc6 100644 --- a/src/web/route/mod.rs +++ b/src/web/route/mod.rs @@ -19,6 +19,8 @@ pub mod notebook; pub mod portal; pub mod proxy; pub mod resource; +#[cfg(feature = "mcp")] +pub mod mcp; #[get("")] async fn index(data: Data, ctx: Data, req: HttpRequest) -> Result { diff --git a/src/web/route/proxy/mod.rs b/src/web/route/proxy/mod.rs index 4ed1fee0..58d9fc6d 100644 --- a/src/web/route/proxy/mod.rs +++ b/src/web/route/proxy/mod.rs @@ -53,7 +53,7 @@ async fn forward( pub fn config_proxy(cfg: &mut web::ServiceConfig) { let auth_validator = HttpAuthentication::bearer(validator); cfg.service( - web::scope("/proxy") + web::scope(BRIDGE_PREFIX) .wrap(auth_validator) .default_service(web::to(forward)), ); diff --git a/src/web/route/proxy/services.rs b/src/web/route/proxy/services.rs index 8af6bdef..114c315b 100644 --- a/src/web/route/proxy/services.rs +++ b/src/web/route/proxy/services.rs @@ -79,6 +79,21 @@ impl Catalog { self.get_inner("services", service_name) } + #[cfg(feature = "mcp")] + pub fn is_servise_mcp(&self, service_name: &str) -> Result { + Ok(self + .0 + .get("services") + .ok_or_else(|| { + BridgeError::GeneralError("services definition not found in config".to_string()) + })? + .get(service_name) + .ok_or_else(|| BridgeError::ServiceDoesNotExist(service_name.to_string()))? + .get("mcp") + .and_then(Value::as_bool) + .unwrap_or(false)) + } + pub fn get_resource(&self, resource_name: &str) -> Result { self.get_inner("resources", resource_name) } @@ -193,6 +208,13 @@ mod test { assert!(postman.is_some()); } + #[test] + fn test_mcp_bool() { + let catalog = &CATALOG; + let mcp = catalog.is_servise_mcp("postman").unwrap(); + assert!(!mcp); + } + #[test] fn test_catalog_all_names() { let names = CATALOG.get_all_by_name(); From 6869854da34f0a9a07e549dc083dbdd86e647a47 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 28 May 2025 00:57:44 -0400 Subject: [PATCH 18/26] observability impl still need to update observability to be non-blocking --- Cargo.lock | 49 ++++++++++---------- src/config/mod.rs | 23 ++++++++++ src/errors/mod.rs | 10 +++++ src/logger/mod.rs | 13 ++++-- src/logger/observability.rs | 90 +++++++++++++++++++++++++++++-------- src/web/route/mod.rs | 4 +- 6 files changed, 141 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 645ef096..74c3f1c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,9 +800,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -1831,12 +1831,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body", @@ -2188,9 +2189,9 @@ dependencies = [ [[package]] name = "kube" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b49c39074089233c2bb7b1791d1b6c06c84dbab26757491fad9d233db0d432f" +checksum = "778f98664beaf4c3c11372721e14310d1ae00f5e2d9aabcf8906c881aa4e9f51" dependencies = [ "k8s-openapi", "kube-client", @@ -2201,9 +2202,9 @@ dependencies = [ [[package]] name = "kube-client" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e199797b1b08865041c9c698f0d11a91de0a8143e808b71e250cd4a1d7ce2b9f" +checksum = "7cb276b85b6e94ded00ac8ea2c68fcf4697ea0553cb25fddc35d4a0ab718db8d" dependencies = [ "base64 0.22.1", "bytes", @@ -2238,9 +2239,9 @@ dependencies = [ [[package]] name = "kube-core" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bdefbba89dea2d99ea822a1d7cd6945535efbfb10b790056ee9284bf9e698e7" +checksum = "e3c56ff45deb0031f2a476017eed60c06872251f271b8387ad8020b8fef60960" dependencies = [ "chrono", "derive_more 2.0.1", @@ -2257,9 +2258,9 @@ dependencies = [ [[package]] name = "kube-derive" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e609a3633689a50869352a3c16e01d863b6137863c80eeb038383d5ab9f83bf" +checksum = "079fc8c1c397538628309cfdee20696ebdcc26745f9fb17f89b78782205bd995" dependencies = [ "darling", "proc-macro2", @@ -2271,9 +2272,9 @@ dependencies = [ [[package]] name = "kube-runtime" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4bd8a4554786f8f9a87bfa977fb7dbaa1d7f102a30477338b044b65de29d8e" +checksum = "2f1326e946fadf6248febdf8a1c001809c3899ccf48cb9768cbc536b741040dc" dependencies = [ "ahash", "async-broadcast", @@ -2325,9 +2326,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", "windows-targets 0.53.0", @@ -2527,14 +2528,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3783,7 +3784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -4079,9 +4080,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4365,9 +4366,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", diff --git a/src/config/mod.rs b/src/config/mod.rs index 4a77f98d..9f447fd2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -36,6 +36,7 @@ pub struct Configuration { pub app_discription: String, pub company: String, pub oidc: HashMap, + pub observability_cred: Option<(String, String)>, } pub struct Database { @@ -188,6 +189,18 @@ pub fn init_once() -> Configuration { let company = app_conf["company"].as_str().unwrap().to_string(); let notebook_namespace = app_conf["notebook_namespace"].as_str().unwrap().to_string(); + let observability_cred = match ( + app_conf["observability_refresh_token"] + .as_str() + .map(|s| s.to_string()), + app_conf["observability_refresh_token"] + .as_str() + .map(|s| s.to_string()), + ) { + (Some(token), Some(endpoint)) => Some((token, endpoint)), + _ => None, + }; + let notebooks: HashMap = toml::from_str( &read_to_string(PathBuf::from_str("config/notebook.toml").unwrap()).unwrap(), ) @@ -208,6 +221,7 @@ pub fn init_once() -> Configuration { app_discription, company, oidc: oidc_map, + observability_cred, } } @@ -253,4 +267,13 @@ mod tests { assert!(jwt::validate_token(&jwt, &decoder, &config.validation).is_ok()); } + + #[test] + fn observability_refresh_token() { + let config = init_once(); + assert!(config.observability_refresh_token.is_some()); + let token = config.observability_refresh_token.as_ref().unwrap(); + assert!(!token.is_empty()); + assert!(token.len() > 10); + } } diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 2ac4203a..f3fa912a 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -1,4 +1,6 @@ use std::convert::Infallible; +#[cfg(feature = "observe")] +use std::sync::mpsc; use actix_web::{ResponseError, http::StatusCode}; use thiserror::Error; @@ -87,6 +89,11 @@ pub enum BridgeError { #[error("Cannot parse MCP from URL")] #[cfg(feature = "mcp")] MCPParseIssue, + #[error("{0}")] + #[cfg(feature = "observe")] + MSGSendError(#[from] mpsc::SendError), + #[error("{0}")] + TokioJoinError(#[from] tokio::task::JoinError), } // Workaround for Infallible, which may get solved by rust-lang: https://github.com/rust-lang/rust/issues/64715 @@ -102,6 +109,9 @@ impl ResponseError for BridgeError { BridgeError::GeneralError(_) => StatusCode::INTERNAL_SERVER_ERROR, BridgeError::TeraError(_) => StatusCode::INTERNAL_SERVER_ERROR, BridgeError::SystemTimeError(_) => StatusCode::INTERNAL_SERVER_ERROR, + #[cfg(feature = "observe")] + BridgeError::MSGSendError(_) => StatusCode::INTERNAL_SERVER_ERROR, + BridgeError::TokioJoinError(_) => StatusCode::INTERNAL_SERVER_ERROR, BridgeError::QueryDeserializeError(_) => StatusCode::BAD_REQUEST, BridgeError::InferenceServiceHeaderNotFound => StatusCode::BAD_REQUEST, diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 043685b7..72c5fb7a 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -1,6 +1,9 @@ use reqwest::Client; use tracing_subscriber::{filter::LevelFilter, prelude::*}; +use crate::config::CONFIG; + +#[cfg(feature = "observe")] mod observability; pub fn start_logger(level: LevelFilter, _client: Client) { @@ -18,9 +21,13 @@ pub fn start_logger(level: LevelFilter, _client: Client) { #[cfg(feature = "observe")] let ts = { - let writer = observability::Observe::new("".to_string(), "".to_string(), _client) - .expect("Failed to create observability for logger"); - ts.with(writer.wrap_layer(level)) + if let Some((ref api_key, ref endpoint)) = CONFIG.observability_cred { + let writer = observability::Observe::new(api_key, endpoint, _client) + .expect("Failed to create observability for logger"); + ts.with(writer.wrap_layer(level)) + } else { + panic!("Observability credentials are not set in the configuration") + } }; ts.init() diff --git a/src/logger/observability.rs b/src/logger/observability.rs index 2bf95069..64173d6b 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -1,8 +1,11 @@ -#![allow(dead_code)] - -use std::io::Write; +use std::{ + io::{self, Write}, + sync::mpsc::{Sender, channel}, +}; -use reqwest::Client; +use reqwest::{Client, header::CONTENT_TYPE}; +use serde_json::json; +use tokio::task::JoinHandle; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ Layer, Registry, filter, @@ -16,10 +19,8 @@ use crate::errors::{BridgeError, Result}; pub const MESSAGE_DELIMITER: &str = "*~*~*"; pub struct Observe { - client: Client, - api_key: String, - endpoint: Url, - // sender: Sender, + sender: Sender, + handler: Option>, } type LayerAlias = filter::Filtered< @@ -48,13 +49,35 @@ type LayerAlias = filter::Filtered< >; impl Observe { - pub fn new(api_key: String, endpoint: String, client: Client) -> Result { + pub fn new(api_key: &'static str, endpoint: &'static str, client: Client) -> Result { + let (sender, recv) = channel(); + let endpoint = Url::parse(&endpoint) + .map_err(|_| BridgeError::GeneralError("Invalid URL".to_string()))?; + + let handler = tokio::spawn(async move { + let endpoint = endpoint.as_str(); + let api_key = api_key; + while let Ok(msg) = recv.recv() { + println!("Sending observability message: {}", msg); + let msg = json!( + { + "text": msg + } + ) + .to_string(); + let _ = client + .post(endpoint) + .bearer_auth(&api_key) + .header(CONTENT_TYPE, "application/json") + .body(msg) + .send() + .await; + } + }); + Ok(Observe { - client, - api_key, - // validate and sanitize that the endpoint is a proper url - endpoint: Url::parse(&endpoint) - .map_err(|_| BridgeError::GeneralError("Invalid URL".to_string()))?, + sender, + handler: Some(handler), }) } @@ -67,21 +90,30 @@ impl Observe { .with_filter(level) } - pub fn send_message(&self, _msg: T) -> Result<()> { - Ok(()) + pub fn send_message(&self, msg: T) -> Result<()> { + Ok(self.sender.send(msg.to_string())?) + } + + pub async fn close(mut self) -> Result<()> { + let handler = self.handler.take(); + drop(self); + Ok(handler.unwrap().await?) } } impl Write for &Observe { - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> io::Result { let message = String::from_utf8(buf.to_vec()).unwrap_or_default(); if let Some(message) = message.split(MESSAGE_DELIMITER).nth(1) { - println!("!!!message: {}", message); + if !message.is_empty() { + self.send_message(message) + .map_err(|_| io::Error::other("Failed to send message for observability"))?; + } } Ok(buf.len()) } - fn flush(&mut self) -> std::io::Result<()> { + fn flush(&mut self) -> io::Result<()> { // Implement the flush logic here Ok(()) } @@ -94,3 +126,23 @@ impl<'a> MakeWriter<'a> for Observe { self } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_observability() { + let observe = Observe::new( + "api-key", + "https://api.slack.com/api", + Client::new(), + ).unwrap(); + let mut writer = &observe; + let _ = writer + .write(b"Nothing*~*~*Hello there from open bridge") + .unwrap(); + + assert!(observe.close().await.is_ok()); + } +} diff --git a/src/web/route/mod.rs b/src/web/route/mod.rs index 1fae8bc6..31d3b931 100644 --- a/src/web/route/mod.rs +++ b/src/web/route/mod.rs @@ -14,13 +14,13 @@ use crate::{ pub mod auth; pub mod foo; pub mod health; +#[cfg(feature = "mcp")] +pub mod mcp; #[cfg(feature = "notebook")] pub mod notebook; pub mod portal; pub mod proxy; pub mod resource; -#[cfg(feature = "mcp")] -pub mod mcp; #[get("")] async fn index(data: Data, ctx: Data, req: HttpRequest) -> Result { From 448a0c4fe6f5e4686a1b59ab385c11de81766c56 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 28 May 2025 03:27:15 -0400 Subject: [PATCH 19/26] update --- Cargo.lock | 37 ++++++++++++++---------- Cargo.toml | 2 +- justfile | 72 +++++++++++++++++++++++++++++++++++++++++------ src/logger/mod.rs | 4 +-- 4 files changed, 89 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74c3f1c1..a5044ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1835,6 +1835,7 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -1842,12 +1843,16 @@ dependencies = [ "http 1.3.1", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2049,6 +2054,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itertools" version = "0.10.5" @@ -3379,9 +3394,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "2bf597b113be201cb2269b4c39b39a804d01b99ee95a4278f0ed04e45cff1c71" dependencies = [ "base64 0.22.1", "bytes", @@ -3406,26 +3421,24 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.27", - "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.26.2", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.11", - "windows-registry", + "webpki-roots 1.0.0", ] [[package]] @@ -4533,10 +4546,13 @@ dependencies = [ "base64 0.22.1", "bitflags 2.9.1", "bytes", + "futures-util", "http 1.3.1", "http-body", + "iri-string", "mime", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -4986,15 +5002,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.0", -] - [[package]] name = "webpki-roots" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index e12b02bc..0e0cc2d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ redis = { version = "0.31", features = ["tokio-comp", "num-bigint"] } #macro utils = { path = "utils" } # Kubernetes -kube = { version = "1.0", features = ["runtime", "derive"], optional = true } +kube = { version = "~1.1", features = ["runtime", "derive"], optional = true } k8s-openapi = { version = "0.25", features = ["latest"], optional = true } schemars = { version = "0.8.21", optional = true } either = { version = "1.13.0", optional = true } diff --git a/justfile b/justfile index e3408cb8..6982a290 100644 --- a/justfile +++ b/justfile @@ -1,23 +1,76 @@ -down-local-mongo: - podman stop mongodb +# Default values for build arguments (can be overridden by Dockerfile if not provided here) +NOTEBOOK_DEFAULT := "false" +LIFECYCLE_DEFAULT := "false" +OBSERVE_DEFAULT := "false" +MCP_DEFAULT := "false" + +# Consolidated build recipe that accepts a comma-separated string of features +# Usage examples: +# just build_features "notebook,lifecycle" +# just build_features "mcp,observe" +# just build_features "notebook" +# just build_features "" # (or just `just build_features`) -> uses all defaults +build-features features_string="": + #!/usr/bin/env bash + set -e -u -o pipefail + + # Initialize all build args to their default values + # These will be overridden if the feature is present in features_string + current_notebook={{NOTEBOOK_DEFAULT}} + current_lifecycle={{LIFECYCLE_DEFAULT}} + current_observe={{OBSERVE_DEFAULT}} + current_mcp={{MCP_DEFAULT}} + + # If features_string is not empty, parse it + if [[ -n "{{features_string}}" ]]; then + # Convert comma-separated string to an array (Bash specific) + IFS=',' read -r -a features_array <<< "{{features_string}}" + + for feature in "${features_array[@]}"; do + # Trim whitespace (optional, but good for robustness) + trimmed_feature=$(echo "$feature" | xargs) + echo "Processing feature: $trimmed_feature" # Debugging + if [[ "$trimmed_feature" == "notebook" ]]; then + current_notebook="true" + elif [[ "$trimmed_feature" == "lifecycle" ]]; then + current_lifecycle="true" + elif [[ "$trimmed_feature" == "observe" ]]; then + current_observe="true" + elif [[ "$trimmed_feature" == "mcp" ]]; then + current_mcp="true" + elif [[ -n "$trimmed_feature" ]]; then # Check if trimmed_feature is not empty + echo "Warning: Unknown feature '$trimmed_feature' in '{{features_string}}'" + fi + done + fi + + cmd="podman build -t bridge" + cmd="$cmd --build-arg NOTEBOOK=${current_notebook}" + cmd="$cmd --build-arg LIFECYCLE=${current_lifecycle}" + cmd="$cmd --build-arg OBSERVE=${current_observe}" + cmd="$cmd --build-arg MCP=${current_mcp}" + cmd="$cmd ." + + echo "Executing: $cmd" + eval "$cmd" build: - podman build -t bridge . + build-features "" build-notebook: - podman build -t bridge --build-arg NOTEBOOK=true --build-arg LIFECYCLE=false . + build-features "notebook" build-notebook-lifecycle: - podman build -t bridge --build-arg NOTEBOOK=true --build-arg LIFECYCLE=true . + build-features "notebook,lifecycle" build-notebook-lifecycle-observe: - podman build -t bridge --build-arg NOTEBOOK=true --build-arg LIFECYCLE=true --build-arg OBSERVE=true . + build-features "notebook,lifecycle,observe" build-notebook-lifecycle-mcp: - podman build -t bridge --build-arg NOTEBOOK=true --build-arg LIFECYCLE=true --build-arg MCP=true . + build-features "notebook,lifecycle,mcp" build-full: - podman build -t bridge --build-arg NOTEBOOK=true --build-arg LIFECYCLE=true --build-arg OBSERVE=true --build-arg MCP=true . + build-features "notebook,lifecycle,observe,mcp" mini-js: uglifyjs ./static/js/main.js -o ./static/js/main.js -c -m @@ -45,6 +98,9 @@ local-keydb: -e KEYDB_PASSWORD="admin123456789" \ -p 6379:6379 bitnami/keydb:latest +down-local-mongo: + podman stop mongodb + down-local-keydb: podman stop keydb diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 72c5fb7a..4189fcb7 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -1,8 +1,6 @@ use reqwest::Client; use tracing_subscriber::{filter::LevelFilter, prelude::*}; -use crate::config::CONFIG; - #[cfg(feature = "observe")] mod observability; @@ -21,6 +19,8 @@ pub fn start_logger(level: LevelFilter, _client: Client) { #[cfg(feature = "observe")] let ts = { + use crate::config::CONFIG; + if let Some((ref api_key, ref endpoint)) = CONFIG.observability_cred { let writer = observability::Observe::new(api_key, endpoint, _client) .expect("Failed to create observability for logger"); From 19326c8f1ae36f3a2486b20534cbd9dee8145689 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Thu, 29 May 2025 02:42:12 -0400 Subject: [PATCH 20/26] update channel to be non-blocking --- Cargo.lock | 12 ++++++------ Cargo.toml | 1 + src/config/mod.rs | 4 ++-- src/errors/mod.rs | 6 +++--- src/logger/observability.rs | 28 +++++++++++++--------------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5044ffa..7b0393ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2835,9 +2835,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -2867,9 +2867,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -3394,9 +3394,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.16" +version = "0.12.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf597b113be201cb2269b4c39b39a804d01b99ee95a4278f0ed04e45cff1c71" +checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" dependencies = [ "base64 0.22.1", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 0e0cc2d5..0a18f6f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ notebook = ["dep:kube", "dep:k8s-openapi", "dep:schemars", "dep:either"] lifecycle = ["notebook"] observe = [] mcp = [] +full = ["notebook", "lifecycle", "observe", "mcp"] [dependencies] # Async dep diff --git a/src/config/mod.rs b/src/config/mod.rs index 9f447fd2..26ee0688 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -190,10 +190,10 @@ pub fn init_once() -> Configuration { let notebook_namespace = app_conf["notebook_namespace"].as_str().unwrap().to_string(); let observability_cred = match ( - app_conf["observability_refresh_token"] + app_conf["observability_access_token"] .as_str() .map(|s| s.to_string()), - app_conf["observability_refresh_token"] + app_conf["observability_endpoint"] .as_str() .map(|s| s.to_string()), ) { diff --git a/src/errors/mod.rs b/src/errors/mod.rs index f3fa912a..c2ba121f 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -1,9 +1,9 @@ use std::convert::Infallible; -#[cfg(feature = "observe")] -use std::sync::mpsc; use actix_web::{ResponseError, http::StatusCode}; use thiserror::Error; +#[cfg(feature = "observe")] +use tokio::sync::mpsc; pub type Result = std::result::Result; @@ -91,7 +91,7 @@ pub enum BridgeError { MCPParseIssue, #[error("{0}")] #[cfg(feature = "observe")] - MSGSendError(#[from] mpsc::SendError), + MSGSendError(#[from] mpsc::error::SendError), #[error("{0}")] TokioJoinError(#[from] tokio::task::JoinError), } diff --git a/src/logger/observability.rs b/src/logger/observability.rs index 64173d6b..ed6cbf2e 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -1,11 +1,11 @@ -use std::{ - io::{self, Write}, - sync::mpsc::{Sender, channel}, -}; +use std::io::{self, Write}; use reqwest::{Client, header::CONTENT_TYPE}; use serde_json::json; -use tokio::task::JoinHandle; +use tokio::{ + sync::mpsc::{Sender, channel}, + task::JoinHandle, +}; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ Layer, Registry, filter, @@ -48,16 +48,18 @@ type LayerAlias = filter::Filtered< >, >; +const CHANNEL_SIZE: usize = 100; + impl Observe { pub fn new(api_key: &'static str, endpoint: &'static str, client: Client) -> Result { - let (sender, recv) = channel(); - let endpoint = Url::parse(&endpoint) + let (sender, mut recv) = channel(CHANNEL_SIZE); + let endpoint = Url::parse(endpoint) .map_err(|_| BridgeError::GeneralError("Invalid URL".to_string()))?; let handler = tokio::spawn(async move { let endpoint = endpoint.as_str(); let api_key = api_key; - while let Ok(msg) = recv.recv() { + while let Some(msg) = recv.recv().await { println!("Sending observability message: {}", msg); let msg = json!( { @@ -67,7 +69,7 @@ impl Observe { .to_string(); let _ = client .post(endpoint) - .bearer_auth(&api_key) + .bearer_auth(api_key) .header(CONTENT_TYPE, "application/json") .body(msg) .send() @@ -91,7 +93,7 @@ impl Observe { } pub fn send_message(&self, msg: T) -> Result<()> { - Ok(self.sender.send(msg.to_string())?) + Ok(self.sender.blocking_send(msg.to_string())?) } pub async fn close(mut self) -> Result<()> { @@ -133,11 +135,7 @@ mod tests { #[tokio::test] async fn test_observability() { - let observe = Observe::new( - "api-key", - "https://api.slack.com/api", - Client::new(), - ).unwrap(); + let observe = Observe::new("api-key", "https://api.slack.com/api", Client::new()).unwrap(); let mut writer = &observe; let _ = writer .write(b"Nothing*~*~*Hello there from open bridge") From bd46337d2d3f2e8a4fa2ee5aa2775a6b1bd95e72 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Thu, 29 May 2025 19:16:13 -0400 Subject: [PATCH 21/26] fix non-block issue --- Cargo.lock | 16 ++++++++-------- justfile | 24 +++++++++++------------- src/config/mod.rs | 8 ++++---- src/logger/mod.rs | 2 ++ src/logger/observability.rs | 20 +++++++++++++++----- src/web/route/auth/mod.rs | 17 +++++++++++++++++ 6 files changed, 57 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b0393ac..8965702c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,9 +425,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0b50b1b78dbadd44ab18b3c794e496f3a139abb9fbc27d9c94c4eebbb96496" +checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" dependencies = [ "fastrand", "gloo-timers", @@ -2408,9 +2408,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -2924,9 +2924,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2934,9 +2934,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", diff --git a/justfile b/justfile index 6982a290..cfdd7fab 100644 --- a/justfile +++ b/justfile @@ -54,24 +54,19 @@ build-features features_string="": echo "Executing: $cmd" eval "$cmd" -build: - build-features "" +# --- Build Recipes --- +# build: calls build-features with its default empty features_string, resulting in all default build args +build-notebook: (build-features "notebook") -build-notebook: - build-features "notebook" +build-notebook-lifecycle: (build-features "notebook,lifecycle") -build-notebook-lifecycle: - build-features "notebook,lifecycle" +build-notebook-lifecycle-observe: (build-features "notebook,lifecycle,observe") -build-notebook-lifecycle-observe: - build-features "notebook,lifecycle,observe" +build-notebook-lifecycle-mcp: (build-features "notebook,lifecycle,mcp") -build-notebook-lifecycle-mcp: - build-features "notebook,lifecycle,mcp" - -build-full: - build-features "notebook,lifecycle,observe,mcp" +build-full: (build-features "notebook,lifecycle,observe,mcp") +# --- Frontend & Minification --- mini-js: uglifyjs ./static/js/main.js -o ./static/js/main.js -c -m @@ -80,6 +75,7 @@ build-front: tsc uglifyjs ./static/js/main.js -o ./static/js/main.js -c -m +# --- Local Development Services --- local-mongo: podman run -d --rm --name mongodb \ -e MONGODB_ROOT_PASSWORD="admin123456789" \ @@ -104,6 +100,7 @@ down-local-mongo: down-local-keydb: podman stop keydb +# --- Watchers --- watch-tailwind: tailwindcss -i ./static/css/input.css -o ./static/css/output.css --minify --watch @@ -116,6 +113,7 @@ watch-backend: watch: bacon --features "notebook lifecycle" +# --- Certificates --- certs: mkdir certs @openssl req -x509 -newkey rsa:2048 -nodes -keyout certs/key.pem -out certs/cert.pem -days 365 -subj '/CN=open.accelerator.cafe' diff --git a/src/config/mod.rs b/src/config/mod.rs index 26ee0688..617c51a1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -271,9 +271,9 @@ mod tests { #[test] fn observability_refresh_token() { let config = init_once(); - assert!(config.observability_refresh_token.is_some()); - let token = config.observability_refresh_token.as_ref().unwrap(); - assert!(!token.is_empty()); - assert!(token.len() > 10); + assert!(config.observability_cred.is_some()); + let cred = config.observability_cred.as_ref().unwrap(); + assert!(!cred.1.is_empty()); + assert!(cred.1.len() > 10); } } diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 4189fcb7..ba1dff6c 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -3,6 +3,8 @@ use tracing_subscriber::{filter::LevelFilter, prelude::*}; #[cfg(feature = "observe")] mod observability; +#[cfg(feature = "observe")] +pub use observability::MESSAGE_DELIMITER; pub fn start_logger(level: LevelFilter, _client: Client) { // let file = std::fs::File::create("./log").unwrap(); diff --git a/src/logger/observability.rs b/src/logger/observability.rs index ed6cbf2e..0a36b632 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -6,7 +6,7 @@ use tokio::{ sync::mpsc::{Sender, channel}, task::JoinHandle, }; -use tracing::level_filters::LevelFilter; +use tracing::{error, level_filters::LevelFilter, warn}; use tracing_subscriber::{ Layer, Registry, filter, fmt::{self, MakeWriter, format}, @@ -92,8 +92,19 @@ impl Observe { .with_filter(level) } - pub fn send_message(&self, msg: T) -> Result<()> { - Ok(self.sender.blocking_send(msg.to_string())?) + pub fn send_message(&self, msg: T) { + if let Err(e) = self.sender.try_send(msg.to_string()) { + match e { + tokio::sync::mpsc::error::TrySendError::Full(_) => warn!( + "Observability channel is full, dropping message: {}", + msg.to_string() + ), + tokio::sync::mpsc::error::TrySendError::Closed(_) => error!( + "Observability channel is closed, cannot send message: {}", + msg.to_string() + ), + } + } } pub async fn close(mut self) -> Result<()> { @@ -108,8 +119,7 @@ impl Write for &Observe { let message = String::from_utf8(buf.to_vec()).unwrap_or_default(); if let Some(message) = message.split(MESSAGE_DELIMITER).nth(1) { if !message.is_empty() { - self.send_message(message) - .map_err(|_| io::Error::other("Failed to send message for observability"))?; + self.send_message(message); } } Ok(buf.len()) diff --git a/src/web/route/auth/mod.rs b/src/web/route/auth/mod.rs index 4c7454d2..837fd35d 100644 --- a/src/web/route/auth/mod.rs +++ b/src/web/route/auth/mod.rs @@ -9,6 +9,8 @@ use mongodb::bson::{doc, oid::ObjectId}; use openidconnect::{EndUserEmail, Nonce}; use serde::Deserialize; use tera::{Context, Tera}; +#[cfg(feature = "observe")] +use tracing::info; use tracing::instrument; use crate::{ @@ -25,6 +27,8 @@ use crate::{ errors::{BridgeError, Result}, web::helper::{self}, }; +#[cfg(feature = "observe")] +use crate::{config::CONFIG, logger::MESSAGE_DELIMITER}; use self::{ deserialize::CallBackResponse, @@ -93,6 +97,7 @@ async fn callback( code_to_response(callback_response.code, req, openid, data, db, cache).await } +#[instrument(skip_all, parent = None)] async fn code_to_response( code: String, req: HttpRequest, @@ -200,6 +205,18 @@ async fn code_to_response( None => None, }; + #[cfg(feature = "observe")] + info!( + "{}User: {} has successfully logged into {}", + MESSAGE_DELIMITER, id, CONFIG.company + ); + + tracing::info!( + "User: {} has successfully logged into {}", + id, + crate::config::CONFIG.company + ); + let bridge_cookie_json = BridgeCookie { subject: id, user_type, From 393b8b86f8758d16eb829361352d6649b8676ac5 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Mon, 2 Jun 2025 22:23:41 -0400 Subject: [PATCH 22/26] more observability added --- src/logger/observability.rs | 4 +++- src/web/helper.rs | 18 ++++++++++++++++++ src/web/route/auth/mod.rs | 6 ------ src/web/route/notebook/mod.rs | 18 ++++++++++++++++++ src/web/route/portal/mod.rs | 19 ++++++++++++++++++- 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/logger/observability.rs b/src/logger/observability.rs index 0a36b632..05caecb7 100644 --- a/src/logger/observability.rs +++ b/src/logger/observability.rs @@ -51,6 +51,8 @@ type LayerAlias = filter::Filtered< const CHANNEL_SIZE: usize = 100; impl Observe { + /// Warning! Ensure you don't instrustment this with sensitive data, as it will be sent to the + /// observability endpoint. pub fn new(api_key: &'static str, endpoint: &'static str, client: Client) -> Result { let (sender, mut recv) = channel(CHANNEL_SIZE); let endpoint = Url::parse(endpoint) @@ -126,7 +128,7 @@ impl Write for &Observe { } fn flush(&mut self) -> io::Result<()> { - // Implement the flush logic here + // We write the data directly to the underlying sink, so no flush is needed. Ok(()) } } diff --git a/src/web/helper.rs b/src/web/helper.rs index ed5901fc..adf1e99c 100644 --- a/src/web/helper.rs +++ b/src/web/helper.rs @@ -5,8 +5,12 @@ use rand::{Rng, rng}; use serde::Deserialize; use tera::Context; use tokio_stream::StreamExt; +#[cfg(feature = "observe")] +use tracing::instrument; use tracing::{error, info, warn}; +#[cfg(feature = "observe")] +use crate::db::models::BridgeCookie; use crate::{ auth::jwt::validate_token, config::CONFIG, @@ -109,6 +113,20 @@ pub fn add_token_exp_to_tera(tera: &mut Context, token: &str) { } } +#[instrument(skip_all, parent = None)] +#[cfg(feature = "observe")] +#[inline] +pub fn observability_post(msg: &'static str, bc: &BridgeCookie) { + use crate::logger::MESSAGE_DELIMITER; + + let user = &bc.subject; + let user_type = &bc.user_type; + info!( + "{}User: {} with type: {:?} {} for {}", + MESSAGE_DELIMITER, user, user_type, msg, CONFIG.company + ); +} + pub(super) async fn payload_to_struct(mut payload: web::Payload) -> Result where T: Deserialize<'static>, diff --git a/src/web/route/auth/mod.rs b/src/web/route/auth/mod.rs index 837fd35d..b0fad8f1 100644 --- a/src/web/route/auth/mod.rs +++ b/src/web/route/auth/mod.rs @@ -211,12 +211,6 @@ async fn code_to_response( MESSAGE_DELIMITER, id, CONFIG.company ); - tracing::info!( - "User: {} has successfully logged into {}", - id, - crate::config::CONFIG.company - ); - let bridge_cookie_json = BridgeCookie { subject: id, user_type, diff --git a/src/web/route/notebook/mod.rs b/src/web/route/notebook/mod.rs index 67f5f551..d40f2dfb 100644 --- a/src/web/route/notebook/mod.rs +++ b/src/web/route/notebook/mod.rs @@ -289,6 +289,12 @@ async fn notebook_create( ) .await?; + #[cfg(feature = "observe")] + { + use crate::web::helper::observability_post; + observability_post("has created a workbench", &bridge_cookie); + } + let bridge_cookie_json = serde_json::to_string(&bridge_cookie).map_err(|e| { BridgeError::GeneralError(format!("Could not serialize bridge cookie: {}", e)) })?; @@ -389,6 +395,12 @@ async fn notebook_delete( helper::utils::notebook_destroy(**db, &bridge_cookie.subject, persist_pvc, &user.email).await?; + #[cfg(feature = "observe")] + { + use crate::web::helper::observability_post; + observability_post("has deleted their workbench", &bridge_cookie); + } + // delete the cookies let mut notebook_cookie = Cookie::build(NOTEBOOK_COOKIE_NAME, "") .path("/notebook") @@ -552,6 +564,12 @@ async fn notebook_enter( ))? .into_inner(); + #[cfg(feature = "observe")] + { + use crate::web::helper::observability_post; + observability_post("is entering their workbench", &bridge_cookie); + } + let new_nsc = NotebookStatusCookie { start_time: nsc.start_time.clone(), status: nsc.status.clone(), diff --git a/src/web/route/portal/mod.rs b/src/web/route/portal/mod.rs index f4229235..10b3beae 100644 --- a/src/web/route/portal/mod.rs +++ b/src/web/route/portal/mod.rs @@ -10,7 +10,10 @@ use actix_web::{ }; use mongodb::bson::{doc, oid::ObjectId}; use tera::{Context, Tera}; +use tracing::instrument; +#[cfg(feature = "observe")] +use crate::web::helper::observability_post; use crate::{ auth::{COOKIE_NAME, NOTEBOOK_COOKIE_NAME, NOTEBOOK_STATUS_COOKIE_NAME}, db::{ @@ -34,6 +37,7 @@ mod token; mod user; mod user_htmx; +#[instrument(skip_all, parent = None)] #[get("")] async fn index(data: Option>, db: Data<&DB>) -> Result { // get cookie if it exists @@ -71,6 +75,11 @@ async fn index(data: Option>, db: Data<&DB>) -> Result Ok(resp .append_header((header::LOCATION, "/portal/user")) @@ -187,7 +196,7 @@ async fn search_by_email( } #[post("logout")] -async fn logout() -> HttpResponse { +async fn logout(#[cfg(feature = "observe")] cookie: Option>) -> HttpResponse { // clear all the cookie let mut cookie_remove = Cookie::build(COOKIE_NAME, "") .same_site(SameSite::Strict) @@ -213,6 +222,14 @@ async fn logout() -> HttpResponse { .finish(); notebook_status_cookie.make_removal(); + #[cfg(feature = "observe")] + { + if let Some(bc) = cookie { + let bridge_cookie = bc.into_inner(); + observability_post("has logged out from the portal", &bridge_cookie); + } + } + HttpResponse::Ok() .append_header(("HX-Redirect", "/")) .cookie(cookie_remove) From f845b9a55fb5d7751bb3332cec2bf86626e893e5 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Wed, 11 Jun 2025 19:26:37 -0400 Subject: [PATCH 23/26] dep update and additional observe log --- Cargo.lock | 373 ++++++++++++++++-------------- Cargo.toml | 4 +- README.md | 2 - config/configurations_sample.toml | 2 + src/kube/models.rs | 67 +++--- src/web/route/portal/mod.rs | 9 +- 6 files changed, 243 insertions(+), 214 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8965702c..553ea5ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -226,7 +226,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -374,7 +374,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -385,7 +385,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -475,9 +475,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bindgen" @@ -498,7 +498,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", "which", ] @@ -602,9 +602,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byteorder" @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.24" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -649,9 +649,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -909,7 +909,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -933,7 +933,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -944,7 +944,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -982,18 +982,18 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1006,7 +1006,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1026,7 +1026,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] @@ -1056,7 +1056,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1118,7 +1118,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1169,7 +1169,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1189,7 +1189,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1253,9 +1253,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1365,7 +1365,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1418,7 +1418,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1541,9 +1541,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -1552,11 +1552,11 @@ dependencies = [ [[package]] name = "headers" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "headers-core", "http 1.3.1", @@ -1783,15 +1783,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", "hyper", "hyper-util", "log", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -1831,9 +1831,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -2032,7 +2032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -2239,7 +2239,7 @@ dependencies = [ "k8s-openapi", "kube-core", "pem 3.0.5", - "rustls 0.23.27", + "rustls 0.23.28", "secrecy", "serde", "serde_json", @@ -2264,7 +2264,7 @@ dependencies = [ "http 1.3.1", "json-patch", "k8s-openapi", - "schemars", + "schemars 0.8.22", "serde", "serde-value", "serde_json", @@ -2282,7 +2282,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2297,7 +2297,7 @@ dependencies = [ "backon", "educe", "futures", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "hostname", "json-patch", "k8s-openapi", @@ -2335,9 +2335,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libloading" @@ -2346,7 +2346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -2357,9 +2357,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -2446,7 +2446,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2460,7 +2460,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2471,7 +2471,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2482,7 +2482,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2497,15 +2497,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] @@ -2534,9 +2534,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2549,7 +2549,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2611,7 +2611,7 @@ dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2778,10 +2778,10 @@ dependencies = [ "regex", "reqwest", "rust-argon2", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pemfile 2.2.0", "rustls-pki-types", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_urlencoded", @@ -2856,7 +2856,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2999,9 +2999,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -3010,9 +3010,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -3020,24 +3020,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -3097,7 +3096,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3165,12 +3164,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3203,7 +3202,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.27", + "rustls 0.23.28", "socket2", "thiserror 2.0.12", "tokio", @@ -3223,7 +3222,7 @@ dependencies = [ "rand 0.9.1", "ring 0.17.14", "rustc-hash 2.1.1", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -3257,9 +3256,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -3328,9 +3327,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc1ea653e0b2e097db3ebb5b7f678be339620b8041f66b30a308c1d45d36a7f" +checksum = "bd673deb3d184140109c1e27b043c2354cb79399c3ef304c365fc628092ed773" dependencies = [ "bytes", "cfg-if", @@ -3350,13 +3349,33 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "regex" version = "1.11.1" @@ -3394,9 +3413,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.18" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64 0.22.1", "bytes", @@ -3411,16 +3430,14 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pki-types", "serde", "serde_json", @@ -3519,9 +3536,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -3594,9 +3611,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "log", @@ -3725,6 +3742,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -3734,7 +3763,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3855,7 +3884,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3866,7 +3895,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3903,9 +3932,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -3924,15 +3953,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars 0.9.0", "serde", "serde_derive", "serde_json", @@ -3942,14 +3972,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4068,12 +4098,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slug" @@ -4087,9 +4114,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" @@ -4165,9 +4192,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -4191,7 +4218,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4288,7 +4315,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4299,17 +4326,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -4403,7 +4429,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4432,7 +4458,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.27", + "rustls 0.23.28", "tokio", ] @@ -4450,13 +4476,13 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" dependencies = [ "futures-util", "log", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -4481,9 +4507,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -4493,18 +4519,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", @@ -4516,9 +4542,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" @@ -4539,9 +4565,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "base64 0.22.1", "bitflags 2.9.1", @@ -4584,20 +4610,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -4636,9 +4662,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" dependencies = [ "bytes", "data-encoding", @@ -4646,7 +4672,7 @@ dependencies = [ "httparse", "log", "rand 0.9.1", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pki-types", "sha1", "thiserror 2.0.12", @@ -4819,7 +4845,7 @@ name = "utils" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4879,9 +4905,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -4914,7 +4940,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -4949,7 +4975,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5070,7 +5096,7 @@ dependencies = [ "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -5081,7 +5107,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5092,24 +5118,24 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -5121,15 +5147,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -5199,9 +5216,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -5353,9 +5370,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -5423,7 +5440,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -5444,7 +5461,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5464,7 +5481,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -5485,7 +5502,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5518,7 +5535,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0a18f6f9..271743a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ url = "2.5.0" toml = "0.8.14" urlencoding = "2.1.3" actix-ws = "0.3.0" -tokio-tungstenite = { version = "0.26", features = ["url", "rustls-tls-native-roots"] } +tokio-tungstenite = { version = "0.27", features = ["url", "rustls-tls-native-roots"] } # Auth deps rust-argon2 = "~2.1" sha2 = "~0.10" @@ -61,7 +61,7 @@ regex = "~1.11" thiserror = "2" # DB mongodb = "~3.2" -redis = { version = "0.31", features = ["tokio-comp", "num-bigint"] } +redis = { version = "0.32", features = ["tokio-comp", "num-bigint"] } #macro utils = { path = "utils" } # Kubernetes diff --git a/README.md b/README.md index 2a11a6f0..adad9cf7 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ **A proxy server for the Accelerated Discovery Platform** -This repository also contains the [GUI](https://open.accelerator.cafe) to (re)generate access tokens for use with the models. -
## Documentation diff --git a/config/configurations_sample.toml b/config/configurations_sample.toml index 3c3cbba1..354f7e90 100644 --- a/config/configurations_sample.toml +++ b/config/configurations_sample.toml @@ -40,3 +40,5 @@ description = "This is your gateway to the future of scientific discovery" company = "IBM Research" notebook_namespace = "notebook" honeycomb_api_key = "" +observability_access_token = "make-believe-token" +observability_endpoint = "https://postman-echo.com/post" diff --git a/src/kube/models.rs b/src/kube/models.rs index 8546045d..fb419152 100644 --- a/src/kube/models.rs +++ b/src/kube/models.rs @@ -230,40 +230,51 @@ mod test { vec![], ); + // TODO: Remove this test or refactor, so it doesn't break all the time the notebok config + // is updated let expected = json!({ "template": { "spec": { "containers": [{ - "name": "notebook", - "image": "quay.io/ibmdpdev/openad_workbench_stage:latest", - "resources": { - "requests": { - "cpu": "2", - "memory": "4Gi" - }, - "limits": { - "cpu": "2", - "memory": "4Gi" - } + "name": "notebook", + "image": "quay.io/ibmdpdev/openad_workbench_prod:latest", + "resources": { + "requests": { + "cpu": "2", + "memory": "4Gi" }, - "workingDir": "/opt/app-root/src", - "imagePullPolicy": "Always", - "volumeMounts": [ - { - "name": "notebook-volume", - "mountPath": "/opt/app-root/src" - } - ], - "command": null, - "args": null, - "env": [ - { - "name": "NOTEBOOK_ARGS", - "value": "--ServerApp.token='' --ServerApp.password='' --ServerApp.notebook_dir='/opt/app-root/src' --ServerApp.quit_button=False --LabApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.trust_xheaders=True --ServerApp.base_url='notebook/notebook/notebook'", - }]}], - "imagePullSecrets": [{ - "name": "ibmdpdev-openad-pull-secret" + "limits": { + "cpu": "2", + "memory": "4Gi" + } + }, + "workingDir": "/opt/app-root/src", + "imagePullPolicy": "Always", + "volumeMounts": [ + { + "name": "notebook-volume", + "mountPath": "/opt/app-root/src" + } + ], + "command": null, + "args": null, + "env": [ + { + "name": "NOTEBOOK_ARGS", + "value": "--ServerApp.token='' --ServerApp.password='' --ServerApp.notebook_dir='/opt/app-root/src' --ServerApp.quit_button=False --LabApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.trust_xheaders=True --ServerApp.base_url='notebook/notebook-openad/notebook'" + }, + { + + "name":"PROXY_URL", + "value": "https://open.accelerate.science/proxy" + } + ], }], + "imagePullSecrets": [ + { + "name": "ibmdpdev-openad-pull-secret" + } + ], "volumes": [{ "name": "notebook-volume", "persistentVolumeClaim": { diff --git a/src/web/route/portal/mod.rs b/src/web/route/portal/mod.rs index 10b3beae..5046ea20 100644 --- a/src/web/route/portal/mod.rs +++ b/src/web/route/portal/mod.rs @@ -196,7 +196,7 @@ async fn search_by_email( } #[post("logout")] -async fn logout(#[cfg(feature = "observe")] cookie: Option>) -> HttpResponse { +async fn logout(#[cfg(feature = "observe")] req: HttpRequest) -> HttpResponse { // clear all the cookie let mut cookie_remove = Cookie::build(COOKIE_NAME, "") .same_site(SameSite::Strict) @@ -224,9 +224,10 @@ async fn logout(#[cfg(feature = "observe")] cookie: Option #[cfg(feature = "observe")] { - if let Some(bc) = cookie { - let bridge_cookie = bc.into_inner(); - observability_post("has logged out from the portal", &bridge_cookie); + if let Some(cookie) = req.cookie(COOKIE_NAME).map(|c| c.value().to_string()) { + if let Ok(bridge_cookie) = serde_json::from_str::(&cookie) { + observability_post("has logged out from the portal", &bridge_cookie); + } } } From 1a9ecb21a7b63169c8d470a375a29b4955089ab5 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 17 Jun 2025 13:14:53 -0400 Subject: [PATCH 24/26] test fix --- .github/workflows/merge.yml | 4 ++-- src/web/route/mcp/mod.rs | 2 +- src/web/route/proxy/services.rs | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 8b199d57..cb7817ca 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -47,7 +47,7 @@ jobs: run: cargo install just - name: Build - run: cargo build --verbose + run: cargo build --features full - name: Run Lint run: | @@ -57,4 +57,4 @@ jobs: - name: Run tests run: | - cargo test --verbose --features notebook,lifecycle + cargo test --features full diff --git a/src/web/route/mcp/mod.rs b/src/web/route/mcp/mod.rs index 6c9ca766..72bfaccc 100644 --- a/src/web/route/mcp/mod.rs +++ b/src/web/route/mcp/mod.rs @@ -30,7 +30,7 @@ async fn forward( if let Some(mcp) = path.split('/').next() { let path = path.strip_prefix(mcp).unwrap_or(path); - if !CATALOG.is_servise_mcp(mcp)? { + if !CATALOG.is_service_mcp(mcp)? { return Err(BridgeError::ServiceDoesNotExist(mcp.to_string())); } diff --git a/src/web/route/proxy/services.rs b/src/web/route/proxy/services.rs index 114c315b..507e4abb 100644 --- a/src/web/route/proxy/services.rs +++ b/src/web/route/proxy/services.rs @@ -80,7 +80,7 @@ impl Catalog { } #[cfg(feature = "mcp")] - pub fn is_servise_mcp(&self, service_name: &str) -> Result { + pub fn is_service_mcp(&self, service_name: &str) -> Result { Ok(self .0 .get("services") @@ -208,10 +208,11 @@ mod test { assert!(postman.is_some()); } + #[cfg(feature = "mcp")] #[test] fn test_mcp_bool() { let catalog = &CATALOG; - let mcp = catalog.is_servise_mcp("postman").unwrap(); + let mcp = catalog.is_service_mcp("postman").unwrap(); assert!(!mcp); } From 6ab082f0d5f10a16580b40028db42301ac803758 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 17 Jun 2025 13:22:41 -0400 Subject: [PATCH 25/26] test fix 2 --- src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 617c51a1..b4118fa6 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -122,7 +122,7 @@ pub fn init_once() -> Configuration { validation.leeway = 0; let (config_location_str, database_location_str) = if cfg!(debug_assertions) { - ("config/configurations.toml", "config/database_sample.toml") + ("config/configurations_sample.toml", "config/database_sample.toml") } else { ("config/configurations.toml", "config/database.toml") }; From f8a8214e0929544ed9895e2e6c02bc12413ef1a9 Mon Sep 17 00:00:00 2001 From: Daniel Choi Date: Tue, 17 Jun 2025 14:01:36 -0400 Subject: [PATCH 26/26] test fix 3 --- src/kube/models.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kube/models.rs b/src/kube/models.rs index fb419152..945ad6dc 100644 --- a/src/kube/models.rs +++ b/src/kube/models.rs @@ -261,7 +261,7 @@ mod test { "env": [ { "name": "NOTEBOOK_ARGS", - "value": "--ServerApp.token='' --ServerApp.password='' --ServerApp.notebook_dir='/opt/app-root/src' --ServerApp.quit_button=False --LabApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.trust_xheaders=True --ServerApp.base_url='notebook/notebook-openad/notebook'" + "value": "--ServerApp.token='' --ServerApp.password='' --ServerApp.notebook_dir='/opt/app-root/src' --ServerApp.quit_button=False --LabApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.default_url='/lab/tree/start_menu.ipynb' --ServerApp.trust_xheaders=True --ServerApp.base_url='notebook/notebook/notebook'" }, {