From f7e1d1539fb7f4e1d9f9181513fabe12b90c9a3e Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 31 Oct 2025 17:59:20 +0100 Subject: [PATCH 01/21] Reset repository content --- .drone.yml | 12 - Cargo.lock | 4256 ------ Cargo.toml | 35 - Makefile | 14 - assets/bootstrap.css | 12199 ---------------- assets/script.js | 30 - assets/style.css | 12 - assets/ws_debug.js | 68 - docker/dex/dex.config.yaml | 26 - .gitignore => matrixgw_backend/.gitignore | 0 matrixgw_backend/Cargo.toml | 6 + Dockerfile => matrixgw_backend/Dockerfile | 0 .../docker-compose.yml | 8 - .../docker}/element/config.json | 0 .../docker}/mas/config.yaml | 0 .../docker}/synapse/homeserver.yaml | 0 .../docker}/synapse/localhost.log.config | 0 .../docker}/synapse/localhost.signing.key | 0 .../examples}/api_curl.rs | 0 matrixgw_backend/src/main.rs | 3 + matrixgw_frontend/.gitignore | 24 + matrixgw_frontend/README.md | 73 + matrixgw_frontend/eslint.config.js | 23 + {assets => matrixgw_frontend}/favicon.png | Bin matrixgw_frontend/index.html | 13 + matrixgw_frontend/package-lock.json | 3265 +++++ matrixgw_frontend/package.json | 33 + matrixgw_frontend/public/vite.svg | 1 + matrixgw_frontend/src/App.css | 42 + matrixgw_frontend/src/App.tsx | 35 + matrixgw_frontend/src/assets/react.svg | 1 + matrixgw_frontend/src/index.css | 68 + matrixgw_frontend/src/main.tsx | 10 + matrixgw_frontend/tsconfig.app.json | 28 + matrixgw_frontend/tsconfig.json | 7 + matrixgw_frontend/tsconfig.node.json | 26 + matrixgw_frontend/vite.config.ts | 7 + src/app_config.rs | 187 - src/broadcast_messages.rs | 46 - src/constants.rs | 18 - src/extractors/client_auth.rs | 256 - src/extractors/mod.rs | 1 - src/lib.rs | 8 - src/main.rs | 80 - src/server/api/account.rs | 23 - src/server/api/media.rs | 59 - src/server/api/mod.rs | 14 - src/server/api/profile.rs | 29 - src/server/api/room.rs | 81 - src/server/api/ws.rs | 176 - src/server/mod.rs | 49 - src/server/web_ui.rs | 252 - src/sync_client.rs | 145 - src/user.rs | 241 - src/utils/base_utils.rs | 20 - src/utils/matrix_utils.rs | 18 - src/utils/mod.rs | 2 - templates/base_page.html | 46 - templates/index.html | 146 - templates/ws_debug.html | 46 - 60 files changed, 3665 insertions(+), 18603 deletions(-) delete mode 100644 .drone.yml delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 Makefile delete mode 100644 assets/bootstrap.css delete mode 100644 assets/script.js delete mode 100644 assets/style.css delete mode 100644 assets/ws_debug.js delete mode 100644 docker/dex/dex.config.yaml rename .gitignore => matrixgw_backend/.gitignore (100%) create mode 100644 matrixgw_backend/Cargo.toml rename Dockerfile => matrixgw_backend/Dockerfile (100%) rename docker-compose.yml => matrixgw_backend/docker-compose.yml (95%) rename {docker => matrixgw_backend/docker}/element/config.json (100%) rename {docker => matrixgw_backend/docker}/mas/config.yaml (100%) rename {docker => matrixgw_backend/docker}/synapse/homeserver.yaml (100%) rename {docker => matrixgw_backend/docker}/synapse/localhost.log.config (100%) rename {docker => matrixgw_backend/docker}/synapse/localhost.signing.key (100%) rename {examples => matrixgw_backend/examples}/api_curl.rs (100%) create mode 100644 matrixgw_backend/src/main.rs create mode 100644 matrixgw_frontend/.gitignore create mode 100644 matrixgw_frontend/README.md create mode 100644 matrixgw_frontend/eslint.config.js rename {assets => matrixgw_frontend}/favicon.png (100%) create mode 100644 matrixgw_frontend/index.html create mode 100644 matrixgw_frontend/package-lock.json create mode 100644 matrixgw_frontend/package.json create mode 100644 matrixgw_frontend/public/vite.svg create mode 100644 matrixgw_frontend/src/App.css create mode 100644 matrixgw_frontend/src/App.tsx create mode 100644 matrixgw_frontend/src/assets/react.svg create mode 100644 matrixgw_frontend/src/index.css create mode 100644 matrixgw_frontend/src/main.tsx create mode 100644 matrixgw_frontend/tsconfig.app.json create mode 100644 matrixgw_frontend/tsconfig.json create mode 100644 matrixgw_frontend/tsconfig.node.json create mode 100644 matrixgw_frontend/vite.config.ts delete mode 100644 src/app_config.rs delete mode 100644 src/broadcast_messages.rs delete mode 100644 src/constants.rs delete mode 100644 src/extractors/client_auth.rs delete mode 100644 src/extractors/mod.rs delete mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/server/api/account.rs delete mode 100644 src/server/api/media.rs delete mode 100644 src/server/api/mod.rs delete mode 100644 src/server/api/profile.rs delete mode 100644 src/server/api/room.rs delete mode 100644 src/server/api/ws.rs delete mode 100644 src/server/mod.rs delete mode 100644 src/server/web_ui.rs delete mode 100644 src/sync_client.rs delete mode 100644 src/user.rs delete mode 100644 src/utils/base_utils.rs delete mode 100644 src/utils/matrix_utils.rs delete mode 100644 src/utils/mod.rs delete mode 100644 templates/base_page.html delete mode 100644 templates/index.html delete mode 100644 templates/ws_debug.html diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 15eb5af..0000000 --- a/.drone.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -kind: pipeline -type: docker -name: default - -steps: - - name: cargo_check - image: rust - commands: - - rustup component add clippy - - cargo clippy -- -D warnings - - cargo test \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index cc2690a..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,4256 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "base64 0.22.1", - "bitflags", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash", - "futures-core", - "h2 0.3.26", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.9.2", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-remote-ip" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e" -dependencies = [ - "actix-web", - "futures-util", - "log", -] - -[[package]] -name = "actix-router" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.8", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-session" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "400c27fd4cdbe0082b7bbd29ac44a3070cbda1b2114138dc106ba39fe2f90dff" -dependencies = [ - "actix-service", - "actix-utils", - "actix-web", - "anyhow", - "derive_more", - "rand 0.9.2", - "redis", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash", - "futures-core", - "futures-util", - "impl-more", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.5.8", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "actix-ws" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99" -dependencies = [ - "actix-codec", - "actix-http", - "actix-web", - "bytestring", - "futures-core", - "tokio", -] - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.6", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" -dependencies = [ - "anstyle", - "once_cell", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as_variant" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38fa22307249f86fb7fad906fcae77f2564caeb56d7209103c551cd1cf4798f" - -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "basic-toml", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "serde", - "serde_derive", - "winnow", -] - -[[package]] -name = "assign" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "attohttpc" -version = "0.28.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07a9b245ba0739fc90935094c29adbaee3f977218b5fb95e822e261cda7f56a3" -dependencies = [ - "http 1.3.1", - "log", - "native-tls", - "serde", - "serde_json", - "url", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "aws-creds" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba912106484991c456adb3364338a2534d0818bd9374b324b608074e3b55f581" -dependencies = [ - "attohttpc", - "home", - "log", - "quick-xml 0.32.0", - "rust-ini", - "serde", - "thiserror 1.0.69", - "time", - "url", -] - -[[package]] -name = "aws-region" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ae4ae7c45238b60af0a3b27ef2fcc7bd5b8fdcd8a6d679919558b40d3eff7a" -dependencies = [ - "thiserror 1.0.69", -] - -[[package]] -name = "backon" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" -dependencies = [ - "fastrand", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base16ct" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" - -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" - -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - -[[package]] -name = "binstring" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed79c2a8151273c70956b5e3cdfdc1ff6c1a8b9779ba59c6807d281b32ee2f86" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "blake2b_simd" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.11.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "brotli" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "bytestring" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" -dependencies = [ - "bytes", -] - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.6", - "inout", -] - -[[package]] -name = "clap" -version = "4.5.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "coarsetime" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" -dependencies = [ - "libc", - "wasix", - "wasm-bindgen", -] - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const-oid" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f" - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "const_panic" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "aes-gcm", - "base64 0.20.0", - "hkdf", - "hmac", - "percent-encoding", - "rand 0.8.5", - "sha2 0.10.8", - "subtle", - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "crypto-common" -version = "0.2.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ct-codecs" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4" - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "date_header" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.6", - "subtle", -] - -[[package]] -name = "digest" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460dd7f37e4950526b54a5a6b1f41b6c8e763c58eb9a8fc8fc05ba5c2f44ca7b" -dependencies = [ - "block-buffer 0.11.0-rc.4", - "const-oid 0.10.0", - "crypto-common 0.2.0-rc.3", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519-compact" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" -dependencies = [ - "ct-codecs", - "getrandom 0.2.15", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "flate2" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.3.1", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-sha1-compact" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" - -[[package]] -name = "hmac-sha256" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-sha512" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.3.1", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http 1.3.1", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dab50e193aebe510fe0e40230145820e02f48dae0cf339ea4204e6e708ff7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.8", - "http 1.3.1", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http 1.3.1", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.3.1", - "http-body", - "hyper", - "pin-project-lite", - "socket2 0.5.8", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "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", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "impl-more" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - -[[package]] -name = "indexmap" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" -dependencies = [ - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "jiff" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "js_int" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" -dependencies = [ - "serde", -] - -[[package]] -name = "js_option" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c" -dependencies = [ - "serde", -] - -[[package]] -name = "jwt-simple" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a" -dependencies = [ - "anyhow", - "binstring", - "blake2b_simd", - "coarsetime", - "ct-codecs", - "ed25519-compact", - "hmac-sha1-compact", - "hmac-sha256", - "hmac-sha512", - "k256", - "p256", - "p384", - "rand 0.8.5", - "serde", - "serde_json", - "superboring", - "thiserror 2.0.16", - "zeroize", -] - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2 0.10.8", - "signature", -] - -[[package]] -name = "konst" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" -dependencies = [ - "const_panic", - "konst_kernel", - "typewit", -] - -[[package]] -name = "konst_kernel" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" -dependencies = [ - "typewit", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "light-openid" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a15777d080e807d5b6b3c0b5a293f7d4680d74a4c66b0cdf9db0441ea9f548" -dependencies = [ - "base64 0.22.1", - "log", - "reqwest", - "serde", - "serde_json", - "urlencoding", -] - -[[package]] -name = "linux-raw-sys" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" - -[[package]] -name = "litemap" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" - -[[package]] -name = "local-channel" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matrix_gateway" -version = "0.1.0" -dependencies = [ - "actix-remote-ip", - "actix-session", - "actix-web", - "actix-ws", - "anyhow", - "askama", - "base16ct 0.3.0", - "bytes", - "chrono", - "clap", - "env_logger", - "futures-util", - "ipnet", - "jwt-simple", - "lazy_static", - "light-openid", - "log", - "mime_guess", - "rand 0.9.2", - "ruma", - "rust-embed", - "rust-s3", - "serde", - "serde_json", - "sha2 0.11.0-rc.0", - "thiserror 2.0.16", - "tokio", - "urlencoding", - "uuid", -] - -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minidom" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" -dependencies = [ - "rxml", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quick-xml" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.1", -] - -[[package]] -name = "redis" -version = "0.32.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8" -dependencies = [ - "arc-swap", - "backon", - "bytes", - "cfg-if", - "combine", - "futures-channel", - "futures-util", - "itoa", - "num-bigint", - "percent-encoding", - "pin-project-lite", - "ryu", - "socket2 0.6.0", - "tokio", - "tokio-util", - "url", -] - -[[package]] -name = "redox_syscall" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.4.8", - "http 1.3.1", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "sha2 0.10.8", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "ruma" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3714d4ebd4314e6510bc64194fcdea1b51fe47898169a08f1bb4912e5c10e2c5" -dependencies = [ - "assign", - "js_int", - "js_option", - "ruma-client", - "ruma-client-api", - "ruma-common", - "ruma-events", - "web-time", -] - -[[package]] -name = "ruma-client" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df765f1917f28ef0bf307b19c2c845be4fc2bb77f76e00b1eafbfa8921f7952" -dependencies = [ - "as_variant", - "assign", - "async-stream", - "bytes", - "futures-core", - "http 1.3.1", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "ruma-client-api", - "ruma-common", - "serde_html_form", - "tracing", -] - -[[package]] -name = "ruma-client-api" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9e9c613cfda4923b851c5d8bc442305905bee4f0c2b924564b00e71636c8d4" -dependencies = [ - "as_variant", - "assign", - "bytes", - "date_header", - "http 1.3.1", - "js_int", - "js_option", - "maplit", - "ruma-common", - "ruma-events", - "serde", - "serde_html_form", - "serde_json", - "thiserror 2.0.16", - "url", - "web-time", -] - -[[package]] -name = "ruma-common" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387e1898e868d32ff7b205e7db327361d5dcf635c00a8ae5865068607595a9cf" -dependencies = [ - "as_variant", - "base64 0.22.1", - "bytes", - "form_urlencoded", - "getrandom 0.2.15", - "http 1.3.1", - "indexmap", - "js_int", - "konst", - "percent-encoding", - "rand 0.8.5", - "regex", - "ruma-identifiers-validation", - "ruma-macros", - "serde", - "serde_html_form", - "serde_json", - "thiserror 2.0.16", - "time", - "tracing", - "url", - "uuid", - "web-time", - "wildmatch", -] - -[[package]] -name = "ruma-events" -version = "0.30.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f141b37dcd3cfa1199d6a13929db59be529b2c69107edc9f1702b81015e970b2" -dependencies = [ - "as_variant", - "indexmap", - "js_int", - "js_option", - "percent-encoding", - "regex", - "ruma-common", - "ruma-identifiers-validation", - "ruma-macros", - "serde", - "serde_json", - "thiserror 2.0.16", - "tracing", - "url", - "web-time", - "wildmatch", -] - -[[package]] -name = "ruma-identifiers-validation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad674b5e5368c53a2c90fde7dac7e30747004aaf7b1827b72874a25fc06d4d8" -dependencies = [ - "js_int", - "thiserror 2.0.16", -] - -[[package]] -name = "ruma-macros" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff13fbd6045a7278533390826de316d6116d8582ed828352661337b0c422e1c" -dependencies = [ - "cfg-if", - "proc-macro-crate", - "proc-macro2", - "quote", - "ruma-identifiers-validation", - "serde", - "syn", - "toml", -] - -[[package]] -name = "rust-embed" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" -dependencies = [ - "sha2 0.10.8", - "walkdir", -] - -[[package]] -name = "rust-ini" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" -dependencies = [ - "cfg-if", - "ordered-multimap", - "trim-in-place", -] - -[[package]] -name = "rust-s3" -version = "0.36.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7d6f3a3dd397743e8f344ffc80ea7137aee423983ae25b512e5332ad11362f" -dependencies = [ - "async-trait", - "aws-creds", - "aws-region", - "base64 0.22.1", - "bytes", - "cfg-if", - "futures", - "hex", - "hmac", - "http 1.3.1", - "log", - "maybe-async", - "md5", - "minidom", - "percent-encoding", - "quick-xml 0.36.2", - "reqwest", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.8", - "thiserror 1.0.69", - "time", - "tokio", - "tokio-stream", - "url", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "rxml" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" -dependencies = [ - "bytes", - "rxml_validation", -] - -[[package]] -name = "rxml_validation" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" -dependencies = [ - "compact_str", -] - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct 0.2.0", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_html_form" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" -dependencies = [ - "form_urlencoded", - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.143" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.0", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "superboring" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" -dependencies = [ - "getrandom 0.2.15", - "hmac-sha256", - "hmac-sha512", - "rand 0.8.5", - "rsa", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom 0.3.1", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" -dependencies = [ - "thiserror-impl 2.0.16", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" - -[[package]] -name = "time-macros" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2 0.6.0", - "tokio-macros", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "trim-in-place" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "typewit" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" -dependencies = [ - "typewit_proc_macros", -] - -[[package]] -name = "typewit_proc_macros" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.6", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" -dependencies = [ - "getrandom 0.3.1", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasix" -version = "0.12.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" -dependencies = [ - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wildmatch" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-link" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" - -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-result" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" -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-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index dba39d8..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "matrix_gateway" -version = "0.1.0" -edition = "2021" - -[dependencies] -log = "0.4.28" -env_logger = "0.11.8" -clap = { version = "4.5.51", features = ["derive", "env"] } -lazy_static = "1.5.0" -anyhow = "1.0.100" -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.143" -rust-s3 = { version = "0.37.0", features = ["tokio"] } -actix-web = "4.11.0" -actix-session = { version = "0.11.0", features = ["redis-session"] } -light-openid = "1.0.4" -thiserror = "2.0.17" -rand = "0.9.2" -rust-embed = "8.8.0" -mime_guess = "2.0.5" -askama = "0.14.0" -urlencoding = "2.1.3" -uuid = { version = "1.18.0", features = ["v4", "serde"] } -ipnet = { version = "2.11.0", features = ["serde"] } -chrono = "0.4.42" -futures-util = { version = "0.3.31", features = ["sink"] } -jwt-simple = { version = "0.12.13", default-features = false, features = ["pure-rust"] } -actix-remote-ip = "0.1.0" -bytes = "1.10.1" -sha2 = "0.11.0-rc.2" -base16ct = { version = "0.3.0", features = ["alloc"] } -ruma = { version = "0.13.0", features = ["client-api-c", "client-ext-client-api", "client-hyper-native-tls", "rand"] } -actix-ws = "0.3.0" -tokio = { version = "1.48.0", features = ["rt", "time", "macros", "rt-multi-thread"] } diff --git a/Makefile b/Makefile deleted file mode 100644 index 2834da9..0000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -DOCKER_TEMP_DIR=temp - -all: gateway - -gateway: - cargo clippy -- -D warnings && cargo build --release - -gateway_docker: gateway - rm -rf $(DOCKER_TEMP_DIR) - mkdir $(DOCKER_TEMP_DIR) - cp target/release/matrix_gateway $(DOCKER_TEMP_DIR) - docker build -t pierre42100/matrix_gateway -f ./Dockerfile "$(DOCKER_TEMP_DIR)" - rm -rf $(DOCKER_TEMP_DIR) - diff --git a/assets/bootstrap.css b/assets/bootstrap.css deleted file mode 100644 index bcabb7e..0000000 --- a/assets/bootstrap.css +++ /dev/null @@ -1,12199 +0,0 @@ -@charset "UTF-8"; -/*! - * Bootswatch v5.3.3 (https://bootswatch.com) - * Theme: cyborg - * Copyright 2012-2024 Thomas Park - * Licensed under MIT - * Based on Bootstrap -*/ -/*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); -:root, -[data-bs-theme=light] { - --bs-blue: #2a9fd6; - --bs-indigo: #6610f2; - --bs-purple: #6f42c1; - --bs-pink: #e83e8c; - --bs-red: #c00; - --bs-orange: #fd7e14; - --bs-yellow: #f80; - --bs-green: #77b300; - --bs-teal: #20c997; - --bs-cyan: #93c; - --bs-black: #000; - --bs-white: #fff; - --bs-gray: #555; - --bs-gray-dark: #222; - --bs-gray-100: #f8f9fa; - --bs-gray-200: #e9ecef; - --bs-gray-300: #dee2e6; - --bs-gray-400: #adafae; - --bs-gray-500: #888; - --bs-gray-600: #555; - --bs-gray-700: #282828; - --bs-gray-800: #222; - --bs-gray-900: #212529; - --bs-primary: #2a9fd6; - --bs-secondary: #555; - --bs-success: #77b300; - --bs-info: #93c; - --bs-warning: #f80; - --bs-danger: #c00; - --bs-light: #222; - --bs-dark: #adafae; - --bs-primary-rgb: 42, 159, 214; - --bs-secondary-rgb: 85, 85, 85; - --bs-success-rgb: 119, 179, 0; - --bs-info-rgb: 153, 51, 204; - --bs-warning-rgb: 255, 136, 0; - --bs-danger-rgb: 204, 0, 0; - --bs-light-rgb: 34, 34, 34; - --bs-dark-rgb: 173, 175, 174; - --bs-primary-text-emphasis: #114056; - --bs-secondary-text-emphasis: #222222; - --bs-success-text-emphasis: #304800; - --bs-info-text-emphasis: #3d1452; - --bs-warning-text-emphasis: #663600; - --bs-danger-text-emphasis: #520000; - --bs-light-text-emphasis: #282828; - --bs-dark-text-emphasis: #282828; - --bs-primary-bg-subtle: #d4ecf7; - --bs-secondary-bg-subtle: #dddddd; - --bs-success-bg-subtle: #e4f0cc; - --bs-info-bg-subtle: #ebd6f5; - --bs-warning-bg-subtle: #ffe7cc; - --bs-danger-bg-subtle: #f5cccc; - --bs-light-bg-subtle: #fcfcfd; - --bs-dark-bg-subtle: #adafae; - --bs-primary-border-subtle: #aad9ef; - --bs-secondary-border-subtle: #bbbbbb; - --bs-success-border-subtle: #c9e199; - --bs-info-border-subtle: #d6adeb; - --bs-warning-border-subtle: #ffcf99; - --bs-danger-border-subtle: #eb9999; - --bs-light-border-subtle: #e9ecef; - --bs-dark-border-subtle: #888; - --bs-white-rgb: 255, 255, 255; - --bs-black-rgb: 0, 0, 0; - --bs-font-sans-serif: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; - --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - --bs-body-font-family: var(--bs-font-sans-serif); - --bs-body-font-size: 1rem; - --bs-body-font-weight: 400; - --bs-body-line-height: 1.5; - --bs-body-color: #adafae; - --bs-body-color-rgb: 173, 175, 174; - --bs-body-bg: #060606; - --bs-body-bg-rgb: 6, 6, 6; - --bs-emphasis-color: #000; - --bs-emphasis-color-rgb: 0, 0, 0; - --bs-secondary-color: rgba(173, 175, 174, 0.75); - --bs-secondary-color-rgb: 173, 175, 174; - --bs-secondary-bg: #e9ecef; - --bs-secondary-bg-rgb: 233, 236, 239; - --bs-tertiary-color: rgba(173, 175, 174, 0.5); - --bs-tertiary-color-rgb: 173, 175, 174; - --bs-tertiary-bg: #f8f9fa; - --bs-tertiary-bg-rgb: 248, 249, 250; - --bs-heading-color: #fff; - --bs-link-color: #2a9fd6; - --bs-link-color-rgb: 42, 159, 214; - --bs-link-decoration: underline; - --bs-link-hover-color: #227fab; - --bs-link-hover-color-rgb: 34, 127, 171; - --bs-code-color: #e83e8c; - --bs-highlight-color: #adafae; - --bs-highlight-bg: #ffe7cc; - --bs-border-width: 1px; - --bs-border-style: solid; - --bs-border-color: #dee2e6; - --bs-border-color-translucent: rgba(0, 0, 0, 0.175); - --bs-border-radius: 0.375rem; - --bs-border-radius-sm: 0.25rem; - --bs-border-radius-lg: 0.5rem; - --bs-border-radius-xl: 1rem; - --bs-border-radius-xxl: 2rem; - --bs-border-radius-2xl: var(--bs-border-radius-xxl); - --bs-border-radius-pill: 50rem; - --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); - --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); - --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); - --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); - --bs-focus-ring-width: 0.25rem; - --bs-focus-ring-opacity: 0.25; - --bs-focus-ring-color: rgba(42, 159, 214, 0.25); - --bs-form-valid-color: #77b300; - --bs-form-valid-border-color: #77b300; - --bs-form-invalid-color: #c00; - --bs-form-invalid-border-color: #c00; -} - -[data-bs-theme=dark] { - color-scheme: dark; - --bs-body-color: #dee2e6; - --bs-body-color-rgb: 222, 226, 230; - --bs-body-bg: #212529; - --bs-body-bg-rgb: 33, 37, 41; - --bs-emphasis-color: #fff; - --bs-emphasis-color-rgb: 255, 255, 255; - --bs-secondary-color: rgba(222, 226, 230, 0.75); - --bs-secondary-color-rgb: 222, 226, 230; - --bs-secondary-bg: #222; - --bs-secondary-bg-rgb: 34, 34, 34; - --bs-tertiary-color: rgba(222, 226, 230, 0.5); - --bs-tertiary-color-rgb: 222, 226, 230; - --bs-tertiary-bg: #222426; - --bs-tertiary-bg-rgb: 34, 36, 38; - --bs-primary-text-emphasis: #7fc5e6; - --bs-secondary-text-emphasis: #999999; - --bs-success-text-emphasis: #add166; - --bs-info-text-emphasis: #c285e0; - --bs-warning-text-emphasis: #ffb866; - --bs-danger-text-emphasis: #e06666; - --bs-light-text-emphasis: #f8f9fa; - --bs-dark-text-emphasis: #dee2e6; - --bs-primary-bg-subtle: #08202b; - --bs-secondary-bg-subtle: #111111; - --bs-success-bg-subtle: #182400; - --bs-info-bg-subtle: #1f0a29; - --bs-warning-bg-subtle: #331b00; - --bs-danger-bg-subtle: #290000; - --bs-light-bg-subtle: #222; - --bs-dark-bg-subtle: #111111; - --bs-primary-border-subtle: #195f80; - --bs-secondary-border-subtle: #333333; - --bs-success-border-subtle: #476b00; - --bs-info-border-subtle: #5c1f7a; - --bs-warning-border-subtle: #995200; - --bs-danger-border-subtle: #7a0000; - --bs-light-border-subtle: #282828; - --bs-dark-border-subtle: #222; - --bs-heading-color: inherit; - --bs-link-color: #7fc5e6; - --bs-link-hover-color: #99d1eb; - --bs-link-color-rgb: 127, 197, 230; - --bs-link-hover-color-rgb: 153, 209, 235; - --bs-code-color: #f18bba; - --bs-highlight-color: #dee2e6; - --bs-highlight-bg: #663600; - --bs-border-color: #282828; - --bs-border-color-translucent: rgba(255, 255, 255, 0.15); - --bs-form-valid-color: #add166; - --bs-form-valid-border-color: #add166; - --bs-form-invalid-color: #e06666; - --bs-form-invalid-border-color: #e06666; -} - -*, -*::before, -*::after { - box-sizing: border-box; -} - -@media (prefers-reduced-motion: no-preference) { - :root { - scroll-behavior: smooth; - } -} - -body { - margin: 0; - font-family: var(--bs-body-font-family); - font-size: var(--bs-body-font-size); - font-weight: var(--bs-body-font-weight); - line-height: var(--bs-body-line-height); - color: var(--bs-body-color); - text-align: var(--bs-body-text-align); - background-color: var(--bs-body-bg); - -webkit-text-size-adjust: 100%; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -hr { - margin: 1rem 0; - color: inherit; - border: 0; - border-top: var(--bs-border-width) solid; - opacity: 0.25; -} - -h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 { - margin-top: 0; - margin-bottom: 0.5rem; - font-weight: 500; - line-height: 1.2; - color: var(--bs-heading-color); -} - -h1, .h1 { - font-size: calc(1.525rem + 3.3vw); -} -@media (min-width: 1200px) { - h1, .h1 { - font-size: 4rem; - } -} - -h2, .h2 { - font-size: calc(1.425rem + 2.1vw); -} -@media (min-width: 1200px) { - h2, .h2 { - font-size: 3rem; - } -} - -h3, .h3 { - font-size: calc(1.375rem + 1.5vw); -} -@media (min-width: 1200px) { - h3, .h3 { - font-size: 2.5rem; - } -} - -h4, .h4 { - font-size: calc(1.325rem + 0.9vw); -} -@media (min-width: 1200px) { - h4, .h4 { - font-size: 2rem; - } -} - -h5, .h5 { - font-size: calc(1.275rem + 0.3vw); -} -@media (min-width: 1200px) { - h5, .h5 { - font-size: 1.5rem; - } -} - -h6, .h6 { - font-size: 1rem; -} - -p { - margin-top: 0; - margin-bottom: 1rem; -} - -abbr[title] { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; - cursor: help; - -webkit-text-decoration-skip-ink: none; - text-decoration-skip-ink: none; -} - -address { - margin-bottom: 1rem; - font-style: normal; - line-height: inherit; -} - -ol, -ul { - padding-left: 2rem; -} - -ol, -ul, -dl { - margin-top: 0; - margin-bottom: 1rem; -} - -ol ol, -ul ul, -ol ul, -ul ol { - margin-bottom: 0; -} - -dt { - font-weight: 700; -} - -dd { - margin-bottom: 0.5rem; - margin-left: 0; -} - -blockquote { - margin: 0 0 1rem; -} - -b, -strong { - font-weight: bolder; -} - -small, .small { - font-size: 0.875em; -} - -mark, .mark { - padding: 0.1875em; - color: var(--bs-highlight-color); - background-color: var(--bs-highlight-bg); -} - -sub, -sup { - position: relative; - font-size: 0.75em; - line-height: 0; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -a { - color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); - text-decoration: underline; -} -a:hover { - --bs-link-color-rgb: var(--bs-link-hover-color-rgb); -} - -a:not([href]):not([class]), a:not([href]):not([class]):hover { - color: inherit; - text-decoration: none; -} - -pre, -code, -kbd, -samp { - font-family: var(--bs-font-monospace); - font-size: 1em; -} - -pre { - display: block; - margin-top: 0; - margin-bottom: 1rem; - overflow: auto; - font-size: 0.875em; - color: inherit; -} -pre code { - font-size: inherit; - color: inherit; - word-break: normal; -} - -code { - font-size: 0.875em; - color: var(--bs-code-color); - word-wrap: break-word; -} -a > code { - color: inherit; -} - -kbd { - padding: 0.1875rem 0.375rem; - font-size: 0.875em; - color: var(--bs-body-bg); - background-color: var(--bs-body-color); - border-radius: 0.25rem; -} -kbd kbd { - padding: 0; - font-size: 1em; -} - -figure { - margin: 0 0 1rem; -} - -img, -svg { - vertical-align: middle; -} - -table { - caption-side: bottom; - border-collapse: collapse; -} - -caption { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - color: var(--bs-secondary-color); - text-align: left; -} - -th { - text-align: inherit; - text-align: -webkit-match-parent; -} - -thead, -tbody, -tfoot, -tr, -td, -th { - border-color: inherit; - border-style: solid; - border-width: 0; -} - -label { - display: inline-block; -} - -button { - border-radius: 0; -} - -button:focus:not(:focus-visible) { - outline: 0; -} - -input, -button, -select, -optgroup, -textarea { - margin: 0; - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -button, -select { - text-transform: none; -} - -[role=button] { - cursor: pointer; -} - -select { - word-wrap: normal; -} -select:disabled { - opacity: 1; -} - -[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { - display: none !important; -} - -button, -[type=button], -[type=reset], -[type=submit] { - -webkit-appearance: button; -} -button:not(:disabled), -[type=button]:not(:disabled), -[type=reset]:not(:disabled), -[type=submit]:not(:disabled) { - cursor: pointer; -} - -::-moz-focus-inner { - padding: 0; - border-style: none; -} - -textarea { - resize: vertical; -} - -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} - -legend { - float: left; - width: 100%; - padding: 0; - margin-bottom: 0.5rem; - font-size: calc(1.275rem + 0.3vw); - line-height: inherit; -} -@media (min-width: 1200px) { - legend { - font-size: 1.5rem; - } -} -legend + * { - clear: left; -} - -::-webkit-datetime-edit-fields-wrapper, -::-webkit-datetime-edit-text, -::-webkit-datetime-edit-minute, -::-webkit-datetime-edit-hour-field, -::-webkit-datetime-edit-day-field, -::-webkit-datetime-edit-month-field, -::-webkit-datetime-edit-year-field { - padding: 0; -} - -::-webkit-inner-spin-button { - height: auto; -} - -[type=search] { - -webkit-appearance: textfield; - outline-offset: -2px; -} - -/* rtl:raw: -[type="tel"], -[type="url"], -[type="email"], -[type="number"] { - direction: ltr; -} -*/ -::-webkit-search-decoration { - -webkit-appearance: none; -} - -::-webkit-color-swatch-wrapper { - padding: 0; -} - -::-webkit-file-upload-button { - font: inherit; - -webkit-appearance: button; -} - -::file-selector-button { - font: inherit; - -webkit-appearance: button; -} - -output { - display: inline-block; -} - -iframe { - border: 0; -} - -summary { - display: list-item; - cursor: pointer; -} - -progress { - vertical-align: baseline; -} - -[hidden] { - display: none !important; -} - -.lead { - font-size: 1.25rem; - font-weight: 300; -} - -.display-1 { - font-size: calc(1.625rem + 4.5vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-1 { - font-size: 5rem; - } -} - -.display-2 { - font-size: calc(1.575rem + 3.9vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-2 { - font-size: 4.5rem; - } -} - -.display-3 { - font-size: calc(1.525rem + 3.3vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-3 { - font-size: 4rem; - } -} - -.display-4 { - font-size: calc(1.475rem + 2.7vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-4 { - font-size: 3.5rem; - } -} - -.display-5 { - font-size: calc(1.425rem + 2.1vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-5 { - font-size: 3rem; - } -} - -.display-6 { - font-size: calc(1.375rem + 1.5vw); - font-weight: 300; - line-height: 1.2; -} -@media (min-width: 1200px) { - .display-6 { - font-size: 2.5rem; - } -} - -.list-unstyled { - padding-left: 0; - list-style: none; -} - -.list-inline { - padding-left: 0; - list-style: none; -} - -.list-inline-item { - display: inline-block; -} -.list-inline-item:not(:last-child) { - margin-right: 0.5rem; -} - -.initialism { - font-size: 0.875em; - text-transform: uppercase; -} - -.blockquote { - margin-bottom: 1rem; - font-size: 1.25rem; -} -.blockquote > :last-child { - margin-bottom: 0; -} - -.blockquote-footer { - margin-top: -1rem; - margin-bottom: 1rem; - font-size: 0.875em; - color: #555; -} -.blockquote-footer::before { - content: "— "; -} - -.img-fluid { - max-width: 100%; - height: auto; -} - -.img-thumbnail { - padding: 0.25rem; - background-color: var(--bs-body-bg); - border: var(--bs-border-width) solid var(--bs-border-color); - border-radius: var(--bs-border-radius); - max-width: 100%; - height: auto; -} - -.figure { - display: inline-block; -} - -.figure-img { - margin-bottom: 0.5rem; - line-height: 1; -} - -.figure-caption { - font-size: 0.875em; - color: var(--bs-secondary-color); -} - -.container, -.container-fluid, -.container-xxl, -.container-xl, -.container-lg, -.container-md, -.container-sm { - --bs-gutter-x: 1.5rem; - --bs-gutter-y: 0; - width: 100%; - padding-right: calc(var(--bs-gutter-x) * 0.5); - padding-left: calc(var(--bs-gutter-x) * 0.5); - margin-right: auto; - margin-left: auto; -} - -@media (min-width: 576px) { - .container-sm, .container { - max-width: 540px; - } -} -@media (min-width: 768px) { - .container-md, .container-sm, .container { - max-width: 720px; - } -} -@media (min-width: 992px) { - .container-lg, .container-md, .container-sm, .container { - max-width: 960px; - } -} -@media (min-width: 1200px) { - .container-xl, .container-lg, .container-md, .container-sm, .container { - max-width: 1140px; - } -} -@media (min-width: 1400px) { - .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container { - max-width: 1320px; - } -} -:root { - --bs-breakpoint-xs: 0; - --bs-breakpoint-sm: 576px; - --bs-breakpoint-md: 768px; - --bs-breakpoint-lg: 992px; - --bs-breakpoint-xl: 1200px; - --bs-breakpoint-xxl: 1400px; -} - -.row { - --bs-gutter-x: 1.5rem; - --bs-gutter-y: 0; - display: flex; - flex-wrap: wrap; - margin-top: calc(-1 * var(--bs-gutter-y)); - margin-right: calc(-0.5 * var(--bs-gutter-x)); - margin-left: calc(-0.5 * var(--bs-gutter-x)); -} -.row > * { - flex-shrink: 0; - width: 100%; - max-width: 100%; - padding-right: calc(var(--bs-gutter-x) * 0.5); - padding-left: calc(var(--bs-gutter-x) * 0.5); - margin-top: var(--bs-gutter-y); -} - -.col { - flex: 1 0 0%; -} - -.row-cols-auto > * { - flex: 0 0 auto; - width: auto; -} - -.row-cols-1 > * { - flex: 0 0 auto; - width: 100%; -} - -.row-cols-2 > * { - flex: 0 0 auto; - width: 50%; -} - -.row-cols-3 > * { - flex: 0 0 auto; - width: 33.33333333%; -} - -.row-cols-4 > * { - flex: 0 0 auto; - width: 25%; -} - -.row-cols-5 > * { - flex: 0 0 auto; - width: 20%; -} - -.row-cols-6 > * { - flex: 0 0 auto; - width: 16.66666667%; -} - -.col-auto { - flex: 0 0 auto; - width: auto; -} - -.col-1 { - flex: 0 0 auto; - width: 8.33333333%; -} - -.col-2 { - flex: 0 0 auto; - width: 16.66666667%; -} - -.col-3 { - flex: 0 0 auto; - width: 25%; -} - -.col-4 { - flex: 0 0 auto; - width: 33.33333333%; -} - -.col-5 { - flex: 0 0 auto; - width: 41.66666667%; -} - -.col-6 { - flex: 0 0 auto; - width: 50%; -} - -.col-7 { - flex: 0 0 auto; - width: 58.33333333%; -} - -.col-8 { - flex: 0 0 auto; - width: 66.66666667%; -} - -.col-9 { - flex: 0 0 auto; - width: 75%; -} - -.col-10 { - flex: 0 0 auto; - width: 83.33333333%; -} - -.col-11 { - flex: 0 0 auto; - width: 91.66666667%; -} - -.col-12 { - flex: 0 0 auto; - width: 100%; -} - -.offset-1 { - margin-left: 8.33333333%; -} - -.offset-2 { - margin-left: 16.66666667%; -} - -.offset-3 { - margin-left: 25%; -} - -.offset-4 { - margin-left: 33.33333333%; -} - -.offset-5 { - margin-left: 41.66666667%; -} - -.offset-6 { - margin-left: 50%; -} - -.offset-7 { - margin-left: 58.33333333%; -} - -.offset-8 { - margin-left: 66.66666667%; -} - -.offset-9 { - margin-left: 75%; -} - -.offset-10 { - margin-left: 83.33333333%; -} - -.offset-11 { - margin-left: 91.66666667%; -} - -.g-0, -.gx-0 { - --bs-gutter-x: 0; -} - -.g-0, -.gy-0 { - --bs-gutter-y: 0; -} - -.g-1, -.gx-1 { - --bs-gutter-x: 0.25rem; -} - -.g-1, -.gy-1 { - --bs-gutter-y: 0.25rem; -} - -.g-2, -.gx-2 { - --bs-gutter-x: 0.5rem; -} - -.g-2, -.gy-2 { - --bs-gutter-y: 0.5rem; -} - -.g-3, -.gx-3 { - --bs-gutter-x: 1rem; -} - -.g-3, -.gy-3 { - --bs-gutter-y: 1rem; -} - -.g-4, -.gx-4 { - --bs-gutter-x: 1.5rem; -} - -.g-4, -.gy-4 { - --bs-gutter-y: 1.5rem; -} - -.g-5, -.gx-5 { - --bs-gutter-x: 3rem; -} - -.g-5, -.gy-5 { - --bs-gutter-y: 3rem; -} - -@media (min-width: 576px) { - .col-sm { - flex: 1 0 0%; - } - .row-cols-sm-auto > * { - flex: 0 0 auto; - width: auto; - } - .row-cols-sm-1 > * { - flex: 0 0 auto; - width: 100%; - } - .row-cols-sm-2 > * { - flex: 0 0 auto; - width: 50%; - } - .row-cols-sm-3 > * { - flex: 0 0 auto; - width: 33.33333333%; - } - .row-cols-sm-4 > * { - flex: 0 0 auto; - width: 25%; - } - .row-cols-sm-5 > * { - flex: 0 0 auto; - width: 20%; - } - .row-cols-sm-6 > * { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-sm-auto { - flex: 0 0 auto; - width: auto; - } - .col-sm-1 { - flex: 0 0 auto; - width: 8.33333333%; - } - .col-sm-2 { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-sm-3 { - flex: 0 0 auto; - width: 25%; - } - .col-sm-4 { - flex: 0 0 auto; - width: 33.33333333%; - } - .col-sm-5 { - flex: 0 0 auto; - width: 41.66666667%; - } - .col-sm-6 { - flex: 0 0 auto; - width: 50%; - } - .col-sm-7 { - flex: 0 0 auto; - width: 58.33333333%; - } - .col-sm-8 { - flex: 0 0 auto; - width: 66.66666667%; - } - .col-sm-9 { - flex: 0 0 auto; - width: 75%; - } - .col-sm-10 { - flex: 0 0 auto; - width: 83.33333333%; - } - .col-sm-11 { - flex: 0 0 auto; - width: 91.66666667%; - } - .col-sm-12 { - flex: 0 0 auto; - width: 100%; - } - .offset-sm-0 { - margin-left: 0; - } - .offset-sm-1 { - margin-left: 8.33333333%; - } - .offset-sm-2 { - margin-left: 16.66666667%; - } - .offset-sm-3 { - margin-left: 25%; - } - .offset-sm-4 { - margin-left: 33.33333333%; - } - .offset-sm-5 { - margin-left: 41.66666667%; - } - .offset-sm-6 { - margin-left: 50%; - } - .offset-sm-7 { - margin-left: 58.33333333%; - } - .offset-sm-8 { - margin-left: 66.66666667%; - } - .offset-sm-9 { - margin-left: 75%; - } - .offset-sm-10 { - margin-left: 83.33333333%; - } - .offset-sm-11 { - margin-left: 91.66666667%; - } - .g-sm-0, - .gx-sm-0 { - --bs-gutter-x: 0; - } - .g-sm-0, - .gy-sm-0 { - --bs-gutter-y: 0; - } - .g-sm-1, - .gx-sm-1 { - --bs-gutter-x: 0.25rem; - } - .g-sm-1, - .gy-sm-1 { - --bs-gutter-y: 0.25rem; - } - .g-sm-2, - .gx-sm-2 { - --bs-gutter-x: 0.5rem; - } - .g-sm-2, - .gy-sm-2 { - --bs-gutter-y: 0.5rem; - } - .g-sm-3, - .gx-sm-3 { - --bs-gutter-x: 1rem; - } - .g-sm-3, - .gy-sm-3 { - --bs-gutter-y: 1rem; - } - .g-sm-4, - .gx-sm-4 { - --bs-gutter-x: 1.5rem; - } - .g-sm-4, - .gy-sm-4 { - --bs-gutter-y: 1.5rem; - } - .g-sm-5, - .gx-sm-5 { - --bs-gutter-x: 3rem; - } - .g-sm-5, - .gy-sm-5 { - --bs-gutter-y: 3rem; - } -} -@media (min-width: 768px) { - .col-md { - flex: 1 0 0%; - } - .row-cols-md-auto > * { - flex: 0 0 auto; - width: auto; - } - .row-cols-md-1 > * { - flex: 0 0 auto; - width: 100%; - } - .row-cols-md-2 > * { - flex: 0 0 auto; - width: 50%; - } - .row-cols-md-3 > * { - flex: 0 0 auto; - width: 33.33333333%; - } - .row-cols-md-4 > * { - flex: 0 0 auto; - width: 25%; - } - .row-cols-md-5 > * { - flex: 0 0 auto; - width: 20%; - } - .row-cols-md-6 > * { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-md-auto { - flex: 0 0 auto; - width: auto; - } - .col-md-1 { - flex: 0 0 auto; - width: 8.33333333%; - } - .col-md-2 { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-md-3 { - flex: 0 0 auto; - width: 25%; - } - .col-md-4 { - flex: 0 0 auto; - width: 33.33333333%; - } - .col-md-5 { - flex: 0 0 auto; - width: 41.66666667%; - } - .col-md-6 { - flex: 0 0 auto; - width: 50%; - } - .col-md-7 { - flex: 0 0 auto; - width: 58.33333333%; - } - .col-md-8 { - flex: 0 0 auto; - width: 66.66666667%; - } - .col-md-9 { - flex: 0 0 auto; - width: 75%; - } - .col-md-10 { - flex: 0 0 auto; - width: 83.33333333%; - } - .col-md-11 { - flex: 0 0 auto; - width: 91.66666667%; - } - .col-md-12 { - flex: 0 0 auto; - width: 100%; - } - .offset-md-0 { - margin-left: 0; - } - .offset-md-1 { - margin-left: 8.33333333%; - } - .offset-md-2 { - margin-left: 16.66666667%; - } - .offset-md-3 { - margin-left: 25%; - } - .offset-md-4 { - margin-left: 33.33333333%; - } - .offset-md-5 { - margin-left: 41.66666667%; - } - .offset-md-6 { - margin-left: 50%; - } - .offset-md-7 { - margin-left: 58.33333333%; - } - .offset-md-8 { - margin-left: 66.66666667%; - } - .offset-md-9 { - margin-left: 75%; - } - .offset-md-10 { - margin-left: 83.33333333%; - } - .offset-md-11 { - margin-left: 91.66666667%; - } - .g-md-0, - .gx-md-0 { - --bs-gutter-x: 0; - } - .g-md-0, - .gy-md-0 { - --bs-gutter-y: 0; - } - .g-md-1, - .gx-md-1 { - --bs-gutter-x: 0.25rem; - } - .g-md-1, - .gy-md-1 { - --bs-gutter-y: 0.25rem; - } - .g-md-2, - .gx-md-2 { - --bs-gutter-x: 0.5rem; - } - .g-md-2, - .gy-md-2 { - --bs-gutter-y: 0.5rem; - } - .g-md-3, - .gx-md-3 { - --bs-gutter-x: 1rem; - } - .g-md-3, - .gy-md-3 { - --bs-gutter-y: 1rem; - } - .g-md-4, - .gx-md-4 { - --bs-gutter-x: 1.5rem; - } - .g-md-4, - .gy-md-4 { - --bs-gutter-y: 1.5rem; - } - .g-md-5, - .gx-md-5 { - --bs-gutter-x: 3rem; - } - .g-md-5, - .gy-md-5 { - --bs-gutter-y: 3rem; - } -} -@media (min-width: 992px) { - .col-lg { - flex: 1 0 0%; - } - .row-cols-lg-auto > * { - flex: 0 0 auto; - width: auto; - } - .row-cols-lg-1 > * { - flex: 0 0 auto; - width: 100%; - } - .row-cols-lg-2 > * { - flex: 0 0 auto; - width: 50%; - } - .row-cols-lg-3 > * { - flex: 0 0 auto; - width: 33.33333333%; - } - .row-cols-lg-4 > * { - flex: 0 0 auto; - width: 25%; - } - .row-cols-lg-5 > * { - flex: 0 0 auto; - width: 20%; - } - .row-cols-lg-6 > * { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-lg-auto { - flex: 0 0 auto; - width: auto; - } - .col-lg-1 { - flex: 0 0 auto; - width: 8.33333333%; - } - .col-lg-2 { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-lg-3 { - flex: 0 0 auto; - width: 25%; - } - .col-lg-4 { - flex: 0 0 auto; - width: 33.33333333%; - } - .col-lg-5 { - flex: 0 0 auto; - width: 41.66666667%; - } - .col-lg-6 { - flex: 0 0 auto; - width: 50%; - } - .col-lg-7 { - flex: 0 0 auto; - width: 58.33333333%; - } - .col-lg-8 { - flex: 0 0 auto; - width: 66.66666667%; - } - .col-lg-9 { - flex: 0 0 auto; - width: 75%; - } - .col-lg-10 { - flex: 0 0 auto; - width: 83.33333333%; - } - .col-lg-11 { - flex: 0 0 auto; - width: 91.66666667%; - } - .col-lg-12 { - flex: 0 0 auto; - width: 100%; - } - .offset-lg-0 { - margin-left: 0; - } - .offset-lg-1 { - margin-left: 8.33333333%; - } - .offset-lg-2 { - margin-left: 16.66666667%; - } - .offset-lg-3 { - margin-left: 25%; - } - .offset-lg-4 { - margin-left: 33.33333333%; - } - .offset-lg-5 { - margin-left: 41.66666667%; - } - .offset-lg-6 { - margin-left: 50%; - } - .offset-lg-7 { - margin-left: 58.33333333%; - } - .offset-lg-8 { - margin-left: 66.66666667%; - } - .offset-lg-9 { - margin-left: 75%; - } - .offset-lg-10 { - margin-left: 83.33333333%; - } - .offset-lg-11 { - margin-left: 91.66666667%; - } - .g-lg-0, - .gx-lg-0 { - --bs-gutter-x: 0; - } - .g-lg-0, - .gy-lg-0 { - --bs-gutter-y: 0; - } - .g-lg-1, - .gx-lg-1 { - --bs-gutter-x: 0.25rem; - } - .g-lg-1, - .gy-lg-1 { - --bs-gutter-y: 0.25rem; - } - .g-lg-2, - .gx-lg-2 { - --bs-gutter-x: 0.5rem; - } - .g-lg-2, - .gy-lg-2 { - --bs-gutter-y: 0.5rem; - } - .g-lg-3, - .gx-lg-3 { - --bs-gutter-x: 1rem; - } - .g-lg-3, - .gy-lg-3 { - --bs-gutter-y: 1rem; - } - .g-lg-4, - .gx-lg-4 { - --bs-gutter-x: 1.5rem; - } - .g-lg-4, - .gy-lg-4 { - --bs-gutter-y: 1.5rem; - } - .g-lg-5, - .gx-lg-5 { - --bs-gutter-x: 3rem; - } - .g-lg-5, - .gy-lg-5 { - --bs-gutter-y: 3rem; - } -} -@media (min-width: 1200px) { - .col-xl { - flex: 1 0 0%; - } - .row-cols-xl-auto > * { - flex: 0 0 auto; - width: auto; - } - .row-cols-xl-1 > * { - flex: 0 0 auto; - width: 100%; - } - .row-cols-xl-2 > * { - flex: 0 0 auto; - width: 50%; - } - .row-cols-xl-3 > * { - flex: 0 0 auto; - width: 33.33333333%; - } - .row-cols-xl-4 > * { - flex: 0 0 auto; - width: 25%; - } - .row-cols-xl-5 > * { - flex: 0 0 auto; - width: 20%; - } - .row-cols-xl-6 > * { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-xl-auto { - flex: 0 0 auto; - width: auto; - } - .col-xl-1 { - flex: 0 0 auto; - width: 8.33333333%; - } - .col-xl-2 { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-xl-3 { - flex: 0 0 auto; - width: 25%; - } - .col-xl-4 { - flex: 0 0 auto; - width: 33.33333333%; - } - .col-xl-5 { - flex: 0 0 auto; - width: 41.66666667%; - } - .col-xl-6 { - flex: 0 0 auto; - width: 50%; - } - .col-xl-7 { - flex: 0 0 auto; - width: 58.33333333%; - } - .col-xl-8 { - flex: 0 0 auto; - width: 66.66666667%; - } - .col-xl-9 { - flex: 0 0 auto; - width: 75%; - } - .col-xl-10 { - flex: 0 0 auto; - width: 83.33333333%; - } - .col-xl-11 { - flex: 0 0 auto; - width: 91.66666667%; - } - .col-xl-12 { - flex: 0 0 auto; - width: 100%; - } - .offset-xl-0 { - margin-left: 0; - } - .offset-xl-1 { - margin-left: 8.33333333%; - } - .offset-xl-2 { - margin-left: 16.66666667%; - } - .offset-xl-3 { - margin-left: 25%; - } - .offset-xl-4 { - margin-left: 33.33333333%; - } - .offset-xl-5 { - margin-left: 41.66666667%; - } - .offset-xl-6 { - margin-left: 50%; - } - .offset-xl-7 { - margin-left: 58.33333333%; - } - .offset-xl-8 { - margin-left: 66.66666667%; - } - .offset-xl-9 { - margin-left: 75%; - } - .offset-xl-10 { - margin-left: 83.33333333%; - } - .offset-xl-11 { - margin-left: 91.66666667%; - } - .g-xl-0, - .gx-xl-0 { - --bs-gutter-x: 0; - } - .g-xl-0, - .gy-xl-0 { - --bs-gutter-y: 0; - } - .g-xl-1, - .gx-xl-1 { - --bs-gutter-x: 0.25rem; - } - .g-xl-1, - .gy-xl-1 { - --bs-gutter-y: 0.25rem; - } - .g-xl-2, - .gx-xl-2 { - --bs-gutter-x: 0.5rem; - } - .g-xl-2, - .gy-xl-2 { - --bs-gutter-y: 0.5rem; - } - .g-xl-3, - .gx-xl-3 { - --bs-gutter-x: 1rem; - } - .g-xl-3, - .gy-xl-3 { - --bs-gutter-y: 1rem; - } - .g-xl-4, - .gx-xl-4 { - --bs-gutter-x: 1.5rem; - } - .g-xl-4, - .gy-xl-4 { - --bs-gutter-y: 1.5rem; - } - .g-xl-5, - .gx-xl-5 { - --bs-gutter-x: 3rem; - } - .g-xl-5, - .gy-xl-5 { - --bs-gutter-y: 3rem; - } -} -@media (min-width: 1400px) { - .col-xxl { - flex: 1 0 0%; - } - .row-cols-xxl-auto > * { - flex: 0 0 auto; - width: auto; - } - .row-cols-xxl-1 > * { - flex: 0 0 auto; - width: 100%; - } - .row-cols-xxl-2 > * { - flex: 0 0 auto; - width: 50%; - } - .row-cols-xxl-3 > * { - flex: 0 0 auto; - width: 33.33333333%; - } - .row-cols-xxl-4 > * { - flex: 0 0 auto; - width: 25%; - } - .row-cols-xxl-5 > * { - flex: 0 0 auto; - width: 20%; - } - .row-cols-xxl-6 > * { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-xxl-auto { - flex: 0 0 auto; - width: auto; - } - .col-xxl-1 { - flex: 0 0 auto; - width: 8.33333333%; - } - .col-xxl-2 { - flex: 0 0 auto; - width: 16.66666667%; - } - .col-xxl-3 { - flex: 0 0 auto; - width: 25%; - } - .col-xxl-4 { - flex: 0 0 auto; - width: 33.33333333%; - } - .col-xxl-5 { - flex: 0 0 auto; - width: 41.66666667%; - } - .col-xxl-6 { - flex: 0 0 auto; - width: 50%; - } - .col-xxl-7 { - flex: 0 0 auto; - width: 58.33333333%; - } - .col-xxl-8 { - flex: 0 0 auto; - width: 66.66666667%; - } - .col-xxl-9 { - flex: 0 0 auto; - width: 75%; - } - .col-xxl-10 { - flex: 0 0 auto; - width: 83.33333333%; - } - .col-xxl-11 { - flex: 0 0 auto; - width: 91.66666667%; - } - .col-xxl-12 { - flex: 0 0 auto; - width: 100%; - } - .offset-xxl-0 { - margin-left: 0; - } - .offset-xxl-1 { - margin-left: 8.33333333%; - } - .offset-xxl-2 { - margin-left: 16.66666667%; - } - .offset-xxl-3 { - margin-left: 25%; - } - .offset-xxl-4 { - margin-left: 33.33333333%; - } - .offset-xxl-5 { - margin-left: 41.66666667%; - } - .offset-xxl-6 { - margin-left: 50%; - } - .offset-xxl-7 { - margin-left: 58.33333333%; - } - .offset-xxl-8 { - margin-left: 66.66666667%; - } - .offset-xxl-9 { - margin-left: 75%; - } - .offset-xxl-10 { - margin-left: 83.33333333%; - } - .offset-xxl-11 { - margin-left: 91.66666667%; - } - .g-xxl-0, - .gx-xxl-0 { - --bs-gutter-x: 0; - } - .g-xxl-0, - .gy-xxl-0 { - --bs-gutter-y: 0; - } - .g-xxl-1, - .gx-xxl-1 { - --bs-gutter-x: 0.25rem; - } - .g-xxl-1, - .gy-xxl-1 { - --bs-gutter-y: 0.25rem; - } - .g-xxl-2, - .gx-xxl-2 { - --bs-gutter-x: 0.5rem; - } - .g-xxl-2, - .gy-xxl-2 { - --bs-gutter-y: 0.5rem; - } - .g-xxl-3, - .gx-xxl-3 { - --bs-gutter-x: 1rem; - } - .g-xxl-3, - .gy-xxl-3 { - --bs-gutter-y: 1rem; - } - .g-xxl-4, - .gx-xxl-4 { - --bs-gutter-x: 1.5rem; - } - .g-xxl-4, - .gy-xxl-4 { - --bs-gutter-y: 1.5rem; - } - .g-xxl-5, - .gx-xxl-5 { - --bs-gutter-x: 3rem; - } - .g-xxl-5, - .gy-xxl-5 { - --bs-gutter-y: 3rem; - } -} -.table { - --bs-table-color-type: initial; - --bs-table-bg-type: initial; - --bs-table-color-state: initial; - --bs-table-bg-state: initial; - --bs-table-color: #fff; - --bs-table-bg: var(--bs-body-bg); - --bs-table-border-color: #282828; - --bs-table-accent-bg: rgba(255, 255, 255, 0.05); - --bs-table-striped-color: #fff; - --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05); - --bs-table-active-color: #fff; - --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1); - --bs-table-hover-color: #fff; - --bs-table-hover-bg: rgba(255, 255, 255, 0.075); - width: 100%; - margin-bottom: 1rem; - vertical-align: top; - border-color: var(--bs-table-border-color); -} -.table > :not(caption) > * > * { - padding: 0.5rem 0.5rem; - color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color))); - background-color: var(--bs-table-bg); - border-bottom-width: var(--bs-border-width); - box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg))); -} -.table > tbody { - vertical-align: inherit; -} -.table > thead { - vertical-align: bottom; -} - -.table-group-divider { - border-top: calc(var(--bs-border-width) * 2) solid currentcolor; -} - -.caption-top { - caption-side: top; -} - -.table-sm > :not(caption) > * > * { - padding: 0.25rem 0.25rem; -} - -.table-bordered > :not(caption) > * { - border-width: var(--bs-border-width) 0; -} -.table-bordered > :not(caption) > * > * { - border-width: 0 var(--bs-border-width); -} - -.table-borderless > :not(caption) > * > * { - border-bottom-width: 0; -} -.table-borderless > :not(:first-child) { - border-top-width: 0; -} - -.table-striped > tbody > tr:nth-of-type(odd) > * { - --bs-table-color-type: var(--bs-table-striped-color); - --bs-table-bg-type: var(--bs-table-striped-bg); -} - -.table-striped-columns > :not(caption) > tr > :nth-child(even) { - --bs-table-color-type: var(--bs-table-striped-color); - --bs-table-bg-type: var(--bs-table-striped-bg); -} - -.table-active { - --bs-table-color-state: var(--bs-table-active-color); - --bs-table-bg-state: var(--bs-table-active-bg); -} - -.table-hover > tbody > tr:hover > * { - --bs-table-color-state: var(--bs-table-hover-color); - --bs-table-bg-state: var(--bs-table-hover-bg); -} - -.table-primary { - --bs-table-color: #fff; - --bs-table-bg: #2a9fd6; - --bs-table-border-color: #55b2de; - --bs-table-striped-bg: #35a4d8; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #3fa9da; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #3aa6d9; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-secondary { - --bs-table-color: #fff; - --bs-table-bg: #555555; - --bs-table-border-color: #777777; - --bs-table-striped-bg: #5e5e5e; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #666666; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #626262; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-success { - --bs-table-color: #fff; - --bs-table-bg: #77b300; - --bs-table-border-color: #92c233; - --bs-table-striped-bg: #7eb70d; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #85bb1a; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #81b913; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-info { - --bs-table-color: #fff; - --bs-table-bg: #9933cc; - --bs-table-border-color: #ad5cd6; - --bs-table-striped-bg: #9e3dcf; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #a347d1; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #a142d0; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-warning { - --bs-table-color: #fff; - --bs-table-bg: #ff8800; - --bs-table-border-color: #ffa033; - --bs-table-striped-bg: #ff8e0d; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #ff941a; - --bs-table-active-color: #000; - --bs-table-hover-bg: #ff9113; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-danger { - --bs-table-color: #fff; - --bs-table-bg: #cc0000; - --bs-table-border-color: #d63333; - --bs-table-striped-bg: #cf0d0d; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #d11a1a; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #d01313; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-light { - --bs-table-color: #fff; - --bs-table-bg: #222; - --bs-table-border-color: #4e4e4e; - --bs-table-striped-bg: #2d2d2d; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #383838; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #333333; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-dark { - --bs-table-color: #000; - --bs-table-bg: #adafae; - --bs-table-border-color: #8a8c8b; - --bs-table-striped-bg: #a4a6a5; - --bs-table-striped-color: #fff; - --bs-table-active-bg: #9c9e9d; - --bs-table-active-color: #fff; - --bs-table-hover-bg: #a0a2a1; - --bs-table-hover-color: #fff; - color: var(--bs-table-color); - border-color: var(--bs-table-border-color); -} - -.table-responsive { - overflow-x: auto; - -webkit-overflow-scrolling: touch; -} - -@media (max-width: 575.98px) { - .table-responsive-sm { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} -@media (max-width: 767.98px) { - .table-responsive-md { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} -@media (max-width: 991.98px) { - .table-responsive-lg { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} -@media (max-width: 1199.98px) { - .table-responsive-xl { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} -@media (max-width: 1399.98px) { - .table-responsive-xxl { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} -.form-label { - margin-bottom: 0.5rem; -} - -.col-form-label { - padding-top: 0.375rem; - padding-bottom: 0.375rem; - margin-bottom: 0; - font-size: inherit; - line-height: 1.5; -} - -.col-form-label-lg { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - font-size: 1.25rem; -} - -.col-form-label-sm { - padding-top: 0.25rem; - padding-bottom: 0.25rem; - font-size: 0.875rem; -} - -.form-text { - margin-top: 0.25rem; - font-size: 0.875em; - color: var(--bs-secondary-color); -} - -.form-control { - display: block; - width: 100%; - padding: 0.375rem 1rem; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #212529; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-color: #fff; - background-clip: padding-box; - border: 0 solid #fff; - border-radius: var(--bs-border-radius); - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-control { - transition: none; - } -} -.form-control[type=file] { - overflow: hidden; -} -.form-control[type=file]:not(:disabled):not([readonly]) { - cursor: pointer; -} -.form-control:focus { - color: #212529; - background-color: #fff; - border-color: #95cfeb; - outline: 0; - box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.form-control::-webkit-date-and-time-value { - min-width: 85px; - height: 1.5em; - margin: 0; -} -.form-control::-webkit-datetime-edit { - display: block; - padding: 0; -} -.form-control::-moz-placeholder { - color: var(--bs-secondary-color); - opacity: 1; -} -.form-control::placeholder { - color: var(--bs-secondary-color); - opacity: 1; -} -.form-control:disabled { - background-color: #adafae; - opacity: 1; -} -.form-control::-webkit-file-upload-button { - padding: 0.375rem 1rem; - margin: -0.375rem -1rem; - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; - color: #fff; - background-color: #282828; - pointer-events: none; - border-color: inherit; - border-style: solid; - border-width: 0; - border-inline-end-width: 0; - border-radius: 0; - -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -.form-control::file-selector-button { - padding: 0.375rem 1rem; - margin: -0.375rem -1rem; - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; - color: #fff; - background-color: #282828; - pointer-events: none; - border-color: inherit; - border-style: solid; - border-width: 0; - border-inline-end-width: 0; - border-radius: 0; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-control::-webkit-file-upload-button { - -webkit-transition: none; - transition: none; - } - .form-control::file-selector-button { - transition: none; - } -} -.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { - background-color: #232323; -} -.form-control:hover:not(:disabled):not([readonly])::file-selector-button { - background-color: #232323; -} - -.form-control-plaintext { - display: block; - width: 100%; - padding: 0.375rem 0; - margin-bottom: 0; - line-height: 1.5; - color: var(--bs-body-color); - background-color: transparent; - border: solid transparent; - border-width: 0 0; -} -.form-control-plaintext:focus { - outline: 0; -} -.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { - padding-right: 0; - padding-left: 0; -} - -.form-control-sm { - min-height: calc(1.5em + 0.5rem + calc(0 * 2)); - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - border-radius: var(--bs-border-radius-sm); -} -.form-control-sm::-webkit-file-upload-button { - padding: 0.25rem 0.5rem; - margin: -0.25rem -0.5rem; - -webkit-margin-end: 0.5rem; - margin-inline-end: 0.5rem; -} -.form-control-sm::file-selector-button { - padding: 0.25rem 0.5rem; - margin: -0.25rem -0.5rem; - -webkit-margin-end: 0.5rem; - margin-inline-end: 0.5rem; -} - -.form-control-lg { - min-height: calc(1.5em + 1rem + calc(0 * 2)); - padding: 0.5rem 1rem; - font-size: 1.25rem; - border-radius: var(--bs-border-radius-lg); -} -.form-control-lg::-webkit-file-upload-button { - padding: 0.5rem 1rem; - margin: -0.5rem -1rem; - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; -} -.form-control-lg::file-selector-button { - padding: 0.5rem 1rem; - margin: -0.5rem -1rem; - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; -} - -textarea.form-control { - min-height: calc(1.5em + 0.75rem + calc(0 * 2)); -} -textarea.form-control-sm { - min-height: calc(1.5em + 0.5rem + calc(0 * 2)); -} -textarea.form-control-lg { - min-height: calc(1.5em + 1rem + calc(0 * 2)); -} - -.form-control-color { - width: 3rem; - height: calc(1.5em + 0.75rem + calc(0 * 2)); - padding: 0.375rem; -} -.form-control-color:not(:disabled):not([readonly]) { - cursor: pointer; -} -.form-control-color::-moz-color-swatch { - border: 0 !important; - border-radius: var(--bs-border-radius); -} -.form-control-color::-webkit-color-swatch { - border: 0 !important; - border-radius: var(--bs-border-radius); -} -.form-control-color.form-control-sm { - height: calc(1.5em + 0.5rem + calc(0 * 2)); -} -.form-control-color.form-control-lg { - height: calc(1.5em + 1rem + calc(0 * 2)); -} - -.form-select { - --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23222' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); - display: block; - width: 100%; - padding: 0.375rem 3rem 0.375rem 1rem; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #212529; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-color: #fff; - background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none); - background-repeat: no-repeat; - background-position: right 1rem center; - background-size: 16px 12px; - border: 0 solid #fff; - border-radius: var(--bs-border-radius); - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-select { - transition: none; - } -} -.form-select:focus { - border-color: #95cfeb; - outline: 0; - box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.form-select[multiple], .form-select[size]:not([size="1"]) { - padding-right: 1rem; - background-image: none; -} -.form-select:disabled { - background-color: #adafae; -} -.form-select:-moz-focusring { - color: transparent; - text-shadow: 0 0 0 #212529; -} - -.form-select-sm { - padding-top: 0.25rem; - padding-bottom: 0.25rem; - padding-left: 0.5rem; - font-size: 0.875rem; - border-radius: var(--bs-border-radius-sm); -} - -.form-select-lg { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-left: 1rem; - font-size: 1.25rem; - border-radius: var(--bs-border-radius-lg); -} - -[data-bs-theme=dark] .form-select { - --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); -} - -.form-check { - display: block; - min-height: 1.5rem; - padding-left: 1.5em; - margin-bottom: 0.125rem; -} -.form-check .form-check-input { - float: left; - margin-left: -1.5em; -} - -.form-check-reverse { - padding-right: 1.5em; - padding-left: 0; - text-align: right; -} -.form-check-reverse .form-check-input { - float: right; - margin-right: -1.5em; - margin-left: 0; -} - -.form-check-input { - --bs-form-check-bg: #fff; - flex-shrink: 0; - width: 1em; - height: 1em; - margin-top: 0.25em; - vertical-align: top; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-color: var(--bs-form-check-bg); - background-image: var(--bs-form-check-bg-image); - background-repeat: no-repeat; - background-position: center; - background-size: contain; - border: none; - -webkit-print-color-adjust: exact; - color-adjust: exact; - print-color-adjust: exact; -} -.form-check-input[type=checkbox] { - border-radius: 0.25em; -} -.form-check-input[type=radio] { - border-radius: 50%; -} -.form-check-input:active { - filter: brightness(90%); -} -.form-check-input:focus { - border-color: #95cfeb; - outline: 0; - box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.form-check-input:checked { - background-color: #2a9fd6; - border-color: #2a9fd6; -} -.form-check-input:checked[type=checkbox] { - --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); -} -.form-check-input:checked[type=radio] { - --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); -} -.form-check-input[type=checkbox]:indeterminate { - background-color: #2a9fd6; - border-color: #2a9fd6; - --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); -} -.form-check-input:disabled { - pointer-events: none; - filter: none; - opacity: 0.5; -} -.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { - cursor: default; - opacity: 0.5; -} - -.form-switch { - padding-left: 2.5em; -} -.form-switch .form-check-input { - --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); - width: 2em; - margin-left: -2.5em; - background-image: var(--bs-form-switch-bg); - background-position: left center; - border-radius: 2em; - transition: background-position 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-switch .form-check-input { - transition: none; - } -} -.form-switch .form-check-input:focus { - --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2395cfeb'/%3e%3c/svg%3e"); -} -.form-switch .form-check-input:checked { - background-position: right center; - --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); -} -.form-switch.form-check-reverse { - padding-right: 2.5em; - padding-left: 0; -} -.form-switch.form-check-reverse .form-check-input { - margin-right: -2.5em; - margin-left: 0; -} - -.form-check-inline { - display: inline-block; - margin-right: 1rem; -} - -.btn-check { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none; -} -.btn-check[disabled] + .btn, .btn-check:disabled + .btn { - pointer-events: none; - filter: none; - opacity: 0.65; -} - -[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) { - --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e"); -} - -.form-range { - width: 100%; - height: 1.5rem; - padding: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-color: transparent; -} -.form-range:focus { - outline: 0; -} -.form-range:focus::-webkit-slider-thumb { - box-shadow: 0 0 0 1px #060606, 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.form-range:focus::-moz-range-thumb { - box-shadow: 0 0 0 1px #060606, 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.form-range::-moz-focus-outer { - border: 0; -} -.form-range::-webkit-slider-thumb { - width: 1rem; - height: 1rem; - margin-top: -0.25rem; - -webkit-appearance: none; - appearance: none; - background-color: #2a9fd6; - border: 0; - border-radius: 1rem; - -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-range::-webkit-slider-thumb { - -webkit-transition: none; - transition: none; - } -} -.form-range::-webkit-slider-thumb:active { - background-color: #bfe2f3; -} -.form-range::-webkit-slider-runnable-track { - width: 100%; - height: 0.5rem; - color: transparent; - cursor: pointer; - background-color: var(--bs-secondary-bg); - border-color: transparent; - border-radius: 1rem; -} -.form-range::-moz-range-thumb { - width: 1rem; - height: 1rem; - -moz-appearance: none; - appearance: none; - background-color: #2a9fd6; - border: 0; - border-radius: 1rem; - -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-range::-moz-range-thumb { - -moz-transition: none; - transition: none; - } -} -.form-range::-moz-range-thumb:active { - background-color: #bfe2f3; -} -.form-range::-moz-range-track { - width: 100%; - height: 0.5rem; - color: transparent; - cursor: pointer; - background-color: var(--bs-secondary-bg); - border-color: transparent; - border-radius: 1rem; -} -.form-range:disabled { - pointer-events: none; -} -.form-range:disabled::-webkit-slider-thumb { - background-color: var(--bs-secondary-color); -} -.form-range:disabled::-moz-range-thumb { - background-color: var(--bs-secondary-color); -} - -.form-floating { - position: relative; -} -.form-floating > .form-control, -.form-floating > .form-control-plaintext, -.form-floating > .form-select { - height: calc(3.5rem + calc(0 * 2)); - min-height: calc(3.5rem + calc(0 * 2)); - line-height: 1.25; -} -.form-floating > label { - position: absolute; - top: 0; - left: 0; - z-index: 2; - height: 100%; - padding: 1rem 1rem; - overflow: hidden; - text-align: start; - text-overflow: ellipsis; - white-space: nowrap; - pointer-events: none; - border: 0 solid transparent; - transform-origin: 0 0; - transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-floating > label { - transition: none; - } -} -.form-floating > .form-control, -.form-floating > .form-control-plaintext { - padding: 1rem 1rem; -} -.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder { - color: transparent; -} -.form-floating > .form-control::placeholder, -.form-floating > .form-control-plaintext::placeholder { - color: transparent; -} -.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} -.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown), -.form-floating > .form-control-plaintext:focus, -.form-floating > .form-control-plaintext:not(:placeholder-shown) { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} -.form-floating > .form-control:-webkit-autofill, -.form-floating > .form-control-plaintext:-webkit-autofill { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} -.form-floating > .form-select { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} -.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { - color: rgba(var(--bs-body-color-rgb), 0.65); - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} -.form-floating > .form-control:focus ~ label, -.form-floating > .form-control:not(:placeholder-shown) ~ label, -.form-floating > .form-control-plaintext ~ label, -.form-floating > .form-select ~ label { - color: rgba(var(--bs-body-color-rgb), 0.65); - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} -.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after { - position: absolute; - inset: 1rem 0.5rem; - z-index: -1; - height: 1.5em; - content: ""; - background-color: #fff; - border-radius: var(--bs-border-radius); -} -.form-floating > .form-control:focus ~ label::after, -.form-floating > .form-control:not(:placeholder-shown) ~ label::after, -.form-floating > .form-control-plaintext ~ label::after, -.form-floating > .form-select ~ label::after { - position: absolute; - inset: 1rem 0.5rem; - z-index: -1; - height: 1.5em; - content: ""; - background-color: #fff; - border-radius: var(--bs-border-radius); -} -.form-floating > .form-control:-webkit-autofill ~ label { - color: rgba(var(--bs-body-color-rgb), 0.65); - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} -.form-floating > .form-control-plaintext ~ label { - border-width: 0 0; -} -.form-floating > :disabled ~ label, -.form-floating > .form-control:disabled ~ label { - color: #555; -} -.form-floating > :disabled ~ label::after, -.form-floating > .form-control:disabled ~ label::after { - background-color: #adafae; -} - -.input-group { - position: relative; - display: flex; - flex-wrap: wrap; - align-items: stretch; - width: 100%; -} -.input-group > .form-control, -.input-group > .form-select, -.input-group > .form-floating { - position: relative; - flex: 1 1 auto; - width: 1%; - min-width: 0; -} -.input-group > .form-control:focus, -.input-group > .form-select:focus, -.input-group > .form-floating:focus-within { - z-index: 5; -} -.input-group .btn { - position: relative; - z-index: 2; -} -.input-group .btn:focus { - z-index: 5; -} - -.input-group-text { - display: flex; - align-items: center; - padding: 0.375rem 1rem; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #fff; - text-align: center; - white-space: nowrap; - background-color: #282828; - border: 0 solid transparent; - border-radius: var(--bs-border-radius); -} - -.input-group-lg > .form-control, -.input-group-lg > .form-select, -.input-group-lg > .input-group-text, -.input-group-lg > .btn { - padding: 0.5rem 1rem; - font-size: 1.25rem; - border-radius: var(--bs-border-radius-lg); -} - -.input-group-sm > .form-control, -.input-group-sm > .form-select, -.input-group-sm > .input-group-text, -.input-group-sm > .btn { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - border-radius: var(--bs-border-radius-sm); -} - -.input-group-lg > .form-select, -.input-group-sm > .form-select { - padding-right: 4rem; -} - -.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), -.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3), -.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control, -.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), -.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4), -.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control, -.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { - margin-left: calc(0 * -1); - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.input-group > .form-floating:not(:first-child) > .form-control, -.input-group > .form-floating:not(:first-child) > .form-select { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.valid-feedback { - display: none; - width: 100%; - margin-top: 0.25rem; - font-size: 0.875em; - color: var(--bs-form-valid-color); -} - -.valid-tooltip { - position: absolute; - top: 100%; - z-index: 5; - display: none; - max-width: 100%; - padding: 0.25rem 0.5rem; - margin-top: 0.1rem; - font-size: 0.875rem; - color: #fff; - background-color: var(--bs-success); - border-radius: var(--bs-border-radius); -} - -.was-validated :valid ~ .valid-feedback, -.was-validated :valid ~ .valid-tooltip, -.is-valid ~ .valid-feedback, -.is-valid ~ .valid-tooltip { - display: block; -} - -.was-validated .form-control:valid, .form-control.is-valid { - border-color: var(--bs-form-valid-border-color); - padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2377b300' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right calc(0.375em + 0.1875rem) center; - background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} -.was-validated .form-control:valid:focus, .form-control.is-valid:focus { - border-color: var(--bs-form-valid-border-color); - box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); -} - -.was-validated textarea.form-control:valid, textarea.form-control.is-valid { - padding-right: calc(1.5em + 0.75rem); - background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); -} - -.was-validated .form-select:valid, .form-select.is-valid { - border-color: var(--bs-form-valid-border-color); -} -.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { - --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2377b300' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); - padding-right: 5.5rem; - background-position: right 1rem center, center right 3rem; - background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} -.was-validated .form-select:valid:focus, .form-select.is-valid:focus { - border-color: var(--bs-form-valid-border-color); - box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); -} - -.was-validated .form-control-color:valid, .form-control-color.is-valid { - width: calc(3rem + calc(1.5em + 0.75rem)); -} - -.was-validated .form-check-input:valid, .form-check-input.is-valid { - border-color: var(--bs-form-valid-border-color); -} -.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked { - background-color: var(--bs-form-valid-color); -} -.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus { - box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); -} -.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { - color: var(--bs-form-valid-color); -} - -.form-check-inline .form-check-input ~ .valid-feedback { - margin-left: 0.5em; -} - -.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid, -.was-validated .input-group > .form-select:not(:focus):valid, -.input-group > .form-select:not(:focus).is-valid, -.was-validated .input-group > .form-floating:not(:focus-within):valid, -.input-group > .form-floating:not(:focus-within).is-valid { - z-index: 3; -} - -.invalid-feedback { - display: none; - width: 100%; - margin-top: 0.25rem; - font-size: 0.875em; - color: var(--bs-form-invalid-color); -} - -.invalid-tooltip { - position: absolute; - top: 100%; - z-index: 5; - display: none; - max-width: 100%; - padding: 0.25rem 0.5rem; - margin-top: 0.1rem; - font-size: 0.875rem; - color: #fff; - background-color: var(--bs-danger); - border-radius: var(--bs-border-radius); -} - -.was-validated :invalid ~ .invalid-feedback, -.was-validated :invalid ~ .invalid-tooltip, -.is-invalid ~ .invalid-feedback, -.is-invalid ~ .invalid-tooltip { - display: block; -} - -.was-validated .form-control:invalid, .form-control.is-invalid { - border-color: var(--bs-form-invalid-border-color); - padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23c00'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23c00' stroke='none'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right calc(0.375em + 0.1875rem) center; - background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} -.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { - border-color: var(--bs-form-invalid-border-color); - box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); -} - -.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { - padding-right: calc(1.5em + 0.75rem); - background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); -} - -.was-validated .form-select:invalid, .form-select.is-invalid { - border-color: var(--bs-form-invalid-border-color); -} -.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] { - --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23c00'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23c00' stroke='none'/%3e%3c/svg%3e"); - padding-right: 5.5rem; - background-position: right 1rem center, center right 3rem; - background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} -.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus { - border-color: var(--bs-form-invalid-border-color); - box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); -} - -.was-validated .form-control-color:invalid, .form-control-color.is-invalid { - width: calc(3rem + calc(1.5em + 0.75rem)); -} - -.was-validated .form-check-input:invalid, .form-check-input.is-invalid { - border-color: var(--bs-form-invalid-border-color); -} -.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked { - background-color: var(--bs-form-invalid-color); -} -.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus { - box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); -} -.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { - color: var(--bs-form-invalid-color); -} - -.form-check-inline .form-check-input ~ .invalid-feedback { - margin-left: 0.5em; -} - -.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid, -.was-validated .input-group > .form-select:not(:focus):invalid, -.input-group > .form-select:not(:focus).is-invalid, -.was-validated .input-group > .form-floating:not(:focus-within):invalid, -.input-group > .form-floating:not(:focus-within).is-invalid { - z-index: 4; -} - -.btn { - --bs-btn-padding-x: 1rem; - --bs-btn-padding-y: 0.375rem; - --bs-btn-font-family: ; - --bs-btn-font-size: 1rem; - --bs-btn-font-weight: 400; - --bs-btn-line-height: 1.5; - --bs-btn-color: var(--bs-body-color); - --bs-btn-bg: transparent; - --bs-btn-border-width: var(--bs-border-width); - --bs-btn-border-color: transparent; - --bs-btn-border-radius: var(--bs-border-radius); - --bs-btn-hover-border-color: transparent; - --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); - --bs-btn-disabled-opacity: 0.65; - --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); - display: inline-block; - padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); - font-family: var(--bs-btn-font-family); - font-size: var(--bs-btn-font-size); - font-weight: var(--bs-btn-font-weight); - line-height: var(--bs-btn-line-height); - color: var(--bs-btn-color); - text-align: center; - text-decoration: none; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); - border-radius: var(--bs-btn-border-radius); - background-color: var(--bs-btn-bg); - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .btn { - transition: none; - } -} -.btn:hover { - color: var(--bs-btn-hover-color); - background-color: var(--bs-btn-hover-bg); - border-color: var(--bs-btn-hover-border-color); -} -.btn-check + .btn:hover { - color: var(--bs-btn-color); - background-color: var(--bs-btn-bg); - border-color: var(--bs-btn-border-color); -} -.btn:focus-visible { - color: var(--bs-btn-hover-color); - background-color: var(--bs-btn-hover-bg); - border-color: var(--bs-btn-hover-border-color); - outline: 0; - box-shadow: var(--bs-btn-focus-box-shadow); -} -.btn-check:focus-visible + .btn { - border-color: var(--bs-btn-hover-border-color); - outline: 0; - box-shadow: var(--bs-btn-focus-box-shadow); -} -.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show { - color: var(--bs-btn-active-color); - background-color: var(--bs-btn-active-bg); - border-color: var(--bs-btn-active-border-color); -} -.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible { - box-shadow: var(--bs-btn-focus-box-shadow); -} -.btn-check:checked:focus-visible + .btn { - box-shadow: var(--bs-btn-focus-box-shadow); -} -.btn:disabled, .btn.disabled, fieldset:disabled .btn { - color: var(--bs-btn-disabled-color); - pointer-events: none; - background-color: var(--bs-btn-disabled-bg); - border-color: var(--bs-btn-disabled-border-color); - opacity: var(--bs-btn-disabled-opacity); -} - -.btn-primary { - --bs-btn-color: #fff; - --bs-btn-bg: #2a9fd6; - --bs-btn-border-color: #2a9fd6; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #2487b6; - --bs-btn-hover-border-color: #227fab; - --bs-btn-focus-shadow-rgb: 74, 173, 220; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #227fab; - --bs-btn-active-border-color: #2077a1; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #2a9fd6; - --bs-btn-disabled-border-color: #2a9fd6; -} - -.btn-secondary { - --bs-btn-color: #fff; - --bs-btn-bg: #555; - --bs-btn-border-color: #555; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #484848; - --bs-btn-hover-border-color: #444444; - --bs-btn-focus-shadow-rgb: 111, 111, 111; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #444444; - --bs-btn-active-border-color: #404040; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #555; - --bs-btn-disabled-border-color: #555; -} - -.btn-success { - --bs-btn-color: #fff; - --bs-btn-bg: #77b300; - --bs-btn-border-color: #77b300; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #659800; - --bs-btn-hover-border-color: #5f8f00; - --bs-btn-focus-shadow-rgb: 139, 190, 38; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #5f8f00; - --bs-btn-active-border-color: #598600; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #77b300; - --bs-btn-disabled-border-color: #77b300; -} - -.btn-info { - --bs-btn-color: #fff; - --bs-btn-bg: #93c; - --bs-btn-border-color: #93c; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #822bad; - --bs-btn-hover-border-color: #7a29a3; - --bs-btn-focus-shadow-rgb: 168, 82, 212; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #7a29a3; - --bs-btn-active-border-color: #732699; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #93c; - --bs-btn-disabled-border-color: #93c; -} - -.btn-warning { - --bs-btn-color: #fff; - --bs-btn-bg: #f80; - --bs-btn-border-color: #f80; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #d97400; - --bs-btn-hover-border-color: #cc6d00; - --bs-btn-focus-shadow-rgb: 255, 154, 38; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #cc6d00; - --bs-btn-active-border-color: #bf6600; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #f80; - --bs-btn-disabled-border-color: #f80; -} - -.btn-danger { - --bs-btn-color: #fff; - --bs-btn-bg: #c00; - --bs-btn-border-color: #c00; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #ad0000; - --bs-btn-hover-border-color: #a30000; - --bs-btn-focus-shadow-rgb: 212, 38, 38; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #a30000; - --bs-btn-active-border-color: #990000; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #c00; - --bs-btn-disabled-border-color: #c00; -} - -.btn-light { - --bs-btn-color: #fff; - --bs-btn-bg: #222; - --bs-btn-border-color: #222; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #1d1d1d; - --bs-btn-hover-border-color: #1b1b1b; - --bs-btn-focus-shadow-rgb: 67, 67, 67; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #1b1b1b; - --bs-btn-active-border-color: #1a1a1a; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #fff; - --bs-btn-disabled-bg: #222; - --bs-btn-disabled-border-color: #222; -} - -.btn-dark { - --bs-btn-color: #000; - --bs-btn-bg: #adafae; - --bs-btn-border-color: #adafae; - --bs-btn-hover-color: #000; - --bs-btn-hover-bg: #b9bbba; - --bs-btn-hover-border-color: #b5b7b6; - --bs-btn-focus-shadow-rgb: 147, 149, 148; - --bs-btn-active-color: #000; - --bs-btn-active-bg: #bdbfbe; - --bs-btn-active-border-color: #b5b7b6; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #000; - --bs-btn-disabled-bg: #adafae; - --bs-btn-disabled-border-color: #adafae; -} - -.btn-outline-primary { - --bs-btn-color: #2a9fd6; - --bs-btn-border-color: #2a9fd6; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #2a9fd6; - --bs-btn-hover-border-color: #2a9fd6; - --bs-btn-focus-shadow-rgb: 42, 159, 214; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #2a9fd6; - --bs-btn-active-border-color: #2a9fd6; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #2a9fd6; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #2a9fd6; - --bs-gradient: none; -} - -.btn-outline-secondary { - --bs-btn-color: #555; - --bs-btn-border-color: #555; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #555; - --bs-btn-hover-border-color: #555; - --bs-btn-focus-shadow-rgb: 85, 85, 85; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #555; - --bs-btn-active-border-color: #555; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #555; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #555; - --bs-gradient: none; -} - -.btn-outline-success { - --bs-btn-color: #77b300; - --bs-btn-border-color: #77b300; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #77b300; - --bs-btn-hover-border-color: #77b300; - --bs-btn-focus-shadow-rgb: 119, 179, 0; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #77b300; - --bs-btn-active-border-color: #77b300; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #77b300; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #77b300; - --bs-gradient: none; -} - -.btn-outline-info { - --bs-btn-color: #93c; - --bs-btn-border-color: #93c; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #93c; - --bs-btn-hover-border-color: #93c; - --bs-btn-focus-shadow-rgb: 153, 51, 204; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #93c; - --bs-btn-active-border-color: #93c; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #93c; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #93c; - --bs-gradient: none; -} - -.btn-outline-warning { - --bs-btn-color: #f80; - --bs-btn-border-color: #f80; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #f80; - --bs-btn-hover-border-color: #f80; - --bs-btn-focus-shadow-rgb: 255, 136, 0; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #f80; - --bs-btn-active-border-color: #f80; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #f80; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #f80; - --bs-gradient: none; -} - -.btn-outline-danger { - --bs-btn-color: #c00; - --bs-btn-border-color: #c00; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #c00; - --bs-btn-hover-border-color: #c00; - --bs-btn-focus-shadow-rgb: 204, 0, 0; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #c00; - --bs-btn-active-border-color: #c00; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #c00; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #c00; - --bs-gradient: none; -} - -.btn-outline-light { - --bs-btn-color: #222; - --bs-btn-border-color: #222; - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #222; - --bs-btn-hover-border-color: #222; - --bs-btn-focus-shadow-rgb: 34, 34, 34; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: #222; - --bs-btn-active-border-color: #222; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #222; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #222; - --bs-gradient: none; -} - -.btn-outline-dark { - --bs-btn-color: #adafae; - --bs-btn-border-color: #adafae; - --bs-btn-hover-color: #000; - --bs-btn-hover-bg: #adafae; - --bs-btn-hover-border-color: #adafae; - --bs-btn-focus-shadow-rgb: 173, 175, 174; - --bs-btn-active-color: #000; - --bs-btn-active-bg: #adafae; - --bs-btn-active-border-color: #adafae; - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: #adafae; - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: #adafae; - --bs-gradient: none; -} - -.btn-link { - --bs-btn-font-weight: 400; - --bs-btn-color: var(--bs-link-color); - --bs-btn-bg: transparent; - --bs-btn-border-color: transparent; - --bs-btn-hover-color: var(--bs-link-hover-color); - --bs-btn-hover-border-color: transparent; - --bs-btn-active-color: var(--bs-link-hover-color); - --bs-btn-active-border-color: transparent; - --bs-btn-disabled-color: #555; - --bs-btn-disabled-border-color: transparent; - --bs-btn-box-shadow: 0 0 0 #000; - --bs-btn-focus-shadow-rgb: 74, 173, 220; - text-decoration: underline; -} -.btn-link:focus-visible { - color: var(--bs-btn-color); -} -.btn-link:hover { - color: var(--bs-btn-hover-color); -} - -.btn-lg, .btn-group-lg > .btn { - --bs-btn-padding-y: 0.5rem; - --bs-btn-padding-x: 1rem; - --bs-btn-font-size: 1.25rem; - --bs-btn-border-radius: var(--bs-border-radius-lg); -} - -.btn-sm, .btn-group-sm > .btn { - --bs-btn-padding-y: 0.25rem; - --bs-btn-padding-x: 0.5rem; - --bs-btn-font-size: 0.875rem; - --bs-btn-border-radius: var(--bs-border-radius-sm); -} - -.fade { - transition: opacity 0.15s linear; -} -@media (prefers-reduced-motion: reduce) { - .fade { - transition: none; - } -} -.fade:not(.show) { - opacity: 0; -} - -.collapse:not(.show) { - display: none; -} - -.collapsing { - height: 0; - overflow: hidden; - transition: height 0.35s ease; -} -@media (prefers-reduced-motion: reduce) { - .collapsing { - transition: none; - } -} -.collapsing.collapse-horizontal { - width: 0; - height: auto; - transition: width 0.35s ease; -} -@media (prefers-reduced-motion: reduce) { - .collapsing.collapse-horizontal { - transition: none; - } -} - -.dropup, -.dropend, -.dropdown, -.dropstart, -.dropup-center, -.dropdown-center { - position: relative; -} - -.dropdown-toggle { - white-space: nowrap; -} -.dropdown-toggle::after { - display: inline-block; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid; - border-right: 0.3em solid transparent; - border-bottom: 0; - border-left: 0.3em solid transparent; -} -.dropdown-toggle:empty::after { - margin-left: 0; -} - -.dropdown-menu { - --bs-dropdown-zindex: 1000; - --bs-dropdown-min-width: 10rem; - --bs-dropdown-padding-x: 0; - --bs-dropdown-padding-y: 0.5rem; - --bs-dropdown-spacer: 0.125rem; - --bs-dropdown-font-size: 1rem; - --bs-dropdown-color: var(--bs-body-color); - --bs-dropdown-bg: #282828; - --bs-dropdown-border-color: var(--bs-border-color-translucent); - --bs-dropdown-border-radius: var(--bs-border-radius); - --bs-dropdown-border-width: var(--bs-border-width); - --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width)); - --bs-dropdown-divider-bg: #222; - --bs-dropdown-divider-margin-y: 0.5rem; - --bs-dropdown-box-shadow: var(--bs-box-shadow); - --bs-dropdown-link-color: #fff; - --bs-dropdown-link-hover-color: #fff; - --bs-dropdown-link-hover-bg: #2a9fd6; - --bs-dropdown-link-active-color: #fff; - --bs-dropdown-link-active-bg: #2a9fd6; - --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); - --bs-dropdown-item-padding-x: 1rem; - --bs-dropdown-item-padding-y: 0.25rem; - --bs-dropdown-header-color: #555; - --bs-dropdown-header-padding-x: 1rem; - --bs-dropdown-header-padding-y: 0.5rem; - position: absolute; - z-index: var(--bs-dropdown-zindex); - display: none; - min-width: var(--bs-dropdown-min-width); - padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); - margin: 0; - font-size: var(--bs-dropdown-font-size); - color: var(--bs-dropdown-color); - text-align: left; - list-style: none; - background-color: var(--bs-dropdown-bg); - background-clip: padding-box; - border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); - border-radius: var(--bs-dropdown-border-radius); -} -.dropdown-menu[data-bs-popper] { - top: 100%; - left: 0; - margin-top: var(--bs-dropdown-spacer); -} - -.dropdown-menu-start { - --bs-position: start; -} -.dropdown-menu-start[data-bs-popper] { - right: auto; - left: 0; -} - -.dropdown-menu-end { - --bs-position: end; -} -.dropdown-menu-end[data-bs-popper] { - right: 0; - left: auto; -} - -@media (min-width: 576px) { - .dropdown-menu-sm-start { - --bs-position: start; - } - .dropdown-menu-sm-start[data-bs-popper] { - right: auto; - left: 0; - } - .dropdown-menu-sm-end { - --bs-position: end; - } - .dropdown-menu-sm-end[data-bs-popper] { - right: 0; - left: auto; - } -} -@media (min-width: 768px) { - .dropdown-menu-md-start { - --bs-position: start; - } - .dropdown-menu-md-start[data-bs-popper] { - right: auto; - left: 0; - } - .dropdown-menu-md-end { - --bs-position: end; - } - .dropdown-menu-md-end[data-bs-popper] { - right: 0; - left: auto; - } -} -@media (min-width: 992px) { - .dropdown-menu-lg-start { - --bs-position: start; - } - .dropdown-menu-lg-start[data-bs-popper] { - right: auto; - left: 0; - } - .dropdown-menu-lg-end { - --bs-position: end; - } - .dropdown-menu-lg-end[data-bs-popper] { - right: 0; - left: auto; - } -} -@media (min-width: 1200px) { - .dropdown-menu-xl-start { - --bs-position: start; - } - .dropdown-menu-xl-start[data-bs-popper] { - right: auto; - left: 0; - } - .dropdown-menu-xl-end { - --bs-position: end; - } - .dropdown-menu-xl-end[data-bs-popper] { - right: 0; - left: auto; - } -} -@media (min-width: 1400px) { - .dropdown-menu-xxl-start { - --bs-position: start; - } - .dropdown-menu-xxl-start[data-bs-popper] { - right: auto; - left: 0; - } - .dropdown-menu-xxl-end { - --bs-position: end; - } - .dropdown-menu-xxl-end[data-bs-popper] { - right: 0; - left: auto; - } -} -.dropup .dropdown-menu[data-bs-popper] { - top: auto; - bottom: 100%; - margin-top: 0; - margin-bottom: var(--bs-dropdown-spacer); -} -.dropup .dropdown-toggle::after { - display: inline-block; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0; - border-right: 0.3em solid transparent; - border-bottom: 0.3em solid; - border-left: 0.3em solid transparent; -} -.dropup .dropdown-toggle:empty::after { - margin-left: 0; -} - -.dropend .dropdown-menu[data-bs-popper] { - top: 0; - right: auto; - left: 100%; - margin-top: 0; - margin-left: var(--bs-dropdown-spacer); -} -.dropend .dropdown-toggle::after { - display: inline-block; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid transparent; - border-right: 0; - border-bottom: 0.3em solid transparent; - border-left: 0.3em solid; -} -.dropend .dropdown-toggle:empty::after { - margin-left: 0; -} -.dropend .dropdown-toggle::after { - vertical-align: 0; -} - -.dropstart .dropdown-menu[data-bs-popper] { - top: 0; - right: 100%; - left: auto; - margin-top: 0; - margin-right: var(--bs-dropdown-spacer); -} -.dropstart .dropdown-toggle::after { - display: inline-block; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; -} -.dropstart .dropdown-toggle::after { - display: none; -} -.dropstart .dropdown-toggle::before { - display: inline-block; - margin-right: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid transparent; - border-right: 0.3em solid; - border-bottom: 0.3em solid transparent; -} -.dropstart .dropdown-toggle:empty::after { - margin-left: 0; -} -.dropstart .dropdown-toggle::before { - vertical-align: 0; -} - -.dropdown-divider { - height: 0; - margin: var(--bs-dropdown-divider-margin-y) 0; - overflow: hidden; - border-top: 1px solid var(--bs-dropdown-divider-bg); - opacity: 1; -} - -.dropdown-item { - display: block; - width: 100%; - padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); - clear: both; - font-weight: 400; - color: var(--bs-dropdown-link-color); - text-align: inherit; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border: 0; - border-radius: var(--bs-dropdown-item-border-radius, 0); -} -.dropdown-item:hover, .dropdown-item:focus { - color: var(--bs-dropdown-link-hover-color); - background-color: var(--bs-dropdown-link-hover-bg); -} -.dropdown-item.active, .dropdown-item:active { - color: var(--bs-dropdown-link-active-color); - text-decoration: none; - background-color: var(--bs-dropdown-link-active-bg); -} -.dropdown-item.disabled, .dropdown-item:disabled { - color: var(--bs-dropdown-link-disabled-color); - pointer-events: none; - background-color: transparent; -} - -.dropdown-menu.show { - display: block; -} - -.dropdown-header { - display: block; - padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x); - margin-bottom: 0; - font-size: 0.875rem; - color: var(--bs-dropdown-header-color); - white-space: nowrap; -} - -.dropdown-item-text { - display: block; - padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); - color: var(--bs-dropdown-link-color); -} - -.dropdown-menu-dark { - --bs-dropdown-color: #dee2e6; - --bs-dropdown-bg: #222; - --bs-dropdown-border-color: var(--bs-border-color-translucent); - --bs-dropdown-box-shadow: ; - --bs-dropdown-link-color: #dee2e6; - --bs-dropdown-link-hover-color: #fff; - --bs-dropdown-divider-bg: #222; - --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); - --bs-dropdown-link-active-color: #fff; - --bs-dropdown-link-active-bg: #2a9fd6; - --bs-dropdown-link-disabled-color: #888; - --bs-dropdown-header-color: #888; -} - -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-flex; - vertical-align: middle; -} -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - flex: 1 1 auto; -} -.btn-group > .btn-check:checked + .btn, -.btn-group > .btn-check:focus + .btn, -.btn-group > .btn:hover, -.btn-group > .btn:focus, -.btn-group > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn-check:checked + .btn, -.btn-group-vertical > .btn-check:focus + .btn, -.btn-group-vertical > .btn:hover, -.btn-group-vertical > .btn:focus, -.btn-group-vertical > .btn:active, -.btn-group-vertical > .btn.active { - z-index: 1; -} - -.btn-toolbar { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; -} -.btn-toolbar .input-group { - width: auto; -} - -.btn-group { - border-radius: var(--bs-border-radius); -} -.btn-group > :not(.btn-check:first-child) + .btn, -.btn-group > .btn-group:not(:first-child) { - margin-left: calc(var(--bs-border-width) * -1); -} -.btn-group > .btn:not(:last-child):not(.dropdown-toggle), -.btn-group > .btn.dropdown-toggle-split:first-child, -.btn-group > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn:nth-child(n+3), -.btn-group > :not(.btn-check) + .btn, -.btn-group > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.dropdown-toggle-split { - padding-right: 0.75rem; - padding-left: 0.75rem; -} -.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after { - margin-left: 0; -} -.dropstart .dropdown-toggle-split::before { - margin-right: 0; -} - -.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { - padding-right: 0.375rem; - padding-left: 0.375rem; -} - -.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { - padding-right: 0.75rem; - padding-left: 0.75rem; -} - -.btn-group-vertical { - flex-direction: column; - align-items: flex-start; - justify-content: center; -} -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group { - width: 100%; -} -.btn-group-vertical > .btn:not(:first-child), -.btn-group-vertical > .btn-group:not(:first-child) { - margin-top: calc(var(--bs-border-width) * -1); -} -.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), -.btn-group-vertical > .btn-group:not(:last-child) > .btn { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn ~ .btn, -.btn-group-vertical > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.nav { - --bs-nav-link-padding-x: 1rem; - --bs-nav-link-padding-y: 0.5rem; - --bs-nav-link-font-weight: ; - --bs-nav-link-color: var(--bs-link-color); - --bs-nav-link-hover-color: var(--bs-link-hover-color); - --bs-nav-link-disabled-color: var(--bs-secondary-color); - display: flex; - flex-wrap: wrap; - padding-left: 0; - margin-bottom: 0; - list-style: none; -} - -.nav-link { - display: block; - padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); - font-size: var(--bs-nav-link-font-size); - font-weight: var(--bs-nav-link-font-weight); - color: var(--bs-nav-link-color); - text-decoration: none; - background: none; - border: 0; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .nav-link { - transition: none; - } -} -.nav-link:hover, .nav-link:focus { - color: var(--bs-nav-link-hover-color); -} -.nav-link:focus-visible { - outline: 0; - box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); -} -.nav-link.disabled, .nav-link:disabled { - color: var(--bs-nav-link-disabled-color); - pointer-events: none; - cursor: default; -} - -.nav-tabs { - --bs-nav-tabs-border-width: var(--bs-border-width); - --bs-nav-tabs-border-color: #282828; - --bs-nav-tabs-border-radius: var(--bs-border-radius); - --bs-nav-tabs-link-hover-border-color: #282828; - --bs-nav-tabs-link-active-color: #fff; - --bs-nav-tabs-link-active-bg: #282828; - --bs-nav-tabs-link-active-border-color: #282828; - border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color); -} -.nav-tabs .nav-link { - margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); - border: var(--bs-nav-tabs-border-width) solid transparent; - border-top-left-radius: var(--bs-nav-tabs-border-radius); - border-top-right-radius: var(--bs-nav-tabs-border-radius); -} -.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { - isolation: isolate; - border-color: var(--bs-nav-tabs-link-hover-border-color); -} -.nav-tabs .nav-link.active, -.nav-tabs .nav-item.show .nav-link { - color: var(--bs-nav-tabs-link-active-color); - background-color: var(--bs-nav-tabs-link-active-bg); - border-color: var(--bs-nav-tabs-link-active-border-color); -} -.nav-tabs .dropdown-menu { - margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.nav-pills { - --bs-nav-pills-border-radius: var(--bs-border-radius); - --bs-nav-pills-link-active-color: #fff; - --bs-nav-pills-link-active-bg: #2a9fd6; -} -.nav-pills .nav-link { - border-radius: var(--bs-nav-pills-border-radius); -} -.nav-pills .nav-link.active, -.nav-pills .show > .nav-link { - color: var(--bs-nav-pills-link-active-color); - background-color: var(--bs-nav-pills-link-active-bg); -} - -.nav-underline { - --bs-nav-underline-gap: 1rem; - --bs-nav-underline-border-width: 0.125rem; - --bs-nav-underline-link-active-color: var(--bs-emphasis-color); - gap: var(--bs-nav-underline-gap); -} -.nav-underline .nav-link { - padding-right: 0; - padding-left: 0; - border-bottom: var(--bs-nav-underline-border-width) solid transparent; -} -.nav-underline .nav-link:hover, .nav-underline .nav-link:focus { - border-bottom-color: currentcolor; -} -.nav-underline .nav-link.active, -.nav-underline .show > .nav-link { - font-weight: 700; - color: var(--bs-nav-underline-link-active-color); - border-bottom-color: currentcolor; -} - -.nav-fill > .nav-link, -.nav-fill .nav-item { - flex: 1 1 auto; - text-align: center; -} - -.nav-justified > .nav-link, -.nav-justified .nav-item { - flex-basis: 0; - flex-grow: 1; - text-align: center; -} - -.nav-fill .nav-item .nav-link, -.nav-justified .nav-item .nav-link { - width: 100%; -} - -.tab-content > .tab-pane { - display: none; -} -.tab-content > .active { - display: block; -} - -.navbar { - --bs-navbar-padding-x: 0; - --bs-navbar-padding-y: 0.5rem; - --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); - --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8); - --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3); - --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); - --bs-navbar-brand-padding-y: 0.3125rem; - --bs-navbar-brand-margin-end: 1rem; - --bs-navbar-brand-font-size: 1.25rem; - --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); - --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1); - --bs-navbar-nav-link-padding-x: 0.5rem; - --bs-navbar-toggler-padding-y: 0.25rem; - --bs-navbar-toggler-padding-x: 0.75rem; - --bs-navbar-toggler-font-size: 1.25rem; - --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28173, 175, 174, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); - --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15); - --bs-navbar-toggler-border-radius: var(--bs-border-radius); - --bs-navbar-toggler-focus-width: 0.25rem; - --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; - position: relative; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); -} -.navbar > .container, -.navbar > .container-fluid, -.navbar > .container-sm, -.navbar > .container-md, -.navbar > .container-lg, -.navbar > .container-xl, -.navbar > .container-xxl { - display: flex; - flex-wrap: inherit; - align-items: center; - justify-content: space-between; -} -.navbar-brand { - padding-top: var(--bs-navbar-brand-padding-y); - padding-bottom: var(--bs-navbar-brand-padding-y); - margin-right: var(--bs-navbar-brand-margin-end); - font-size: var(--bs-navbar-brand-font-size); - color: var(--bs-navbar-brand-color); - text-decoration: none; - white-space: nowrap; -} -.navbar-brand:hover, .navbar-brand:focus { - color: var(--bs-navbar-brand-hover-color); -} - -.navbar-nav { - --bs-nav-link-padding-x: 0; - --bs-nav-link-padding-y: 0.5rem; - --bs-nav-link-font-weight: ; - --bs-nav-link-color: var(--bs-navbar-color); - --bs-nav-link-hover-color: var(--bs-navbar-hover-color); - --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); - display: flex; - flex-direction: column; - padding-left: 0; - margin-bottom: 0; - list-style: none; -} -.navbar-nav .nav-link.active, .navbar-nav .nav-link.show { - color: var(--bs-navbar-active-color); -} -.navbar-nav .dropdown-menu { - position: static; -} - -.navbar-text { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - color: var(--bs-navbar-color); -} -.navbar-text a, -.navbar-text a:hover, -.navbar-text a:focus { - color: var(--bs-navbar-active-color); -} - -.navbar-collapse { - flex-basis: 100%; - flex-grow: 1; - align-items: center; -} - -.navbar-toggler { - padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); - font-size: var(--bs-navbar-toggler-font-size); - line-height: 1; - color: var(--bs-navbar-color); - background-color: transparent; - border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); - border-radius: var(--bs-navbar-toggler-border-radius); - transition: var(--bs-navbar-toggler-transition); -} -@media (prefers-reduced-motion: reduce) { - .navbar-toggler { - transition: none; - } -} -.navbar-toggler:hover { - text-decoration: none; -} -.navbar-toggler:focus { - text-decoration: none; - outline: 0; - box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); -} - -.navbar-toggler-icon { - display: inline-block; - width: 1.5em; - height: 1.5em; - vertical-align: middle; - background-image: var(--bs-navbar-toggler-icon-bg); - background-repeat: no-repeat; - background-position: center; - background-size: 100%; -} - -.navbar-nav-scroll { - max-height: var(--bs-scroll-height, 75vh); - overflow-y: auto; -} - -@media (min-width: 576px) { - .navbar-expand-sm { - flex-wrap: nowrap; - justify-content: flex-start; - } - .navbar-expand-sm .navbar-nav { - flex-direction: row; - } - .navbar-expand-sm .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-sm .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); - } - .navbar-expand-sm .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-sm .navbar-collapse { - display: flex !important; - flex-basis: auto; - } - .navbar-expand-sm .navbar-toggler { - display: none; - } - .navbar-expand-sm .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; - } - .navbar-expand-sm .offcanvas .offcanvas-header { - display: none; - } - .navbar-expand-sm .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } -} -@media (min-width: 768px) { - .navbar-expand-md { - flex-wrap: nowrap; - justify-content: flex-start; - } - .navbar-expand-md .navbar-nav { - flex-direction: row; - } - .navbar-expand-md .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-md .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); - } - .navbar-expand-md .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-md .navbar-collapse { - display: flex !important; - flex-basis: auto; - } - .navbar-expand-md .navbar-toggler { - display: none; - } - .navbar-expand-md .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; - } - .navbar-expand-md .offcanvas .offcanvas-header { - display: none; - } - .navbar-expand-md .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } -} -@media (min-width: 992px) { - .navbar-expand-lg { - flex-wrap: nowrap; - justify-content: flex-start; - } - .navbar-expand-lg .navbar-nav { - flex-direction: row; - } - .navbar-expand-lg .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-lg .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); - } - .navbar-expand-lg .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-lg .navbar-collapse { - display: flex !important; - flex-basis: auto; - } - .navbar-expand-lg .navbar-toggler { - display: none; - } - .navbar-expand-lg .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; - } - .navbar-expand-lg .offcanvas .offcanvas-header { - display: none; - } - .navbar-expand-lg .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } -} -@media (min-width: 1200px) { - .navbar-expand-xl { - flex-wrap: nowrap; - justify-content: flex-start; - } - .navbar-expand-xl .navbar-nav { - flex-direction: row; - } - .navbar-expand-xl .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-xl .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); - } - .navbar-expand-xl .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-xl .navbar-collapse { - display: flex !important; - flex-basis: auto; - } - .navbar-expand-xl .navbar-toggler { - display: none; - } - .navbar-expand-xl .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; - } - .navbar-expand-xl .offcanvas .offcanvas-header { - display: none; - } - .navbar-expand-xl .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } -} -@media (min-width: 1400px) { - .navbar-expand-xxl { - flex-wrap: nowrap; - justify-content: flex-start; - } - .navbar-expand-xxl .navbar-nav { - flex-direction: row; - } - .navbar-expand-xxl .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-xxl .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); - } - .navbar-expand-xxl .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-xxl .navbar-collapse { - display: flex !important; - flex-basis: auto; - } - .navbar-expand-xxl .navbar-toggler { - display: none; - } - .navbar-expand-xxl .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; - } - .navbar-expand-xxl .offcanvas .offcanvas-header { - display: none; - } - .navbar-expand-xxl .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } -} -.navbar-expand { - flex-wrap: nowrap; - justify-content: flex-start; -} -.navbar-expand .navbar-nav { - flex-direction: row; -} -.navbar-expand .navbar-nav .dropdown-menu { - position: absolute; -} -.navbar-expand .navbar-nav .nav-link { - padding-right: var(--bs-navbar-nav-link-padding-x); - padding-left: var(--bs-navbar-nav-link-padding-x); -} -.navbar-expand .navbar-nav-scroll { - overflow: visible; -} -.navbar-expand .navbar-collapse { - display: flex !important; - flex-basis: auto; -} -.navbar-expand .navbar-toggler { - display: none; -} -.navbar-expand .offcanvas { - position: static; - z-index: auto; - flex-grow: 1; - width: auto !important; - height: auto !important; - visibility: visible !important; - background-color: transparent !important; - border: 0 !important; - transform: none !important; - transition: none; -} -.navbar-expand .offcanvas .offcanvas-header { - display: none; -} -.navbar-expand .offcanvas .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; -} - -.navbar-dark, -.navbar[data-bs-theme=dark] { - --bs-navbar-color: rgba(255, 255, 255, 0.55); - --bs-navbar-hover-color: #fff; - --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); - --bs-navbar-active-color: #fff; - --bs-navbar-brand-color: #fff; - --bs-navbar-brand-hover-color: #fff; - --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); - --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); -} - -[data-bs-theme=dark] .navbar-toggler-icon { - --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); -} - -.card { - --bs-card-spacer-y: 1rem; - --bs-card-spacer-x: 1rem; - --bs-card-title-spacer-y: 0.5rem; - --bs-card-title-color: ; - --bs-card-subtitle-color: ; - --bs-card-border-width: var(--bs-border-width); - --bs-card-border-color: var(--bs-border-color-translucent); - --bs-card-border-radius: var(--bs-border-radius); - --bs-card-box-shadow: ; - --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); - --bs-card-cap-padding-y: 0.5rem; - --bs-card-cap-padding-x: 1rem; - --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); - --bs-card-cap-color: ; - --bs-card-height: ; - --bs-card-color: ; - --bs-card-bg: #282828; - --bs-card-img-overlay-padding: 1rem; - --bs-card-group-margin: 0.75rem; - position: relative; - display: flex; - flex-direction: column; - min-width: 0; - height: var(--bs-card-height); - color: var(--bs-body-color); - word-wrap: break-word; - background-color: var(--bs-card-bg); - background-clip: border-box; - border: var(--bs-card-border-width) solid var(--bs-card-border-color); - border-radius: var(--bs-card-border-radius); -} -.card > hr { - margin-right: 0; - margin-left: 0; -} -.card > .list-group { - border-top: inherit; - border-bottom: inherit; -} -.card > .list-group:first-child { - border-top-width: 0; - border-top-left-radius: var(--bs-card-inner-border-radius); - border-top-right-radius: var(--bs-card-inner-border-radius); -} -.card > .list-group:last-child { - border-bottom-width: 0; - border-bottom-right-radius: var(--bs-card-inner-border-radius); - border-bottom-left-radius: var(--bs-card-inner-border-radius); -} -.card > .card-header + .list-group, -.card > .list-group + .card-footer { - border-top: 0; -} - -.card-body { - flex: 1 1 auto; - padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); - color: var(--bs-card-color); -} - -.card-title { - margin-bottom: var(--bs-card-title-spacer-y); - color: var(--bs-card-title-color); -} - -.card-subtitle { - margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); - margin-bottom: 0; - color: var(--bs-card-subtitle-color); -} - -.card-text:last-child { - margin-bottom: 0; -} - -.card-link + .card-link { - margin-left: var(--bs-card-spacer-x); -} - -.card-header { - padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); - margin-bottom: 0; - color: var(--bs-card-cap-color); - background-color: var(--bs-card-cap-bg); - border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color); -} -.card-header:first-child { - border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0; -} - -.card-footer { - padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); - color: var(--bs-card-cap-color); - background-color: var(--bs-card-cap-bg); - border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); -} -.card-footer:last-child { - border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius); -} - -.card-header-tabs { - margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); - margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); - margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); - border-bottom: 0; -} -.card-header-tabs .nav-link.active { - background-color: var(--bs-card-bg); - border-bottom-color: var(--bs-card-bg); -} - -.card-header-pills { - margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); - margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); -} - -.card-img-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - padding: var(--bs-card-img-overlay-padding); - border-radius: var(--bs-card-inner-border-radius); -} - -.card-img, -.card-img-top, -.card-img-bottom { - width: 100%; -} - -.card-img, -.card-img-top { - border-top-left-radius: var(--bs-card-inner-border-radius); - border-top-right-radius: var(--bs-card-inner-border-radius); -} - -.card-img, -.card-img-bottom { - border-bottom-right-radius: var(--bs-card-inner-border-radius); - border-bottom-left-radius: var(--bs-card-inner-border-radius); -} - -.card-group > .card { - margin-bottom: var(--bs-card-group-margin); -} -@media (min-width: 576px) { - .card-group { - display: flex; - flex-flow: row wrap; - } - .card-group > .card { - flex: 1 0 0%; - margin-bottom: 0; - } - .card-group > .card + .card { - margin-left: 0; - border-left: 0; - } - .card-group > .card:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .card-group > .card:not(:last-child) .card-img-top, - .card-group > .card:not(:last-child) .card-header { - border-top-right-radius: 0; - } - .card-group > .card:not(:last-child) .card-img-bottom, - .card-group > .card:not(:last-child) .card-footer { - border-bottom-right-radius: 0; - } - .card-group > .card:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - .card-group > .card:not(:first-child) .card-img-top, - .card-group > .card:not(:first-child) .card-header { - border-top-left-radius: 0; - } - .card-group > .card:not(:first-child) .card-img-bottom, - .card-group > .card:not(:first-child) .card-footer { - border-bottom-left-radius: 0; - } -} - -.accordion { - --bs-accordion-color: var(--bs-body-color); - --bs-accordion-bg: var(--bs-body-bg); - --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; - --bs-accordion-border-color: var(--bs-border-color); - --bs-accordion-border-width: var(--bs-border-width); - --bs-accordion-border-radius: var(--bs-border-radius); - --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); - --bs-accordion-btn-padding-x: 1.25rem; - --bs-accordion-btn-padding-y: 1rem; - --bs-accordion-btn-color: var(--bs-body-color); - --bs-accordion-btn-bg: var(--bs-accordion-bg); - --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23adafae' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); - --bs-accordion-btn-icon-width: 1.25rem; - --bs-accordion-btn-icon-transform: rotate(-180deg); - --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; - --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23114056' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); - --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); - --bs-accordion-body-padding-x: 1.25rem; - --bs-accordion-body-padding-y: 1rem; - --bs-accordion-active-color: var(--bs-primary-text-emphasis); - --bs-accordion-active-bg: var(--bs-primary-bg-subtle); -} - -.accordion-button { - position: relative; - display: flex; - align-items: center; - width: 100%; - padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); - font-size: 1rem; - color: var(--bs-accordion-btn-color); - text-align: left; - background-color: var(--bs-accordion-btn-bg); - border: 0; - border-radius: 0; - overflow-anchor: none; - transition: var(--bs-accordion-transition); -} -@media (prefers-reduced-motion: reduce) { - .accordion-button { - transition: none; - } -} -.accordion-button:not(.collapsed) { - color: var(--bs-accordion-active-color); - background-color: var(--bs-accordion-active-bg); - box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color); -} -.accordion-button:not(.collapsed)::after { - background-image: var(--bs-accordion-btn-active-icon); - transform: var(--bs-accordion-btn-icon-transform); -} -.accordion-button::after { - flex-shrink: 0; - width: var(--bs-accordion-btn-icon-width); - height: var(--bs-accordion-btn-icon-width); - margin-left: auto; - content: ""; - background-image: var(--bs-accordion-btn-icon); - background-repeat: no-repeat; - background-size: var(--bs-accordion-btn-icon-width); - transition: var(--bs-accordion-btn-icon-transition); -} -@media (prefers-reduced-motion: reduce) { - .accordion-button::after { - transition: none; - } -} -.accordion-button:hover { - z-index: 2; -} -.accordion-button:focus { - z-index: 3; - outline: 0; - box-shadow: var(--bs-accordion-btn-focus-box-shadow); -} - -.accordion-header { - margin-bottom: 0; -} - -.accordion-item { - color: var(--bs-accordion-color); - background-color: var(--bs-accordion-bg); - border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); -} -.accordion-item:first-of-type { - border-top-left-radius: var(--bs-accordion-border-radius); - border-top-right-radius: var(--bs-accordion-border-radius); -} -.accordion-item:first-of-type > .accordion-header .accordion-button { - border-top-left-radius: var(--bs-accordion-inner-border-radius); - border-top-right-radius: var(--bs-accordion-inner-border-radius); -} -.accordion-item:not(:first-of-type) { - border-top: 0; -} -.accordion-item:last-of-type { - border-bottom-right-radius: var(--bs-accordion-border-radius); - border-bottom-left-radius: var(--bs-accordion-border-radius); -} -.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed { - border-bottom-right-radius: var(--bs-accordion-inner-border-radius); - border-bottom-left-radius: var(--bs-accordion-inner-border-radius); -} -.accordion-item:last-of-type > .accordion-collapse { - border-bottom-right-radius: var(--bs-accordion-border-radius); - border-bottom-left-radius: var(--bs-accordion-border-radius); -} - -.accordion-body { - padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x); -} - -.accordion-flush > .accordion-item { - border-right: 0; - border-left: 0; - border-radius: 0; -} -.accordion-flush > .accordion-item:first-child { - border-top: 0; -} -.accordion-flush > .accordion-item:last-child { - border-bottom: 0; -} -.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed { - border-radius: 0; -} -.accordion-flush > .accordion-item > .accordion-collapse { - border-radius: 0; -} - -[data-bs-theme=dark] .accordion-button::after { - --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237fc5e6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); - --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237fc5e6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); -} - -.breadcrumb { - --bs-breadcrumb-padding-x: 0.75rem; - --bs-breadcrumb-padding-y: 0.375rem; - --bs-breadcrumb-margin-bottom: 1rem; - --bs-breadcrumb-bg: #282828; - --bs-breadcrumb-border-radius: 0.25rem; - --bs-breadcrumb-divider-color: var(--bs-secondary-color); - --bs-breadcrumb-item-padding-x: 0.5rem; - --bs-breadcrumb-item-active-color: var(--bs-secondary-color); - display: flex; - flex-wrap: wrap; - padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); - margin-bottom: var(--bs-breadcrumb-margin-bottom); - font-size: var(--bs-breadcrumb-font-size); - list-style: none; - background-color: var(--bs-breadcrumb-bg); - border-radius: var(--bs-breadcrumb-border-radius); -} - -.breadcrumb-item + .breadcrumb-item { - padding-left: var(--bs-breadcrumb-item-padding-x); -} -.breadcrumb-item + .breadcrumb-item::before { - float: left; - padding-right: var(--bs-breadcrumb-item-padding-x); - color: var(--bs-breadcrumb-divider-color); - content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; -} -.breadcrumb-item.active { - color: var(--bs-breadcrumb-item-active-color); -} - -.pagination { - --bs-pagination-padding-x: 0.75rem; - --bs-pagination-padding-y: 0.375rem; - --bs-pagination-font-size: 1rem; - --bs-pagination-color: #fff; - --bs-pagination-bg: #282828; - --bs-pagination-border-width: var(--bs-border-width); - --bs-pagination-border-color: transparent; - --bs-pagination-border-radius: var(--bs-border-radius); - --bs-pagination-hover-color: #fff; - --bs-pagination-hover-bg: #2a9fd6; - --bs-pagination-hover-border-color: transparent; - --bs-pagination-focus-color: var(--bs-link-hover-color); - --bs-pagination-focus-bg: var(--bs-secondary-bg); - --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); - --bs-pagination-active-color: #fff; - --bs-pagination-active-bg: #2a9fd6; - --bs-pagination-active-border-color: #2a9fd6; - --bs-pagination-disabled-color: var(--bs-secondary-color); - --bs-pagination-disabled-bg: #282828; - --bs-pagination-disabled-border-color: transparent; - display: flex; - padding-left: 0; - list-style: none; -} - -.page-link { - position: relative; - display: block; - padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); - font-size: var(--bs-pagination-font-size); - color: var(--bs-pagination-color); - text-decoration: none; - background-color: var(--bs-pagination-bg); - border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color); - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .page-link { - transition: none; - } -} -.page-link:hover { - z-index: 2; - color: var(--bs-pagination-hover-color); - background-color: var(--bs-pagination-hover-bg); - border-color: var(--bs-pagination-hover-border-color); -} -.page-link:focus { - z-index: 3; - color: var(--bs-pagination-focus-color); - background-color: var(--bs-pagination-focus-bg); - outline: 0; - box-shadow: var(--bs-pagination-focus-box-shadow); -} -.page-link.active, .active > .page-link { - z-index: 3; - color: var(--bs-pagination-active-color); - background-color: var(--bs-pagination-active-bg); - border-color: var(--bs-pagination-active-border-color); -} -.page-link.disabled, .disabled > .page-link { - color: var(--bs-pagination-disabled-color); - pointer-events: none; - background-color: var(--bs-pagination-disabled-bg); - border-color: var(--bs-pagination-disabled-border-color); -} - -.page-item:not(:first-child) .page-link { - margin-left: calc(var(--bs-border-width) * -1); -} -.page-item:first-child .page-link { - border-top-left-radius: var(--bs-pagination-border-radius); - border-bottom-left-radius: var(--bs-pagination-border-radius); -} -.page-item:last-child .page-link { - border-top-right-radius: var(--bs-pagination-border-radius); - border-bottom-right-radius: var(--bs-pagination-border-radius); -} - -.pagination-lg { - --bs-pagination-padding-x: 1.5rem; - --bs-pagination-padding-y: 0.75rem; - --bs-pagination-font-size: 1.25rem; - --bs-pagination-border-radius: var(--bs-border-radius-lg); -} - -.pagination-sm { - --bs-pagination-padding-x: 0.5rem; - --bs-pagination-padding-y: 0.25rem; - --bs-pagination-font-size: 0.875rem; - --bs-pagination-border-radius: var(--bs-border-radius-sm); -} - -.badge { - --bs-badge-padding-x: 0.65em; - --bs-badge-padding-y: 0.35em; - --bs-badge-font-size: 0.75em; - --bs-badge-font-weight: 700; - --bs-badge-color: #fff; - --bs-badge-border-radius: var(--bs-border-radius); - display: inline-block; - padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); - font-size: var(--bs-badge-font-size); - font-weight: var(--bs-badge-font-weight); - line-height: 1; - color: var(--bs-badge-color); - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: var(--bs-badge-border-radius); -} -.badge:empty { - display: none; -} - -.btn .badge { - position: relative; - top: -1px; -} - -.alert { - --bs-alert-bg: transparent; - --bs-alert-padding-x: 1rem; - --bs-alert-padding-y: 1rem; - --bs-alert-margin-bottom: 1rem; - --bs-alert-color: inherit; - --bs-alert-border-color: transparent; - --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color); - --bs-alert-border-radius: var(--bs-border-radius); - --bs-alert-link-color: inherit; - position: relative; - padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); - margin-bottom: var(--bs-alert-margin-bottom); - color: var(--bs-alert-color); - background-color: var(--bs-alert-bg); - border: var(--bs-alert-border); - border-radius: var(--bs-alert-border-radius); -} - -.alert-heading { - color: inherit; -} - -.alert-link { - font-weight: 700; - color: var(--bs-alert-link-color); -} - -.alert-dismissible { - padding-right: 3rem; -} -.alert-dismissible .btn-close { - position: absolute; - top: 0; - right: 0; - z-index: 2; - padding: 1.25rem 1rem; -} - -.alert-primary { - --bs-alert-color: var(--bs-primary-text-emphasis); - --bs-alert-bg: var(--bs-primary-bg-subtle); - --bs-alert-border-color: var(--bs-primary-border-subtle); - --bs-alert-link-color: var(--bs-primary-text-emphasis); -} - -.alert-secondary { - --bs-alert-color: var(--bs-secondary-text-emphasis); - --bs-alert-bg: var(--bs-secondary-bg-subtle); - --bs-alert-border-color: var(--bs-secondary-border-subtle); - --bs-alert-link-color: var(--bs-secondary-text-emphasis); -} - -.alert-success { - --bs-alert-color: var(--bs-success-text-emphasis); - --bs-alert-bg: var(--bs-success-bg-subtle); - --bs-alert-border-color: var(--bs-success-border-subtle); - --bs-alert-link-color: var(--bs-success-text-emphasis); -} - -.alert-info { - --bs-alert-color: var(--bs-info-text-emphasis); - --bs-alert-bg: var(--bs-info-bg-subtle); - --bs-alert-border-color: var(--bs-info-border-subtle); - --bs-alert-link-color: var(--bs-info-text-emphasis); -} - -.alert-warning { - --bs-alert-color: var(--bs-warning-text-emphasis); - --bs-alert-bg: var(--bs-warning-bg-subtle); - --bs-alert-border-color: var(--bs-warning-border-subtle); - --bs-alert-link-color: var(--bs-warning-text-emphasis); -} - -.alert-danger { - --bs-alert-color: var(--bs-danger-text-emphasis); - --bs-alert-bg: var(--bs-danger-bg-subtle); - --bs-alert-border-color: var(--bs-danger-border-subtle); - --bs-alert-link-color: var(--bs-danger-text-emphasis); -} - -.alert-light { - --bs-alert-color: var(--bs-light-text-emphasis); - --bs-alert-bg: var(--bs-light-bg-subtle); - --bs-alert-border-color: var(--bs-light-border-subtle); - --bs-alert-link-color: var(--bs-light-text-emphasis); -} - -.alert-dark { - --bs-alert-color: var(--bs-dark-text-emphasis); - --bs-alert-bg: var(--bs-dark-bg-subtle); - --bs-alert-border-color: var(--bs-dark-border-subtle); - --bs-alert-link-color: var(--bs-dark-text-emphasis); -} - -@keyframes progress-bar-stripes { - 0% { - background-position-x: 1rem; - } -} -.progress, -.progress-stacked { - --bs-progress-height: 1rem; - --bs-progress-font-size: 0.75rem; - --bs-progress-bg: #282828; - --bs-progress-border-radius: var(--bs-border-radius); - --bs-progress-box-shadow: var(--bs-box-shadow-inset); - --bs-progress-bar-color: #fff; - --bs-progress-bar-bg: #2a9fd6; - --bs-progress-bar-transition: width 0.6s ease; - display: flex; - height: var(--bs-progress-height); - overflow: hidden; - font-size: var(--bs-progress-font-size); - background-color: var(--bs-progress-bg); - border-radius: var(--bs-progress-border-radius); -} - -.progress-bar { - display: flex; - flex-direction: column; - justify-content: center; - overflow: hidden; - color: var(--bs-progress-bar-color); - text-align: center; - white-space: nowrap; - background-color: var(--bs-progress-bar-bg); - transition: var(--bs-progress-bar-transition); -} -@media (prefers-reduced-motion: reduce) { - .progress-bar { - transition: none; - } -} - -.progress-bar-striped { - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-size: var(--bs-progress-height) var(--bs-progress-height); -} - -.progress-stacked > .progress { - overflow: visible; -} - -.progress-stacked > .progress > .progress-bar { - width: 100%; -} - -.progress-bar-animated { - animation: 1s linear infinite progress-bar-stripes; -} -@media (prefers-reduced-motion: reduce) { - .progress-bar-animated { - animation: none; - } -} - -.list-group { - --bs-list-group-color: #fff; - --bs-list-group-bg: #222; - --bs-list-group-border-color: #282828; - --bs-list-group-border-width: var(--bs-border-width); - --bs-list-group-border-radius: var(--bs-border-radius); - --bs-list-group-item-padding-x: 1rem; - --bs-list-group-item-padding-y: 0.5rem; - --bs-list-group-action-color: #fff; - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: #2a9fd6; - --bs-list-group-action-active-color: var(--bs-body-color); - --bs-list-group-action-active-bg: #2a9fd6; - --bs-list-group-disabled-color: var(--bs-secondary-color); - --bs-list-group-disabled-bg: #282828; - --bs-list-group-active-color: #fff; - --bs-list-group-active-bg: #2a9fd6; - --bs-list-group-active-border-color: #2a9fd6; - display: flex; - flex-direction: column; - padding-left: 0; - margin-bottom: 0; - border-radius: var(--bs-list-group-border-radius); -} - -.list-group-numbered { - list-style-type: none; - counter-reset: section; -} -.list-group-numbered > .list-group-item::before { - content: counters(section, ".") ". "; - counter-increment: section; -} - -.list-group-item-action { - width: 100%; - color: var(--bs-list-group-action-color); - text-align: inherit; -} -.list-group-item-action:hover, .list-group-item-action:focus { - z-index: 1; - color: var(--bs-list-group-action-hover-color); - text-decoration: none; - background-color: var(--bs-list-group-action-hover-bg); -} -.list-group-item-action:active { - color: var(--bs-list-group-action-active-color); - background-color: var(--bs-list-group-action-active-bg); -} - -.list-group-item { - position: relative; - display: block; - padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x); - color: var(--bs-list-group-color); - text-decoration: none; - background-color: var(--bs-list-group-bg); - border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color); -} -.list-group-item:first-child { - border-top-left-radius: inherit; - border-top-right-radius: inherit; -} -.list-group-item:last-child { - border-bottom-right-radius: inherit; - border-bottom-left-radius: inherit; -} -.list-group-item.disabled, .list-group-item:disabled { - color: var(--bs-list-group-disabled-color); - pointer-events: none; - background-color: var(--bs-list-group-disabled-bg); -} -.list-group-item.active { - z-index: 2; - color: var(--bs-list-group-active-color); - background-color: var(--bs-list-group-active-bg); - border-color: var(--bs-list-group-active-border-color); -} -.list-group-item + .list-group-item { - border-top-width: 0; -} -.list-group-item + .list-group-item.active { - margin-top: calc(-1 * var(--bs-list-group-border-width)); - border-top-width: var(--bs-list-group-border-width); -} - -.list-group-horizontal { - flex-direction: row; -} -.list-group-horizontal > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; -} -.list-group-horizontal > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; -} -.list-group-horizontal > .list-group-item.active { - margin-top: 0; -} -.list-group-horizontal > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; -} -.list-group-horizontal > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); -} - -@media (min-width: 576px) { - .list-group-horizontal-sm { - flex-direction: row; - } - .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; - } - .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; - } - .list-group-horizontal-sm > .list-group-item.active { - margin-top: 0; - } - .list-group-horizontal-sm > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; - } - .list-group-horizontal-sm > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); - } -} -@media (min-width: 768px) { - .list-group-horizontal-md { - flex-direction: row; - } - .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; - } - .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; - } - .list-group-horizontal-md > .list-group-item.active { - margin-top: 0; - } - .list-group-horizontal-md > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; - } - .list-group-horizontal-md > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); - } -} -@media (min-width: 992px) { - .list-group-horizontal-lg { - flex-direction: row; - } - .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; - } - .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; - } - .list-group-horizontal-lg > .list-group-item.active { - margin-top: 0; - } - .list-group-horizontal-lg > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; - } - .list-group-horizontal-lg > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); - } -} -@media (min-width: 1200px) { - .list-group-horizontal-xl { - flex-direction: row; - } - .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; - } - .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; - } - .list-group-horizontal-xl > .list-group-item.active { - margin-top: 0; - } - .list-group-horizontal-xl > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; - } - .list-group-horizontal-xl > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); - } -} -@media (min-width: 1400px) { - .list-group-horizontal-xxl { - flex-direction: row; - } - .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) { - border-bottom-left-radius: var(--bs-list-group-border-radius); - border-top-right-radius: 0; - } - .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) { - border-top-right-radius: var(--bs-list-group-border-radius); - border-bottom-left-radius: 0; - } - .list-group-horizontal-xxl > .list-group-item.active { - margin-top: 0; - } - .list-group-horizontal-xxl > .list-group-item + .list-group-item { - border-top-width: var(--bs-list-group-border-width); - border-left-width: 0; - } - .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { - margin-left: calc(-1 * var(--bs-list-group-border-width)); - border-left-width: var(--bs-list-group-border-width); - } -} -.list-group-flush { - border-radius: 0; -} -.list-group-flush > .list-group-item { - border-width: 0 0 var(--bs-list-group-border-width); -} -.list-group-flush > .list-group-item:last-child { - border-bottom-width: 0; -} - -.list-group-item-primary { - --bs-list-group-color: var(--bs-primary-text-emphasis); - --bs-list-group-bg: var(--bs-primary-bg-subtle); - --bs-list-group-border-color: var(--bs-primary-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-primary-border-subtle); - --bs-list-group-active-color: var(--bs-primary-bg-subtle); - --bs-list-group-active-bg: var(--bs-primary-text-emphasis); - --bs-list-group-active-border-color: var(--bs-primary-text-emphasis); -} - -.list-group-item-secondary { - --bs-list-group-color: var(--bs-secondary-text-emphasis); - --bs-list-group-bg: var(--bs-secondary-bg-subtle); - --bs-list-group-border-color: var(--bs-secondary-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle); - --bs-list-group-active-color: var(--bs-secondary-bg-subtle); - --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); - --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis); -} - -.list-group-item-success { - --bs-list-group-color: var(--bs-success-text-emphasis); - --bs-list-group-bg: var(--bs-success-bg-subtle); - --bs-list-group-border-color: var(--bs-success-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-success-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-success-border-subtle); - --bs-list-group-active-color: var(--bs-success-bg-subtle); - --bs-list-group-active-bg: var(--bs-success-text-emphasis); - --bs-list-group-active-border-color: var(--bs-success-text-emphasis); -} - -.list-group-item-info { - --bs-list-group-color: var(--bs-info-text-emphasis); - --bs-list-group-bg: var(--bs-info-bg-subtle); - --bs-list-group-border-color: var(--bs-info-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-info-border-subtle); - --bs-list-group-active-color: var(--bs-info-bg-subtle); - --bs-list-group-active-bg: var(--bs-info-text-emphasis); - --bs-list-group-active-border-color: var(--bs-info-text-emphasis); -} - -.list-group-item-warning { - --bs-list-group-color: var(--bs-warning-text-emphasis); - --bs-list-group-bg: var(--bs-warning-bg-subtle); - --bs-list-group-border-color: var(--bs-warning-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-warning-border-subtle); - --bs-list-group-active-color: var(--bs-warning-bg-subtle); - --bs-list-group-active-bg: var(--bs-warning-text-emphasis); - --bs-list-group-active-border-color: var(--bs-warning-text-emphasis); -} - -.list-group-item-danger { - --bs-list-group-color: var(--bs-danger-text-emphasis); - --bs-list-group-bg: var(--bs-danger-bg-subtle); - --bs-list-group-border-color: var(--bs-danger-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-danger-border-subtle); - --bs-list-group-active-color: var(--bs-danger-bg-subtle); - --bs-list-group-active-bg: var(--bs-danger-text-emphasis); - --bs-list-group-active-border-color: var(--bs-danger-text-emphasis); -} - -.list-group-item-light { - --bs-list-group-color: var(--bs-light-text-emphasis); - --bs-list-group-bg: var(--bs-light-bg-subtle); - --bs-list-group-border-color: var(--bs-light-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-light-border-subtle); - --bs-list-group-active-color: var(--bs-light-bg-subtle); - --bs-list-group-active-bg: var(--bs-light-text-emphasis); - --bs-list-group-active-border-color: var(--bs-light-text-emphasis); -} - -.list-group-item-dark { - --bs-list-group-color: var(--bs-dark-text-emphasis); - --bs-list-group-bg: var(--bs-dark-bg-subtle); - --bs-list-group-border-color: var(--bs-dark-border-subtle); - --bs-list-group-action-hover-color: var(--bs-emphasis-color); - --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); - --bs-list-group-action-active-color: var(--bs-emphasis-color); - --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); - --bs-list-group-active-color: var(--bs-dark-bg-subtle); - --bs-list-group-active-bg: var(--bs-dark-text-emphasis); - --bs-list-group-active-border-color: var(--bs-dark-text-emphasis); -} - -.btn-close { - --bs-btn-close-color: #fff; - --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); - --bs-btn-close-opacity: 0.6; - --bs-btn-close-hover-opacity: 1; - --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(42, 159, 214, 0.25); - --bs-btn-close-focus-opacity: 1; - --bs-btn-close-disabled-opacity: 0.25; - --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%); - box-sizing: content-box; - width: 1em; - height: 1em; - padding: 0.25em 0.25em; - color: var(--bs-btn-close-color); - background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; - border: 0; - border-radius: 0.375rem; - opacity: var(--bs-btn-close-opacity); -} -.btn-close:hover { - color: var(--bs-btn-close-color); - text-decoration: none; - opacity: var(--bs-btn-close-hover-opacity); -} -.btn-close:focus { - outline: 0; - box-shadow: var(--bs-btn-close-focus-shadow); - opacity: var(--bs-btn-close-focus-opacity); -} -.btn-close:disabled, .btn-close.disabled { - pointer-events: none; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - opacity: var(--bs-btn-close-disabled-opacity); -} - -.btn-close-white { - filter: var(--bs-btn-close-white-filter); -} - -[data-bs-theme=dark] .btn-close { - filter: var(--bs-btn-close-white-filter); -} - -.toast { - --bs-toast-zindex: 1090; - --bs-toast-padding-x: 0.75rem; - --bs-toast-padding-y: 0.5rem; - --bs-toast-spacing: 1.5rem; - --bs-toast-max-width: 350px; - --bs-toast-font-size: 0.875rem; - --bs-toast-color: #fff; - --bs-toast-bg: #222; - --bs-toast-border-width: var(--bs-border-width); - --bs-toast-border-color: #282828; - --bs-toast-border-radius: var(--bs-border-radius); - --bs-toast-box-shadow: var(--bs-box-shadow); - --bs-toast-header-color: #adafae; - --bs-toast-header-bg: #222; - --bs-toast-header-border-color: #282828; - width: var(--bs-toast-max-width); - max-width: 100%; - font-size: var(--bs-toast-font-size); - color: var(--bs-toast-color); - pointer-events: auto; - background-color: var(--bs-toast-bg); - background-clip: padding-box; - border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); - box-shadow: var(--bs-toast-box-shadow); - border-radius: var(--bs-toast-border-radius); -} -.toast.showing { - opacity: 0; -} -.toast:not(.show) { - display: none; -} - -.toast-container { - --bs-toast-zindex: 1090; - position: absolute; - z-index: var(--bs-toast-zindex); - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - max-width: 100%; - pointer-events: none; -} -.toast-container > :not(:last-child) { - margin-bottom: var(--bs-toast-spacing); -} - -.toast-header { - display: flex; - align-items: center; - padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); - color: var(--bs-toast-header-color); - background-color: var(--bs-toast-header-bg); - background-clip: padding-box; - border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color); - border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); - border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); -} -.toast-header .btn-close { - margin-right: calc(-0.5 * var(--bs-toast-padding-x)); - margin-left: var(--bs-toast-padding-x); -} - -.toast-body { - padding: var(--bs-toast-padding-x); - word-wrap: break-word; -} - -.modal { - --bs-modal-zindex: 1055; - --bs-modal-width: 500px; - --bs-modal-padding: 1rem; - --bs-modal-margin: 0.5rem; - --bs-modal-color: ; - --bs-modal-bg: #222; - --bs-modal-border-color: var(--bs-border-color-translucent); - --bs-modal-border-width: var(--bs-border-width); - --bs-modal-border-radius: var(--bs-border-radius-lg); - --bs-modal-box-shadow: var(--bs-box-shadow-sm); - --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width))); - --bs-modal-header-padding-x: 1rem; - --bs-modal-header-padding-y: 1rem; - --bs-modal-header-padding: 1rem 1rem; - --bs-modal-header-border-color: #282828; - --bs-modal-header-border-width: var(--bs-border-width); - --bs-modal-title-line-height: 1.5; - --bs-modal-footer-gap: 0.5rem; - --bs-modal-footer-bg: ; - --bs-modal-footer-border-color: #282828; - --bs-modal-footer-border-width: var(--bs-border-width); - position: fixed; - top: 0; - left: 0; - z-index: var(--bs-modal-zindex); - display: none; - width: 100%; - height: 100%; - overflow-x: hidden; - overflow-y: auto; - outline: 0; -} - -.modal-dialog { - position: relative; - width: auto; - margin: var(--bs-modal-margin); - pointer-events: none; -} -.modal.fade .modal-dialog { - transition: transform 0.3s ease-out; - transform: translate(0, -50px); -} -@media (prefers-reduced-motion: reduce) { - .modal.fade .modal-dialog { - transition: none; - } -} -.modal.show .modal-dialog { - transform: none; -} -.modal.modal-static .modal-dialog { - transform: scale(1.02); -} - -.modal-dialog-scrollable { - height: calc(100% - var(--bs-modal-margin) * 2); -} -.modal-dialog-scrollable .modal-content { - max-height: 100%; - overflow: hidden; -} -.modal-dialog-scrollable .modal-body { - overflow-y: auto; -} - -.modal-dialog-centered { - display: flex; - align-items: center; - min-height: calc(100% - var(--bs-modal-margin) * 2); -} - -.modal-content { - position: relative; - display: flex; - flex-direction: column; - width: 100%; - color: var(--bs-modal-color); - pointer-events: auto; - background-color: var(--bs-modal-bg); - background-clip: padding-box; - border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); - border-radius: var(--bs-modal-border-radius); - outline: 0; -} - -.modal-backdrop { - --bs-backdrop-zindex: 1050; - --bs-backdrop-bg: #000; - --bs-backdrop-opacity: 0.5; - position: fixed; - top: 0; - left: 0; - z-index: var(--bs-backdrop-zindex); - width: 100vw; - height: 100vh; - background-color: var(--bs-backdrop-bg); -} -.modal-backdrop.fade { - opacity: 0; -} -.modal-backdrop.show { - opacity: var(--bs-backdrop-opacity); -} - -.modal-header { - display: flex; - flex-shrink: 0; - align-items: center; - padding: var(--bs-modal-header-padding); - border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color); - border-top-left-radius: var(--bs-modal-inner-border-radius); - border-top-right-radius: var(--bs-modal-inner-border-radius); -} -.modal-header .btn-close { - padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); - margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto; -} - -.modal-title { - margin-bottom: 0; - line-height: var(--bs-modal-title-line-height); -} - -.modal-body { - position: relative; - flex: 1 1 auto; - padding: var(--bs-modal-padding); -} - -.modal-footer { - display: flex; - flex-shrink: 0; - flex-wrap: wrap; - align-items: center; - justify-content: flex-end; - padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5); - background-color: var(--bs-modal-footer-bg); - border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color); - border-bottom-right-radius: var(--bs-modal-inner-border-radius); - border-bottom-left-radius: var(--bs-modal-inner-border-radius); -} -.modal-footer > * { - margin: calc(var(--bs-modal-footer-gap) * 0.5); -} - -@media (min-width: 576px) { - .modal { - --bs-modal-margin: 1.75rem; - --bs-modal-box-shadow: var(--bs-box-shadow); - } - .modal-dialog { - max-width: var(--bs-modal-width); - margin-right: auto; - margin-left: auto; - } - .modal-sm { - --bs-modal-width: 300px; - } -} -@media (min-width: 992px) { - .modal-lg, - .modal-xl { - --bs-modal-width: 800px; - } -} -@media (min-width: 1200px) { - .modal-xl { - --bs-modal-width: 1140px; - } -} -.modal-fullscreen { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; -} -.modal-fullscreen .modal-content { - height: 100%; - border: 0; - border-radius: 0; -} -.modal-fullscreen .modal-header, -.modal-fullscreen .modal-footer { - border-radius: 0; -} -.modal-fullscreen .modal-body { - overflow-y: auto; -} - -@media (max-width: 575.98px) { - .modal-fullscreen-sm-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - .modal-fullscreen-sm-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - .modal-fullscreen-sm-down .modal-header, - .modal-fullscreen-sm-down .modal-footer { - border-radius: 0; - } - .modal-fullscreen-sm-down .modal-body { - overflow-y: auto; - } -} -@media (max-width: 767.98px) { - .modal-fullscreen-md-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - .modal-fullscreen-md-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - .modal-fullscreen-md-down .modal-header, - .modal-fullscreen-md-down .modal-footer { - border-radius: 0; - } - .modal-fullscreen-md-down .modal-body { - overflow-y: auto; - } -} -@media (max-width: 991.98px) { - .modal-fullscreen-lg-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - .modal-fullscreen-lg-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - .modal-fullscreen-lg-down .modal-header, - .modal-fullscreen-lg-down .modal-footer { - border-radius: 0; - } - .modal-fullscreen-lg-down .modal-body { - overflow-y: auto; - } -} -@media (max-width: 1199.98px) { - .modal-fullscreen-xl-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - .modal-fullscreen-xl-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - .modal-fullscreen-xl-down .modal-header, - .modal-fullscreen-xl-down .modal-footer { - border-radius: 0; - } - .modal-fullscreen-xl-down .modal-body { - overflow-y: auto; - } -} -@media (max-width: 1399.98px) { - .modal-fullscreen-xxl-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - .modal-fullscreen-xxl-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - .modal-fullscreen-xxl-down .modal-header, - .modal-fullscreen-xxl-down .modal-footer { - border-radius: 0; - } - .modal-fullscreen-xxl-down .modal-body { - overflow-y: auto; - } -} -.tooltip { - --bs-tooltip-zindex: 1080; - --bs-tooltip-max-width: 200px; - --bs-tooltip-padding-x: 0.5rem; - --bs-tooltip-padding-y: 0.25rem; - --bs-tooltip-margin: ; - --bs-tooltip-font-size: 0.875rem; - --bs-tooltip-color: var(--bs-body-bg); - --bs-tooltip-bg: var(--bs-emphasis-color); - --bs-tooltip-border-radius: var(--bs-border-radius); - --bs-tooltip-opacity: 1; - --bs-tooltip-arrow-width: 0.8rem; - --bs-tooltip-arrow-height: 0.4rem; - z-index: var(--bs-tooltip-zindex); - display: block; - margin: var(--bs-tooltip-margin); - font-family: var(--bs-font-sans-serif); - font-style: normal; - font-weight: 400; - line-height: 1.5; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - white-space: normal; - word-spacing: normal; - line-break: auto; - font-size: var(--bs-tooltip-font-size); - word-wrap: break-word; - opacity: 0; -} -.tooltip.show { - opacity: var(--bs-tooltip-opacity); -} -.tooltip .tooltip-arrow { - display: block; - width: var(--bs-tooltip-arrow-width); - height: var(--bs-tooltip-arrow-height); -} -.tooltip .tooltip-arrow::before { - position: absolute; - content: ""; - border-color: transparent; - border-style: solid; -} - -.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { - bottom: calc(-1 * var(--bs-tooltip-arrow-height)); -} -.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { - top: -1px; - border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; - border-top-color: var(--bs-tooltip-bg); -} - -/* rtl:begin:ignore */ -.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { - left: calc(-1 * var(--bs-tooltip-arrow-height)); - width: var(--bs-tooltip-arrow-height); - height: var(--bs-tooltip-arrow-width); -} -.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { - right: -1px; - border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; - border-right-color: var(--bs-tooltip-bg); -} - -/* rtl:end:ignore */ -.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { - top: calc(-1 * var(--bs-tooltip-arrow-height)); -} -.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { - bottom: -1px; - border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); - border-bottom-color: var(--bs-tooltip-bg); -} - -/* rtl:begin:ignore */ -.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { - right: calc(-1 * var(--bs-tooltip-arrow-height)); - width: var(--bs-tooltip-arrow-height); - height: var(--bs-tooltip-arrow-width); -} -.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { - left: -1px; - border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); - border-left-color: var(--bs-tooltip-bg); -} - -/* rtl:end:ignore */ -.tooltip-inner { - max-width: var(--bs-tooltip-max-width); - padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); - color: var(--bs-tooltip-color); - text-align: center; - background-color: var(--bs-tooltip-bg); - border-radius: var(--bs-tooltip-border-radius); -} - -.popover { - --bs-popover-zindex: 1070; - --bs-popover-max-width: 276px; - --bs-popover-font-size: 0.875rem; - --bs-popover-bg: #282828; - --bs-popover-border-width: var(--bs-border-width); - --bs-popover-border-color: var(--bs-border-color-translucent); - --bs-popover-border-radius: var(--bs-border-radius-lg); - --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width)); - --bs-popover-box-shadow: var(--bs-box-shadow); - --bs-popover-header-padding-x: 1rem; - --bs-popover-header-padding-y: 0.5rem; - --bs-popover-header-font-size: 1rem; - --bs-popover-header-color: #fff; - --bs-popover-header-bg: var(--bs-secondary-bg); - --bs-popover-body-padding-x: 1rem; - --bs-popover-body-padding-y: 1rem; - --bs-popover-body-color: var(--bs-body-color); - --bs-popover-arrow-width: 1rem; - --bs-popover-arrow-height: 0.5rem; - --bs-popover-arrow-border: var(--bs-popover-border-color); - z-index: var(--bs-popover-zindex); - display: block; - max-width: var(--bs-popover-max-width); - font-family: var(--bs-font-sans-serif); - font-style: normal; - font-weight: 400; - line-height: 1.5; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - white-space: normal; - word-spacing: normal; - line-break: auto; - font-size: var(--bs-popover-font-size); - word-wrap: break-word; - background-color: var(--bs-popover-bg); - background-clip: padding-box; - border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); - border-radius: var(--bs-popover-border-radius); -} -.popover .popover-arrow { - display: block; - width: var(--bs-popover-arrow-width); - height: var(--bs-popover-arrow-height); -} -.popover .popover-arrow::before, .popover .popover-arrow::after { - position: absolute; - display: block; - content: ""; - border-color: transparent; - border-style: solid; - border-width: 0; -} - -.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow { - bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); -} -.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { - border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; -} -.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before { - bottom: 0; - border-top-color: var(--bs-popover-arrow-border); -} -.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { - bottom: var(--bs-popover-border-width); - border-top-color: var(--bs-popover-bg); -} - -/* rtl:begin:ignore */ -.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow { - left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); - width: var(--bs-popover-arrow-height); - height: var(--bs-popover-arrow-width); -} -.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { - border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; -} -.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before { - left: 0; - border-right-color: var(--bs-popover-arrow-border); -} -.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { - left: var(--bs-popover-border-width); - border-right-color: var(--bs-popover-bg); -} - -/* rtl:end:ignore */ -.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { - top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); -} -.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { - border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); -} -.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before { - top: 0; - border-bottom-color: var(--bs-popover-arrow-border); -} -.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { - top: var(--bs-popover-border-width); - border-bottom-color: var(--bs-popover-bg); -} -.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: var(--bs-popover-arrow-width); - margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); - content: ""; - border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg); -} - -/* rtl:begin:ignore */ -.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow { - right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); - width: var(--bs-popover-arrow-height); - height: var(--bs-popover-arrow-width); -} -.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { - border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); -} -.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before { - right: 0; - border-left-color: var(--bs-popover-arrow-border); -} -.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { - right: var(--bs-popover-border-width); - border-left-color: var(--bs-popover-bg); -} - -/* rtl:end:ignore */ -.popover-header { - padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); - margin-bottom: 0; - font-size: var(--bs-popover-header-font-size); - color: var(--bs-popover-header-color); - background-color: var(--bs-popover-header-bg); - border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color); - border-top-left-radius: var(--bs-popover-inner-border-radius); - border-top-right-radius: var(--bs-popover-inner-border-radius); -} -.popover-header:empty { - display: none; -} - -.popover-body { - padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); - color: var(--bs-popover-body-color); -} - -.carousel { - position: relative; -} - -.carousel.pointer-event { - touch-action: pan-y; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} -.carousel-inner::after { - display: block; - clear: both; - content: ""; -} - -.carousel-item { - position: relative; - display: none; - float: left; - width: 100%; - margin-right: -100%; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - transition: transform 0.6s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .carousel-item { - transition: none; - } -} - -.carousel-item.active, -.carousel-item-next, -.carousel-item-prev { - display: block; -} - -.carousel-item-next:not(.carousel-item-start), -.active.carousel-item-end { - transform: translateX(100%); -} - -.carousel-item-prev:not(.carousel-item-end), -.active.carousel-item-start { - transform: translateX(-100%); -} - -.carousel-fade .carousel-item { - opacity: 0; - transition-property: opacity; - transform: none; -} -.carousel-fade .carousel-item.active, -.carousel-fade .carousel-item-next.carousel-item-start, -.carousel-fade .carousel-item-prev.carousel-item-end { - z-index: 1; - opacity: 1; -} -.carousel-fade .active.carousel-item-start, -.carousel-fade .active.carousel-item-end { - z-index: 0; - opacity: 0; - transition: opacity 0s 0.6s; -} -@media (prefers-reduced-motion: reduce) { - .carousel-fade .active.carousel-item-start, - .carousel-fade .active.carousel-item-end { - transition: none; - } -} - -.carousel-control-prev, -.carousel-control-next { - position: absolute; - top: 0; - bottom: 0; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 15%; - padding: 0; - color: #fff; - text-align: center; - background: none; - border: 0; - opacity: 0.5; - transition: opacity 0.15s ease; -} -@media (prefers-reduced-motion: reduce) { - .carousel-control-prev, - .carousel-control-next { - transition: none; - } -} -.carousel-control-prev:hover, .carousel-control-prev:focus, -.carousel-control-next:hover, -.carousel-control-next:focus { - color: #fff; - text-decoration: none; - outline: 0; - opacity: 0.9; -} - -.carousel-control-prev { - left: 0; -} - -.carousel-control-next { - right: 0; -} - -.carousel-control-prev-icon, -.carousel-control-next-icon { - display: inline-block; - width: 2rem; - height: 2rem; - background-repeat: no-repeat; - background-position: 50%; - background-size: 100% 100%; -} - -.carousel-control-prev-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/; -} - -.carousel-control-next-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/; -} - -.carousel-indicators { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 2; - display: flex; - justify-content: center; - padding: 0; - margin-right: 15%; - margin-bottom: 1rem; - margin-left: 15%; -} -.carousel-indicators [data-bs-target] { - box-sizing: content-box; - flex: 0 1 auto; - width: 30px; - height: 3px; - padding: 0; - margin-right: 3px; - margin-left: 3px; - text-indent: -999px; - cursor: pointer; - background-color: #fff; - background-clip: padding-box; - border: 0; - border-top: 10px solid transparent; - border-bottom: 10px solid transparent; - opacity: 0.5; - transition: opacity 0.6s ease; -} -@media (prefers-reduced-motion: reduce) { - .carousel-indicators [data-bs-target] { - transition: none; - } -} -.carousel-indicators .active { - opacity: 1; -} - -.carousel-caption { - position: absolute; - right: 15%; - bottom: 1.25rem; - left: 15%; - padding-top: 1.25rem; - padding-bottom: 1.25rem; - color: #fff; - text-align: center; -} - -.carousel-dark .carousel-control-prev-icon, -.carousel-dark .carousel-control-next-icon { - filter: invert(1) grayscale(100); -} -.carousel-dark .carousel-indicators [data-bs-target] { - background-color: #000; -} -.carousel-dark .carousel-caption { - color: #000; -} - -[data-bs-theme=dark] .carousel .carousel-control-prev-icon, -[data-bs-theme=dark] .carousel .carousel-control-next-icon, [data-bs-theme=dark].carousel .carousel-control-prev-icon, -[data-bs-theme=dark].carousel .carousel-control-next-icon { - filter: invert(1) grayscale(100); -} -[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target], [data-bs-theme=dark].carousel .carousel-indicators [data-bs-target] { - background-color: #000; -} -[data-bs-theme=dark] .carousel .carousel-caption, [data-bs-theme=dark].carousel .carousel-caption { - color: #000; -} - -.spinner-grow, -.spinner-border { - display: inline-block; - width: var(--bs-spinner-width); - height: var(--bs-spinner-height); - vertical-align: var(--bs-spinner-vertical-align); - border-radius: 50%; - animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); -} - -@keyframes spinner-border { - to { - transform: rotate(360deg) /* rtl:ignore */; - } -} -.spinner-border { - --bs-spinner-width: 2rem; - --bs-spinner-height: 2rem; - --bs-spinner-vertical-align: -0.125em; - --bs-spinner-border-width: 0.25em; - --bs-spinner-animation-speed: 0.75s; - --bs-spinner-animation-name: spinner-border; - border: var(--bs-spinner-border-width) solid currentcolor; - border-right-color: transparent; -} - -.spinner-border-sm { - --bs-spinner-width: 1rem; - --bs-spinner-height: 1rem; - --bs-spinner-border-width: 0.2em; -} - -@keyframes spinner-grow { - 0% { - transform: scale(0); - } - 50% { - opacity: 1; - transform: none; - } -} -.spinner-grow { - --bs-spinner-width: 2rem; - --bs-spinner-height: 2rem; - --bs-spinner-vertical-align: -0.125em; - --bs-spinner-animation-speed: 0.75s; - --bs-spinner-animation-name: spinner-grow; - background-color: currentcolor; - opacity: 0; -} - -.spinner-grow-sm { - --bs-spinner-width: 1rem; - --bs-spinner-height: 1rem; -} - -@media (prefers-reduced-motion: reduce) { - .spinner-border, - .spinner-grow { - --bs-spinner-animation-speed: 1.5s; - } -} -.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm { - --bs-offcanvas-zindex: 1045; - --bs-offcanvas-width: 400px; - --bs-offcanvas-height: 30vh; - --bs-offcanvas-padding-x: 1rem; - --bs-offcanvas-padding-y: 1rem; - --bs-offcanvas-color: var(--bs-body-color); - --bs-offcanvas-bg: var(--bs-body-bg); - --bs-offcanvas-border-width: var(--bs-border-width); - --bs-offcanvas-border-color: var(--bs-border-color-translucent); - --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm); - --bs-offcanvas-transition: transform 0.3s ease-in-out; - --bs-offcanvas-title-line-height: 1.5; -} - -@media (max-width: 575.98px) { - .offcanvas-sm { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); - } -} -@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) { - .offcanvas-sm { - transition: none; - } -} -@media (max-width: 575.98px) { - .offcanvas-sm.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); - } - .offcanvas-sm.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); - } - .offcanvas-sm.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); - } - .offcanvas-sm.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); - } - .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) { - transform: none; - } - .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show { - visibility: visible; - } -} -@media (min-width: 576px) { - .offcanvas-sm { - --bs-offcanvas-height: auto; - --bs-offcanvas-border-width: 0; - background-color: transparent !important; - } - .offcanvas-sm .offcanvas-header { - display: none; - } - .offcanvas-sm .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - background-color: transparent !important; - } -} - -@media (max-width: 767.98px) { - .offcanvas-md { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); - } -} -@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) { - .offcanvas-md { - transition: none; - } -} -@media (max-width: 767.98px) { - .offcanvas-md.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); - } - .offcanvas-md.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); - } - .offcanvas-md.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); - } - .offcanvas-md.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); - } - .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) { - transform: none; - } - .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show { - visibility: visible; - } -} -@media (min-width: 768px) { - .offcanvas-md { - --bs-offcanvas-height: auto; - --bs-offcanvas-border-width: 0; - background-color: transparent !important; - } - .offcanvas-md .offcanvas-header { - display: none; - } - .offcanvas-md .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - background-color: transparent !important; - } -} - -@media (max-width: 991.98px) { - .offcanvas-lg { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); - } -} -@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) { - .offcanvas-lg { - transition: none; - } -} -@media (max-width: 991.98px) { - .offcanvas-lg.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); - } - .offcanvas-lg.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); - } - .offcanvas-lg.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); - } - .offcanvas-lg.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); - } - .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) { - transform: none; - } - .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show { - visibility: visible; - } -} -@media (min-width: 992px) { - .offcanvas-lg { - --bs-offcanvas-height: auto; - --bs-offcanvas-border-width: 0; - background-color: transparent !important; - } - .offcanvas-lg .offcanvas-header { - display: none; - } - .offcanvas-lg .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - background-color: transparent !important; - } -} - -@media (max-width: 1199.98px) { - .offcanvas-xl { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); - } -} -@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) { - .offcanvas-xl { - transition: none; - } -} -@media (max-width: 1199.98px) { - .offcanvas-xl.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); - } - .offcanvas-xl.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); - } - .offcanvas-xl.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); - } - .offcanvas-xl.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); - } - .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) { - transform: none; - } - .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show { - visibility: visible; - } -} -@media (min-width: 1200px) { - .offcanvas-xl { - --bs-offcanvas-height: auto; - --bs-offcanvas-border-width: 0; - background-color: transparent !important; - } - .offcanvas-xl .offcanvas-header { - display: none; - } - .offcanvas-xl .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - background-color: transparent !important; - } -} - -@media (max-width: 1399.98px) { - .offcanvas-xxl { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); - } -} -@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) { - .offcanvas-xxl { - transition: none; - } -} -@media (max-width: 1399.98px) { - .offcanvas-xxl.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); - } - .offcanvas-xxl.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); - } - .offcanvas-xxl.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); - } - .offcanvas-xxl.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); - } - .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) { - transform: none; - } - .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show { - visibility: visible; - } -} -@media (min-width: 1400px) { - .offcanvas-xxl { - --bs-offcanvas-height: auto; - --bs-offcanvas-border-width: 0; - background-color: transparent !important; - } - .offcanvas-xxl .offcanvas-header { - display: none; - } - .offcanvas-xxl .offcanvas-body { - display: flex; - flex-grow: 0; - padding: 0; - overflow-y: visible; - background-color: transparent !important; - } -} - -.offcanvas { - position: fixed; - bottom: 0; - z-index: var(--bs-offcanvas-zindex); - display: flex; - flex-direction: column; - max-width: 100%; - color: var(--bs-offcanvas-color); - visibility: hidden; - background-color: var(--bs-offcanvas-bg); - background-clip: padding-box; - outline: 0; - transition: var(--bs-offcanvas-transition); -} -@media (prefers-reduced-motion: reduce) { - .offcanvas { - transition: none; - } -} -.offcanvas.offcanvas-start { - top: 0; - left: 0; - width: var(--bs-offcanvas-width); - border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(-100%); -} -.offcanvas.offcanvas-end { - top: 0; - right: 0; - width: var(--bs-offcanvas-width); - border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateX(100%); -} -.offcanvas.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(-100%); -} -.offcanvas.offcanvas-bottom { - right: 0; - left: 0; - height: var(--bs-offcanvas-height); - max-height: 100%; - border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); - transform: translateY(100%); -} -.offcanvas.showing, .offcanvas.show:not(.hiding) { - transform: none; -} -.offcanvas.showing, .offcanvas.hiding, .offcanvas.show { - visibility: visible; -} - -.offcanvas-backdrop { - position: fixed; - top: 0; - left: 0; - z-index: 1040; - width: 100vw; - height: 100vh; - background-color: #000; -} -.offcanvas-backdrop.fade { - opacity: 0; -} -.offcanvas-backdrop.show { - opacity: 0.5; -} - -.offcanvas-header { - display: flex; - align-items: center; - padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); -} -.offcanvas-header .btn-close { - padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); - margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto; -} - -.offcanvas-title { - margin-bottom: 0; - line-height: var(--bs-offcanvas-title-line-height); -} - -.offcanvas-body { - flex-grow: 1; - padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); - overflow-y: auto; -} - -.placeholder { - display: inline-block; - min-height: 1em; - vertical-align: middle; - cursor: wait; - background-color: currentcolor; - opacity: 0.5; -} -.placeholder.btn::before { - display: inline-block; - content: ""; -} - -.placeholder-xs { - min-height: 0.6em; -} - -.placeholder-sm { - min-height: 0.8em; -} - -.placeholder-lg { - min-height: 1.2em; -} - -.placeholder-glow .placeholder { - animation: placeholder-glow 2s ease-in-out infinite; -} - -@keyframes placeholder-glow { - 50% { - opacity: 0.2; - } -} -.placeholder-wave { - -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); - mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); - -webkit-mask-size: 200% 100%; - mask-size: 200% 100%; - animation: placeholder-wave 2s linear infinite; -} - -@keyframes placeholder-wave { - 100% { - -webkit-mask-position: -200% 0%; - mask-position: -200% 0%; - } -} -.clearfix::after { - display: block; - clear: both; - content: ""; -} - -.text-bg-primary { - color: #fff !important; - background-color: RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-secondary { - color: #fff !important; - background-color: RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-success { - color: #fff !important; - background-color: RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-info { - color: #fff !important; - background-color: RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-warning { - color: #fff !important; - background-color: RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-danger { - color: #fff !important; - background-color: RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-light { - color: #fff !important; - background-color: RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.text-bg-dark { - color: #000 !important; - background-color: RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important; -} - -.link-primary { - color: RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-primary:hover, .link-primary:focus { - color: RGBA(34, 127, 171, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(34, 127, 171, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(34, 127, 171, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-secondary { - color: RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-secondary:hover, .link-secondary:focus { - color: RGBA(68, 68, 68, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(68, 68, 68, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(68, 68, 68, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-success { - color: RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-success:hover, .link-success:focus { - color: RGBA(95, 143, 0, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(95, 143, 0, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(95, 143, 0, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-info { - color: RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-info:hover, .link-info:focus { - color: RGBA(122, 41, 163, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(122, 41, 163, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(122, 41, 163, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-warning { - color: RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-warning:hover, .link-warning:focus { - color: RGBA(204, 109, 0, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(204, 109, 0, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(204, 109, 0, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-danger { - color: RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-danger:hover, .link-danger:focus { - color: RGBA(163, 0, 0, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(163, 0, 0, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(163, 0, 0, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-light { - color: RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-light:hover, .link-light:focus { - color: RGBA(27, 27, 27, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(27, 27, 27, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-dark { - color: RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-dark:hover, .link-dark:focus { - color: RGBA(189, 191, 190, var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(189, 191, 190, var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(189, 191, 190, var(--bs-link-underline-opacity, 1)) !important; -} - -.link-body-emphasis { - color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; -} -.link-body-emphasis:hover, .link-body-emphasis:focus { - color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important; - -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; - text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; -} - -.focus-ring:focus { - outline: 0; - box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color); -} - -.icon-link { - display: inline-flex; - gap: 0.375rem; - align-items: center; - -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); - text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); - text-underline-offset: 0.25em; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; -} -.icon-link > .bi { - flex-shrink: 0; - width: 1em; - height: 1em; - fill: currentcolor; - transition: 0.2s ease-in-out transform; -} -@media (prefers-reduced-motion: reduce) { - .icon-link > .bi { - transition: none; - } -} - -.icon-link-hover:hover > .bi, .icon-link-hover:focus-visible > .bi { - transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); -} - -.ratio { - position: relative; - width: 100%; -} -.ratio::before { - display: block; - padding-top: var(--bs-aspect-ratio); - content: ""; -} -.ratio > * { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.ratio-1x1 { - --bs-aspect-ratio: 100%; -} - -.ratio-4x3 { - --bs-aspect-ratio: 75%; -} - -.ratio-16x9 { - --bs-aspect-ratio: 56.25%; -} - -.ratio-21x9 { - --bs-aspect-ratio: 42.8571428571%; -} - -.fixed-top { - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: 1030; -} - -.fixed-bottom { - position: fixed; - right: 0; - bottom: 0; - left: 0; - z-index: 1030; -} - -.sticky-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; -} - -.sticky-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; -} - -@media (min-width: 576px) { - .sticky-sm-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - .sticky-sm-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; - } -} -@media (min-width: 768px) { - .sticky-md-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - .sticky-md-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; - } -} -@media (min-width: 992px) { - .sticky-lg-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - .sticky-lg-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; - } -} -@media (min-width: 1200px) { - .sticky-xl-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - .sticky-xl-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; - } -} -@media (min-width: 1400px) { - .sticky-xxl-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - .sticky-xxl-bottom { - position: -webkit-sticky; - position: sticky; - bottom: 0; - z-index: 1020; - } -} -.hstack { - display: flex; - flex-direction: row; - align-items: center; - align-self: stretch; -} - -.vstack { - display: flex; - flex: 1 1 auto; - flex-direction: column; - align-self: stretch; -} - -.visually-hidden, -.visually-hidden-focusable:not(:focus):not(:focus-within) { - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - white-space: nowrap !important; - border: 0 !important; -} -.visually-hidden:not(caption), -.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) { - position: absolute !important; -} - -.stretched-link::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - content: ""; -} - -.text-truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.vr { - display: inline-block; - align-self: stretch; - width: var(--bs-border-width); - min-height: 1em; - background-color: currentcolor; - opacity: 0.25; -} - -.align-baseline { - vertical-align: baseline !important; -} - -.align-top { - vertical-align: top !important; -} - -.align-middle { - vertical-align: middle !important; -} - -.align-bottom { - vertical-align: bottom !important; -} - -.align-text-bottom { - vertical-align: text-bottom !important; -} - -.align-text-top { - vertical-align: text-top !important; -} - -.float-start { - float: left !important; -} - -.float-end { - float: right !important; -} - -.float-none { - float: none !important; -} - -.object-fit-contain { - -o-object-fit: contain !important; - object-fit: contain !important; -} - -.object-fit-cover { - -o-object-fit: cover !important; - object-fit: cover !important; -} - -.object-fit-fill { - -o-object-fit: fill !important; - object-fit: fill !important; -} - -.object-fit-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; -} - -.object-fit-none { - -o-object-fit: none !important; - object-fit: none !important; -} - -.opacity-0 { - opacity: 0 !important; -} - -.opacity-25 { - opacity: 0.25 !important; -} - -.opacity-50 { - opacity: 0.5 !important; -} - -.opacity-75 { - opacity: 0.75 !important; -} - -.opacity-100 { - opacity: 1 !important; -} - -.overflow-auto { - overflow: auto !important; -} - -.overflow-hidden { - overflow: hidden !important; -} - -.overflow-visible { - overflow: visible !important; -} - -.overflow-scroll { - overflow: scroll !important; -} - -.overflow-x-auto { - overflow-x: auto !important; -} - -.overflow-x-hidden { - overflow-x: hidden !important; -} - -.overflow-x-visible { - overflow-x: visible !important; -} - -.overflow-x-scroll { - overflow-x: scroll !important; -} - -.overflow-y-auto { - overflow-y: auto !important; -} - -.overflow-y-hidden { - overflow-y: hidden !important; -} - -.overflow-y-visible { - overflow-y: visible !important; -} - -.overflow-y-scroll { - overflow-y: scroll !important; -} - -.d-inline { - display: inline !important; -} - -.d-inline-block { - display: inline-block !important; -} - -.d-block { - display: block !important; -} - -.d-grid { - display: grid !important; -} - -.d-inline-grid { - display: inline-grid !important; -} - -.d-table { - display: table !important; -} - -.d-table-row { - display: table-row !important; -} - -.d-table-cell { - display: table-cell !important; -} - -.d-flex { - display: flex !important; -} - -.d-inline-flex { - display: inline-flex !important; -} - -.d-none { - display: none !important; -} - -.shadow { - box-shadow: var(--bs-box-shadow) !important; -} - -.shadow-sm { - box-shadow: var(--bs-box-shadow-sm) !important; -} - -.shadow-lg { - box-shadow: var(--bs-box-shadow-lg) !important; -} - -.shadow-none { - box-shadow: none !important; -} - -.focus-ring-primary { - --bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-secondary { - --bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-success { - --bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-info { - --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-warning { - --bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-danger { - --bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-light { - --bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity)); -} - -.focus-ring-dark { - --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity)); -} - -.position-static { - position: static !important; -} - -.position-relative { - position: relative !important; -} - -.position-absolute { - position: absolute !important; -} - -.position-fixed { - position: fixed !important; -} - -.position-sticky { - position: -webkit-sticky !important; - position: sticky !important; -} - -.top-0 { - top: 0 !important; -} - -.top-50 { - top: 50% !important; -} - -.top-100 { - top: 100% !important; -} - -.bottom-0 { - bottom: 0 !important; -} - -.bottom-50 { - bottom: 50% !important; -} - -.bottom-100 { - bottom: 100% !important; -} - -.start-0 { - left: 0 !important; -} - -.start-50 { - left: 50% !important; -} - -.start-100 { - left: 100% !important; -} - -.end-0 { - right: 0 !important; -} - -.end-50 { - right: 50% !important; -} - -.end-100 { - right: 100% !important; -} - -.translate-middle { - transform: translate(-50%, -50%) !important; -} - -.translate-middle-x { - transform: translateX(-50%) !important; -} - -.translate-middle-y { - transform: translateY(-50%) !important; -} - -.border { - border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; -} - -.border-0 { - border: 0 !important; -} - -.border-top { - border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; -} - -.border-top-0 { - border-top: 0 !important; -} - -.border-end { - border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; -} - -.border-end-0 { - border-right: 0 !important; -} - -.border-bottom { - border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; -} - -.border-bottom-0 { - border-bottom: 0 !important; -} - -.border-start { - border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; -} - -.border-start-0 { - border-left: 0 !important; -} - -.border-primary { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important; -} - -.border-secondary { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important; -} - -.border-success { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important; -} - -.border-info { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important; -} - -.border-warning { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important; -} - -.border-danger { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; -} - -.border-light { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important; -} - -.border-dark { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important; -} - -.border-black { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important; -} - -.border-white { - --bs-border-opacity: 1; - border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important; -} - -.border-primary-subtle { - border-color: var(--bs-primary-border-subtle) !important; -} - -.border-secondary-subtle { - border-color: var(--bs-secondary-border-subtle) !important; -} - -.border-success-subtle { - border-color: var(--bs-success-border-subtle) !important; -} - -.border-info-subtle { - border-color: var(--bs-info-border-subtle) !important; -} - -.border-warning-subtle { - border-color: var(--bs-warning-border-subtle) !important; -} - -.border-danger-subtle { - border-color: var(--bs-danger-border-subtle) !important; -} - -.border-light-subtle { - border-color: var(--bs-light-border-subtle) !important; -} - -.border-dark-subtle { - border-color: var(--bs-dark-border-subtle) !important; -} - -.border-1 { - border-width: 1px !important; -} - -.border-2 { - border-width: 2px !important; -} - -.border-3 { - border-width: 3px !important; -} - -.border-4 { - border-width: 4px !important; -} - -.border-5 { - border-width: 5px !important; -} - -.border-opacity-10 { - --bs-border-opacity: 0.1; -} - -.border-opacity-25 { - --bs-border-opacity: 0.25; -} - -.border-opacity-50 { - --bs-border-opacity: 0.5; -} - -.border-opacity-75 { - --bs-border-opacity: 0.75; -} - -.border-opacity-100 { - --bs-border-opacity: 1; -} - -.w-25 { - width: 25% !important; -} - -.w-50 { - width: 50% !important; -} - -.w-75 { - width: 75% !important; -} - -.w-100 { - width: 100% !important; -} - -.w-auto { - width: auto !important; -} - -.mw-100 { - max-width: 100% !important; -} - -.vw-100 { - width: 100vw !important; -} - -.min-vw-100 { - min-width: 100vw !important; -} - -.h-25 { - height: 25% !important; -} - -.h-50 { - height: 50% !important; -} - -.h-75 { - height: 75% !important; -} - -.h-100 { - height: 100% !important; -} - -.h-auto { - height: auto !important; -} - -.mh-100 { - max-height: 100% !important; -} - -.vh-100 { - height: 100vh !important; -} - -.min-vh-100 { - min-height: 100vh !important; -} - -.flex-fill { - flex: 1 1 auto !important; -} - -.flex-row { - flex-direction: row !important; -} - -.flex-column { - flex-direction: column !important; -} - -.flex-row-reverse { - flex-direction: row-reverse !important; -} - -.flex-column-reverse { - flex-direction: column-reverse !important; -} - -.flex-grow-0 { - flex-grow: 0 !important; -} - -.flex-grow-1 { - flex-grow: 1 !important; -} - -.flex-shrink-0 { - flex-shrink: 0 !important; -} - -.flex-shrink-1 { - flex-shrink: 1 !important; -} - -.flex-wrap { - flex-wrap: wrap !important; -} - -.flex-nowrap { - flex-wrap: nowrap !important; -} - -.flex-wrap-reverse { - flex-wrap: wrap-reverse !important; -} - -.justify-content-start { - justify-content: flex-start !important; -} - -.justify-content-end { - justify-content: flex-end !important; -} - -.justify-content-center { - justify-content: center !important; -} - -.justify-content-between { - justify-content: space-between !important; -} - -.justify-content-around { - justify-content: space-around !important; -} - -.justify-content-evenly { - justify-content: space-evenly !important; -} - -.align-items-start { - align-items: flex-start !important; -} - -.align-items-end { - align-items: flex-end !important; -} - -.align-items-center { - align-items: center !important; -} - -.align-items-baseline { - align-items: baseline !important; -} - -.align-items-stretch { - align-items: stretch !important; -} - -.align-content-start { - align-content: flex-start !important; -} - -.align-content-end { - align-content: flex-end !important; -} - -.align-content-center { - align-content: center !important; -} - -.align-content-between { - align-content: space-between !important; -} - -.align-content-around { - align-content: space-around !important; -} - -.align-content-stretch { - align-content: stretch !important; -} - -.align-self-auto { - align-self: auto !important; -} - -.align-self-start { - align-self: flex-start !important; -} - -.align-self-end { - align-self: flex-end !important; -} - -.align-self-center { - align-self: center !important; -} - -.align-self-baseline { - align-self: baseline !important; -} - -.align-self-stretch { - align-self: stretch !important; -} - -.order-first { - order: -1 !important; -} - -.order-0 { - order: 0 !important; -} - -.order-1 { - order: 1 !important; -} - -.order-2 { - order: 2 !important; -} - -.order-3 { - order: 3 !important; -} - -.order-4 { - order: 4 !important; -} - -.order-5 { - order: 5 !important; -} - -.order-last { - order: 6 !important; -} - -.m-0 { - margin: 0 !important; -} - -.m-1 { - margin: 0.25rem !important; -} - -.m-2 { - margin: 0.5rem !important; -} - -.m-3 { - margin: 1rem !important; -} - -.m-4 { - margin: 1.5rem !important; -} - -.m-5 { - margin: 3rem !important; -} - -.m-auto { - margin: auto !important; -} - -.mx-0 { - margin-right: 0 !important; - margin-left: 0 !important; -} - -.mx-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; -} - -.mx-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; -} - -.mx-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; -} - -.mx-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; -} - -.mx-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; -} - -.mx-auto { - margin-right: auto !important; - margin-left: auto !important; -} - -.my-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -.my-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; -} - -.my-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; -} - -.my-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; -} - -.my-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; -} - -.my-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; -} - -.my-auto { - margin-top: auto !important; - margin-bottom: auto !important; -} - -.mt-0 { - margin-top: 0 !important; -} - -.mt-1 { - margin-top: 0.25rem !important; -} - -.mt-2 { - margin-top: 0.5rem !important; -} - -.mt-3 { - margin-top: 1rem !important; -} - -.mt-4 { - margin-top: 1.5rem !important; -} - -.mt-5 { - margin-top: 3rem !important; -} - -.mt-auto { - margin-top: auto !important; -} - -.me-0 { - margin-right: 0 !important; -} - -.me-1 { - margin-right: 0.25rem !important; -} - -.me-2 { - margin-right: 0.5rem !important; -} - -.me-3 { - margin-right: 1rem !important; -} - -.me-4 { - margin-right: 1.5rem !important; -} - -.me-5 { - margin-right: 3rem !important; -} - -.me-auto { - margin-right: auto !important; -} - -.mb-0 { - margin-bottom: 0 !important; -} - -.mb-1 { - margin-bottom: 0.25rem !important; -} - -.mb-2 { - margin-bottom: 0.5rem !important; -} - -.mb-3 { - margin-bottom: 1rem !important; -} - -.mb-4 { - margin-bottom: 1.5rem !important; -} - -.mb-5 { - margin-bottom: 3rem !important; -} - -.mb-auto { - margin-bottom: auto !important; -} - -.ms-0 { - margin-left: 0 !important; -} - -.ms-1 { - margin-left: 0.25rem !important; -} - -.ms-2 { - margin-left: 0.5rem !important; -} - -.ms-3 { - margin-left: 1rem !important; -} - -.ms-4 { - margin-left: 1.5rem !important; -} - -.ms-5 { - margin-left: 3rem !important; -} - -.ms-auto { - margin-left: auto !important; -} - -.p-0 { - padding: 0 !important; -} - -.p-1 { - padding: 0.25rem !important; -} - -.p-2 { - padding: 0.5rem !important; -} - -.p-3 { - padding: 1rem !important; -} - -.p-4 { - padding: 1.5rem !important; -} - -.p-5 { - padding: 3rem !important; -} - -.px-0 { - padding-right: 0 !important; - padding-left: 0 !important; -} - -.px-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; -} - -.px-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; -} - -.px-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; -} - -.px-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; -} - -.px-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; -} - -.py-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; -} - -.py-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; -} - -.py-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; -} - -.py-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; -} - -.py-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; -} - -.py-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; -} - -.pt-0 { - padding-top: 0 !important; -} - -.pt-1 { - padding-top: 0.25rem !important; -} - -.pt-2 { - padding-top: 0.5rem !important; -} - -.pt-3 { - padding-top: 1rem !important; -} - -.pt-4 { - padding-top: 1.5rem !important; -} - -.pt-5 { - padding-top: 3rem !important; -} - -.pe-0 { - padding-right: 0 !important; -} - -.pe-1 { - padding-right: 0.25rem !important; -} - -.pe-2 { - padding-right: 0.5rem !important; -} - -.pe-3 { - padding-right: 1rem !important; -} - -.pe-4 { - padding-right: 1.5rem !important; -} - -.pe-5 { - padding-right: 3rem !important; -} - -.pb-0 { - padding-bottom: 0 !important; -} - -.pb-1 { - padding-bottom: 0.25rem !important; -} - -.pb-2 { - padding-bottom: 0.5rem !important; -} - -.pb-3 { - padding-bottom: 1rem !important; -} - -.pb-4 { - padding-bottom: 1.5rem !important; -} - -.pb-5 { - padding-bottom: 3rem !important; -} - -.ps-0 { - padding-left: 0 !important; -} - -.ps-1 { - padding-left: 0.25rem !important; -} - -.ps-2 { - padding-left: 0.5rem !important; -} - -.ps-3 { - padding-left: 1rem !important; -} - -.ps-4 { - padding-left: 1.5rem !important; -} - -.ps-5 { - padding-left: 3rem !important; -} - -.gap-0 { - gap: 0 !important; -} - -.gap-1 { - gap: 0.25rem !important; -} - -.gap-2 { - gap: 0.5rem !important; -} - -.gap-3 { - gap: 1rem !important; -} - -.gap-4 { - gap: 1.5rem !important; -} - -.gap-5 { - gap: 3rem !important; -} - -.row-gap-0 { - row-gap: 0 !important; -} - -.row-gap-1 { - row-gap: 0.25rem !important; -} - -.row-gap-2 { - row-gap: 0.5rem !important; -} - -.row-gap-3 { - row-gap: 1rem !important; -} - -.row-gap-4 { - row-gap: 1.5rem !important; -} - -.row-gap-5 { - row-gap: 3rem !important; -} - -.column-gap-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; -} - -.column-gap-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; -} - -.column-gap-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; -} - -.column-gap-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; -} - -.column-gap-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; -} - -.column-gap-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; -} - -.font-monospace { - font-family: var(--bs-font-monospace) !important; -} - -.fs-1 { - font-size: calc(1.525rem + 3.3vw) !important; -} - -.fs-2 { - font-size: calc(1.425rem + 2.1vw) !important; -} - -.fs-3 { - font-size: calc(1.375rem + 1.5vw) !important; -} - -.fs-4 { - font-size: calc(1.325rem + 0.9vw) !important; -} - -.fs-5 { - font-size: calc(1.275rem + 0.3vw) !important; -} - -.fs-6 { - font-size: 1rem !important; -} - -.fst-italic { - font-style: italic !important; -} - -.fst-normal { - font-style: normal !important; -} - -.fw-lighter { - font-weight: lighter !important; -} - -.fw-light { - font-weight: 300 !important; -} - -.fw-normal { - font-weight: 400 !important; -} - -.fw-medium { - font-weight: 500 !important; -} - -.fw-semibold { - font-weight: 600 !important; -} - -.fw-bold { - font-weight: 700 !important; -} - -.fw-bolder { - font-weight: bolder !important; -} - -.lh-1 { - line-height: 1 !important; -} - -.lh-sm { - line-height: 1.25 !important; -} - -.lh-base { - line-height: 1.5 !important; -} - -.lh-lg { - line-height: 2 !important; -} - -.text-start { - text-align: left !important; -} - -.text-end { - text-align: right !important; -} - -.text-center { - text-align: center !important; -} - -.text-decoration-none { - text-decoration: none !important; -} - -.text-decoration-underline { - text-decoration: underline !important; -} - -.text-decoration-line-through { - text-decoration: line-through !important; -} - -.text-lowercase { - text-transform: lowercase !important; -} - -.text-uppercase { - text-transform: uppercase !important; -} - -.text-capitalize { - text-transform: capitalize !important; -} - -.text-wrap { - white-space: normal !important; -} - -.text-nowrap { - white-space: nowrap !important; -} - -/* rtl:begin:remove */ -.text-break { - word-wrap: break-word !important; - word-break: break-word !important; -} - -/* rtl:end:remove */ -.text-primary { - --bs-text-opacity: 1; - color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important; -} - -.text-secondary { - --bs-text-opacity: 1; - color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important; -} - -.text-success { - --bs-text-opacity: 1; - color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important; -} - -.text-info { - --bs-text-opacity: 1; - color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important; -} - -.text-warning { - --bs-text-opacity: 1; - color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important; -} - -.text-danger { - --bs-text-opacity: 1; - color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important; -} - -.text-light { - --bs-text-opacity: 1; - color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important; -} - -.text-dark { - --bs-text-opacity: 1; - color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important; -} - -.text-black { - --bs-text-opacity: 1; - color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important; -} - -.text-white { - --bs-text-opacity: 1; - color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important; -} - -.text-body { - --bs-text-opacity: 1; - color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important; -} - -.text-muted { - --bs-text-opacity: 1; - color: var(--bs-secondary-color) !important; -} - -.text-black-50 { - --bs-text-opacity: 1; - color: rgba(0, 0, 0, 0.5) !important; -} - -.text-white-50 { - --bs-text-opacity: 1; - color: rgba(255, 255, 255, 0.5) !important; -} - -.text-body-secondary { - --bs-text-opacity: 1; - color: var(--bs-secondary-color) !important; -} - -.text-body-tertiary { - --bs-text-opacity: 1; - color: var(--bs-tertiary-color) !important; -} - -.text-body-emphasis { - --bs-text-opacity: 1; - color: var(--bs-emphasis-color) !important; -} - -.text-reset { - --bs-text-opacity: 1; - color: inherit !important; -} - -.text-opacity-25 { - --bs-text-opacity: 0.25; -} - -.text-opacity-50 { - --bs-text-opacity: 0.5; -} - -.text-opacity-75 { - --bs-text-opacity: 0.75; -} - -.text-opacity-100 { - --bs-text-opacity: 1; -} - -.text-primary-emphasis { - color: var(--bs-primary-text-emphasis) !important; -} - -.text-secondary-emphasis { - color: var(--bs-secondary-text-emphasis) !important; -} - -.text-success-emphasis { - color: var(--bs-success-text-emphasis) !important; -} - -.text-info-emphasis { - color: var(--bs-info-text-emphasis) !important; -} - -.text-warning-emphasis { - color: var(--bs-warning-text-emphasis) !important; -} - -.text-danger-emphasis { - color: var(--bs-danger-text-emphasis) !important; -} - -.text-light-emphasis { - color: var(--bs-light-text-emphasis) !important; -} - -.text-dark-emphasis { - color: var(--bs-dark-text-emphasis) !important; -} - -.link-opacity-10 { - --bs-link-opacity: 0.1; -} - -.link-opacity-10-hover:hover { - --bs-link-opacity: 0.1; -} - -.link-opacity-25 { - --bs-link-opacity: 0.25; -} - -.link-opacity-25-hover:hover { - --bs-link-opacity: 0.25; -} - -.link-opacity-50 { - --bs-link-opacity: 0.5; -} - -.link-opacity-50-hover:hover { - --bs-link-opacity: 0.5; -} - -.link-opacity-75 { - --bs-link-opacity: 0.75; -} - -.link-opacity-75-hover:hover { - --bs-link-opacity: 0.75; -} - -.link-opacity-100 { - --bs-link-opacity: 1; -} - -.link-opacity-100-hover:hover { - --bs-link-opacity: 1; -} - -.link-offset-1 { - text-underline-offset: 0.125em !important; -} - -.link-offset-1-hover:hover { - text-underline-offset: 0.125em !important; -} - -.link-offset-2 { - text-underline-offset: 0.25em !important; -} - -.link-offset-2-hover:hover { - text-underline-offset: 0.25em !important; -} - -.link-offset-3 { - text-underline-offset: 0.375em !important; -} - -.link-offset-3-hover:hover { - text-underline-offset: 0.375em !important; -} - -.link-underline-primary { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-secondary { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-success { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-info { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-warning { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-danger { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-light { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline-dark { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; - text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; -} - -.link-underline { - --bs-link-underline-opacity: 1; - -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; - text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; -} - -.link-underline-opacity-0 { - --bs-link-underline-opacity: 0; -} - -.link-underline-opacity-0-hover:hover { - --bs-link-underline-opacity: 0; -} - -.link-underline-opacity-10 { - --bs-link-underline-opacity: 0.1; -} - -.link-underline-opacity-10-hover:hover { - --bs-link-underline-opacity: 0.1; -} - -.link-underline-opacity-25 { - --bs-link-underline-opacity: 0.25; -} - -.link-underline-opacity-25-hover:hover { - --bs-link-underline-opacity: 0.25; -} - -.link-underline-opacity-50 { - --bs-link-underline-opacity: 0.5; -} - -.link-underline-opacity-50-hover:hover { - --bs-link-underline-opacity: 0.5; -} - -.link-underline-opacity-75 { - --bs-link-underline-opacity: 0.75; -} - -.link-underline-opacity-75-hover:hover { - --bs-link-underline-opacity: 0.75; -} - -.link-underline-opacity-100 { - --bs-link-underline-opacity: 1; -} - -.link-underline-opacity-100-hover:hover { - --bs-link-underline-opacity: 1; -} - -.bg-primary { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-secondary { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-success { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-info { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-warning { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-danger { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-light { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-dark { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-black { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-white { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-body { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-transparent { - --bs-bg-opacity: 1; - background-color: transparent !important; -} - -.bg-body-secondary { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-body-tertiary { - --bs-bg-opacity: 1; - background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important; -} - -.bg-opacity-10 { - --bs-bg-opacity: 0.1; -} - -.bg-opacity-25 { - --bs-bg-opacity: 0.25; -} - -.bg-opacity-50 { - --bs-bg-opacity: 0.5; -} - -.bg-opacity-75 { - --bs-bg-opacity: 0.75; -} - -.bg-opacity-100 { - --bs-bg-opacity: 1; -} - -.bg-primary-subtle { - background-color: var(--bs-primary-bg-subtle) !important; -} - -.bg-secondary-subtle { - background-color: var(--bs-secondary-bg-subtle) !important; -} - -.bg-success-subtle { - background-color: var(--bs-success-bg-subtle) !important; -} - -.bg-info-subtle { - background-color: var(--bs-info-bg-subtle) !important; -} - -.bg-warning-subtle { - background-color: var(--bs-warning-bg-subtle) !important; -} - -.bg-danger-subtle { - background-color: var(--bs-danger-bg-subtle) !important; -} - -.bg-light-subtle { - background-color: var(--bs-light-bg-subtle) !important; -} - -.bg-dark-subtle { - background-color: var(--bs-dark-bg-subtle) !important; -} - -.bg-gradient { - background-image: var(--bs-gradient) !important; -} - -.user-select-all { - -webkit-user-select: all !important; - -moz-user-select: all !important; - user-select: all !important; -} - -.user-select-auto { - -webkit-user-select: auto !important; - -moz-user-select: auto !important; - user-select: auto !important; -} - -.user-select-none { - -webkit-user-select: none !important; - -moz-user-select: none !important; - user-select: none !important; -} - -.pe-none { - pointer-events: none !important; -} - -.pe-auto { - pointer-events: auto !important; -} - -.rounded { - border-radius: var(--bs-border-radius) !important; -} - -.rounded-0 { - border-radius: 0 !important; -} - -.rounded-1 { - border-radius: var(--bs-border-radius-sm) !important; -} - -.rounded-2 { - border-radius: var(--bs-border-radius) !important; -} - -.rounded-3 { - border-radius: var(--bs-border-radius-lg) !important; -} - -.rounded-4 { - border-radius: var(--bs-border-radius-xl) !important; -} - -.rounded-5 { - border-radius: var(--bs-border-radius-xxl) !important; -} - -.rounded-circle { - border-radius: 50% !important; -} - -.rounded-pill { - border-radius: var(--bs-border-radius-pill) !important; -} - -.rounded-top { - border-top-left-radius: var(--bs-border-radius) !important; - border-top-right-radius: var(--bs-border-radius) !important; -} - -.rounded-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; -} - -.rounded-top-1 { - border-top-left-radius: var(--bs-border-radius-sm) !important; - border-top-right-radius: var(--bs-border-radius-sm) !important; -} - -.rounded-top-2 { - border-top-left-radius: var(--bs-border-radius) !important; - border-top-right-radius: var(--bs-border-radius) !important; -} - -.rounded-top-3 { - border-top-left-radius: var(--bs-border-radius-lg) !important; - border-top-right-radius: var(--bs-border-radius-lg) !important; -} - -.rounded-top-4 { - border-top-left-radius: var(--bs-border-radius-xl) !important; - border-top-right-radius: var(--bs-border-radius-xl) !important; -} - -.rounded-top-5 { - border-top-left-radius: var(--bs-border-radius-xxl) !important; - border-top-right-radius: var(--bs-border-radius-xxl) !important; -} - -.rounded-top-circle { - border-top-left-radius: 50% !important; - border-top-right-radius: 50% !important; -} - -.rounded-top-pill { - border-top-left-radius: var(--bs-border-radius-pill) !important; - border-top-right-radius: var(--bs-border-radius-pill) !important; -} - -.rounded-end { - border-top-right-radius: var(--bs-border-radius) !important; - border-bottom-right-radius: var(--bs-border-radius) !important; -} - -.rounded-end-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -.rounded-end-1 { - border-top-right-radius: var(--bs-border-radius-sm) !important; - border-bottom-right-radius: var(--bs-border-radius-sm) !important; -} - -.rounded-end-2 { - border-top-right-radius: var(--bs-border-radius) !important; - border-bottom-right-radius: var(--bs-border-radius) !important; -} - -.rounded-end-3 { - border-top-right-radius: var(--bs-border-radius-lg) !important; - border-bottom-right-radius: var(--bs-border-radius-lg) !important; -} - -.rounded-end-4 { - border-top-right-radius: var(--bs-border-radius-xl) !important; - border-bottom-right-radius: var(--bs-border-radius-xl) !important; -} - -.rounded-end-5 { - border-top-right-radius: var(--bs-border-radius-xxl) !important; - border-bottom-right-radius: var(--bs-border-radius-xxl) !important; -} - -.rounded-end-circle { - border-top-right-radius: 50% !important; - border-bottom-right-radius: 50% !important; -} - -.rounded-end-pill { - border-top-right-radius: var(--bs-border-radius-pill) !important; - border-bottom-right-radius: var(--bs-border-radius-pill) !important; -} - -.rounded-bottom { - border-bottom-right-radius: var(--bs-border-radius) !important; - border-bottom-left-radius: var(--bs-border-radius) !important; -} - -.rounded-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; -} - -.rounded-bottom-1 { - border-bottom-right-radius: var(--bs-border-radius-sm) !important; - border-bottom-left-radius: var(--bs-border-radius-sm) !important; -} - -.rounded-bottom-2 { - border-bottom-right-radius: var(--bs-border-radius) !important; - border-bottom-left-radius: var(--bs-border-radius) !important; -} - -.rounded-bottom-3 { - border-bottom-right-radius: var(--bs-border-radius-lg) !important; - border-bottom-left-radius: var(--bs-border-radius-lg) !important; -} - -.rounded-bottom-4 { - border-bottom-right-radius: var(--bs-border-radius-xl) !important; - border-bottom-left-radius: var(--bs-border-radius-xl) !important; -} - -.rounded-bottom-5 { - border-bottom-right-radius: var(--bs-border-radius-xxl) !important; - border-bottom-left-radius: var(--bs-border-radius-xxl) !important; -} - -.rounded-bottom-circle { - border-bottom-right-radius: 50% !important; - border-bottom-left-radius: 50% !important; -} - -.rounded-bottom-pill { - border-bottom-right-radius: var(--bs-border-radius-pill) !important; - border-bottom-left-radius: var(--bs-border-radius-pill) !important; -} - -.rounded-start { - border-bottom-left-radius: var(--bs-border-radius) !important; - border-top-left-radius: var(--bs-border-radius) !important; -} - -.rounded-start-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; -} - -.rounded-start-1 { - border-bottom-left-radius: var(--bs-border-radius-sm) !important; - border-top-left-radius: var(--bs-border-radius-sm) !important; -} - -.rounded-start-2 { - border-bottom-left-radius: var(--bs-border-radius) !important; - border-top-left-radius: var(--bs-border-radius) !important; -} - -.rounded-start-3 { - border-bottom-left-radius: var(--bs-border-radius-lg) !important; - border-top-left-radius: var(--bs-border-radius-lg) !important; -} - -.rounded-start-4 { - border-bottom-left-radius: var(--bs-border-radius-xl) !important; - border-top-left-radius: var(--bs-border-radius-xl) !important; -} - -.rounded-start-5 { - border-bottom-left-radius: var(--bs-border-radius-xxl) !important; - border-top-left-radius: var(--bs-border-radius-xxl) !important; -} - -.rounded-start-circle { - border-bottom-left-radius: 50% !important; - border-top-left-radius: 50% !important; -} - -.rounded-start-pill { - border-bottom-left-radius: var(--bs-border-radius-pill) !important; - border-top-left-radius: var(--bs-border-radius-pill) !important; -} - -.visible { - visibility: visible !important; -} - -.invisible { - visibility: hidden !important; -} - -.z-n1 { - z-index: -1 !important; -} - -.z-0 { - z-index: 0 !important; -} - -.z-1 { - z-index: 1 !important; -} - -.z-2 { - z-index: 2 !important; -} - -.z-3 { - z-index: 3 !important; -} - -@media (min-width: 576px) { - .float-sm-start { - float: left !important; - } - .float-sm-end { - float: right !important; - } - .float-sm-none { - float: none !important; - } - .object-fit-sm-contain { - -o-object-fit: contain !important; - object-fit: contain !important; - } - .object-fit-sm-cover { - -o-object-fit: cover !important; - object-fit: cover !important; - } - .object-fit-sm-fill { - -o-object-fit: fill !important; - object-fit: fill !important; - } - .object-fit-sm-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; - } - .object-fit-sm-none { - -o-object-fit: none !important; - object-fit: none !important; - } - .d-sm-inline { - display: inline !important; - } - .d-sm-inline-block { - display: inline-block !important; - } - .d-sm-block { - display: block !important; - } - .d-sm-grid { - display: grid !important; - } - .d-sm-inline-grid { - display: inline-grid !important; - } - .d-sm-table { - display: table !important; - } - .d-sm-table-row { - display: table-row !important; - } - .d-sm-table-cell { - display: table-cell !important; - } - .d-sm-flex { - display: flex !important; - } - .d-sm-inline-flex { - display: inline-flex !important; - } - .d-sm-none { - display: none !important; - } - .flex-sm-fill { - flex: 1 1 auto !important; - } - .flex-sm-row { - flex-direction: row !important; - } - .flex-sm-column { - flex-direction: column !important; - } - .flex-sm-row-reverse { - flex-direction: row-reverse !important; - } - .flex-sm-column-reverse { - flex-direction: column-reverse !important; - } - .flex-sm-grow-0 { - flex-grow: 0 !important; - } - .flex-sm-grow-1 { - flex-grow: 1 !important; - } - .flex-sm-shrink-0 { - flex-shrink: 0 !important; - } - .flex-sm-shrink-1 { - flex-shrink: 1 !important; - } - .flex-sm-wrap { - flex-wrap: wrap !important; - } - .flex-sm-nowrap { - flex-wrap: nowrap !important; - } - .flex-sm-wrap-reverse { - flex-wrap: wrap-reverse !important; - } - .justify-content-sm-start { - justify-content: flex-start !important; - } - .justify-content-sm-end { - justify-content: flex-end !important; - } - .justify-content-sm-center { - justify-content: center !important; - } - .justify-content-sm-between { - justify-content: space-between !important; - } - .justify-content-sm-around { - justify-content: space-around !important; - } - .justify-content-sm-evenly { - justify-content: space-evenly !important; - } - .align-items-sm-start { - align-items: flex-start !important; - } - .align-items-sm-end { - align-items: flex-end !important; - } - .align-items-sm-center { - align-items: center !important; - } - .align-items-sm-baseline { - align-items: baseline !important; - } - .align-items-sm-stretch { - align-items: stretch !important; - } - .align-content-sm-start { - align-content: flex-start !important; - } - .align-content-sm-end { - align-content: flex-end !important; - } - .align-content-sm-center { - align-content: center !important; - } - .align-content-sm-between { - align-content: space-between !important; - } - .align-content-sm-around { - align-content: space-around !important; - } - .align-content-sm-stretch { - align-content: stretch !important; - } - .align-self-sm-auto { - align-self: auto !important; - } - .align-self-sm-start { - align-self: flex-start !important; - } - .align-self-sm-end { - align-self: flex-end !important; - } - .align-self-sm-center { - align-self: center !important; - } - .align-self-sm-baseline { - align-self: baseline !important; - } - .align-self-sm-stretch { - align-self: stretch !important; - } - .order-sm-first { - order: -1 !important; - } - .order-sm-0 { - order: 0 !important; - } - .order-sm-1 { - order: 1 !important; - } - .order-sm-2 { - order: 2 !important; - } - .order-sm-3 { - order: 3 !important; - } - .order-sm-4 { - order: 4 !important; - } - .order-sm-5 { - order: 5 !important; - } - .order-sm-last { - order: 6 !important; - } - .m-sm-0 { - margin: 0 !important; - } - .m-sm-1 { - margin: 0.25rem !important; - } - .m-sm-2 { - margin: 0.5rem !important; - } - .m-sm-3 { - margin: 1rem !important; - } - .m-sm-4 { - margin: 1.5rem !important; - } - .m-sm-5 { - margin: 3rem !important; - } - .m-sm-auto { - margin: auto !important; - } - .mx-sm-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - .mx-sm-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; - } - .mx-sm-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - } - .mx-sm-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; - } - .mx-sm-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; - } - .mx-sm-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; - } - .mx-sm-auto { - margin-right: auto !important; - margin-left: auto !important; - } - .my-sm-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - .my-sm-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; - } - .my-sm-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - } - .my-sm-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; - } - .my-sm-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; - } - .my-sm-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; - } - .my-sm-auto { - margin-top: auto !important; - margin-bottom: auto !important; - } - .mt-sm-0 { - margin-top: 0 !important; - } - .mt-sm-1 { - margin-top: 0.25rem !important; - } - .mt-sm-2 { - margin-top: 0.5rem !important; - } - .mt-sm-3 { - margin-top: 1rem !important; - } - .mt-sm-4 { - margin-top: 1.5rem !important; - } - .mt-sm-5 { - margin-top: 3rem !important; - } - .mt-sm-auto { - margin-top: auto !important; - } - .me-sm-0 { - margin-right: 0 !important; - } - .me-sm-1 { - margin-right: 0.25rem !important; - } - .me-sm-2 { - margin-right: 0.5rem !important; - } - .me-sm-3 { - margin-right: 1rem !important; - } - .me-sm-4 { - margin-right: 1.5rem !important; - } - .me-sm-5 { - margin-right: 3rem !important; - } - .me-sm-auto { - margin-right: auto !important; - } - .mb-sm-0 { - margin-bottom: 0 !important; - } - .mb-sm-1 { - margin-bottom: 0.25rem !important; - } - .mb-sm-2 { - margin-bottom: 0.5rem !important; - } - .mb-sm-3 { - margin-bottom: 1rem !important; - } - .mb-sm-4 { - margin-bottom: 1.5rem !important; - } - .mb-sm-5 { - margin-bottom: 3rem !important; - } - .mb-sm-auto { - margin-bottom: auto !important; - } - .ms-sm-0 { - margin-left: 0 !important; - } - .ms-sm-1 { - margin-left: 0.25rem !important; - } - .ms-sm-2 { - margin-left: 0.5rem !important; - } - .ms-sm-3 { - margin-left: 1rem !important; - } - .ms-sm-4 { - margin-left: 1.5rem !important; - } - .ms-sm-5 { - margin-left: 3rem !important; - } - .ms-sm-auto { - margin-left: auto !important; - } - .p-sm-0 { - padding: 0 !important; - } - .p-sm-1 { - padding: 0.25rem !important; - } - .p-sm-2 { - padding: 0.5rem !important; - } - .p-sm-3 { - padding: 1rem !important; - } - .p-sm-4 { - padding: 1.5rem !important; - } - .p-sm-5 { - padding: 3rem !important; - } - .px-sm-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - .px-sm-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; - } - .px-sm-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; - } - .px-sm-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; - } - .px-sm-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; - } - .px-sm-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; - } - .py-sm-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - .py-sm-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; - } - .py-sm-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - .py-sm-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - .py-sm-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - .py-sm-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; - } - .pt-sm-0 { - padding-top: 0 !important; - } - .pt-sm-1 { - padding-top: 0.25rem !important; - } - .pt-sm-2 { - padding-top: 0.5rem !important; - } - .pt-sm-3 { - padding-top: 1rem !important; - } - .pt-sm-4 { - padding-top: 1.5rem !important; - } - .pt-sm-5 { - padding-top: 3rem !important; - } - .pe-sm-0 { - padding-right: 0 !important; - } - .pe-sm-1 { - padding-right: 0.25rem !important; - } - .pe-sm-2 { - padding-right: 0.5rem !important; - } - .pe-sm-3 { - padding-right: 1rem !important; - } - .pe-sm-4 { - padding-right: 1.5rem !important; - } - .pe-sm-5 { - padding-right: 3rem !important; - } - .pb-sm-0 { - padding-bottom: 0 !important; - } - .pb-sm-1 { - padding-bottom: 0.25rem !important; - } - .pb-sm-2 { - padding-bottom: 0.5rem !important; - } - .pb-sm-3 { - padding-bottom: 1rem !important; - } - .pb-sm-4 { - padding-bottom: 1.5rem !important; - } - .pb-sm-5 { - padding-bottom: 3rem !important; - } - .ps-sm-0 { - padding-left: 0 !important; - } - .ps-sm-1 { - padding-left: 0.25rem !important; - } - .ps-sm-2 { - padding-left: 0.5rem !important; - } - .ps-sm-3 { - padding-left: 1rem !important; - } - .ps-sm-4 { - padding-left: 1.5rem !important; - } - .ps-sm-5 { - padding-left: 3rem !important; - } - .gap-sm-0 { - gap: 0 !important; - } - .gap-sm-1 { - gap: 0.25rem !important; - } - .gap-sm-2 { - gap: 0.5rem !important; - } - .gap-sm-3 { - gap: 1rem !important; - } - .gap-sm-4 { - gap: 1.5rem !important; - } - .gap-sm-5 { - gap: 3rem !important; - } - .row-gap-sm-0 { - row-gap: 0 !important; - } - .row-gap-sm-1 { - row-gap: 0.25rem !important; - } - .row-gap-sm-2 { - row-gap: 0.5rem !important; - } - .row-gap-sm-3 { - row-gap: 1rem !important; - } - .row-gap-sm-4 { - row-gap: 1.5rem !important; - } - .row-gap-sm-5 { - row-gap: 3rem !important; - } - .column-gap-sm-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; - } - .column-gap-sm-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; - } - .column-gap-sm-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; - } - .column-gap-sm-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; - } - .column-gap-sm-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; - } - .column-gap-sm-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; - } - .text-sm-start { - text-align: left !important; - } - .text-sm-end { - text-align: right !important; - } - .text-sm-center { - text-align: center !important; - } -} -@media (min-width: 768px) { - .float-md-start { - float: left !important; - } - .float-md-end { - float: right !important; - } - .float-md-none { - float: none !important; - } - .object-fit-md-contain { - -o-object-fit: contain !important; - object-fit: contain !important; - } - .object-fit-md-cover { - -o-object-fit: cover !important; - object-fit: cover !important; - } - .object-fit-md-fill { - -o-object-fit: fill !important; - object-fit: fill !important; - } - .object-fit-md-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; - } - .object-fit-md-none { - -o-object-fit: none !important; - object-fit: none !important; - } - .d-md-inline { - display: inline !important; - } - .d-md-inline-block { - display: inline-block !important; - } - .d-md-block { - display: block !important; - } - .d-md-grid { - display: grid !important; - } - .d-md-inline-grid { - display: inline-grid !important; - } - .d-md-table { - display: table !important; - } - .d-md-table-row { - display: table-row !important; - } - .d-md-table-cell { - display: table-cell !important; - } - .d-md-flex { - display: flex !important; - } - .d-md-inline-flex { - display: inline-flex !important; - } - .d-md-none { - display: none !important; - } - .flex-md-fill { - flex: 1 1 auto !important; - } - .flex-md-row { - flex-direction: row !important; - } - .flex-md-column { - flex-direction: column !important; - } - .flex-md-row-reverse { - flex-direction: row-reverse !important; - } - .flex-md-column-reverse { - flex-direction: column-reverse !important; - } - .flex-md-grow-0 { - flex-grow: 0 !important; - } - .flex-md-grow-1 { - flex-grow: 1 !important; - } - .flex-md-shrink-0 { - flex-shrink: 0 !important; - } - .flex-md-shrink-1 { - flex-shrink: 1 !important; - } - .flex-md-wrap { - flex-wrap: wrap !important; - } - .flex-md-nowrap { - flex-wrap: nowrap !important; - } - .flex-md-wrap-reverse { - flex-wrap: wrap-reverse !important; - } - .justify-content-md-start { - justify-content: flex-start !important; - } - .justify-content-md-end { - justify-content: flex-end !important; - } - .justify-content-md-center { - justify-content: center !important; - } - .justify-content-md-between { - justify-content: space-between !important; - } - .justify-content-md-around { - justify-content: space-around !important; - } - .justify-content-md-evenly { - justify-content: space-evenly !important; - } - .align-items-md-start { - align-items: flex-start !important; - } - .align-items-md-end { - align-items: flex-end !important; - } - .align-items-md-center { - align-items: center !important; - } - .align-items-md-baseline { - align-items: baseline !important; - } - .align-items-md-stretch { - align-items: stretch !important; - } - .align-content-md-start { - align-content: flex-start !important; - } - .align-content-md-end { - align-content: flex-end !important; - } - .align-content-md-center { - align-content: center !important; - } - .align-content-md-between { - align-content: space-between !important; - } - .align-content-md-around { - align-content: space-around !important; - } - .align-content-md-stretch { - align-content: stretch !important; - } - .align-self-md-auto { - align-self: auto !important; - } - .align-self-md-start { - align-self: flex-start !important; - } - .align-self-md-end { - align-self: flex-end !important; - } - .align-self-md-center { - align-self: center !important; - } - .align-self-md-baseline { - align-self: baseline !important; - } - .align-self-md-stretch { - align-self: stretch !important; - } - .order-md-first { - order: -1 !important; - } - .order-md-0 { - order: 0 !important; - } - .order-md-1 { - order: 1 !important; - } - .order-md-2 { - order: 2 !important; - } - .order-md-3 { - order: 3 !important; - } - .order-md-4 { - order: 4 !important; - } - .order-md-5 { - order: 5 !important; - } - .order-md-last { - order: 6 !important; - } - .m-md-0 { - margin: 0 !important; - } - .m-md-1 { - margin: 0.25rem !important; - } - .m-md-2 { - margin: 0.5rem !important; - } - .m-md-3 { - margin: 1rem !important; - } - .m-md-4 { - margin: 1.5rem !important; - } - .m-md-5 { - margin: 3rem !important; - } - .m-md-auto { - margin: auto !important; - } - .mx-md-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - .mx-md-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; - } - .mx-md-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - } - .mx-md-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; - } - .mx-md-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; - } - .mx-md-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; - } - .mx-md-auto { - margin-right: auto !important; - margin-left: auto !important; - } - .my-md-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - .my-md-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; - } - .my-md-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - } - .my-md-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; - } - .my-md-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; - } - .my-md-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; - } - .my-md-auto { - margin-top: auto !important; - margin-bottom: auto !important; - } - .mt-md-0 { - margin-top: 0 !important; - } - .mt-md-1 { - margin-top: 0.25rem !important; - } - .mt-md-2 { - margin-top: 0.5rem !important; - } - .mt-md-3 { - margin-top: 1rem !important; - } - .mt-md-4 { - margin-top: 1.5rem !important; - } - .mt-md-5 { - margin-top: 3rem !important; - } - .mt-md-auto { - margin-top: auto !important; - } - .me-md-0 { - margin-right: 0 !important; - } - .me-md-1 { - margin-right: 0.25rem !important; - } - .me-md-2 { - margin-right: 0.5rem !important; - } - .me-md-3 { - margin-right: 1rem !important; - } - .me-md-4 { - margin-right: 1.5rem !important; - } - .me-md-5 { - margin-right: 3rem !important; - } - .me-md-auto { - margin-right: auto !important; - } - .mb-md-0 { - margin-bottom: 0 !important; - } - .mb-md-1 { - margin-bottom: 0.25rem !important; - } - .mb-md-2 { - margin-bottom: 0.5rem !important; - } - .mb-md-3 { - margin-bottom: 1rem !important; - } - .mb-md-4 { - margin-bottom: 1.5rem !important; - } - .mb-md-5 { - margin-bottom: 3rem !important; - } - .mb-md-auto { - margin-bottom: auto !important; - } - .ms-md-0 { - margin-left: 0 !important; - } - .ms-md-1 { - margin-left: 0.25rem !important; - } - .ms-md-2 { - margin-left: 0.5rem !important; - } - .ms-md-3 { - margin-left: 1rem !important; - } - .ms-md-4 { - margin-left: 1.5rem !important; - } - .ms-md-5 { - margin-left: 3rem !important; - } - .ms-md-auto { - margin-left: auto !important; - } - .p-md-0 { - padding: 0 !important; - } - .p-md-1 { - padding: 0.25rem !important; - } - .p-md-2 { - padding: 0.5rem !important; - } - .p-md-3 { - padding: 1rem !important; - } - .p-md-4 { - padding: 1.5rem !important; - } - .p-md-5 { - padding: 3rem !important; - } - .px-md-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - .px-md-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; - } - .px-md-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; - } - .px-md-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; - } - .px-md-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; - } - .px-md-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; - } - .py-md-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - .py-md-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; - } - .py-md-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - .py-md-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - .py-md-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - .py-md-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; - } - .pt-md-0 { - padding-top: 0 !important; - } - .pt-md-1 { - padding-top: 0.25rem !important; - } - .pt-md-2 { - padding-top: 0.5rem !important; - } - .pt-md-3 { - padding-top: 1rem !important; - } - .pt-md-4 { - padding-top: 1.5rem !important; - } - .pt-md-5 { - padding-top: 3rem !important; - } - .pe-md-0 { - padding-right: 0 !important; - } - .pe-md-1 { - padding-right: 0.25rem !important; - } - .pe-md-2 { - padding-right: 0.5rem !important; - } - .pe-md-3 { - padding-right: 1rem !important; - } - .pe-md-4 { - padding-right: 1.5rem !important; - } - .pe-md-5 { - padding-right: 3rem !important; - } - .pb-md-0 { - padding-bottom: 0 !important; - } - .pb-md-1 { - padding-bottom: 0.25rem !important; - } - .pb-md-2 { - padding-bottom: 0.5rem !important; - } - .pb-md-3 { - padding-bottom: 1rem !important; - } - .pb-md-4 { - padding-bottom: 1.5rem !important; - } - .pb-md-5 { - padding-bottom: 3rem !important; - } - .ps-md-0 { - padding-left: 0 !important; - } - .ps-md-1 { - padding-left: 0.25rem !important; - } - .ps-md-2 { - padding-left: 0.5rem !important; - } - .ps-md-3 { - padding-left: 1rem !important; - } - .ps-md-4 { - padding-left: 1.5rem !important; - } - .ps-md-5 { - padding-left: 3rem !important; - } - .gap-md-0 { - gap: 0 !important; - } - .gap-md-1 { - gap: 0.25rem !important; - } - .gap-md-2 { - gap: 0.5rem !important; - } - .gap-md-3 { - gap: 1rem !important; - } - .gap-md-4 { - gap: 1.5rem !important; - } - .gap-md-5 { - gap: 3rem !important; - } - .row-gap-md-0 { - row-gap: 0 !important; - } - .row-gap-md-1 { - row-gap: 0.25rem !important; - } - .row-gap-md-2 { - row-gap: 0.5rem !important; - } - .row-gap-md-3 { - row-gap: 1rem !important; - } - .row-gap-md-4 { - row-gap: 1.5rem !important; - } - .row-gap-md-5 { - row-gap: 3rem !important; - } - .column-gap-md-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; - } - .column-gap-md-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; - } - .column-gap-md-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; - } - .column-gap-md-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; - } - .column-gap-md-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; - } - .column-gap-md-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; - } - .text-md-start { - text-align: left !important; - } - .text-md-end { - text-align: right !important; - } - .text-md-center { - text-align: center !important; - } -} -@media (min-width: 992px) { - .float-lg-start { - float: left !important; - } - .float-lg-end { - float: right !important; - } - .float-lg-none { - float: none !important; - } - .object-fit-lg-contain { - -o-object-fit: contain !important; - object-fit: contain !important; - } - .object-fit-lg-cover { - -o-object-fit: cover !important; - object-fit: cover !important; - } - .object-fit-lg-fill { - -o-object-fit: fill !important; - object-fit: fill !important; - } - .object-fit-lg-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; - } - .object-fit-lg-none { - -o-object-fit: none !important; - object-fit: none !important; - } - .d-lg-inline { - display: inline !important; - } - .d-lg-inline-block { - display: inline-block !important; - } - .d-lg-block { - display: block !important; - } - .d-lg-grid { - display: grid !important; - } - .d-lg-inline-grid { - display: inline-grid !important; - } - .d-lg-table { - display: table !important; - } - .d-lg-table-row { - display: table-row !important; - } - .d-lg-table-cell { - display: table-cell !important; - } - .d-lg-flex { - display: flex !important; - } - .d-lg-inline-flex { - display: inline-flex !important; - } - .d-lg-none { - display: none !important; - } - .flex-lg-fill { - flex: 1 1 auto !important; - } - .flex-lg-row { - flex-direction: row !important; - } - .flex-lg-column { - flex-direction: column !important; - } - .flex-lg-row-reverse { - flex-direction: row-reverse !important; - } - .flex-lg-column-reverse { - flex-direction: column-reverse !important; - } - .flex-lg-grow-0 { - flex-grow: 0 !important; - } - .flex-lg-grow-1 { - flex-grow: 1 !important; - } - .flex-lg-shrink-0 { - flex-shrink: 0 !important; - } - .flex-lg-shrink-1 { - flex-shrink: 1 !important; - } - .flex-lg-wrap { - flex-wrap: wrap !important; - } - .flex-lg-nowrap { - flex-wrap: nowrap !important; - } - .flex-lg-wrap-reverse { - flex-wrap: wrap-reverse !important; - } - .justify-content-lg-start { - justify-content: flex-start !important; - } - .justify-content-lg-end { - justify-content: flex-end !important; - } - .justify-content-lg-center { - justify-content: center !important; - } - .justify-content-lg-between { - justify-content: space-between !important; - } - .justify-content-lg-around { - justify-content: space-around !important; - } - .justify-content-lg-evenly { - justify-content: space-evenly !important; - } - .align-items-lg-start { - align-items: flex-start !important; - } - .align-items-lg-end { - align-items: flex-end !important; - } - .align-items-lg-center { - align-items: center !important; - } - .align-items-lg-baseline { - align-items: baseline !important; - } - .align-items-lg-stretch { - align-items: stretch !important; - } - .align-content-lg-start { - align-content: flex-start !important; - } - .align-content-lg-end { - align-content: flex-end !important; - } - .align-content-lg-center { - align-content: center !important; - } - .align-content-lg-between { - align-content: space-between !important; - } - .align-content-lg-around { - align-content: space-around !important; - } - .align-content-lg-stretch { - align-content: stretch !important; - } - .align-self-lg-auto { - align-self: auto !important; - } - .align-self-lg-start { - align-self: flex-start !important; - } - .align-self-lg-end { - align-self: flex-end !important; - } - .align-self-lg-center { - align-self: center !important; - } - .align-self-lg-baseline { - align-self: baseline !important; - } - .align-self-lg-stretch { - align-self: stretch !important; - } - .order-lg-first { - order: -1 !important; - } - .order-lg-0 { - order: 0 !important; - } - .order-lg-1 { - order: 1 !important; - } - .order-lg-2 { - order: 2 !important; - } - .order-lg-3 { - order: 3 !important; - } - .order-lg-4 { - order: 4 !important; - } - .order-lg-5 { - order: 5 !important; - } - .order-lg-last { - order: 6 !important; - } - .m-lg-0 { - margin: 0 !important; - } - .m-lg-1 { - margin: 0.25rem !important; - } - .m-lg-2 { - margin: 0.5rem !important; - } - .m-lg-3 { - margin: 1rem !important; - } - .m-lg-4 { - margin: 1.5rem !important; - } - .m-lg-5 { - margin: 3rem !important; - } - .m-lg-auto { - margin: auto !important; - } - .mx-lg-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - .mx-lg-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; - } - .mx-lg-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - } - .mx-lg-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; - } - .mx-lg-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; - } - .mx-lg-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; - } - .mx-lg-auto { - margin-right: auto !important; - margin-left: auto !important; - } - .my-lg-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - .my-lg-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; - } - .my-lg-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - } - .my-lg-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; - } - .my-lg-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; - } - .my-lg-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; - } - .my-lg-auto { - margin-top: auto !important; - margin-bottom: auto !important; - } - .mt-lg-0 { - margin-top: 0 !important; - } - .mt-lg-1 { - margin-top: 0.25rem !important; - } - .mt-lg-2 { - margin-top: 0.5rem !important; - } - .mt-lg-3 { - margin-top: 1rem !important; - } - .mt-lg-4 { - margin-top: 1.5rem !important; - } - .mt-lg-5 { - margin-top: 3rem !important; - } - .mt-lg-auto { - margin-top: auto !important; - } - .me-lg-0 { - margin-right: 0 !important; - } - .me-lg-1 { - margin-right: 0.25rem !important; - } - .me-lg-2 { - margin-right: 0.5rem !important; - } - .me-lg-3 { - margin-right: 1rem !important; - } - .me-lg-4 { - margin-right: 1.5rem !important; - } - .me-lg-5 { - margin-right: 3rem !important; - } - .me-lg-auto { - margin-right: auto !important; - } - .mb-lg-0 { - margin-bottom: 0 !important; - } - .mb-lg-1 { - margin-bottom: 0.25rem !important; - } - .mb-lg-2 { - margin-bottom: 0.5rem !important; - } - .mb-lg-3 { - margin-bottom: 1rem !important; - } - .mb-lg-4 { - margin-bottom: 1.5rem !important; - } - .mb-lg-5 { - margin-bottom: 3rem !important; - } - .mb-lg-auto { - margin-bottom: auto !important; - } - .ms-lg-0 { - margin-left: 0 !important; - } - .ms-lg-1 { - margin-left: 0.25rem !important; - } - .ms-lg-2 { - margin-left: 0.5rem !important; - } - .ms-lg-3 { - margin-left: 1rem !important; - } - .ms-lg-4 { - margin-left: 1.5rem !important; - } - .ms-lg-5 { - margin-left: 3rem !important; - } - .ms-lg-auto { - margin-left: auto !important; - } - .p-lg-0 { - padding: 0 !important; - } - .p-lg-1 { - padding: 0.25rem !important; - } - .p-lg-2 { - padding: 0.5rem !important; - } - .p-lg-3 { - padding: 1rem !important; - } - .p-lg-4 { - padding: 1.5rem !important; - } - .p-lg-5 { - padding: 3rem !important; - } - .px-lg-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - .px-lg-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; - } - .px-lg-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; - } - .px-lg-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; - } - .px-lg-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; - } - .px-lg-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; - } - .py-lg-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - .py-lg-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; - } - .py-lg-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - .py-lg-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - .py-lg-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - .py-lg-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; - } - .pt-lg-0 { - padding-top: 0 !important; - } - .pt-lg-1 { - padding-top: 0.25rem !important; - } - .pt-lg-2 { - padding-top: 0.5rem !important; - } - .pt-lg-3 { - padding-top: 1rem !important; - } - .pt-lg-4 { - padding-top: 1.5rem !important; - } - .pt-lg-5 { - padding-top: 3rem !important; - } - .pe-lg-0 { - padding-right: 0 !important; - } - .pe-lg-1 { - padding-right: 0.25rem !important; - } - .pe-lg-2 { - padding-right: 0.5rem !important; - } - .pe-lg-3 { - padding-right: 1rem !important; - } - .pe-lg-4 { - padding-right: 1.5rem !important; - } - .pe-lg-5 { - padding-right: 3rem !important; - } - .pb-lg-0 { - padding-bottom: 0 !important; - } - .pb-lg-1 { - padding-bottom: 0.25rem !important; - } - .pb-lg-2 { - padding-bottom: 0.5rem !important; - } - .pb-lg-3 { - padding-bottom: 1rem !important; - } - .pb-lg-4 { - padding-bottom: 1.5rem !important; - } - .pb-lg-5 { - padding-bottom: 3rem !important; - } - .ps-lg-0 { - padding-left: 0 !important; - } - .ps-lg-1 { - padding-left: 0.25rem !important; - } - .ps-lg-2 { - padding-left: 0.5rem !important; - } - .ps-lg-3 { - padding-left: 1rem !important; - } - .ps-lg-4 { - padding-left: 1.5rem !important; - } - .ps-lg-5 { - padding-left: 3rem !important; - } - .gap-lg-0 { - gap: 0 !important; - } - .gap-lg-1 { - gap: 0.25rem !important; - } - .gap-lg-2 { - gap: 0.5rem !important; - } - .gap-lg-3 { - gap: 1rem !important; - } - .gap-lg-4 { - gap: 1.5rem !important; - } - .gap-lg-5 { - gap: 3rem !important; - } - .row-gap-lg-0 { - row-gap: 0 !important; - } - .row-gap-lg-1 { - row-gap: 0.25rem !important; - } - .row-gap-lg-2 { - row-gap: 0.5rem !important; - } - .row-gap-lg-3 { - row-gap: 1rem !important; - } - .row-gap-lg-4 { - row-gap: 1.5rem !important; - } - .row-gap-lg-5 { - row-gap: 3rem !important; - } - .column-gap-lg-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; - } - .column-gap-lg-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; - } - .column-gap-lg-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; - } - .column-gap-lg-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; - } - .column-gap-lg-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; - } - .column-gap-lg-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; - } - .text-lg-start { - text-align: left !important; - } - .text-lg-end { - text-align: right !important; - } - .text-lg-center { - text-align: center !important; - } -} -@media (min-width: 1200px) { - .float-xl-start { - float: left !important; - } - .float-xl-end { - float: right !important; - } - .float-xl-none { - float: none !important; - } - .object-fit-xl-contain { - -o-object-fit: contain !important; - object-fit: contain !important; - } - .object-fit-xl-cover { - -o-object-fit: cover !important; - object-fit: cover !important; - } - .object-fit-xl-fill { - -o-object-fit: fill !important; - object-fit: fill !important; - } - .object-fit-xl-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; - } - .object-fit-xl-none { - -o-object-fit: none !important; - object-fit: none !important; - } - .d-xl-inline { - display: inline !important; - } - .d-xl-inline-block { - display: inline-block !important; - } - .d-xl-block { - display: block !important; - } - .d-xl-grid { - display: grid !important; - } - .d-xl-inline-grid { - display: inline-grid !important; - } - .d-xl-table { - display: table !important; - } - .d-xl-table-row { - display: table-row !important; - } - .d-xl-table-cell { - display: table-cell !important; - } - .d-xl-flex { - display: flex !important; - } - .d-xl-inline-flex { - display: inline-flex !important; - } - .d-xl-none { - display: none !important; - } - .flex-xl-fill { - flex: 1 1 auto !important; - } - .flex-xl-row { - flex-direction: row !important; - } - .flex-xl-column { - flex-direction: column !important; - } - .flex-xl-row-reverse { - flex-direction: row-reverse !important; - } - .flex-xl-column-reverse { - flex-direction: column-reverse !important; - } - .flex-xl-grow-0 { - flex-grow: 0 !important; - } - .flex-xl-grow-1 { - flex-grow: 1 !important; - } - .flex-xl-shrink-0 { - flex-shrink: 0 !important; - } - .flex-xl-shrink-1 { - flex-shrink: 1 !important; - } - .flex-xl-wrap { - flex-wrap: wrap !important; - } - .flex-xl-nowrap { - flex-wrap: nowrap !important; - } - .flex-xl-wrap-reverse { - flex-wrap: wrap-reverse !important; - } - .justify-content-xl-start { - justify-content: flex-start !important; - } - .justify-content-xl-end { - justify-content: flex-end !important; - } - .justify-content-xl-center { - justify-content: center !important; - } - .justify-content-xl-between { - justify-content: space-between !important; - } - .justify-content-xl-around { - justify-content: space-around !important; - } - .justify-content-xl-evenly { - justify-content: space-evenly !important; - } - .align-items-xl-start { - align-items: flex-start !important; - } - .align-items-xl-end { - align-items: flex-end !important; - } - .align-items-xl-center { - align-items: center !important; - } - .align-items-xl-baseline { - align-items: baseline !important; - } - .align-items-xl-stretch { - align-items: stretch !important; - } - .align-content-xl-start { - align-content: flex-start !important; - } - .align-content-xl-end { - align-content: flex-end !important; - } - .align-content-xl-center { - align-content: center !important; - } - .align-content-xl-between { - align-content: space-between !important; - } - .align-content-xl-around { - align-content: space-around !important; - } - .align-content-xl-stretch { - align-content: stretch !important; - } - .align-self-xl-auto { - align-self: auto !important; - } - .align-self-xl-start { - align-self: flex-start !important; - } - .align-self-xl-end { - align-self: flex-end !important; - } - .align-self-xl-center { - align-self: center !important; - } - .align-self-xl-baseline { - align-self: baseline !important; - } - .align-self-xl-stretch { - align-self: stretch !important; - } - .order-xl-first { - order: -1 !important; - } - .order-xl-0 { - order: 0 !important; - } - .order-xl-1 { - order: 1 !important; - } - .order-xl-2 { - order: 2 !important; - } - .order-xl-3 { - order: 3 !important; - } - .order-xl-4 { - order: 4 !important; - } - .order-xl-5 { - order: 5 !important; - } - .order-xl-last { - order: 6 !important; - } - .m-xl-0 { - margin: 0 !important; - } - .m-xl-1 { - margin: 0.25rem !important; - } - .m-xl-2 { - margin: 0.5rem !important; - } - .m-xl-3 { - margin: 1rem !important; - } - .m-xl-4 { - margin: 1.5rem !important; - } - .m-xl-5 { - margin: 3rem !important; - } - .m-xl-auto { - margin: auto !important; - } - .mx-xl-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - .mx-xl-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; - } - .mx-xl-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - } - .mx-xl-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; - } - .mx-xl-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; - } - .mx-xl-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; - } - .mx-xl-auto { - margin-right: auto !important; - margin-left: auto !important; - } - .my-xl-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - .my-xl-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; - } - .my-xl-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - } - .my-xl-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; - } - .my-xl-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; - } - .my-xl-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; - } - .my-xl-auto { - margin-top: auto !important; - margin-bottom: auto !important; - } - .mt-xl-0 { - margin-top: 0 !important; - } - .mt-xl-1 { - margin-top: 0.25rem !important; - } - .mt-xl-2 { - margin-top: 0.5rem !important; - } - .mt-xl-3 { - margin-top: 1rem !important; - } - .mt-xl-4 { - margin-top: 1.5rem !important; - } - .mt-xl-5 { - margin-top: 3rem !important; - } - .mt-xl-auto { - margin-top: auto !important; - } - .me-xl-0 { - margin-right: 0 !important; - } - .me-xl-1 { - margin-right: 0.25rem !important; - } - .me-xl-2 { - margin-right: 0.5rem !important; - } - .me-xl-3 { - margin-right: 1rem !important; - } - .me-xl-4 { - margin-right: 1.5rem !important; - } - .me-xl-5 { - margin-right: 3rem !important; - } - .me-xl-auto { - margin-right: auto !important; - } - .mb-xl-0 { - margin-bottom: 0 !important; - } - .mb-xl-1 { - margin-bottom: 0.25rem !important; - } - .mb-xl-2 { - margin-bottom: 0.5rem !important; - } - .mb-xl-3 { - margin-bottom: 1rem !important; - } - .mb-xl-4 { - margin-bottom: 1.5rem !important; - } - .mb-xl-5 { - margin-bottom: 3rem !important; - } - .mb-xl-auto { - margin-bottom: auto !important; - } - .ms-xl-0 { - margin-left: 0 !important; - } - .ms-xl-1 { - margin-left: 0.25rem !important; - } - .ms-xl-2 { - margin-left: 0.5rem !important; - } - .ms-xl-3 { - margin-left: 1rem !important; - } - .ms-xl-4 { - margin-left: 1.5rem !important; - } - .ms-xl-5 { - margin-left: 3rem !important; - } - .ms-xl-auto { - margin-left: auto !important; - } - .p-xl-0 { - padding: 0 !important; - } - .p-xl-1 { - padding: 0.25rem !important; - } - .p-xl-2 { - padding: 0.5rem !important; - } - .p-xl-3 { - padding: 1rem !important; - } - .p-xl-4 { - padding: 1.5rem !important; - } - .p-xl-5 { - padding: 3rem !important; - } - .px-xl-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - .px-xl-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; - } - .px-xl-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; - } - .px-xl-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; - } - .px-xl-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; - } - .px-xl-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; - } - .py-xl-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - .py-xl-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; - } - .py-xl-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - .py-xl-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - .py-xl-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - .py-xl-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; - } - .pt-xl-0 { - padding-top: 0 !important; - } - .pt-xl-1 { - padding-top: 0.25rem !important; - } - .pt-xl-2 { - padding-top: 0.5rem !important; - } - .pt-xl-3 { - padding-top: 1rem !important; - } - .pt-xl-4 { - padding-top: 1.5rem !important; - } - .pt-xl-5 { - padding-top: 3rem !important; - } - .pe-xl-0 { - padding-right: 0 !important; - } - .pe-xl-1 { - padding-right: 0.25rem !important; - } - .pe-xl-2 { - padding-right: 0.5rem !important; - } - .pe-xl-3 { - padding-right: 1rem !important; - } - .pe-xl-4 { - padding-right: 1.5rem !important; - } - .pe-xl-5 { - padding-right: 3rem !important; - } - .pb-xl-0 { - padding-bottom: 0 !important; - } - .pb-xl-1 { - padding-bottom: 0.25rem !important; - } - .pb-xl-2 { - padding-bottom: 0.5rem !important; - } - .pb-xl-3 { - padding-bottom: 1rem !important; - } - .pb-xl-4 { - padding-bottom: 1.5rem !important; - } - .pb-xl-5 { - padding-bottom: 3rem !important; - } - .ps-xl-0 { - padding-left: 0 !important; - } - .ps-xl-1 { - padding-left: 0.25rem !important; - } - .ps-xl-2 { - padding-left: 0.5rem !important; - } - .ps-xl-3 { - padding-left: 1rem !important; - } - .ps-xl-4 { - padding-left: 1.5rem !important; - } - .ps-xl-5 { - padding-left: 3rem !important; - } - .gap-xl-0 { - gap: 0 !important; - } - .gap-xl-1 { - gap: 0.25rem !important; - } - .gap-xl-2 { - gap: 0.5rem !important; - } - .gap-xl-3 { - gap: 1rem !important; - } - .gap-xl-4 { - gap: 1.5rem !important; - } - .gap-xl-5 { - gap: 3rem !important; - } - .row-gap-xl-0 { - row-gap: 0 !important; - } - .row-gap-xl-1 { - row-gap: 0.25rem !important; - } - .row-gap-xl-2 { - row-gap: 0.5rem !important; - } - .row-gap-xl-3 { - row-gap: 1rem !important; - } - .row-gap-xl-4 { - row-gap: 1.5rem !important; - } - .row-gap-xl-5 { - row-gap: 3rem !important; - } - .column-gap-xl-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; - } - .column-gap-xl-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; - } - .column-gap-xl-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; - } - .column-gap-xl-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; - } - .column-gap-xl-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; - } - .column-gap-xl-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; - } - .text-xl-start { - text-align: left !important; - } - .text-xl-end { - text-align: right !important; - } - .text-xl-center { - text-align: center !important; - } -} -@media (min-width: 1400px) { - .float-xxl-start { - float: left !important; - } - .float-xxl-end { - float: right !important; - } - .float-xxl-none { - float: none !important; - } - .object-fit-xxl-contain { - -o-object-fit: contain !important; - object-fit: contain !important; - } - .object-fit-xxl-cover { - -o-object-fit: cover !important; - object-fit: cover !important; - } - .object-fit-xxl-fill { - -o-object-fit: fill !important; - object-fit: fill !important; - } - .object-fit-xxl-scale { - -o-object-fit: scale-down !important; - object-fit: scale-down !important; - } - .object-fit-xxl-none { - -o-object-fit: none !important; - object-fit: none !important; - } - .d-xxl-inline { - display: inline !important; - } - .d-xxl-inline-block { - display: inline-block !important; - } - .d-xxl-block { - display: block !important; - } - .d-xxl-grid { - display: grid !important; - } - .d-xxl-inline-grid { - display: inline-grid !important; - } - .d-xxl-table { - display: table !important; - } - .d-xxl-table-row { - display: table-row !important; - } - .d-xxl-table-cell { - display: table-cell !important; - } - .d-xxl-flex { - display: flex !important; - } - .d-xxl-inline-flex { - display: inline-flex !important; - } - .d-xxl-none { - display: none !important; - } - .flex-xxl-fill { - flex: 1 1 auto !important; - } - .flex-xxl-row { - flex-direction: row !important; - } - .flex-xxl-column { - flex-direction: column !important; - } - .flex-xxl-row-reverse { - flex-direction: row-reverse !important; - } - .flex-xxl-column-reverse { - flex-direction: column-reverse !important; - } - .flex-xxl-grow-0 { - flex-grow: 0 !important; - } - .flex-xxl-grow-1 { - flex-grow: 1 !important; - } - .flex-xxl-shrink-0 { - flex-shrink: 0 !important; - } - .flex-xxl-shrink-1 { - flex-shrink: 1 !important; - } - .flex-xxl-wrap { - flex-wrap: wrap !important; - } - .flex-xxl-nowrap { - flex-wrap: nowrap !important; - } - .flex-xxl-wrap-reverse { - flex-wrap: wrap-reverse !important; - } - .justify-content-xxl-start { - justify-content: flex-start !important; - } - .justify-content-xxl-end { - justify-content: flex-end !important; - } - .justify-content-xxl-center { - justify-content: center !important; - } - .justify-content-xxl-between { - justify-content: space-between !important; - } - .justify-content-xxl-around { - justify-content: space-around !important; - } - .justify-content-xxl-evenly { - justify-content: space-evenly !important; - } - .align-items-xxl-start { - align-items: flex-start !important; - } - .align-items-xxl-end { - align-items: flex-end !important; - } - .align-items-xxl-center { - align-items: center !important; - } - .align-items-xxl-baseline { - align-items: baseline !important; - } - .align-items-xxl-stretch { - align-items: stretch !important; - } - .align-content-xxl-start { - align-content: flex-start !important; - } - .align-content-xxl-end { - align-content: flex-end !important; - } - .align-content-xxl-center { - align-content: center !important; - } - .align-content-xxl-between { - align-content: space-between !important; - } - .align-content-xxl-around { - align-content: space-around !important; - } - .align-content-xxl-stretch { - align-content: stretch !important; - } - .align-self-xxl-auto { - align-self: auto !important; - } - .align-self-xxl-start { - align-self: flex-start !important; - } - .align-self-xxl-end { - align-self: flex-end !important; - } - .align-self-xxl-center { - align-self: center !important; - } - .align-self-xxl-baseline { - align-self: baseline !important; - } - .align-self-xxl-stretch { - align-self: stretch !important; - } - .order-xxl-first { - order: -1 !important; - } - .order-xxl-0 { - order: 0 !important; - } - .order-xxl-1 { - order: 1 !important; - } - .order-xxl-2 { - order: 2 !important; - } - .order-xxl-3 { - order: 3 !important; - } - .order-xxl-4 { - order: 4 !important; - } - .order-xxl-5 { - order: 5 !important; - } - .order-xxl-last { - order: 6 !important; - } - .m-xxl-0 { - margin: 0 !important; - } - .m-xxl-1 { - margin: 0.25rem !important; - } - .m-xxl-2 { - margin: 0.5rem !important; - } - .m-xxl-3 { - margin: 1rem !important; - } - .m-xxl-4 { - margin: 1.5rem !important; - } - .m-xxl-5 { - margin: 3rem !important; - } - .m-xxl-auto { - margin: auto !important; - } - .mx-xxl-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - .mx-xxl-1 { - margin-right: 0.25rem !important; - margin-left: 0.25rem !important; - } - .mx-xxl-2 { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - } - .mx-xxl-3 { - margin-right: 1rem !important; - margin-left: 1rem !important; - } - .mx-xxl-4 { - margin-right: 1.5rem !important; - margin-left: 1.5rem !important; - } - .mx-xxl-5 { - margin-right: 3rem !important; - margin-left: 3rem !important; - } - .mx-xxl-auto { - margin-right: auto !important; - margin-left: auto !important; - } - .my-xxl-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - .my-xxl-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; - } - .my-xxl-2 { - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - } - .my-xxl-3 { - margin-top: 1rem !important; - margin-bottom: 1rem !important; - } - .my-xxl-4 { - margin-top: 1.5rem !important; - margin-bottom: 1.5rem !important; - } - .my-xxl-5 { - margin-top: 3rem !important; - margin-bottom: 3rem !important; - } - .my-xxl-auto { - margin-top: auto !important; - margin-bottom: auto !important; - } - .mt-xxl-0 { - margin-top: 0 !important; - } - .mt-xxl-1 { - margin-top: 0.25rem !important; - } - .mt-xxl-2 { - margin-top: 0.5rem !important; - } - .mt-xxl-3 { - margin-top: 1rem !important; - } - .mt-xxl-4 { - margin-top: 1.5rem !important; - } - .mt-xxl-5 { - margin-top: 3rem !important; - } - .mt-xxl-auto { - margin-top: auto !important; - } - .me-xxl-0 { - margin-right: 0 !important; - } - .me-xxl-1 { - margin-right: 0.25rem !important; - } - .me-xxl-2 { - margin-right: 0.5rem !important; - } - .me-xxl-3 { - margin-right: 1rem !important; - } - .me-xxl-4 { - margin-right: 1.5rem !important; - } - .me-xxl-5 { - margin-right: 3rem !important; - } - .me-xxl-auto { - margin-right: auto !important; - } - .mb-xxl-0 { - margin-bottom: 0 !important; - } - .mb-xxl-1 { - margin-bottom: 0.25rem !important; - } - .mb-xxl-2 { - margin-bottom: 0.5rem !important; - } - .mb-xxl-3 { - margin-bottom: 1rem !important; - } - .mb-xxl-4 { - margin-bottom: 1.5rem !important; - } - .mb-xxl-5 { - margin-bottom: 3rem !important; - } - .mb-xxl-auto { - margin-bottom: auto !important; - } - .ms-xxl-0 { - margin-left: 0 !important; - } - .ms-xxl-1 { - margin-left: 0.25rem !important; - } - .ms-xxl-2 { - margin-left: 0.5rem !important; - } - .ms-xxl-3 { - margin-left: 1rem !important; - } - .ms-xxl-4 { - margin-left: 1.5rem !important; - } - .ms-xxl-5 { - margin-left: 3rem !important; - } - .ms-xxl-auto { - margin-left: auto !important; - } - .p-xxl-0 { - padding: 0 !important; - } - .p-xxl-1 { - padding: 0.25rem !important; - } - .p-xxl-2 { - padding: 0.5rem !important; - } - .p-xxl-3 { - padding: 1rem !important; - } - .p-xxl-4 { - padding: 1.5rem !important; - } - .p-xxl-5 { - padding: 3rem !important; - } - .px-xxl-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - .px-xxl-1 { - padding-right: 0.25rem !important; - padding-left: 0.25rem !important; - } - .px-xxl-2 { - padding-right: 0.5rem !important; - padding-left: 0.5rem !important; - } - .px-xxl-3 { - padding-right: 1rem !important; - padding-left: 1rem !important; - } - .px-xxl-4 { - padding-right: 1.5rem !important; - padding-left: 1.5rem !important; - } - .px-xxl-5 { - padding-right: 3rem !important; - padding-left: 3rem !important; - } - .py-xxl-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - .py-xxl-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; - } - .py-xxl-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } - .py-xxl-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - .py-xxl-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - .py-xxl-5 { - padding-top: 3rem !important; - padding-bottom: 3rem !important; - } - .pt-xxl-0 { - padding-top: 0 !important; - } - .pt-xxl-1 { - padding-top: 0.25rem !important; - } - .pt-xxl-2 { - padding-top: 0.5rem !important; - } - .pt-xxl-3 { - padding-top: 1rem !important; - } - .pt-xxl-4 { - padding-top: 1.5rem !important; - } - .pt-xxl-5 { - padding-top: 3rem !important; - } - .pe-xxl-0 { - padding-right: 0 !important; - } - .pe-xxl-1 { - padding-right: 0.25rem !important; - } - .pe-xxl-2 { - padding-right: 0.5rem !important; - } - .pe-xxl-3 { - padding-right: 1rem !important; - } - .pe-xxl-4 { - padding-right: 1.5rem !important; - } - .pe-xxl-5 { - padding-right: 3rem !important; - } - .pb-xxl-0 { - padding-bottom: 0 !important; - } - .pb-xxl-1 { - padding-bottom: 0.25rem !important; - } - .pb-xxl-2 { - padding-bottom: 0.5rem !important; - } - .pb-xxl-3 { - padding-bottom: 1rem !important; - } - .pb-xxl-4 { - padding-bottom: 1.5rem !important; - } - .pb-xxl-5 { - padding-bottom: 3rem !important; - } - .ps-xxl-0 { - padding-left: 0 !important; - } - .ps-xxl-1 { - padding-left: 0.25rem !important; - } - .ps-xxl-2 { - padding-left: 0.5rem !important; - } - .ps-xxl-3 { - padding-left: 1rem !important; - } - .ps-xxl-4 { - padding-left: 1.5rem !important; - } - .ps-xxl-5 { - padding-left: 3rem !important; - } - .gap-xxl-0 { - gap: 0 !important; - } - .gap-xxl-1 { - gap: 0.25rem !important; - } - .gap-xxl-2 { - gap: 0.5rem !important; - } - .gap-xxl-3 { - gap: 1rem !important; - } - .gap-xxl-4 { - gap: 1.5rem !important; - } - .gap-xxl-5 { - gap: 3rem !important; - } - .row-gap-xxl-0 { - row-gap: 0 !important; - } - .row-gap-xxl-1 { - row-gap: 0.25rem !important; - } - .row-gap-xxl-2 { - row-gap: 0.5rem !important; - } - .row-gap-xxl-3 { - row-gap: 1rem !important; - } - .row-gap-xxl-4 { - row-gap: 1.5rem !important; - } - .row-gap-xxl-5 { - row-gap: 3rem !important; - } - .column-gap-xxl-0 { - -moz-column-gap: 0 !important; - column-gap: 0 !important; - } - .column-gap-xxl-1 { - -moz-column-gap: 0.25rem !important; - column-gap: 0.25rem !important; - } - .column-gap-xxl-2 { - -moz-column-gap: 0.5rem !important; - column-gap: 0.5rem !important; - } - .column-gap-xxl-3 { - -moz-column-gap: 1rem !important; - column-gap: 1rem !important; - } - .column-gap-xxl-4 { - -moz-column-gap: 1.5rem !important; - column-gap: 1.5rem !important; - } - .column-gap-xxl-5 { - -moz-column-gap: 3rem !important; - column-gap: 3rem !important; - } - .text-xxl-start { - text-align: left !important; - } - .text-xxl-end { - text-align: right !important; - } - .text-xxl-center { - text-align: center !important; - } -} -@media (min-width: 1200px) { - .fs-1 { - font-size: 4rem !important; - } - .fs-2 { - font-size: 3rem !important; - } - .fs-3 { - font-size: 2.5rem !important; - } - .fs-4 { - font-size: 2rem !important; - } - .fs-5 { - font-size: 1.5rem !important; - } -} -@media print { - .d-print-inline { - display: inline !important; - } - .d-print-inline-block { - display: inline-block !important; - } - .d-print-block { - display: block !important; - } - .d-print-grid { - display: grid !important; - } - .d-print-inline-grid { - display: inline-grid !important; - } - .d-print-table { - display: table !important; - } - .d-print-table-row { - display: table-row !important; - } - .d-print-table-cell { - display: table-cell !important; - } - .d-print-flex { - display: flex !important; - } - .d-print-inline-flex { - display: inline-flex !important; - } - .d-print-none { - display: none !important; - } -} -.navbar.bg-primary { - border: 1px solid #282828; -} -.navbar.bg-dark { - background-color: #060606 !important; - border: 1px solid #282828; -} -.navbar.bg-light { - background-color: #888 !important; -} -.navbar.fixed-top { - border-width: 0 0 1px; -} -.navbar.fixed-bottom { - border-width: 1px 0 0; -} - -.btn-primary { - background-color: #2a9fd6; -} -.btn-secondary { - background-color: #555; -} -.btn-success { - background-color: #77b300; -} -.btn-info { - background-color: #93c; -} -.btn-warning { - background-color: #f80; -} -.btn-danger { - background-color: #c00; -} -.btn-light { - background-color: #222; -} -.btn-dark { - background-color: #adafae; -} - -legend { - color: #fff; -} - -.form-control:disabled, .form-control[readonly] { - border-color: transparent; -} - -.nav-tabs .nav-link, -.nav-pills .nav-link { - color: #fff; -} -.nav-tabs .nav-link:hover, -.nav-pills .nav-link:hover { - background-color: #282828; -} -.nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, -.nav-pills .nav-link.disabled, -.nav-pills .nav-link.disabled:hover { - color: var(--bs-secondary-color); - background-color: transparent; -} -.nav-tabs .nav-link.active, -.nav-pills .nav-link.active { - background-color: #2a9fd6; -} - -.breadcrumb a { - color: #fff; -} - -.pagination a:hover { - text-decoration: none; -} - -.alert { - color: #fff; - border: none; -} -.alert a, -.alert .alert-link { - color: #fff; - text-decoration: underline; -} -.alert-primary { - background-color: #2a9fd6; -} -.alert-secondary { - background-color: #555; -} -.alert-success { - background-color: #77b300; -} -.alert-info { - background-color: #93c; -} -.alert-warning { - background-color: #f80; -} -.alert-danger { - background-color: #c00; -} -.alert-light { - background-color: #222; -} -.alert-dark { - background-color: #adafae; -} - -.badge.bg-dark { - color: #212529; -} - -.tooltip { - --bs-tooltip-bg: var(--bs-tertiary-bg); - --bs-tooltip-color: var(--bs-emphasis-color); -} - -.list-group-item-action:hover { - border-color: #2a9fd6; -} - -.popover-title { - border-bottom: none; -} \ No newline at end of file diff --git a/assets/script.js b/assets/script.js deleted file mode 100644 index f987c2c..0000000 --- a/assets/script.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Delete a client referenced by its ID - * - * @param clientID The ID of the client to delete - */ -async function deleteClient(clientID) { - if(!confirm("Do you really want to remove client " + clientID + "? The operation cannot be reverted!")) - return; - - try { - const res = await fetch("/", { - method: "POST", - headers:{ - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - "delete_client_id": clientID - }), - }); - - if(res.status !== 200) - throw new Error(`Invalid status code: ${res.status}`); - - alert("The client was successfully deleted!"); - location.reload(); - } catch (e) { - console.error(`Failed to delete client: ${e}`); - alert("Failed to delete client!"); - } -} \ No newline at end of file diff --git a/assets/style.css b/assets/style.css deleted file mode 100644 index f83653e..0000000 --- a/assets/style.css +++ /dev/null @@ -1,12 +0,0 @@ -.body-content { - max-width: 900px; - margin: 50px auto; -} - -.body-content .card-header { - font-weight: bold; -} - -#user_id_container { - margin: 20px auto; -} \ No newline at end of file diff --git a/assets/ws_debug.js b/assets/ws_debug.js deleted file mode 100644 index b001790..0000000 --- a/assets/ws_debug.js +++ /dev/null @@ -1,68 +0,0 @@ -let ws; - -const JS_MESSAGE = "JS code"; -const IN_MESSAGE = "Incoming"; - -/** - * Log message - */ -function log(src, txt) { - const target = document.getElementById("ws_log"); - const msg = document.createElement("div"); - msg.className = "message"; - msg.innerHTML = `
${src}
${txt}
` - target.insertBefore(msg, target.firstChild); -} - -/** - * Set the state of the WebSocket - */ -function setState(state) { - document.getElementById("state").innerText = state; -} - -/** - * Initialize WebSocket connection - */ -function connect() { - disconnect(); - log(JS_MESSAGE, "Initialize connection..."); - ws = new WebSocket("/api/ws"); - setState("Connecting..."); - ws.onopen = function () { - log(JS_MESSAGE, "Connected to WebSocket !"); - setState("Connected"); - } - ws.onmessage = function (event) { - log(IN_MESSAGE, event.data); - } - ws.onclose = function () { - log(JS_MESSAGE, "Disconnected from WebSocket !"); - setState("Disconnected"); - } - ws.onerror = function (event) { - console.error("WS Error!", event); - log(JS_MESSAGE, `Error with websocket! ${event}`); - setState("Error"); - } -} - -/** - * Close WebSocket connection - */ -function disconnect() { - if (ws && ws.readyState === WebSocket.OPEN) { - log(JS_MESSAGE, "Close connection..."); - ws.close(); - } - - setState("Disconnected"); - ws = undefined; -} - -/** - * Clear WS logs - */ -function clearLogs() { - document.getElementById("ws_log").innerHTML = ""; -} \ No newline at end of file diff --git a/docker/dex/dex.config.yaml b/docker/dex/dex.config.yaml deleted file mode 100644 index 7704800..0000000 --- a/docker/dex/dex.config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -issuer: http://127.0.0.1:9001/dex - -storage: - type: memory - -web: - http: 0.0.0.0:9001 - -oauth2: - # Automate some clicking - # Note: this might actually make some tests pass that otherwise wouldn't. - skipApprovalScreen: false - -connectors: - # Note: this might actually make some tests pass that otherwise wouldn't. - - type: mockCallback - id: mock - name: Example - -# Basic OP test suite requires two clients. -staticClients: - - id: foo - secret: bar - redirectURIs: - - http://localhost:8000/oidc_cb - name: Project diff --git a/.gitignore b/matrixgw_backend/.gitignore similarity index 100% rename from .gitignore rename to matrixgw_backend/.gitignore diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml new file mode 100644 index 0000000..ab0c915 --- /dev/null +++ b/matrixgw_backend/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "matrixgw_backend" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/Dockerfile b/matrixgw_backend/Dockerfile similarity index 100% rename from Dockerfile rename to matrixgw_backend/Dockerfile diff --git a/docker-compose.yml b/matrixgw_backend/docker-compose.yml similarity index 95% rename from docker-compose.yml rename to matrixgw_backend/docker-compose.yml index d37a458..26d2b1d 100644 --- a/docker-compose.yml +++ b/matrixgw_backend/docker-compose.yml @@ -84,14 +84,6 @@ services: volumes: - ./docker/element/config.json:/app/config.json:ro - oidc: - image: dexidp/dex - ports: - - 9001:9001 - volumes: - - ./docker/dex:/conf:ro - command: ["dex", "serve", "/conf/dex.config.yaml"] - minio: image: quay.io/minio/minio command: minio server --console-address ":9002" /data diff --git a/docker/element/config.json b/matrixgw_backend/docker/element/config.json similarity index 100% rename from docker/element/config.json rename to matrixgw_backend/docker/element/config.json diff --git a/docker/mas/config.yaml b/matrixgw_backend/docker/mas/config.yaml similarity index 100% rename from docker/mas/config.yaml rename to matrixgw_backend/docker/mas/config.yaml diff --git a/docker/synapse/homeserver.yaml b/matrixgw_backend/docker/synapse/homeserver.yaml similarity index 100% rename from docker/synapse/homeserver.yaml rename to matrixgw_backend/docker/synapse/homeserver.yaml diff --git a/docker/synapse/localhost.log.config b/matrixgw_backend/docker/synapse/localhost.log.config similarity index 100% rename from docker/synapse/localhost.log.config rename to matrixgw_backend/docker/synapse/localhost.log.config diff --git a/docker/synapse/localhost.signing.key b/matrixgw_backend/docker/synapse/localhost.signing.key similarity index 100% rename from docker/synapse/localhost.signing.key rename to matrixgw_backend/docker/synapse/localhost.signing.key diff --git a/examples/api_curl.rs b/matrixgw_backend/examples/api_curl.rs similarity index 100% rename from examples/api_curl.rs rename to matrixgw_backend/examples/api_curl.rs diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/matrixgw_backend/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/matrixgw_frontend/.gitignore b/matrixgw_frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/matrixgw_frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/matrixgw_frontend/README.md b/matrixgw_frontend/README.md new file mode 100644 index 0000000..d2e7761 --- /dev/null +++ b/matrixgw_frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/matrixgw_frontend/eslint.config.js b/matrixgw_frontend/eslint.config.js new file mode 100644 index 0000000..b19330b --- /dev/null +++ b/matrixgw_frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/assets/favicon.png b/matrixgw_frontend/favicon.png similarity index 100% rename from assets/favicon.png rename to matrixgw_frontend/favicon.png diff --git a/matrixgw_frontend/index.html b/matrixgw_frontend/index.html new file mode 100644 index 0000000..03c4e8f --- /dev/null +++ b/matrixgw_frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + matrixgw_frontend + + +
+ + + diff --git a/matrixgw_frontend/package-lock.json b/matrixgw_frontend/package-lock.json new file mode 100644 index 0000000..612a943 --- /dev/null +++ b/matrixgw_frontend/package-lock.json @@ -0,0 +1,3265 @@ +{ + "name": "matrixgw_frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "matrixgw_frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/node": "^24.6.0", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.22", + "globals": "^16.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "npm:rolldown-vite@7.1.14" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", + "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.92.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.92.0.tgz", + "integrity": "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.93.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.93.0.tgz", + "integrity": "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", + "integrity": "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz", + "integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.41.tgz", + "integrity": "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.41.tgz", + "integrity": "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.41.tgz", + "integrity": "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.41.tgz", + "integrity": "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.41.tgz", + "integrity": "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.41.tgz", + "integrity": "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.41.tgz", + "integrity": "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.41.tgz", + "integrity": "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.41.tgz", + "integrity": "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.41.tgz", + "integrity": "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.43", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz", + "integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz", + "integrity": "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.43", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.22", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz", + "integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001752", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", + "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", + "dev": true, + "license": "ISC" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.38.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.41.tgz", + "integrity": "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.93.0", + "@rolldown/pluginutils": "1.0.0-beta.41", + "ansis": "=4.2.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.41", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", + "@rolldown/binding-darwin-x64": "1.0.0-beta.41", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.41", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.41.tgz", + "integrity": "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", + "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.2", + "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "name": "rolldown-vite", + "version": "7.1.14", + "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", + "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "0.92.0", + "fdir": "^6.5.0", + "lightningcss": "^1.30.1", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rolldown": "1.0.0-beta.41", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "esbuild": "^0.25.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/matrixgw_frontend/package.json b/matrixgw_frontend/package.json new file mode 100644 index 0000000..33d4639 --- /dev/null +++ b/matrixgw_frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "matrixgw_frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/node": "^24.6.0", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.22", + "globals": "^16.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "npm:rolldown-vite@7.1.14" + }, + "overrides": { + "vite": "npm:rolldown-vite@7.1.14" + } +} diff --git a/matrixgw_frontend/public/vite.svg b/matrixgw_frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/matrixgw_frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/matrixgw_frontend/src/App.css b/matrixgw_frontend/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/matrixgw_frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx new file mode 100644 index 0000000..3d7ded3 --- /dev/null +++ b/matrixgw_frontend/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/matrixgw_frontend/src/assets/react.svg b/matrixgw_frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/matrixgw_frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/matrixgw_frontend/src/index.css b/matrixgw_frontend/src/index.css new file mode 100644 index 0000000..08a3ac9 --- /dev/null +++ b/matrixgw_frontend/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/matrixgw_frontend/src/main.tsx b/matrixgw_frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/matrixgw_frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/matrixgw_frontend/tsconfig.app.json b/matrixgw_frontend/tsconfig.app.json new file mode 100644 index 0000000..a9b5a59 --- /dev/null +++ b/matrixgw_frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/matrixgw_frontend/tsconfig.json b/matrixgw_frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/matrixgw_frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/matrixgw_frontend/tsconfig.node.json b/matrixgw_frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/matrixgw_frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/matrixgw_frontend/vite.config.ts b/matrixgw_frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/matrixgw_frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/src/app_config.rs b/src/app_config.rs deleted file mode 100644 index 014effe..0000000 --- a/src/app_config.rs +++ /dev/null @@ -1,187 +0,0 @@ -use clap::Parser; -use s3::creds::Credentials; -use s3::{Bucket, Region}; - -/// Matrix gateway backend API -#[derive(Parser, Debug, Clone)] -#[clap(author, version, about, long_about = None)] -pub struct AppConfig { - /// Listen address - #[clap(short, long, env, default_value = "0.0.0.0:8000")] - pub listen_address: String, - - /// Website origin - #[clap(short, long, env, default_value = "http://localhost:8000")] - pub website_origin: String, - - /// Proxy IP, might end with a star "*" - #[clap(short, long, env)] - pub proxy_ip: Option, - - /// Secret key, used to sign some resources. Must be randomly generated - #[clap(short = 'S', long, env, default_value = "")] - secret: String, - - /// Matrix API origin - #[clap(short, long, env, default_value = "http://127.0.0.1:8448")] - pub matrix_homeserver: String, - - /// Redis connection hostname - #[clap(long, env, default_value = "localhost")] - redis_hostname: String, - - /// Redis connection port - #[clap(long, env, default_value_t = 6379)] - redis_port: u16, - - /// Redis database number - #[clap(long, env, default_value_t = 0)] - redis_db_number: i64, - - /// Redis username - #[clap(long, env)] - redis_username: Option, - - /// Redis password - #[clap(long, env, default_value = "secretredis")] - redis_password: String, - - /// URL where the OpenID configuration can be found - #[arg( - long, - env, - default_value = "http://localhost:9001/dex/.well-known/openid-configuration" - )] - pub oidc_configuration_url: String, - - /// OpenID client ID - #[arg(long, env, default_value = "foo")] - pub oidc_client_id: String, - - /// OpenID client secret - #[arg(long, env, default_value = "bar")] - pub oidc_client_secret: String, - - /// OpenID login redirect URL - #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] - oidc_redirect_url: String, - - /// S3 Bucket name - #[arg(long, env, default_value = "matrix-gw")] - s3_bucket_name: String, - - /// S3 region (if not using Minio) - #[arg(long, env, default_value = "eu-central-1")] - s3_region: String, - - /// S3 API endpoint - #[arg(long, env, default_value = "http://localhost:9000")] - s3_endpoint: String, - - /// S3 access key - #[arg(long, env, default_value = "minioadmin")] - s3_access_key: String, - - /// S3 secret key - #[arg(long, env, default_value = "minioadmin")] - s3_secret_key: String, - - /// S3 skip auto create bucket if not existing - #[arg(long, env)] - pub s3_skip_auto_create_bucket: bool, -} - -lazy_static::lazy_static! { - static ref ARGS: AppConfig = { - AppConfig::parse() - }; -} - -impl AppConfig { - /// Get parsed command line arguments - pub fn get() -> &'static AppConfig { - &ARGS - } - - /// Get app secret - pub fn secret(&self) -> &str { - let mut secret = self.secret.as_str(); - - if cfg!(debug_assertions) && secret.is_empty() { - secret = "DEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEY"; - } - - if secret.is_empty() { - panic!("SECRET is undefined or too short (min 64 chars)!") - } - - secret - } - - /// Get Redis connection configuration - pub fn redis_connection_string(&self) -> String { - format!( - "redis://{}:{}@{}:{}/{}", - self.redis_username.as_deref().unwrap_or(""), - self.redis_password, - self.redis_hostname, - self.redis_port, - self.redis_db_number - ) - } - - /// Get OpenID providers configuration - pub fn openid_provider(&self) -> OIDCProvider<'_> { - OIDCProvider { - client_id: self.oidc_client_id.as_str(), - client_secret: self.oidc_client_secret.as_str(), - configuration_url: self.oidc_configuration_url.as_str(), - redirect_url: self - .oidc_redirect_url - .replace("APP_ORIGIN", &self.website_origin), - } - } - - /// Get s3 bucket credentials - pub fn s3_credentials(&self) -> anyhow::Result { - Ok(Credentials::new( - Some(&self.s3_access_key), - Some(&self.s3_secret_key), - None, - None, - None, - )?) - } - - /// Get S3 bucket - pub fn s3_bucket(&self) -> anyhow::Result> { - Ok(Bucket::new( - &self.s3_bucket_name, - Region::Custom { - region: self.s3_region.to_string(), - endpoint: self.s3_endpoint.to_string(), - }, - self.s3_credentials()?, - )? - .with_path_style()) - } -} - -#[derive(Debug, Clone, serde::Serialize)] -pub struct OIDCProvider<'a> { - pub client_id: &'a str, - pub client_secret: &'a str, - pub configuration_url: &'a str, - pub redirect_url: String, -} - -#[cfg(test)] -mod test { - use crate::app_config::AppConfig; - - #[test] - fn verify_cli() { - use clap::CommandFactory; - AppConfig::command().debug_assert() - } -} diff --git a/src/broadcast_messages.rs b/src/broadcast_messages.rs deleted file mode 100644 index 4a77381..0000000 --- a/src/broadcast_messages.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::sync_client::SyncClientID; -use crate::user::{APIClientID, UserID}; -use ruma::api::client::sync::sync_events::v3::{GlobalAccountData, Presence, Rooms, ToDevice}; -use ruma::api::client::sync::sync_events::DeviceLists; - -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] -pub struct SyncEvent { - /// Updates to rooms. - #[serde(default, skip_serializing_if = "Rooms::is_empty")] - pub rooms: Rooms, - - /// Updates to the presence status of other users. - #[serde(default, skip_serializing_if = "Presence::is_empty")] - pub presence: Presence, - - /// The global private data created by this user. - #[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")] - pub account_data: GlobalAccountData, - - /// Messages sent directly between devices. - #[serde(default, skip_serializing_if = "ToDevice::is_empty")] - pub to_device: ToDevice, - - /// Information on E2E device updates. - /// - /// Only present on an incremental sync. - #[serde(default, skip_serializing_if = "DeviceLists::is_empty")] - pub device_lists: DeviceLists, -} - -/// Broadcast messages -#[derive(Debug, Clone)] -pub enum BroadcastMessage { - /// Request to close the session of a specific client - CloseClientSession(APIClientID), - /// Close all the sessions of a given user - CloseAllUserSessions(UserID), - /// Stop sync client for a given user - StopSyncTaskForUser(UserID), - /// Start sync client for a given user (if not already running) - StartSyncTaskForUser(UserID), - /// Stop a client with a given client ID - StopSyncClient(SyncClientID), - /// Propagate a new sync event - SyncEvent(UserID, Box), -} diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index 539f496..0000000 --- a/src/constants.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::time::Duration; - -/// Session key for OpenID login state -pub const STATE_KEY: &str = "oidc-state"; - -/// Session key for user information -pub const USER_SESSION_KEY: &str = "user"; - -/// Token length -pub const TOKEN_LEN: usize = 20; - -/// How often heartbeat pings are sent. -/// -/// Should be half (or less) of the acceptable client timeout. -pub const WS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); - -/// How long before lack of client response causes a timeout. -pub const WS_CLIENT_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/src/extractors/client_auth.rs b/src/extractors/client_auth.rs deleted file mode 100644 index a7a4a5b..0000000 --- a/src/extractors/client_auth.rs +++ /dev/null @@ -1,256 +0,0 @@ -use crate::constants::USER_SESSION_KEY; -use crate::server::HttpFailure; -use crate::user::{APIClient, APIClientID, RumaClient, User, UserConfig, UserID}; -use crate::utils::base_utils::curr_time; -use actix_remote_ip::RemoteIP; -use actix_session::Session; -use actix_web::dev::Payload; -use actix_web::{FromRequest, HttpRequest}; -use bytes::Bytes; -use jwt_simple::common::VerificationOptions; -use jwt_simple::prelude::{Duration, HS256Key, MACLike}; -use ruma::api::{IncomingResponse, OutgoingRequest}; -use sha2::{Digest, Sha256}; -use std::net::IpAddr; -use std::str::FromStr; - -pub struct APIClientAuth { - pub user: UserConfig, - pub client: Option, - pub payload: Option>, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct TokenClaims { - #[serde(rename = "met")] - pub method: String, - pub uri: String, - #[serde(rename = "pay", skip_serializing_if = "Option::is_none")] - pub payload_sha256: Option, -} - -impl APIClientAuth { - async fn extract_auth( - req: &HttpRequest, - remote_ip: IpAddr, - payload_bytes: Option, - ) -> Result { - // Check if user is authenticated using Web UI - let session = Session::from_request(req, &mut Payload::None).await?; - - if let Some(user) = session.get::(USER_SESSION_KEY)? { - match UserConfig::load(&user.id, false).await { - Ok(config) => { - return Ok(Self { - user: config, - client: None, - payload: payload_bytes.map(|bytes| bytes.to_vec()), - }) - } - Err(e) => { - log::error!("Failed to fetch user information for authentication using cookie token! {e}"); - } - }; - } - - let Some(token) = req.headers().get("x-client-auth") else { - return Err(actix_web::error::ErrorBadRequest( - "Missing authentication header!", - )); - }; - let Ok(jwt_token) = token.to_str() else { - return Err(actix_web::error::ErrorBadRequest( - "Failed to decode token as string!", - )); - }; - - let metadata = match jwt_simple::token::Token::decode_metadata(jwt_token) { - Ok(m) => m, - Err(e) => { - log::error!("Failed to decode JWT header metadata! {e}"); - return Err(actix_web::error::ErrorBadRequest( - "Failed to decode JWT header metadata!", - )); - } - }; - - let Some(kid) = metadata.key_id() else { - return Err(actix_web::error::ErrorBadRequest( - "Missing key id in request!", - )); - }; - - let Some((user_id, client_id)) = kid.split_once("#") else { - return Err(actix_web::error::ErrorBadRequest( - "Invalid key format (missing part)!", - )); - }; - - let (Ok(user_id), Ok(client_id)) = - (urlencoding::decode(user_id), urlencoding::decode(client_id)) - else { - return Err(actix_web::error::ErrorBadRequest( - "Invalid key format (decoding failed)!", - )); - }; - - // Fetch user - const USER_NOT_FOUND_ERROR: &str = "User not found!"; - let user = match UserConfig::load(&UserID(user_id.to_string()), false).await { - Ok(u) => u, - Err(e) => { - log::error!("Failed to get user information! {e}"); - return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); - } - }; - - // Find client - let Ok(client_id) = APIClientID::from_str(&client_id) else { - return Err(actix_web::error::ErrorBadRequest("Invalid token format!")); - }; - let Some(client) = user.find_client_by_id(&client_id) else { - log::error!("Client not found for user!"); - return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); - }; - - // Decode JWT - let key = HS256Key::from_bytes(client.secret.as_bytes()); - let verif = VerificationOptions { - max_validity: Some(Duration::from_mins(15)), - ..Default::default() - }; - - let claims = match key.verify_token::(jwt_token, Some(verif)) { - Ok(t) => t, - Err(e) => { - log::error!("JWT validation failed! {e}"); - return Err(actix_web::error::ErrorForbidden("JWT validation failed!")); - } - }; - - // Check for nonce - if claims.nonce.is_none() { - return Err(actix_web::error::ErrorBadRequest( - "A nonce is required in JWT!", - )); - } - - // Check IP restriction - if let Some(net) = client.network { - if !net.contains(&remote_ip) { - log::error!( - "Trying to use client {} from unauthorized IP address: {remote_ip}", - client.id.0 - ); - return Err(actix_web::error::ErrorForbidden( - "This client cannot be used from this IP address!", - )); - } - } - - // Check URI & verb - if claims.custom.uri != req.uri().to_string() { - return Err(actix_web::error::ErrorBadRequest("URI mismatch!")); - } - if claims.custom.method != req.method().to_string() { - return Err(actix_web::error::ErrorBadRequest("Method mismatch!")); - } - - // Check for write access - if client.readonly_client && !req.method().is_safe() { - return Err(actix_web::error::ErrorBadRequest( - "Read only client cannot perform write operations!", - )); - } - - let payload = match (payload_bytes, claims.custom.payload_sha256) { - (None, _) => None, - (Some(_), None) => { - return Err(actix_web::error::ErrorBadRequest( - "A payload digest must be included in the JWT when the request has a payload!", - )); - } - (Some(payload), Some(provided_digest)) => { - let computed_digest = base16ct::lower::encode_string(&Sha256::digest(&payload)); - if computed_digest != provided_digest { - log::error!( - "Expected digest {provided_digest} but computed {computed_digest}!" - ); - return Err(actix_web::error::ErrorBadRequest( - "Computed digest is different from the one provided in the JWT!", - )); - } - - Some(payload.to_vec()) - } - }; - - // Update last use (if needed) - if client.need_update_last_used() { - let mut user_up = user.clone(); - match user_up.find_client_by_id_mut(&client.id) { - None => log::error!("Client ID disappeared!!!"), - Some(u) => u.used = curr_time().unwrap(), - } - if let Err(e) = user_up.save().await { - log::error!("Failed to update last token usage! {e}"); - } - } - - Ok(Self { - client: Some(client.clone()), - payload, - user, - }) - } - - /// Get an instance of Matrix client - pub async fn client(&self) -> anyhow::Result { - self.user.matrix_client().await - } - - /// Send request to matrix server - pub async fn send_request, E: IncomingResponse>( - &self, - request: R, - ) -> anyhow::Result { - match self.client().await?.send_request(request).await { - Ok(e) => Ok(e), - Err(e) => Err(HttpFailure::MatrixClientError(e.to_string())), - } - } -} - -impl FromRequest for APIClientAuth { - type Error = actix_web::Error; - type Future = futures_util::future::LocalBoxFuture<'static, Result>; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req = req.clone(); - - let remote_ip = match RemoteIP::from_request(&req, &mut Payload::None).into_inner() { - Ok(ip) => ip, - Err(e) => return Box::pin(async { Err(e) }), - }; - - let mut payload = payload.take(); - - Box::pin(async move { - let payload_bytes = match Bytes::from_request(&req, &mut payload).await { - Ok(b) => { - if b.is_empty() { - None - } else { - Some(b) - } - } - Err(e) => { - log::error!("Failed to extract request payload! {e}"); - None - } - }; - - Self::extract_auth(&req, remote_ip.0, payload_bytes).await - }) - } -} diff --git a/src/extractors/mod.rs b/src/extractors/mod.rs deleted file mode 100644 index f2e5226..0000000 --- a/src/extractors/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod client_auth; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e0979b6..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod app_config; -pub mod broadcast_messages; -pub mod constants; -pub mod extractors; -pub mod server; -pub mod sync_client; -pub mod user; -pub mod utils; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 7bc364c..0000000 --- a/src/main.rs +++ /dev/null @@ -1,80 +0,0 @@ -use actix_remote_ip::RemoteIPConfig; -use actix_session::config::SessionLifecycle; -use actix_session::{storage::RedisSessionStore, SessionMiddleware}; -use actix_web::cookie::Key; -use actix_web::{web, App, HttpServer}; -use matrix_gateway::app_config::AppConfig; -use matrix_gateway::broadcast_messages::BroadcastMessage; -use matrix_gateway::server::{api, web_ui}; -use matrix_gateway::sync_client; -use matrix_gateway::user::UserConfig; - -#[tokio::main] -async fn main() -> std::io::Result<()> { - env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - - UserConfig::create_bucket_if_required() - .await - .expect("Failed to create bucket!"); - - let secret_key = Key::from(AppConfig::get().secret().as_bytes()); - - let redis_store = RedisSessionStore::new(AppConfig::get().redis_connection_string()) - .await - .expect("Failed to connect to Redis!"); - - let (ws_tx, _) = tokio::sync::broadcast::channel::(16); - - // Launch sync manager - tokio::spawn(sync_client::sync_client_manager(ws_tx.clone())); - - log::info!( - "Starting to listen on {} for {}", - AppConfig::get().listen_address, - AppConfig::get().website_origin - ); - HttpServer::new(move || { - App::new() - // Add session management to your application using Redis for session state storage - .wrap( - SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) - .cookie_name("matrixgw-session".to_string()) - .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) - .build(), - ) - .app_data(web::Data::new(RemoteIPConfig { - proxy: AppConfig::get().proxy_ip.clone(), - })) - .app_data(web::Data::new(ws_tx.clone())) - // Web configuration routes - .route("/assets/{tail:.*}", web::get().to(web_ui::static_file)) - .route("/", web::get().to(web_ui::home)) - .route("/", web::post().to(web_ui::home)) - .route("/oidc_cb", web::get().to(web_ui::oidc_cb)) - .route("/sign_out", web::get().to(web_ui::sign_out)) - .route("/ws_debug", web::get().to(web_ui::ws_debug)) - // API routes - .route("/api", web::get().to(api::api_home)) - .route("/api", web::post().to(api::api_home)) - .route("/api/account/whoami", web::get().to(api::account::who_am_i)) - .route("/api/room/joined", web::get().to(api::room::joined_rooms)) - .route("/api/room/{room_id}", web::get().to(api::room::info)) - .route( - "/api/media/{server_name}/{media_id}/download", - web::get().to(api::media::download), - ) - .route( - "/api/media/{server_name}/{media_id}/thumbnail", - web::get().to(api::media::thumbnail), - ) - .route( - "/api/profile/{user_id}", - web::get().to(api::profile::get_profile), - ) - .service(web::resource("/api/ws").route(web::get().to(api::ws::ws))) - }) - .workers(4) - .bind(&AppConfig::get().listen_address)? - .run() - .await -} diff --git a/src/server/api/account.rs b/src/server/api/account.rs deleted file mode 100644 index 36d4731..0000000 --- a/src/server/api/account.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::extractors::client_auth::APIClientAuth; -use crate::server::HttpResult; -use actix_web::HttpResponse; -use ruma::api::client::account; -use ruma::DeviceId; - -#[derive(serde::Serialize)] -struct WhoAmIResponse { - user_id: String, - device_id: Option, -} - -/// Get current user identity -pub async fn who_am_i(auth: APIClientAuth) -> HttpResult { - let res = auth - .send_request(account::whoami::v3::Request::default()) - .await?; - - Ok(HttpResponse::Ok().json(WhoAmIResponse { - user_id: res.user_id.to_string(), - device_id: res.device_id.as_deref().map(DeviceId::to_string), - })) -} diff --git a/src/server/api/media.rs b/src/server/api/media.rs deleted file mode 100644 index 9b07e33..0000000 --- a/src/server/api/media.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::extractors::client_auth::APIClientAuth; -use crate::server::HttpResult; -use actix_web::{web, HttpResponse}; -use ruma::api::client::media; -use ruma::{OwnedServerName, UInt}; - -#[derive(serde::Deserialize)] -pub struct MediaInfoInPath { - server_name: OwnedServerName, - media_id: String, -} - -/// Download a media -#[allow(deprecated)] -pub async fn download(auth: APIClientAuth, path: web::Path) -> HttpResult { - let res = auth - .send_request(media::get_content::v3::Request::new( - path.media_id.clone(), - path.server_name.clone(), - )) - .await?; - - let mut http_res = HttpResponse::Ok(); - if let Some(content_type) = res.content_type { - http_res.content_type(content_type); - } - - Ok(http_res.body(res.file)) -} - -#[derive(serde::Deserialize)] -pub struct MediaThumbnailQuery { - width: Option, - height: Option, -} - -/// Get a media thumbnail -#[allow(deprecated)] -pub async fn thumbnail( - auth: APIClientAuth, - path: web::Path, - query: web::Query, -) -> HttpResult { - let res = auth - .send_request(media::get_content_thumbnail::v3::Request::new( - path.media_id.clone(), - path.server_name.clone(), - query.width.unwrap_or(UInt::new(500).unwrap()), - query.height.unwrap_or(UInt::new(500).unwrap()), - )) - .await?; - - let mut http_res = HttpResponse::Ok(); - if let Some(content_type) = res.content_type { - http_res.content_type(content_type); - } - - Ok(http_res.body(res.file)) -} diff --git a/src/server/api/mod.rs b/src/server/api/mod.rs deleted file mode 100644 index 73a26c8..0000000 --- a/src/server/api/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::extractors::client_auth::APIClientAuth; -use crate::server::HttpResult; -use actix_web::HttpResponse; - -pub mod account; -pub mod media; -pub mod profile; -pub mod room; -pub mod ws; - -/// API Home route -pub async fn api_home(auth: APIClientAuth) -> HttpResult { - Ok(HttpResponse::Ok().body(format!("Welcome user {}!", auth.user.user_id.0))) -} diff --git a/src/server/api/profile.rs b/src/server/api/profile.rs deleted file mode 100644 index d37e612..0000000 --- a/src/server/api/profile.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::extractors::client_auth::APIClientAuth; -use crate::server::HttpResult; -use crate::utils::matrix_utils::ApiMxcURI; -use actix_web::{web, HttpResponse}; -use ruma::api::client::profile; -use ruma::OwnedUserId; - -#[derive(serde::Deserialize)] -pub struct UserIDInPath { - user_id: OwnedUserId, -} - -#[derive(serde::Serialize)] -struct ProfileResponse { - display_name: Option, - avatar: Option, -} - -/// Get user profile -pub async fn get_profile(auth: APIClientAuth, path: web::Path) -> HttpResult { - let res = auth - .send_request(profile::get_profile::v3::Request::new(path.user_id.clone())) - .await?; - - Ok(HttpResponse::Ok().json(ProfileResponse { - display_name: res.displayname, - avatar: res.avatar_url.map(ApiMxcURI), - })) -} diff --git a/src/server/api/room.rs b/src/server/api/room.rs deleted file mode 100644 index 6afe1b5..0000000 --- a/src/server/api/room.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::extractors::client_auth::APIClientAuth; -use crate::server::{HttpFailure, HttpResult}; -use crate::utils::matrix_utils::ApiMxcURI; -use actix_web::{web, HttpResponse}; -use ruma::api::client::{membership, state}; -use ruma::events::StateEventType; -use ruma::{OwnedMxcUri, OwnedRoomId}; -use serde::de::DeserializeOwned; - -#[derive(serde::Serialize)] -struct GetRoomsMembershipsResponse { - rooms: Vec, -} - -/// Get the list of rooms the user has joined -pub async fn joined_rooms(auth: APIClientAuth) -> HttpResult { - let res = auth - .send_request(membership::joined_rooms::v3::Request::default()) - .await?; - - Ok(HttpResponse::Ok().json(GetRoomsMembershipsResponse { - rooms: res.joined_rooms, - })) -} - -#[derive(serde::Deserialize)] -pub struct RoomIDInPath { - room_id: OwnedRoomId, -} - -#[derive(serde::Serialize)] -struct GetRoomInfoResponse { - name: Option, - avatar: Option, -} - -/// Get a room information -async fn get_room_info( - auth: &APIClientAuth, - room_id: OwnedRoomId, - event_type: StateEventType, - field: &str, -) -> anyhow::Result, HttpFailure> { - let res = auth - .send_request(state::get_state_events_for_key::v3::Request::new( - room_id, - event_type, - String::default(), - )) - .await?; - - Ok(res.content.get_field(field)?) -} - -/// Get room information -pub async fn info(auth: APIClientAuth, path: web::Path) -> HttpResult { - let room_name: Option = get_room_info( - &auth, - path.room_id.clone(), - StateEventType::RoomName, - "name", - ) - .await - .ok() - .flatten(); - - let room_avatar: Option = get_room_info( - &auth, - path.room_id.clone(), - StateEventType::RoomAvatar, - "url", - ) - .await - .ok() - .flatten(); - - Ok(HttpResponse::Ok().json(GetRoomInfoResponse { - name: room_name, - avatar: room_avatar.map(ApiMxcURI), - })) -} diff --git a/src/server/api/ws.rs b/src/server/api/ws.rs deleted file mode 100644 index 3909956..0000000 --- a/src/server/api/ws.rs +++ /dev/null @@ -1,176 +0,0 @@ -use crate::broadcast_messages::{BroadcastMessage, SyncEvent}; -use crate::constants::{WS_CLIENT_TIMEOUT, WS_HEARTBEAT_INTERVAL}; -use crate::extractors::client_auth::APIClientAuth; -use crate::server::HttpResult; -use actix_web::dev::Payload; -use actix_web::{web, FromRequest, HttpRequest}; -use actix_ws::Message; -use futures_util::StreamExt; -use std::time::Instant; -use tokio::select; -use tokio::sync::broadcast; -use tokio::sync::broadcast::Receiver; -use tokio::time::interval; - -/// Messages send to the client -#[derive(Debug, serde::Deserialize, serde::Serialize)] -#[serde(tag = "type")] -pub enum WsMessage { - Sync(SyncEvent), -} - -/// Main WS route -pub async fn ws( - req: HttpRequest, - stream: web::Payload, - tx: web::Data>, -) -> HttpResult { - // Forcefully ignore request payload by manually extracting authentication information - let auth = APIClientAuth::from_request(&req, &mut Payload::None).await?; - - let (res, session, msg_stream) = actix_ws::handle(&req, stream)?; - - // Ask for sync client to be started - if let Err(e) = tx.send(BroadcastMessage::StartSyncTaskForUser( - auth.user.user_id.clone(), - )) { - log::error!("Failed to send StartSyncTaskForUser: {e}"); - } - - let rx = tx.subscribe(); - - // spawn websocket handler (and don't await it) so that the response is returned immediately - actix_web::rt::spawn(ws_handler(session, msg_stream, auth, rx)); - - Ok(res) -} - -pub async fn ws_handler( - mut session: actix_ws::Session, - mut msg_stream: actix_ws::MessageStream, - auth: APIClientAuth, - mut rx: Receiver, -) { - log::info!("WS connected"); - - let mut last_heartbeat = Instant::now(); - let mut interval = interval(WS_HEARTBEAT_INTERVAL); - - let reason = loop { - // waits for either `msg_stream` to receive a message from the client, the broadcast channel - // to send a message, or the heartbeat interval timer to tick, yielding the value of - // whichever one is ready first - select! { - ws_msg = rx.recv() => { - let msg = match ws_msg { - Ok(msg) => msg, - Err(broadcast::error::RecvError::Closed) => break None, - Err(broadcast::error::RecvError::Lagged(_)) => continue, - }; - - match msg { - BroadcastMessage::CloseClientSession(id) => { - if let Some(client) = &auth.client { - if client.id == id { - log::info!( - "closing client session {id:?} of user {:?} as requested", auth.user.user_id - ); - break None; - } - } - }, - BroadcastMessage::CloseAllUserSessions(userid) => { - if userid == auth.user.user_id { - log::info!( - "closing WS session of user {userid:?} as requested" - ); - break None; - } - } - - BroadcastMessage::SyncEvent(userid, event) => { - if userid != auth.user.user_id { - continue; - } - - // Send the message to the websocket - if let Ok(msg) = serde_json::to_string(&WsMessage::Sync(*event)) { - if let Err(e) = session.text(msg).await { - log::error!("Failed to send SyncEvent: {e}"); - } - } - } - _ => {}}; - - } - - // heartbeat interval ticked - _tick = interval.tick() => { - // if no heartbeat ping/pong received recently, close the connection - if Instant::now().duration_since(last_heartbeat) > WS_CLIENT_TIMEOUT { - log::info!( - "client has not sent heartbeat in over {WS_CLIENT_TIMEOUT:?}; disconnecting" - ); - - break None; - } - - // send heartbeat ping - let _ = session.ping(b"").await; - }, - - msg = msg_stream.next() => { - let msg = match msg { - // received message from WebSocket client - Some(Ok(msg)) => msg, - - // client WebSocket stream error - Some(Err(err)) => { - log::error!("{err}"); - break None; - } - - // client WebSocket stream ended - None => break None - }; - - log::debug!("msg: {msg:?}"); - - match msg { - Message::Text(s) => { - log::info!("Text message: {s}"); - } - - Message::Binary(_) => { - // drop client's binary messages - } - - Message::Close(reason) => { - break reason; - } - - Message::Ping(bytes) => { - last_heartbeat = Instant::now(); - let _ = session.pong(&bytes).await; - } - - Message::Pong(_) => { - last_heartbeat = Instant::now(); - } - - Message::Continuation(_) => { - log::warn!("no support for continuation frames"); - } - - // no-op; ignore - Message::Nop => {} - }; - } - } - }; - - // attempt to close connection gracefully - let _ = session.close(reason).await; - - log::info!("WS disconnected"); -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 007cd9f..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -use actix_web::http::StatusCode; -use actix_web::{HttpResponse, ResponseError}; -use std::error::Error; -use std::fmt::Debug; - -pub mod api; -pub mod web_ui; - -#[derive(thiserror::Error, Debug)] -pub enum HttpFailure { - #[error("this resource requires higher privileges")] - Forbidden, - #[error("this resource was not found")] - NotFound, - #[error("Actix web error")] - ActixError(#[from] actix_web::Error), - #[error("an unhandled session insert error occurred")] - SessionInsertError(#[from] actix_session::SessionInsertError), - #[error("an unhandled session error occurred")] - SessionError(#[from] actix_session::SessionGetError), - #[error("an unspecified open id error occurred: {0}")] - OpenID(Box), - #[error("an error occurred while fetching user configuration: {0}")] - FetchUserConfig(anyhow::Error), - #[error("an unspecified internal error occurred: {0}")] - InternalError(#[from] anyhow::Error), - #[error("a matrix api client error occurred: {0}")] - MatrixApiClientError(#[from] ruma::api::client::Error), - #[error("a matrix client error occurred: {0}")] - MatrixClientError(String), - #[error("a serde_json error occurred: {0}")] - SerdeJsonError(#[from] serde_json::error::Error), -} - -impl ResponseError for HttpFailure { - fn status_code(&self) -> StatusCode { - match &self { - Self::Forbidden => StatusCode::FORBIDDEN, - Self::NotFound => StatusCode::NOT_FOUND, - _ => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()).body(self.to_string()) - } -} - -pub type HttpResult = Result; diff --git a/src/server/web_ui.rs b/src/server/web_ui.rs deleted file mode 100644 index e8f68b1..0000000 --- a/src/server/web_ui.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::app_config::AppConfig; -use crate::broadcast_messages::BroadcastMessage; -use crate::constants::{STATE_KEY, USER_SESSION_KEY}; -use crate::server::{HttpFailure, HttpResult}; -use crate::user::{APIClient, APIClientID, User, UserConfig, UserID}; -use crate::utils::base_utils; -use actix_session::Session; -use actix_web::{web, HttpResponse}; -use askama::Template; -use ipnet::IpNet; -use light_openid::primitives::OpenIDConfig; -use std::str::FromStr; -use tokio::sync::broadcast; - -/// Static assets -#[derive(rust_embed::Embed)] -#[folder = "assets/"] -struct Assets; - -/// Serve static file -pub async fn static_file(path: web::Path) -> HttpResult { - match Assets::get(path.as_ref()) { - Some(content) => Ok(HttpResponse::Ok() - .content_type( - mime_guess::from_path(path.as_str()) - .first_or_octet_stream() - .as_ref(), - ) - .body(content.data.into_owned())), - None => Ok(HttpResponse::NotFound().body("404 Not Found")), - } -} - -#[derive(askama::Template)] -#[template(path = "index.html")] -struct HomeTemplate { - name: String, - user_id: UserID, - matrix_token: String, - clients: Vec, - success_message: Option, - error_message: Option, -} - -/// HTTP form request -#[derive(serde::Deserialize)] -pub struct FormRequest { - /// Update matrix token - new_matrix_token: Option, - - /// Create a new client - new_client_desc: Option, - - /// Restrict new client to a given network - ip_network: Option, - - /// Grant read only access to client - readonly_client: Option, - - /// Delete a specified client id - delete_client_id: Option, -} - -/// Main route -pub async fn home( - session: Session, - form_req: Option>, - tx: web::Data>, -) -> HttpResult { - // Get user information, requesting authentication if information is missing - let Some(user): Option = session.get(USER_SESSION_KEY)? else { - // Generate auth state - let state = base_utils::rand_str(50); - session.insert(STATE_KEY, &state)?; - - let oidc = AppConfig::get().openid_provider(); - let config = OpenIDConfig::load_from_url(oidc.configuration_url) - .await - .map_err(HttpFailure::OpenID)?; - - let auth_url = config.gen_authorization_url(oidc.client_id, &state, &oidc.redirect_url); - - return Ok(HttpResponse::Found() - .append_header(("location", auth_url)) - .finish()); - }; - - let mut success_message = None; - let mut error_message = None; - - // Retrieve user configuration - let mut config = UserConfig::load(&user.id, true) - .await - .map_err(HttpFailure::FetchUserConfig)?; - - if let Some(form_req) = form_req { - // Update matrix token, if requested - if let Some(t) = form_req.0.new_matrix_token { - if t.len() < 3 { - error_message = Some("Specified Matrix token is too short!".to_string()); - } else { - config.matrix_token = t; - config.save().await?; - success_message = Some("Matrix token was successfully updated!".to_string()); - - // Close sync task - if let Err(e) = tx.send(BroadcastMessage::StopSyncTaskForUser(user.id.clone())) { - log::error!("Failed to send StopSyncClientForUser: {e}"); - } - - // Invalidate all Ws connections - if let Err(e) = tx.send(BroadcastMessage::CloseAllUserSessions(user.id.clone())) { - log::error!("Failed to send CloseAllUserSessions: {e}"); - } - } - } - - // Create a new client, if requested - if let Some(new_token_desc) = form_req.0.new_client_desc { - let ip_net = match form_req.0.ip_network.as_deref() { - None | Some("") => None, - Some(e) => match IpNet::from_str(e) { - Ok(n) => Some(n), - Err(e) => { - log::error!("Failed to parse IP network provided by user: {e}"); - error_message = Some(format!("Failed to parse restricted IP network: {e}")); - None - } - }, - }; - - if error_message.is_none() { - let mut token = APIClient::generate(new_token_desc, ip_net); - token.readonly_client = form_req.0.readonly_client.is_some(); - success_message = Some(format!("The secret of your new token is '{}'. Be sure to write it somewhere as you will not be able to recover it later!", token.secret)); - config.clients.push(token); - config.save().await?; - } - } - - // Delete a client - if let Some(delete_client_id) = form_req.0.delete_client_id { - config.clients.retain(|c| c.id != delete_client_id); - config.save().await?; - success_message = Some("The client was successfully deleted!".to_string()); - - if let Err(e) = tx.send(BroadcastMessage::CloseClientSession(delete_client_id)) { - log::error!("Failed to send CloseClientSession: {e}"); - } - } - } - - // Render page - Ok(HttpResponse::Ok() - .insert_header(("content-type", "text/html")) - .body( - HomeTemplate { - name: user.name, - user_id: user.id, - matrix_token: config.obfuscated_matrix_token(), - clients: config.clients, - success_message, - error_message, - } - .render() - .unwrap(), - )) -} - -#[derive(serde::Deserialize)] -pub struct AuthCallbackQuery { - code: String, - state: String, -} - -/// Authenticate user callback -pub async fn oidc_cb(session: Session, query: web::Query) -> HttpResult { - if session.get(STATE_KEY)? != Some(query.state.to_string()) { - return Ok(HttpResponse::BadRequest() - .append_header(("content-type", "text/html")) - .body("State mismatch! Try again")); - } - - let oidc = AppConfig::get().openid_provider(); - let config = OpenIDConfig::load_from_url(oidc.configuration_url) - .await - .map_err(HttpFailure::OpenID)?; - - let (token, _) = match config - .request_token( - oidc.client_id, - oidc.client_secret, - &query.code, - &oidc.redirect_url, - ) - .await - { - Ok(t) => t, - Err(e) => { - log::error!("Failed to request user token! {e}"); - - return Ok(HttpResponse::BadRequest() - .append_header(("content-type", "text/html")) - .body("Authentication failed! Try again")); - } - }; - - let (user, _) = config - .request_user_info(&token) - .await - .map_err(HttpFailure::OpenID)?; - - let user = User { - id: UserID(user.sub), - name: user.name.unwrap_or("no_name".to_string()), - email: user.email.unwrap_or("no@mail.com".to_string()), - }; - log::info!("Successful authentication as {user:?}"); - session.insert(USER_SESSION_KEY, user)?; - - Ok(HttpResponse::Found() - .insert_header(("location", "/")) - .finish()) -} - -/// De-authenticate user -pub async fn sign_out(session: Session) -> HttpResult { - session.remove(USER_SESSION_KEY); - - Ok(HttpResponse::Found() - .insert_header(("location", "/")) - .finish()) -} - -#[derive(askama::Template)] -#[template(path = "ws_debug.html")] -struct WsDebugTemplate { - name: String, -} - -/// WebSocket debug -pub async fn ws_debug(session: Session) -> HttpResult { - let Some(user): Option = session.get(USER_SESSION_KEY)? else { - return Ok(HttpResponse::Found() - .insert_header(("location", "/")) - .finish()); - }; - - Ok(HttpResponse::Ok() - .content_type("text/html") - .body(WsDebugTemplate { name: user.name }.render().unwrap())) -} diff --git a/src/sync_client.rs b/src/sync_client.rs deleted file mode 100644 index a2b9fb2..0000000 --- a/src/sync_client.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::broadcast_messages::{BroadcastMessage, SyncEvent}; -use crate::user::{UserConfig, UserID}; -use futures_util::TryStreamExt; -use ruma::api::client::sync::sync_events; -use ruma::assign; -use ruma::presence::PresenceState; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::broadcast; - -/// ID of sync client -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SyncClientID(uuid::Uuid); - -/// Sync client launcher loop -pub async fn sync_client_manager(tx: broadcast::Sender) -> ! { - let mut rx = tx.subscribe(); - let tx = Arc::new(tx.clone()); - - let mut running_tasks = HashMap::new(); - - while let Ok(msg) = rx.recv().await { - match msg { - BroadcastMessage::StartSyncTaskForUser(user_id) => { - if running_tasks.contains_key(&user_id) { - log::info!("Won't start sync task for user {user_id:?} because a task is already running for this user!"); - continue; - } - - log::info!("Start sync task for user {user_id:?}"); - let task_id = SyncClientID(uuid::Uuid::new_v4()); - running_tasks.insert(user_id.clone(), task_id.clone()); - - let tx = tx.clone(); - tokio::task::spawn(async move { - sync_task(task_id, user_id, tx).await; - }); - } - - BroadcastMessage::StopSyncTaskForUser(user_id) => { - // Check if a task is running for this user - if let Some(task_id) = running_tasks.remove(&user_id) { - log::info!("Stop sync task for user {user_id:?}"); - tx.send(BroadcastMessage::StopSyncClient(task_id)).unwrap(); - } else { - log::info!("Not stopping sync task for user {user_id:?}: not running"); - } - } - - _ => {} - } - } - - panic!("Sync client manager stopped unexpectedly!"); -} - -/// Sync task for a single user -async fn sync_task( - id: SyncClientID, - user_id: UserID, - tx: Arc>, -) { - let mut rx = tx.subscribe(); - - let Ok(user_config) = UserConfig::load(&user_id, false).await else { - log::error!("Failed to load user config in sync thread!"); - return; - }; - - let client = match user_config.matrix_client().await { - Err(e) => { - log::error!("Failed to load matrix client for user {user_id:?}: {e}"); - return; - } - Ok(client) => client, - }; - - let initial_sync_response = match client - .send_request(assign!(sync_events::v3::Request::new(), { - filter: None, - })) - .await - { - Ok(res) => res, - Err(e) => { - log::error!("Failed to perform initial sync request for user {user_id:?}! {e}"); - return; - } - }; - - let mut sync_stream = Box::pin(client.sync( - None, - initial_sync_response.next_batch, - PresenceState::Offline, - Some(Duration::from_secs(30)), - )); - - loop { - tokio::select! { - // Message from tokio broadcast - msg = rx.recv() => { - match msg { - Ok(BroadcastMessage::StopSyncClient(client_id)) => { - if client_id == id { - log::info!("A request was received to stop this client! {id:?} for user {user_id:?}"); - break; - } - } - - Err(e) => { - log::error!("Failed to receive a message from broadcast! {e}"); - return; - } - - Ok(_) => {} - } - } - - // Message from Matrix - msg_stream = sync_stream.try_next() => { - match msg_stream { - Ok(Some(msg)) => { - log::debug!("Received new message from Matrix: {msg:#?}"); - if let Err(e) = tx.send(BroadcastMessage::SyncEvent(user_id.clone(), Box::new(SyncEvent { - rooms: msg.rooms,presence: msg.presence, - account_data: msg.account_data, - to_device: msg.to_device, - device_lists: msg.device_lists, - }))) { - log::error!("Failed to propagate event! {e}"); - } - } - Ok(None) => { - log::debug!("Received no message from Matrix"); - } - Err(e) => { - log::error!("Failed to receive a message from Matrix! {e}"); - return; - } - } - } - } - } -} diff --git a/src/user.rs b/src/user.rs deleted file mode 100644 index c7397f0..0000000 --- a/src/user.rs +++ /dev/null @@ -1,241 +0,0 @@ -use s3::error::S3Error; -use s3::request::ResponseData; -use s3::{Bucket, BucketConfiguration}; -use std::str::FromStr; -use thiserror::Error; - -use crate::app_config::AppConfig; -use crate::constants::TOKEN_LEN; -use crate::utils::base_utils::{curr_time, format_time, rand_str}; - -type HttpClient = ruma::client::http_client::HyperNativeTls; -pub type RumaClient = ruma::Client; - -#[derive(Error, Debug)] -pub enum UserError { - #[error("failed to fetch user configuration: {0}")] - FetchUserConfig(S3Error), - #[error("missing matrix token")] - MissingMatrixToken, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -pub struct UserID(pub String); - -impl UserID { - fn conf_path_in_bucket(&self) -> String { - format!("confs/{}.json", urlencoding::encode(&self.0)) - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct User { - pub id: UserID, - pub name: String, - pub email: String, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] -pub struct APIClientID(pub uuid::Uuid); - -impl APIClientID { - pub fn generate() -> Self { - Self(uuid::Uuid::new_v4()) - } -} - -impl FromStr for APIClientID { - type Err = uuid::Error; - - fn from_str(s: &str) -> Result { - Ok(Self(uuid::Uuid::from_str(s)?)) - } -} - -/// Single API client information -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct APIClient { - /// Client unique ID - pub id: APIClientID, - - /// Client description - pub description: String, - - /// Restricted API network for token - pub network: Option, - - /// Client secret - pub secret: String, - - /// Client creation time - pub created: u64, - - /// Client last usage time - pub used: u64, - - /// Read only access - pub readonly_client: bool, -} - -impl APIClient { - pub fn fmt_created(&self) -> String { - format_time(self.created).unwrap_or_default() - } - - pub fn fmt_used(&self) -> String { - format_time(self.used).unwrap_or_default() - } - - pub fn need_update_last_used(&self) -> bool { - self.used + 60 * 15 < curr_time().unwrap() - } -} - -impl APIClient { - /// Generate a new API client - pub fn generate(description: String, network: Option) -> Self { - Self { - id: APIClientID::generate(), - description, - network, - secret: rand_str(TOKEN_LEN), - created: curr_time().unwrap(), - used: curr_time().unwrap(), - readonly_client: true, - } - } -} - -#[derive(serde::Serialize, serde::Deserialize, Clone)] -pub struct UserConfig { - /// Target user ID - pub user_id: UserID, - - /// Configuration creation time - pub created: u64, - - /// Configuration last update time - pub updated: u64, - - /// Current user matrix token - pub matrix_token: String, - - /// API clients - pub clients: Vec, -} - -impl UserConfig { - /// Create S3 bucket if required - pub async fn create_bucket_if_required() -> anyhow::Result<()> { - if AppConfig::get().s3_skip_auto_create_bucket { - log::debug!("Skipping bucket existence check"); - return Ok(()); - } - - let bucket = AppConfig::get().s3_bucket()?; - - match bucket.location().await { - Ok(_) => { - log::debug!("The bucket already exists."); - return Ok(()); - } - Err(S3Error::HttpFailWithBody(404, s)) if s.contains("NoSuchKey") => { - log::warn!("Failed to fetch bucket location, but it seems that bucket exists."); - return Ok(()); - } - Err(S3Error::HttpFailWithBody(404, s)) if s.contains("NoSuchBucket") => { - log::warn!("The bucket does not seem to exists, trying to create it!") - } - Err(e) => { - log::error!("Got unexpected error when querying bucket info: {e}"); - return Err(e.into()); - } - } - - Bucket::create_with_path_style( - &bucket.name, - bucket.region, - AppConfig::get().s3_credentials()?, - BucketConfiguration::private(), - ) - .await?; - - Ok(()) - } - - /// Get current user configuration - pub async fn load(user_id: &UserID, allow_non_existing: bool) -> anyhow::Result { - let res: Result = AppConfig::get() - .s3_bucket()? - .get_object(user_id.conf_path_in_bucket()) - .await; - - match (res, allow_non_existing) { - (Ok(res), _) => Ok(serde_json::from_slice(res.as_slice())?), - (Err(S3Error::HttpFailWithBody(404, _)), true) => { - log::warn!("User configuration does not exists, generating a new one..."); - Ok(Self { - user_id: user_id.clone(), - created: curr_time()?, - updated: curr_time()?, - matrix_token: "".to_string(), - clients: vec![], - }) - } - (Err(e), _) => Err(UserError::FetchUserConfig(e).into()), - } - } - - /// Set user configuration - pub async fn save(&mut self) -> anyhow::Result<()> { - log::info!("Saving new configuration for user {:?}", self.user_id); - - self.updated = curr_time()?; - - // Save updated configuration - AppConfig::get() - .s3_bucket()? - .put_object( - self.user_id.conf_path_in_bucket(), - &serde_json::to_vec(self)?, - ) - .await?; - - Ok(()) - } - - /// Get current user matrix token, in an obfuscated form - pub fn obfuscated_matrix_token(&self) -> String { - self.matrix_token - .chars() - .enumerate() - .map(|(num, c)| match num { - 0 | 1 => c, - _ => 'X', - }) - .collect() - } - - /// Find a client by its id - pub fn find_client_by_id(&self, id: &APIClientID) -> Option<&APIClient> { - self.clients.iter().find(|c| &c.id == id) - } - - /// Find a client by its id and get a mutable reference - pub fn find_client_by_id_mut(&mut self, id: &APIClientID) -> Option<&mut APIClient> { - self.clients.iter_mut().find(|c| &c.id == id) - } - - /// Get a matrix client instance for the current user - pub async fn matrix_client(&self) -> anyhow::Result { - if self.matrix_token.is_empty() { - return Err(UserError::MissingMatrixToken.into()); - } - - Ok(ruma::Client::builder() - .homeserver_url(AppConfig::get().matrix_homeserver.to_string()) - .access_token(Some(self.matrix_token.clone())) - .build() - .await?) - } -} diff --git a/src/utils/base_utils.rs b/src/utils/base_utils.rs deleted file mode 100644 index 68a919f..0000000 --- a/src/utils/base_utils.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rand::distr::{Alphanumeric, SampleString}; -use std::time::{SystemTime, UNIX_EPOCH}; - -/// Generate a random string of a given size -pub fn rand_str(len: usize) -> String { - Alphanumeric.sample_string(&mut rand::rng(), len) -} - -/// Get current time -pub fn curr_time() -> anyhow::Result { - Ok(SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|t| t.as_secs())?) -} - -/// Format time -pub fn format_time(time: u64) -> Option { - let time = chrono::DateTime::from_timestamp(time as i64, 0)?; - Some(time.naive_local().to_string()) -} diff --git a/src/utils/matrix_utils.rs b/src/utils/matrix_utils.rs deleted file mode 100644 index f780db9..0000000 --- a/src/utils/matrix_utils.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ruma::OwnedMxcUri; -use serde::ser::SerializeMap; -use serde::{Serialize, Serializer}; - -pub struct ApiMxcURI(pub OwnedMxcUri); - -impl Serialize for ApiMxcURI { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(3))?; - map.serialize_entry("uri", &self.0)?; - map.serialize_entry("server_name", &self.0.server_name().ok())?; - map.serialize_entry("media_id", &self.0.media_id().ok())?; - map.end() - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index a45b810..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod base_utils; -pub mod matrix_utils; diff --git a/templates/base_page.html b/templates/base_page.html deleted file mode 100644 index f86c767..0000000 --- a/templates/base_page.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Matrix GW - - - - - - - - -
- -
- - -
- {% block content %} - TO_REPLACE - {% endblock content %} -
- - - - \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index f1360f1..0000000 --- a/templates/index.html +++ /dev/null @@ -1,146 +0,0 @@ -{% extends "base_page.html" %} -{% block content %} - -{% if let Some(msg) = success_message %} -
- {{ msg }} -
-{% endif %} - - -{% if let Some(msg) = error_message %} -
- {{ msg }} -
-{% endif %} - - -
Current user ID: {{ user_id.0 }}
- - -
-
Registered clients
-
- {% if clients.len() > 0 %} - - - - - - - - - - - - - - {% for client in clients %} - - - - - - - - - - {% endfor %} - -
IDDescriptionRead onlyNetworkCreatedUsed
{{ client.id.0 }}{{ client.description }} - {% if client.readonly_client %} - YES - {% else %} - NO - {% endif %} - - {% if let Some(net) = client.network %} - {{ net }} - {% else %} - Unrestricted - {% endif %} - {{ client.fmt_created() }}{{ client.fmt_used() }} - -
- {% endif %} - - {% if clients.len() == 0 %} -

No client registered yet!

- {% endif %} -
-
- - -
-
New client
-
-
-
- - - Client description helps with identification. -
-
- - - Restrict the networks this IP address can be used from. -
- -
- -
- - -
- -
- - - -
-
-
- - -
-
Matrix authentication token
-
-

To obtain a new Matrix authentication token:

-
    -
  1. Sign in to Element from a private browser window
  2. -
  3. Open All settings and access the Help & About tag
  4. -
  5. Expand Access Token and copy the value
  6. -
  7. Paste the copied value below
  8. -
  9. Close the private browser window without signing out!
  10. -
- -

You should not need to replace this value unless you explicitly signed out the associated browser - session.

- -

Tip: you can rename the session to easily identify it among all your other sessions!

- -
-
- - - Changing this value will reset all active - connections - to Matrix GW. -
- - -
-
-
- - -{% endblock content %} \ No newline at end of file diff --git a/templates/ws_debug.html b/templates/ws_debug.html deleted file mode 100644 index d3f9119..0000000 --- a/templates/ws_debug.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "base_page.html" %} -{% block content %} - - - - -

WS Debug

- -
- - - - State: DISCONNECTED -
- -
-
-
INFO
-
Welcome!
-
-
- - -{% endblock content %} \ No newline at end of file -- 2.49.1 From 602edaae79ef5437fd9313ce3957fb61fe981737 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 31 Oct 2025 18:29:31 +0100 Subject: [PATCH 02/21] Start to build backend base --- README.md | 18 +- matrixgw_backend/Cargo.lock | 3097 +++++++++++++++++++ matrixgw_backend/Cargo.toml | 10 + matrixgw_backend/docker-compose.yml | 12 +- matrixgw_backend/docker/dex/dex.config.yaml | 26 + matrixgw_backend/src/app_config.rs | 187 ++ matrixgw_backend/src/lib.rs | 1 + matrixgw_backend/src/main.rs | 28 +- 8 files changed, 3373 insertions(+), 6 deletions(-) create mode 100644 matrixgw_backend/Cargo.lock create mode 100644 matrixgw_backend/docker/dex/dex.config.yaml create mode 100644 matrixgw_backend/src/app_config.rs create mode 100644 matrixgw_backend/src/lib.rs diff --git a/README.md b/README.md index 62e7b9b..8cc8aa6 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ Project that expose a simple API to make use of Matrix API. It acts as a Matrix **Known limitations**: - Supports only a limited subset of Matrix API -- Does not support E2E encryption - Does not support spaces -Project written in Rust. Releases are published on Docker Hub. +Project written in Rust and TypeScript. Releases are published on Docker Hub. ## Docker image options ```bash @@ -17,7 +16,10 @@ docker run --rm -it docker.io/pierre42100/matrix_gateway --help ``` ## Setup dev environment + +### Dependencies ``` +cd matrixgw_backend mkdir -p storage/maspostgres storage/synapse storage/minio docker compose up ``` @@ -42,3 +44,15 @@ Auto-created Matrix accounts: Minio administration credentials: `minioadmin` : `minioadmin` +### Backend +```bash +cd matrixgw_backend +cargo fmt && cargo clippy && cargo run -- +``` + +### Frontend +```bash +cd matrixgw_frontend +npm install +npm run dev +``` \ No newline at end of file diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock new file mode 100644 index 0000000..079c0e7 --- /dev/null +++ b/matrixgw_backend/Cargo.lock @@ -0,0 +1,3097 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64 0.22.1", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-session" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "400c27fd4cdbe0082b7bbd29ac44a3070cbda1b2114138dc106ba39fe2f90dff" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "anyhow", + "derive_more", + "rand 0.9.2", + "redis", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.10", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http 1.3.1", + "log", + "native-tls", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-creds" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13804829a843b3f26e151c97acbb315ee1177a2724690edfcd28f1894146200" +dependencies = [ + "attohttpc", + "home", + "log", + "quick-xml", + "rust-ini", + "serde", + "thiserror", + "time", + "url", +] + +[[package]] +name = "aws-region" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5532f65342f789f9c1b7078ea9c9cd9293cd62dcc284fa99adc4a1c9ba43469c" +dependencies = [ + "thiserror", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", +] + +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "aes-gcm", + "base64 0.20.0", + "hkdf", + "hmac", + "percent-encoding", + "rand 0.8.5", + "sha2", + "subtle", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.3.1", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +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 = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matrixgw_backend" +version = "0.1.0" +dependencies = [ + "actix-session", + "actix-web", + "anyhow", + "clap", + "env_logger", + "lazy_static", + "log", + "rust-s3", + "serde", + "tokio", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minidom" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" +dependencies = [ + "rxml", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redis" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" +dependencies = [ + "arc-swap", + "backon", + "bytes", + "cfg-if", + "combine", + "futures-channel", + "futures-util", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "ryu", + "socket2 0.6.1", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust-s3" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f9b973bd4097f5bb47e5827dcb9fb5dc17e93879e46badc27d2a4e9a4e5588" +dependencies = [ + "async-trait", + "aws-creds", + "aws-region", + "base64 0.22.1", + "bytes", + "cfg-if", + "futures-util", + "hex", + "hmac", + "http 1.3.1", + "log", + "maybe-async", + "md5", + "minidom", + "percent-encoding", + "quick-xml", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "sha2", + "sysinfo", + "thiserror", + "time", + "tokio", + "tokio-stream", + "url", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rxml" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" +dependencies = [ + "bytes", + "rxml_validation", +] + +[[package]] +name = "rxml_validation" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" +dependencies = [ + "compact_str", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.3.1", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index ab0c915..c282d75 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -4,3 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] +env_logger = "0.11.8" +log = "0.4.28" +clap = { version = "4.5.51", features = ["derive", "env"] } +lazy_static = "1.5.0" +anyhow = "1.0.100" +serde = { version = "1.0.228", features = ["derive"] } +rust-s3 = { version = "0.37.0", features = ["tokio"] } +tokio = { version = "1.48.0", features = ["full"] } +actix-web = "4.11.0" +actix-session = { version = "0.11.0", features = ["redis-session"] } \ No newline at end of file diff --git a/matrixgw_backend/docker-compose.yml b/matrixgw_backend/docker-compose.yml index 26d2b1d..fec7c62 100644 --- a/matrixgw_backend/docker-compose.yml +++ b/matrixgw_backend/docker-compose.yml @@ -80,10 +80,18 @@ services: element: image: docker.io/vectorim/element-web ports: - - 8080:80/tcp + - "8080:80/tcp" volumes: - ./docker/element/config.json:/app/config.json:ro + oidc: + image: dexidp/dex + ports: + - "9001:9001" + volumes: + - ./docker/dex:/conf:ro + command: [ "dex", "serve", "/conf/dex.config.yaml" ] + minio: image: quay.io/minio/minio command: minio server --console-address ":9002" /data @@ -101,7 +109,7 @@ services: image: redis:alpine command: redis-server --requirepass ${REDIS_PASS:-secretredis} ports: - - 6379:6379 + - "6379:6379" volumes: - ./storage/redis-data:/data - ./storage/redis-conf:/usr/local/etc/redis/redis.conf diff --git a/matrixgw_backend/docker/dex/dex.config.yaml b/matrixgw_backend/docker/dex/dex.config.yaml new file mode 100644 index 0000000..7704800 --- /dev/null +++ b/matrixgw_backend/docker/dex/dex.config.yaml @@ -0,0 +1,26 @@ +issuer: http://127.0.0.1:9001/dex + +storage: + type: memory + +web: + http: 0.0.0.0:9001 + +oauth2: + # Automate some clicking + # Note: this might actually make some tests pass that otherwise wouldn't. + skipApprovalScreen: false + +connectors: + # Note: this might actually make some tests pass that otherwise wouldn't. + - type: mockCallback + id: mock + name: Example + +# Basic OP test suite requires two clients. +staticClients: + - id: foo + secret: bar + redirectURIs: + - http://localhost:8000/oidc_cb + name: Project diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs new file mode 100644 index 0000000..a1271d7 --- /dev/null +++ b/matrixgw_backend/src/app_config.rs @@ -0,0 +1,187 @@ +use clap::Parser; +use s3::creds::Credentials; +use s3::{Bucket, Region}; + +/// Matrix gateway backend API +#[derive(Parser, Debug, Clone)] +#[clap(author, version, about, long_about = None)] +pub struct AppConfig { + /// Listen address + #[clap(short, long, env, default_value = "0.0.0.0:8000")] + pub listen_address: String, + + /// Website origin + #[clap(short, long, env, default_value = "http://localhost:8000")] + pub website_origin: String, + + /// Proxy IP, might end with a star "*" + #[clap(short, long, env)] + pub proxy_ip: Option, + + /// Secret key, used to secure some resources. Must be randomly generated + #[clap(short = 'S', long, env, default_value = "")] + secret: String, + + /// Matrix homeserver origin + #[clap(short, long, env, default_value = "http://127.0.0.1:8448")] + pub matrix_homeserver: String, + + /// Redis connection hostname + #[clap(long, env, default_value = "localhost")] + redis_hostname: String, + + /// Redis connection port + #[clap(long, env, default_value_t = 6379)] + redis_port: u16, + + /// Redis database number + #[clap(long, env, default_value_t = 0)] + redis_db_number: i64, + + /// Redis username + #[clap(long, env)] + redis_username: Option, + + /// Redis password + #[clap(long, env, default_value = "secretredis")] + redis_password: String, + + /// URL where the OpenID configuration can be found + #[arg( + long, + env, + default_value = "http://localhost:9001/dex/.well-known/openid-configuration" + )] + pub oidc_configuration_url: String, + + /// OpenID client ID + #[arg(long, env, default_value = "foo")] + pub oidc_client_id: String, + + /// OpenID client secret + #[arg(long, env, default_value = "bar")] + pub oidc_client_secret: String, + + /// OpenID login redirect URL + #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] + oidc_redirect_url: String, + + /// S3 Bucket name + #[arg(long, env, default_value = "matrix-gw")] + s3_bucket_name: String, + + /// S3 region (if not using Minio) + #[arg(long, env, default_value = "eu-central-1")] + s3_region: String, + + /// S3 API endpoint + #[arg(long, env, default_value = "http://localhost:9000")] + s3_endpoint: String, + + /// S3 access key + #[arg(long, env, default_value = "minioadmin")] + s3_access_key: String, + + /// S3 secret key + #[arg(long, env, default_value = "minioadmin")] + s3_secret_key: String, + + /// S3 skip auto create bucket if not existing + #[arg(long, env)] + pub s3_skip_auto_create_bucket: bool, +} + +lazy_static::lazy_static! { + static ref ARGS: AppConfig = { + AppConfig::parse() + }; +} + +impl AppConfig { + /// Get parsed command line arguments + pub fn get() -> &'static AppConfig { + &ARGS + } + + /// Get app secret + pub fn secret(&self) -> &str { + let mut secret = self.secret.as_str(); + + if cfg!(debug_assertions) && secret.is_empty() { + secret = "DEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEY"; + } + + if secret.is_empty() { + panic!("SECRET is undefined or too short (min 64 chars)!") + } + + secret + } + + /// Get Redis connection configuration + pub fn redis_connection_string(&self) -> String { + format!( + "redis://{}:{}@{}:{}/{}", + self.redis_username.as_deref().unwrap_or(""), + self.redis_password, + self.redis_hostname, + self.redis_port, + self.redis_db_number + ) + } + + /// Get OpenID providers configuration + pub fn openid_provider(&self) -> OIDCProvider<'_> { + OIDCProvider { + client_id: self.oidc_client_id.as_str(), + client_secret: self.oidc_client_secret.as_str(), + configuration_url: self.oidc_configuration_url.as_str(), + redirect_url: self + .oidc_redirect_url + .replace("APP_ORIGIN", &self.website_origin), + } + } + + /// Get s3 bucket credentials + pub fn s3_credentials(&self) -> anyhow::Result { + Ok(Credentials::new( + Some(&self.s3_access_key), + Some(&self.s3_secret_key), + None, + None, + None, + )?) + } + + /// Get S3 bucket + pub fn s3_bucket(&self) -> anyhow::Result> { + Ok(Bucket::new( + &self.s3_bucket_name, + Region::Custom { + region: self.s3_region.to_string(), + endpoint: self.s3_endpoint.to_string(), + }, + self.s3_credentials()?, + )? + .with_path_style()) + } +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct OIDCProvider<'a> { + pub client_id: &'a str, + pub client_secret: &'a str, + pub configuration_url: &'a str, + pub redirect_url: String, +} + +#[cfg(test)] +mod test { + use crate::app_config::AppConfig; + + #[test] + fn verify_cli() { + use clap::CommandFactory; + AppConfig::command().debug_assert() + } +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs new file mode 100644 index 0000000..62a6f82 --- /dev/null +++ b/matrixgw_backend/src/lib.rs @@ -0,0 +1 @@ +pub mod app_config; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index e7a11a9..8bc6c8c 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -1,3 +1,27 @@ -fn main() { - println!("Hello, world!"); +use actix_session::storage::RedisSessionStore; +use actix_web::cookie::Key; +use actix_web::{App, HttpServer}; +use matrixgw_backend::app_config::AppConfig; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let secret_key = Key::from(AppConfig::get().secret().as_bytes()); + + let redis_store = RedisSessionStore::new(AppConfig::get().redis_connection_string()) + .await + .expect("Failed to connect to Redis!"); + + log::info!( + "Starting to listen on {} for {}", + AppConfig::get().listen_address, + AppConfig::get().website_origin + ); + + HttpServer::new(move || App::new()) + .workers(4) + .bind(&AppConfig::get().listen_address)? + .run() + .await } -- 2.49.1 From 830f47b61fe0ddd698f7992cd29d7a4b53cab123 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Nov 2025 19:13:42 +0100 Subject: [PATCH 03/21] Add server config route --- matrixgw_backend/Cargo.lock | 12 +++ matrixgw_backend/Cargo.toml | 3 +- matrixgw_backend/src/app_config.rs | 24 ++++++ matrixgw_backend/src/controllers/mod.rs | 1 + .../src/controllers/server_controller.rs | 74 +++++++++++++++++++ matrixgw_backend/src/lib.rs | 1 + matrixgw_backend/src/main.rs | 33 +++++++-- 7 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 matrixgw_backend/src/controllers/mod.rs create mode 100644 matrixgw_backend/src/controllers/server_controller.rs diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index 079c0e7..a6569f5 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -68,6 +68,17 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-remote-ip" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e" +dependencies = [ + "actix-web", + "futures-util", + "log", +] + [[package]] name = "actix-router" version = "0.5.3" @@ -1405,6 +1416,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" name = "matrixgw_backend" version = "0.1.0" dependencies = [ + "actix-remote-ip", "actix-session", "actix-web", "anyhow", diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index c282d75..9ae5242 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -13,4 +13,5 @@ serde = { version = "1.0.228", features = ["derive"] } rust-s3 = { version = "0.37.0", features = ["tokio"] } tokio = { version = "1.48.0", features = ["full"] } actix-web = "4.11.0" -actix-session = { version = "0.11.0", features = ["redis-session"] } \ No newline at end of file +actix-session = { version = "0.11.0", features = ["redis-session"] } +actix-remote-ip = "0.1.0" \ No newline at end of file diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index a1271d7..7552544 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -18,6 +18,11 @@ pub struct AppConfig { #[clap(short, long, env)] pub proxy_ip: Option, + /// Unsecure : for development, bypass authentication, using the account with the given + /// email address by default + #[clap(long, env)] + unsecure_auto_login_email: Option, + /// Secret key, used to secure some resources. Must be randomly generated #[clap(short = 'S', long, env, default_value = "")] secret: String, @@ -54,6 +59,10 @@ pub struct AppConfig { )] pub oidc_configuration_url: String, + /// OpenID provider name + #[arg(long, env, default_value = "3rd party provider")] + pub oidc_provider_name: String, + /// OpenID client ID #[arg(long, env, default_value = "foo")] pub oidc_client_id: String, @@ -103,6 +112,14 @@ impl AppConfig { &ARGS } + /// Get auto login email (if not empty) + pub fn unsecure_auto_login_email(&self) -> Option<&str> { + match self.unsecure_auto_login_email.as_deref() { + None | Some("") => None, + s => s, + } + } + /// Get app secret pub fn secret(&self) -> &str { let mut secret = self.secret.as_str(); @@ -118,6 +135,11 @@ impl AppConfig { secret } + /// Check if auth is disabled + pub fn is_auth_disabled(&self) -> bool { + self.unsecure_auto_login_email().is_some() + } + /// Get Redis connection configuration pub fn redis_connection_string(&self) -> String { format!( @@ -136,6 +158,7 @@ impl AppConfig { client_id: self.oidc_client_id.as_str(), client_secret: self.oidc_client_secret.as_str(), configuration_url: self.oidc_configuration_url.as_str(), + name: self.oidc_provider_name.as_str(), redirect_url: self .oidc_redirect_url .replace("APP_ORIGIN", &self.website_origin), @@ -169,6 +192,7 @@ impl AppConfig { #[derive(Debug, Clone, serde::Serialize)] pub struct OIDCProvider<'a> { + pub name: &'a str, pub client_id: &'a str, pub client_secret: &'a str, pub configuration_url: &'a str, diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs new file mode 100644 index 0000000..98a0949 --- /dev/null +++ b/matrixgw_backend/src/controllers/mod.rs @@ -0,0 +1 @@ +pub mod server_controller; diff --git a/matrixgw_backend/src/controllers/server_controller.rs b/matrixgw_backend/src/controllers/server_controller.rs new file mode 100644 index 0000000..22683ee --- /dev/null +++ b/matrixgw_backend/src/controllers/server_controller.rs @@ -0,0 +1,74 @@ +use crate::app_config::AppConfig; +use actix_web::HttpResponse; + +/// Serve robots.txt (disallow ranking) +pub async fn robots_txt() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/plain") + .body("User-agent: *\nDisallow: /\n") +} + +#[derive(serde::Serialize)] +pub struct LenConstraints { + min: usize, + max: usize, +} + +impl LenConstraints { + pub fn new(min: usize, max: usize) -> Self { + Self { min, max } + } + pub fn not_empty(max: usize) -> Self { + Self { min: 1, max } + } + pub fn max_only(max: usize) -> Self { + Self { min: 0, max } + } + + pub fn check_str(&self, s: &str) -> bool { + s.len() >= self.min && s.len() <= self.max + } + + pub fn check_u32(&self, v: u32) -> bool { + v >= self.min as u32 && v <= self.max as u32 + } +} + +#[derive(serde::Serialize)] +pub struct ServerConstraints { + pub token_name: LenConstraints, + pub token_ip_net: LenConstraints, + pub token_max_inactivity: LenConstraints, +} + +impl Default for ServerConstraints { + fn default() -> Self { + Self { + token_name: LenConstraints::new(5, 255), + token_ip_net: LenConstraints::max_only(44), + token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365), + } + } +} + +#[derive(serde::Serialize)] +struct ServerConfig { + auth_disabled: bool, + oidc_provider_name: &'static str, + constraints: ServerConstraints, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + auth_disabled: AppConfig::get().is_auth_disabled(), + oidc_provider_name: AppConfig::get().openid_provider().name, + constraints: Default::default(), + } + } +} + +/// Get server static configuration +pub async fn config() -> HttpResponse { + HttpResponse::Ok().json(ServerConfig::default()) +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 62a6f82..2483671 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -1 +1,2 @@ pub mod app_config; +pub mod controllers; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 8bc6c8c..3d6132f 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -1,7 +1,11 @@ +use actix_remote_ip::RemoteIPConfig; +use actix_session::SessionMiddleware; +use actix_session::config::SessionLifecycle; use actix_session::storage::RedisSessionStore; use actix_web::cookie::Key; -use actix_web::{App, HttpServer}; +use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; +use matrixgw_backend::controllers::server_controller; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -19,9 +23,26 @@ async fn main() -> std::io::Result<()> { AppConfig::get().website_origin ); - HttpServer::new(move || App::new()) - .workers(4) - .bind(&AppConfig::get().listen_address)? - .run() - .await + HttpServer::new(move || { + App::new() + .wrap( + SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) + .cookie_name("matrixgw-session".to_string()) + .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) + .build(), + ) + .app_data(web::Data::new(RemoteIPConfig { + proxy: AppConfig::get().proxy_ip.clone(), + })) + // Server controller + .route("/robots.txt", web::get().to(server_controller::robots_txt)) + .route( + "/api/server/config", + web::get().to(server_controller::config), + ) + }) + .workers(4) + .bind(&AppConfig::get().listen_address)? + .run() + .await } -- 2.49.1 From bc815a5cf16f8cef84041078cf5ef85514e7942f Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Nov 2025 22:17:29 +0100 Subject: [PATCH 04/21] Add users authentication routes --- README.md | 4 +- matrixgw_backend/.gitignore | 1 + matrixgw_backend/Cargo.lock | 948 +++++++++++------- matrixgw_backend/Cargo.toml | 16 +- matrixgw_backend/docker-compose.yml | 13 - matrixgw_backend/docker/dex/dex.config.yaml | 2 +- matrixgw_backend/examples/api_curl.rs | 9 +- matrixgw_backend/src/app_config.rs | 78 +- matrixgw_backend/src/constants.rs | 15 + .../src/controllers/auth_controller.rs | 131 +++ matrixgw_backend/src/controllers/mod.rs | 33 + .../src/extractors/auth_extractor.rs | 305 ++++++ matrixgw_backend/src/extractors/mod.rs | 2 + .../src/extractors/session_extractor.rs | 91 ++ matrixgw_backend/src/lib.rs | 4 + matrixgw_backend/src/main.rs | 24 +- matrixgw_backend/src/users.rs | 168 ++++ matrixgw_backend/src/utils/crypt_utils.rs | 6 + matrixgw_backend/src/utils/mod.rs | 3 + matrixgw_backend/src/utils/rand_utils.rs | 6 + matrixgw_backend/src/utils/time_utils.rs | 9 + 21 files changed, 1417 insertions(+), 451 deletions(-) create mode 100644 matrixgw_backend/src/constants.rs create mode 100644 matrixgw_backend/src/controllers/auth_controller.rs create mode 100644 matrixgw_backend/src/extractors/auth_extractor.rs create mode 100644 matrixgw_backend/src/extractors/mod.rs create mode 100644 matrixgw_backend/src/extractors/session_extractor.rs create mode 100644 matrixgw_backend/src/users.rs create mode 100644 matrixgw_backend/src/utils/crypt_utils.rs create mode 100644 matrixgw_backend/src/utils/mod.rs create mode 100644 matrixgw_backend/src/utils/rand_utils.rs create mode 100644 matrixgw_backend/src/utils/time_utils.rs diff --git a/README.md b/README.md index 8cc8aa6..0c7620e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ docker run --rm -it docker.io/pierre42100/matrix_gateway --help ### Dependencies ``` cd matrixgw_backend -mkdir -p storage/maspostgres storage/synapse storage/minio +mkdir -p storage/maspostgres storage/synapse docker compose up ``` @@ -35,14 +35,12 @@ URLs: * Synapse: http://localhost:8448/ * Matrix Authentication Service: http://localhost:8778/ * OpenID configuration: http://127.0.0.1:9001/dex/.well-known/openid-configuration -* Minio console: http://localhost:9002/ Auto-created Matrix accounts: * `admin1` : `admin1` * `user1` : `user1` -Minio administration credentials: `minioadmin` : `minioadmin` ### Backend ```bash diff --git a/matrixgw_backend/.gitignore b/matrixgw_backend/.gitignore index 79e4ba8..f3a3b05 100644 --- a/matrixgw_backend/.gitignore +++ b/matrixgw_backend/.gitignore @@ -1,3 +1,4 @@ storage +app_storage .idea target diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index a6569f5..ad5c8da 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ "flate2", "foldhash", "futures-core", - "h2", + "h2 0.3.27", "http 0.2.12", "httparse", "httpdate", @@ -342,15 +342,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "async-trait" -version = "0.1.89" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii_utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "atomic-waker" @@ -358,53 +365,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64 0.22.1", - "http 1.3.1", - "log", - "native-tls", - "serde", - "serde_json", - "url", -] - [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-creds" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13804829a843b3f26e151c97acbb315ee1177a2724690edfcd28f1894146200" -dependencies = [ - "attohttpc", - "home", - "log", - "quick-xml", - "rust-ini", - "serde", - "thiserror", - "time", - "url", -] - -[[package]] -name = "aws-region" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5532f65342f789f9c1b7078ea9c9cd9293cd62dcc284fa99adc4a1c9ba43469c" -dependencies = [ - "thiserror", -] - [[package]] name = "backon" version = "1.6.0" @@ -414,6 +380,18 @@ dependencies = [ "fastrand", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base16ct" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" + [[package]] name = "base64" version = "0.20.0" @@ -426,12 +404,35 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "binstring" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef" + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -483,15 +484,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.2.44" @@ -560,6 +552,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "coarsetime" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -581,37 +584,16 @@ dependencies = [ ] [[package]] -name = "compact_str" -version = "0.7.1" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const-random" -version = "0.1.18" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cookie" @@ -666,10 +648,16 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.4" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] [[package]] name = "crypto-common" @@ -682,6 +670,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" + [[package]] name = "ctr" version = "0.9.2" @@ -691,6 +685,17 @@ dependencies = [ "cipher", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -698,7 +703,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -729,6 +733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -745,12 +750,48 @@ dependencies = [ ] [[package]] -name = "dlv-list" -version = "0.5.2" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "const-random", + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom 0.2.16", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -801,12 +842,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fast_chemail" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +dependencies = [ + "ascii_utils", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -874,12 +934,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-macro" version = "0.3.31" @@ -910,11 +964,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -928,6 +980,7 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -937,8 +990,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -963,6 +1018,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.27" @@ -983,10 +1049,23 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "h2" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] name = "hashbrown" @@ -1025,12 +1104,27 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.12" +name = "hmac-sha1-compact" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" + +[[package]] +name = "hmac-sha256" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" dependencies = [ - "windows-sys 0.61.2", + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280" +dependencies = [ + "digest", ] [[package]] @@ -1100,6 +1194,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body", "httparse", @@ -1111,6 +1206,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1146,9 +1257,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1266,7 +1379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -1283,6 +1396,9 @@ name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "serde", +] [[package]] name = "iri-string" @@ -1350,6 +1466,46 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand 0.8.5", + "serde", + "serde_json", + "superboring", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1361,6 +1517,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1368,6 +1527,26 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "light-openid" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a15777d080e807d5b6b3c0b5a293f7d4680d74a4c66b0cdf9db0441ea9f548" +dependencies = [ + "base64 0.22.1", + "log", + "reqwest", + "serde", + "serde_json", + "urlencoding", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1412,6 +1591,16 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "mailchecker" +version = "6.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad4bc63045f04cfc55aa4c55d4ec0a890c377ce56463bfc2adc2bc059c4b84" +dependencies = [ + "fast_chemail", + "once_cell", +] + [[package]] name = "matrixgw_backend" version = "0.1.0" @@ -1420,32 +1609,27 @@ dependencies = [ "actix-session", "actix-web", "anyhow", + "base16ct 0.3.0", + "bytes", "clap", "env_logger", + "futures-util", + "hex", + "ipnet", + "jwt-simple", "lazy_static", + "light-openid", "log", - "rust-s3", + "mailchecker", + "rand 0.9.2", "serde", + "sha2", + "thiserror", "tokio", + "urlencoding", + "uuid", ] -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "md5" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" - [[package]] name = "memchr" version = "2.7.6" @@ -1458,15 +1642,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minidom" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" -dependencies = [ - "rxml", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1506,15 +1681,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -1525,6 +1691,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1540,6 +1722,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1547,25 +1740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" -dependencies = [ - "libc", - "objc2-core-foundation", + "libm", ] [[package]] @@ -1631,13 +1806,27 @@ dependencies = [ ] [[package]] -name = "ordered-multimap" -version = "0.7.3" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "dlv-list", - "hashbrown 0.14.5", + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", ] [[package]] @@ -1663,6 +1852,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1681,6 +1879,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1738,6 +1957,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -1747,16 +1975,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quote" version = "1.0.41" @@ -1907,16 +2125,19 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", - "futures-util", + "h2 0.4.12", "http 1.3.1", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -1927,60 +2148,58 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", ] [[package]] -name = "rust-ini" -version = "0.21.3" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "cfg-if", - "ordered-multimap", + "hmac", + "subtle", ] [[package]] -name = "rust-s3" -version = "0.37.0" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f9b973bd4097f5bb47e5827dcb9fb5dc17e93879e46badc27d2a4e9a4e5588" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "async-trait", - "aws-creds", - "aws-region", - "base64 0.22.1", - "bytes", + "cc", "cfg-if", - "futures-util", - "hex", - "hmac", - "http 1.3.1", - "log", - "maybe-async", - "md5", - "minidom", - "percent-encoding", - "quick-xml", - "reqwest", - "serde", - "serde_derive", - "serde_json", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", "sha2", - "sysinfo", - "thiserror", - "time", - "tokio", - "tokio-stream", - "url", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] @@ -1996,6 +2215,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -2005,31 +2237,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rxml" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" -dependencies = [ - "bytes", - "rxml_validation", -] - -[[package]] -name = "rxml_validation" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" -dependencies = [ - "compact_str", -] - [[package]] name = "ryu" version = "1.0.20" @@ -2051,6 +2275,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2166,6 +2404,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2204,18 +2452,28 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -2228,6 +2486,19 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "superboring" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" +dependencies = [ + "getrandom 0.2.16", + "hmac-sha256", + "hmac-sha512", + "rand 0.8.5", + "rsa", +] + [[package]] name = "syn" version = "2.0.108" @@ -2260,17 +2531,24 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.37.2" +name = "system-configuration" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows", ] [[package]] @@ -2337,15 +2615,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -2395,13 +2664,12 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.17" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls", "tokio", ] @@ -2529,6 +2797,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -2541,6 +2815,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2553,6 +2833,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2589,6 +2881,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" version = "0.2.105" @@ -2647,19 +2948,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.82" @@ -2670,96 +2958,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-link" version = "0.1.3" @@ -2773,13 +2971,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-numerics" -version = "0.2.0" +name = "windows-registry" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-core", "windows-link 0.1.3", + "windows-result", + "windows-strings", ] [[package]] @@ -2860,15 +3059,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index 9ae5242..dfb0d9e 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -10,8 +10,20 @@ clap = { version = "4.5.51", features = ["derive", "env"] } lazy_static = "1.5.0" anyhow = "1.0.100" serde = { version = "1.0.228", features = ["derive"] } -rust-s3 = { version = "0.37.0", features = ["tokio"] } tokio = { version = "1.48.0", features = ["full"] } actix-web = "4.11.0" actix-session = { version = "0.11.0", features = ["redis-session"] } -actix-remote-ip = "0.1.0" \ No newline at end of file +actix-remote-ip = "0.1.0" +light-openid = "1.0.4" +bytes = "1.10.1" +sha2 = "0.10.9" +urlencoding = "2.1.3" +base16ct = { version = "0.3.0", features = ["alloc"] } +futures-util = "0.3.31" +jwt-simple = { version = "0.12.13", default-features = false, features = ["pure-rust"] } +thiserror = "2.0.17" +uuid = { version = "1.18.1", features = ["v4", "serde"] } +ipnet = { version = "2.11.0", features = ["serde"] } +rand = "0.9.2" +hex = "0.4.3" +mailchecker = "6.0.19" \ No newline at end of file diff --git a/matrixgw_backend/docker-compose.yml b/matrixgw_backend/docker-compose.yml index fec7c62..be4a7bb 100644 --- a/matrixgw_backend/docker-compose.yml +++ b/matrixgw_backend/docker-compose.yml @@ -92,19 +92,6 @@ services: - ./docker/dex:/conf:ro command: [ "dex", "serve", "/conf/dex.config.yaml" ] - minio: - image: quay.io/minio/minio - command: minio server --console-address ":9002" /data - ports: - - 9000:9000/tcp - - 9002:9002/tcp - environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - volumes: - # You may store the database tables in a local folder.. - - ./storage/minio:/data - redis: image: redis:alpine command: redis-server --requirepass ${REDIS_PASS:-secretredis} diff --git a/matrixgw_backend/docker/dex/dex.config.yaml b/matrixgw_backend/docker/dex/dex.config.yaml index 7704800..0b9d381 100644 --- a/matrixgw_backend/docker/dex/dex.config.yaml +++ b/matrixgw_backend/docker/dex/dex.config.yaml @@ -22,5 +22,5 @@ staticClients: - id: foo secret: bar redirectURIs: - - http://localhost:8000/oidc_cb + - http://localhost:5173/oidc_cb name: Project diff --git a/matrixgw_backend/examples/api_curl.rs b/matrixgw_backend/examples/api_curl.rs index a2669fd..eb57533 100644 --- a/matrixgw_backend/examples/api_curl.rs +++ b/matrixgw_backend/examples/api_curl.rs @@ -1,11 +1,12 @@ use clap::Parser; use jwt_simple::algorithms::HS256Key; use jwt_simple::prelude::{Clock, Duration, JWTClaims, MACLike}; -use matrix_gateway::extractors::client_auth::TokenClaims; -use matrix_gateway::utils::base_utils::rand_str; +use matrixgw_backend::constants; +use matrixgw_backend::extractors::auth_extractor::TokenClaims; use std::ops::Add; use std::os::unix::prelude::CommandExt; use std::process::Command; +use matrixgw_backend::utils::rand_utils::rand_string; /// cURL wrapper to query MatrixGW #[derive(Parser, Debug)] @@ -59,7 +60,7 @@ fn main() { subject: None, audiences: None, jwt_id: None, - nonce: Some(rand_str(10)), + nonce: Some(rand_string(10)), custom: TokenClaims { method: args.method.to_string(), uri: args.uri, @@ -78,7 +79,7 @@ fn main() { let _ = Command::new("curl") .args(["-X", &args.method]) - .args(["-H", &format!("x-client-auth: {jwt}")]) + .args(["-H", &format!("{}: {jwt}", constants::API_AUTH_HEADER)]) .args(args.run) .arg(full_url) .exec(); diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index 7552544..ed122d8 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -1,6 +1,7 @@ +use crate::users::{APITokenID, UserEmail}; +use crate::utils::crypt_utils::sha256str; use clap::Parser; -use s3::creds::Credentials; -use s3::{Bucket, Region}; +use std::path::{Path, PathBuf}; /// Matrix gateway backend API #[derive(Parser, Debug, Clone)] @@ -11,7 +12,7 @@ pub struct AppConfig { pub listen_address: String, /// Website origin - #[clap(short, long, env, default_value = "http://localhost:8000")] + #[clap(short, long, env, default_value = "http://localhost:5173")] pub website_origin: String, /// Proxy IP, might end with a star "*" @@ -75,29 +76,9 @@ pub struct AppConfig { #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] oidc_redirect_url: String, - /// S3 Bucket name - #[arg(long, env, default_value = "matrix-gw")] - s3_bucket_name: String, - - /// S3 region (if not using Minio) - #[arg(long, env, default_value = "eu-central-1")] - s3_region: String, - - /// S3 API endpoint - #[arg(long, env, default_value = "http://localhost:9000")] - s3_endpoint: String, - - /// S3 access key - #[arg(long, env, default_value = "minioadmin")] - s3_access_key: String, - - /// S3 secret key - #[arg(long, env, default_value = "minioadmin")] - s3_secret_key: String, - - /// S3 skip auto create bucket if not existing - #[arg(long, env)] - pub s3_skip_auto_create_bucket: bool, + /// Application storage path + #[arg(long, env, default_value = "app_storage")] + storage_path: String, } lazy_static::lazy_static! { @@ -113,10 +94,10 @@ impl AppConfig { } /// Get auto login email (if not empty) - pub fn unsecure_auto_login_email(&self) -> Option<&str> { + pub fn unsecure_auto_login_email(&self) -> Option { match self.unsecure_auto_login_email.as_deref() { None | Some("") => None, - s => s, + Some(s) => Some(UserEmail(s.to_owned())), } } @@ -165,28 +146,29 @@ impl AppConfig { } } - /// Get s3 bucket credentials - pub fn s3_credentials(&self) -> anyhow::Result { - Ok(Credentials::new( - Some(&self.s3_access_key), - Some(&self.s3_secret_key), - None, - None, - None, - )?) + /// Get storage path + pub fn storage_path(&self) -> &Path { + Path::new(self.storage_path.as_str()) } - /// Get S3 bucket - pub fn s3_bucket(&self) -> anyhow::Result> { - Ok(Bucket::new( - &self.s3_bucket_name, - Region::Custom { - region: self.s3_region.to_string(), - endpoint: self.s3_endpoint.to_string(), - }, - self.s3_credentials()?, - )? - .with_path_style()) + /// User storage directory + pub fn user_directory(&self, mail: &UserEmail) -> PathBuf { + self.storage_path().join("users").join(sha256str(&mail.0)) + } + + /// User metadata file + pub fn user_metadata_file_path(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("metadata.json") + } + + /// User API tokens directory + pub fn user_api_token_directory(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("api-tokens") + } + + /// User API token metadata file + pub fn user_api_token_metadata_file(&self, mail: &UserEmail, id: &APITokenID) -> PathBuf { + self.user_api_token_directory(mail).join(id.0.to_string()) } } diff --git a/matrixgw_backend/src/constants.rs b/matrixgw_backend/src/constants.rs new file mode 100644 index 0000000..d46042c --- /dev/null +++ b/matrixgw_backend/src/constants.rs @@ -0,0 +1,15 @@ +/// Auth header +pub const API_AUTH_HEADER: &str = "x-client-auth"; + +/// Max token validity, in seconds +pub const API_TOKEN_JWT_MAX_DURATION: u64 = 15 * 60; + +/// Session-specific constants +pub mod sessions { + /// OpenID auth session state key + pub const OIDC_STATE_KEY: &str = "oidc-state"; + /// OpenID auth remote IP address + pub const OIDC_REMOTE_IP: &str = "oidc-remote-ip"; + /// Authenticated ID + pub const USER_ID: &str = "uid"; +} diff --git a/matrixgw_backend/src/controllers/auth_controller.rs b/matrixgw_backend/src/controllers/auth_controller.rs new file mode 100644 index 0000000..ab14ace --- /dev/null +++ b/matrixgw_backend/src/controllers/auth_controller.rs @@ -0,0 +1,131 @@ +use crate::app_config::AppConfig; +use crate::controllers::{HttpFailure, HttpResult}; +use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; +use crate::extractors::session_extractor::MatrixGWSession; +use crate::users::{User, UserEmail}; +use actix_remote_ip::RemoteIP; +use actix_web::{HttpResponse, web}; +use light_openid::primitives::OpenIDConfig; + +#[derive(serde::Serialize)] +struct StartOIDCResponse { + url: String, +} + +/// Start OIDC authentication +pub async fn start_oidc(session: MatrixGWSession, remote_ip: RemoteIP) -> HttpResult { + let prov = AppConfig::get().openid_provider(); + + let conf = match OpenIDConfig::load_from_url(prov.configuration_url).await { + Ok(c) => c, + Err(e) => { + log::error!("Failed to fetch OpenID provider configuration! {e}"); + return Ok(HttpResponse::InternalServerError() + .json("Failed to fetch OpenID provider configuration!")); + } + }; + + let state = match session.gen_oidc_state(remote_ip.0) { + Ok(s) => s, + Err(e) => { + log::error!("Failed to generate auth state! {e}"); + return Ok(HttpResponse::InternalServerError().json("Failed to generate auth state!")); + } + }; + + Ok(HttpResponse::Ok().json(StartOIDCResponse { + url: conf.gen_authorization_url( + prov.client_id, + &state, + &AppConfig::get().openid_provider().redirect_url, + ), + })) +} + +#[derive(serde::Deserialize)] +pub struct FinishOpenIDLoginQuery { + code: String, + state: String, +} + +/// Finish OIDC authentication +pub async fn finish_oidc( + session: MatrixGWSession, + remote_ip: RemoteIP, + req: web::Json, +) -> HttpResult { + if let Err(e) = session.validate_state(&req.state, remote_ip.0) { + log::error!("Failed to validate OIDC CB state! {e}"); + return Ok(HttpResponse::BadRequest().json("Invalid state!")); + } + + let prov = AppConfig::get().openid_provider(); + + let conf = OpenIDConfig::load_from_url(prov.configuration_url) + .await + .map_err(HttpFailure::OpenID)?; + + let (token, _) = conf + .request_token( + prov.client_id, + prov.client_secret, + &req.code, + &AppConfig::get().openid_provider().redirect_url, + ) + .await + .map_err(HttpFailure::OpenID)?; + let (user_info, _) = conf + .request_user_info(&token) + .await + .map_err(HttpFailure::OpenID)?; + + if user_info.email_verified != Some(true) { + log::error!("Email is not verified!"); + return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!")); + } + + let mail = match user_info.email { + Some(m) => m, + None => { + return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!")); + } + }; + + let user_name = user_info.name.unwrap_or_else(|| { + format!( + "{} {}", + user_info.given_name.as_deref().unwrap_or(""), + user_info.family_name.as_deref().unwrap_or("") + ) + }); + + let user = User::create_or_update_user(&UserEmail(mail), &user_name).await?; + + session.set_user(&user)?; + + Ok(HttpResponse::Ok().finish()) +} + +/// Get current user information +pub async fn auth_info(auth: AuthExtractor) -> HttpResult { + Ok(HttpResponse::Ok().json(auth.user)) +} + +/// Sign out user +pub async fn sign_out(auth: AuthExtractor, session: MatrixGWSession) -> HttpResult { + match auth.method { + AuthenticatedMethod::Cookie => { + session.unset_current_user()?; + } + + AuthenticatedMethod::Token(token) => { + token.delete(&auth.user.email).await?; + } + + AuthenticatedMethod::Dev => { + // Nothing to be done, user is always authenticated + } + } + + Ok(HttpResponse::NoContent().finish()) +} diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs index 98a0949..25104bf 100644 --- a/matrixgw_backend/src/controllers/mod.rs +++ b/matrixgw_backend/src/controllers/mod.rs @@ -1 +1,34 @@ +use actix_web::http::StatusCode; +use actix_web::{HttpResponse, ResponseError}; +use std::error::Error; + +pub mod auth_controller; pub mod server_controller; + +#[derive(thiserror::Error, Debug)] +pub enum HttpFailure { + #[error("this resource requires higher privileges")] + Forbidden, + #[error("this resource was not found")] + NotFound, + #[error("an unspecified open id error occurred: {0}")] + OpenID(Box), + #[error("an unspecified internal error occurred: {0}")] + InternalError(#[from] anyhow::Error), +} + +impl ResponseError for HttpFailure { + fn status_code(&self) -> StatusCode { + match &self { + Self::Forbidden => StatusCode::FORBIDDEN, + Self::NotFound => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()).body(self.to_string()) + } +} + +pub type HttpResult = Result; diff --git a/matrixgw_backend/src/extractors/auth_extractor.rs b/matrixgw_backend/src/extractors/auth_extractor.rs new file mode 100644 index 0000000..8c75a29 --- /dev/null +++ b/matrixgw_backend/src/extractors/auth_extractor.rs @@ -0,0 +1,305 @@ +use crate::app_config::AppConfig; +use crate::constants; +use crate::extractors::session_extractor::MatrixGWSession; +use crate::users::{APIToken, APITokenID, User, UserEmail}; +use crate::utils::time_utils::time_secs; +use actix_remote_ip::RemoteIP; +use actix_web::dev::Payload; +use actix_web::error::ErrorPreconditionFailed; +use actix_web::{FromRequest, HttpRequest}; +use anyhow::Context; +use bytes::Bytes; +use jwt_simple::common::VerificationOptions; +use jwt_simple::prelude::{Duration, HS256Key, MACLike}; +use sha2::{Digest, Sha256}; +use std::fmt::Display; +use std::net::IpAddr; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub enum AuthenticatedMethod { + /// User is authenticated using a cookie + Cookie, + /// User is authenticated through command line, for debugging purposes only + Dev, + /// User is authenticated using an API token + Token(APIToken), +} + +pub struct AuthExtractor { + pub user: User, + pub method: AuthenticatedMethod, + pub payload: Option>, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct MatrixJWTKID { + pub user_email: UserEmail, + pub id: APITokenID, +} + +impl Display for MatrixJWTKID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}#{}", self.user_email.0, self.id.0) + } +} + +impl FromStr for MatrixJWTKID { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let (mail, token_id) = s + .split_once("#") + .context("Failed to decode KID in two parts!")?; + + let mail = UserEmail(mail.to_string()); + + if !mail.is_valid() { + anyhow::bail!("Given email is invalid!") + } + + Ok(Self { + user_email: mail, + id: token_id.parse().context("Failed to parse API token ID")?, + }) + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenClaims { + #[serde(rename = "met")] + pub method: String, + pub uri: String, + #[serde(rename = "pay", skip_serializing_if = "Option::is_none")] + pub payload_sha256: Option, +} + +impl AuthExtractor { + async fn extract_auth( + req: &HttpRequest, + remote_ip: IpAddr, + payload_bytes: Option, + ) -> Result { + // Check for authentication using API token + if let Some(token) = req.headers().get(constants::API_AUTH_HEADER) { + let Ok(jwt_token) = token.to_str() else { + return Err(actix_web::error::ErrorBadRequest( + "Failed to decode token as string!", + )); + }; + + let metadata = match jwt_simple::token::Token::decode_metadata(jwt_token) { + Ok(m) => m, + Err(e) => { + log::error!("Failed to decode JWT header metadata! {e}"); + return Err(actix_web::error::ErrorBadRequest( + "Failed to decode JWT header metadata!", + )); + } + }; + + // Extract token ID + let Some(kid) = metadata.key_id() else { + return Err(actix_web::error::ErrorBadRequest( + "Missing key id in request!", + )); + }; + + let jwt_kid = match MatrixJWTKID::from_str(kid) { + Ok(i) => i, + Err(e) => { + log::error!("Failed to parse token id! {e}"); + return Err(actix_web::error::ErrorBadRequest( + "Failed to parse token id!", + )); + } + }; + + // Get token information + let Ok(mut token) = APIToken::load(&jwt_kid.user_email, &jwt_kid.id).await else { + log::error!("Token not found!"); + return Err(actix_web::error::ErrorForbidden("Token not found!")); + }; + + // Decode JWT + let key = HS256Key::from_bytes(token.secret.as_ref()); + let verif = VerificationOptions { + max_validity: Some(Duration::from_secs(constants::API_TOKEN_JWT_MAX_DURATION)), + ..Default::default() + }; + + let claims = match key.verify_token::(jwt_token, Some(verif)) { + Ok(t) => t, + Err(e) => { + log::error!("JWT validation failed! {e}"); + return Err(actix_web::error::ErrorForbidden("JWT validation failed!")); + } + }; + + // Check for nonce + if claims.nonce.is_none() { + return Err(actix_web::error::ErrorBadRequest( + "A nonce is required in auth JWT!", + )); + } + + // Check IP restriction + if let Some(net) = token.network + && !net.contains(&remote_ip) + { + log::error!( + "Trying to use token {:?} from unauthorized IP address: {remote_ip:?}", + token.id + ); + return Err(actix_web::error::ErrorForbidden( + "This token cannot be used from this IP address!", + )); + } + + // Check for write access + if token.read_only && !req.method().is_safe() { + return Err(actix_web::error::ErrorBadRequest( + "Read only token cannot perform write operations!", + )); + } + + // Get user information + let Ok(user) = User::get_by_mail(&jwt_kid.user_email).await else { + return Err(actix_web::error::ErrorBadRequest( + "Failed to get user information from token!", + )); + }; + + // Update last use (if needed) + if token.shall_update_time_used() { + token.last_used = time_secs(); + if let Err(e) = token.write(&jwt_kid.user_email).await { + log::error!("Failed to refresh last usage of token! {e}"); + } + } + + // Handle tokens expiration + if token.is_expired() { + log::error!("Attempted to use expired token! {token:?}"); + return Err(actix_web::error::ErrorBadRequest("Token has expired!")); + } + + // Check payload + let payload = match (payload_bytes, claims.custom.payload_sha256) { + (None, _) => None, + (Some(_), None) => { + return Err(actix_web::error::ErrorBadRequest( + "A payload digest must be included in the JWT when the request has a payload!", + )); + } + (Some(payload), Some(provided_digest)) => { + let computed_digest = base16ct::lower::encode_string(&Sha256::digest(&payload)); + if computed_digest != provided_digest { + log::error!( + "Expected digest {provided_digest} for payload but computed {computed_digest}!" + ); + return Err(actix_web::error::ErrorBadRequest( + "Computed digest is different from the one provided in the JWT!", + )); + } + + Some(payload.to_vec()) + } + }; + + return Ok(Self { + method: AuthenticatedMethod::Token(token), + user, + payload, + }); + } + + // Check if login is hard-coded as program argument + if let Some(email) = &AppConfig::get().unsecure_auto_login_email() { + let user = User::get_by_mail(email).await.map_err(|e| { + log::error!("Failed to retrieve dev user: {e}"); + ErrorPreconditionFailed("Unable to retrieve dev user!") + })?; + return Ok(Self { + method: AuthenticatedMethod::Dev, + user, + payload: payload_bytes.map(|bytes| bytes.to_vec()), + }); + } + + // Check for cookie authentication + let session = MatrixGWSession::extract(req).await?; + if let Some(mail) = session.current_user().map_err(|e| { + log::error!("Failed to retrieve user id: {e}"); + ErrorPreconditionFailed("Failed to read session information!") + })? { + let user = User::get_by_mail(&mail).await.map_err(|e| { + log::error!("Failed to retrieve user from cookie session: {e}"); + ErrorPreconditionFailed("Failed to retrieve user information!") + })?; + return Ok(Self { + method: AuthenticatedMethod::Cookie, + user, + payload: payload_bytes.map(|bytes| bytes.to_vec()), + }); + }; + + Err(ErrorPreconditionFailed("Authentication required!")) + } +} + +impl FromRequest for AuthExtractor { + type Error = actix_web::Error; + type Future = futures_util::future::LocalBoxFuture<'static, Result>; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let req = req.clone(); + + let remote_ip = match RemoteIP::from_request(&req, &mut Payload::None).into_inner() { + Ok(ip) => ip, + Err(e) => return Box::pin(async { Err(e) }), + }; + + let mut payload = payload.take(); + + Box::pin(async move { + let payload_bytes = match Bytes::from_request(&req, &mut payload).await { + Ok(b) => { + if b.is_empty() { + None + } else { + Some(b) + } + } + Err(e) => { + log::error!("Failed to extract request payload! {e}"); + None + } + }; + + Self::extract_auth(&req, remote_ip.0, payload_bytes).await + }) + } +} + +#[cfg(test)] +mod tests { + use crate::extractors::auth_extractor::MatrixJWTKID; + use crate::users::{APITokenID, UserEmail}; + use std::str::FromStr; + + #[test] + fn encode_decode_jwt_kid() { + let src = MatrixJWTKID { + user_email: UserEmail("test@mail.com".to_string()), + id: APITokenID::default(), + }; + let encoded = src.to_string(); + let decoded = encoded.parse::().unwrap(); + assert_eq!(src, decoded); + + MatrixJWTKID::from_str("bad").unwrap_err(); + MatrixJWTKID::from_str("ba#d").unwrap_err(); + MatrixJWTKID::from_str("test@valid.com#d").unwrap_err(); + } +} diff --git a/matrixgw_backend/src/extractors/mod.rs b/matrixgw_backend/src/extractors/mod.rs new file mode 100644 index 0000000..4e4f77f --- /dev/null +++ b/matrixgw_backend/src/extractors/mod.rs @@ -0,0 +1,2 @@ +pub mod auth_extractor; +pub mod session_extractor; diff --git a/matrixgw_backend/src/extractors/session_extractor.rs b/matrixgw_backend/src/extractors/session_extractor.rs new file mode 100644 index 0000000..b33dc59 --- /dev/null +++ b/matrixgw_backend/src/extractors/session_extractor.rs @@ -0,0 +1,91 @@ +use crate::constants; +use crate::users::{User, UserEmail}; +use crate::utils::rand_utils::rand_string; +use actix_session::Session; +use actix_web::dev::Payload; +use actix_web::{Error, FromRequest, HttpRequest}; +use futures_util::future::{Ready, ready}; +use std::net::IpAddr; + +/// Matrix Gateway session errors +#[derive(thiserror::Error, Debug)] +enum MatrixGWSessionError { + #[error("Missing state!")] + OIDCMissingState, + #[error("Missing IP address!")] + OIDCMissingIP, + #[error("Invalid state!")] + OIDCInvalidState, + #[error("Invalid IP address!")] + OIDCInvalidIP, +} + +/// Matrix Gateway session +/// +/// Basic wrapper around actix-session extractor +pub struct MatrixGWSession(Session); + +impl MatrixGWSession { + /// Generate OpenID state for this session + pub fn gen_oidc_state(&self, ip: IpAddr) -> anyhow::Result { + let random_string = rand_string(50); + self.0 + .insert(constants::sessions::OIDC_STATE_KEY, random_string.clone())?; + self.0.insert(constants::sessions::OIDC_REMOTE_IP, ip)?; + Ok(random_string) + } + + /// Validate OpenID state + pub fn validate_state(&self, state: &str, ip: IpAddr) -> anyhow::Result<()> { + let session_state: String = self + .0 + .get(constants::sessions::OIDC_STATE_KEY)? + .ok_or(MatrixGWSessionError::OIDCMissingState)?; + + let session_ip: IpAddr = self + .0 + .get(constants::sessions::OIDC_REMOTE_IP)? + .ok_or(MatrixGWSessionError::OIDCMissingIP)?; + + if session_state != state { + return Err(anyhow::anyhow!(MatrixGWSessionError::OIDCInvalidState)); + } + + if session_ip != ip { + return Err(anyhow::anyhow!(MatrixGWSessionError::OIDCInvalidIP)); + } + + Ok(()) + } + + /// Set current user + pub fn set_user(&self, user: &User) -> anyhow::Result<()> { + self.0.insert(constants::sessions::USER_ID, &user.email)?; + Ok(()) + } + + /// Get current user + pub fn current_user(&self) -> anyhow::Result> { + Ok(self.0.get(constants::sessions::USER_ID)?) + } + + /// Remove defined user + pub fn unset_current_user(&self) -> anyhow::Result<()> { + self.0.remove(constants::sessions::USER_ID); + Ok(()) + } +} + +impl FromRequest for MatrixGWSession { + type Error = Error; + type Future = Ready>; + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ready( + Session::from_request(req, &mut Payload::None) + .into_inner() + .map(MatrixGWSession), + ) + } +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 2483671..82c2228 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -1,2 +1,6 @@ pub mod app_config; +pub mod constants; pub mod controllers; +pub mod extractors; +pub mod users; +pub mod utils; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 3d6132f..3100299 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -5,7 +5,8 @@ use actix_session::storage::RedisSessionStore; use actix_web::cookie::Key; use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; -use matrixgw_backend::controllers::server_controller; +use matrixgw_backend::controllers::{auth_controller, server_controller}; +use matrixgw_backend::users::User; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -17,6 +18,13 @@ async fn main() -> std::io::Result<()> { .await .expect("Failed to connect to Redis!"); + // Auto create default account, if requested + if let Some(mail) = &AppConfig::get().unsecure_auto_login_email() { + User::create_or_update_user(mail, "Anonymous") + .await + .expect("Failed to create auto-login account!"); + } + log::info!( "Starting to listen on {} for {}", AppConfig::get().listen_address, @@ -40,6 +48,20 @@ async fn main() -> std::io::Result<()> { "/api/server/config", web::get().to(server_controller::config), ) + // Auth controller + .route( + "/api/auth/start_oidc", + web::get().to(auth_controller::start_oidc), + ) + .route( + "/api/auth/finish_oidc", + web::post().to(auth_controller::finish_oidc), + ) + .route("/api/auth/info", web::get().to(auth_controller::auth_info)) + .route( + "/api/auth/sign_out", + web::get().to(auth_controller::sign_out), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? diff --git a/matrixgw_backend/src/users.rs b/matrixgw_backend/src/users.rs new file mode 100644 index 0000000..2cb1e0a --- /dev/null +++ b/matrixgw_backend/src/users.rs @@ -0,0 +1,168 @@ +use crate::app_config::AppConfig; +use crate::utils::time_utils::time_secs; +use jwt_simple::reexports::serde_json; +use std::cmp::min; +use std::str::FromStr; + +/// Matrix Gateway user errors +#[derive(thiserror::Error, Debug)] +enum MatrixGWUserError { + #[error("Failed to load user metadata: {0}")] + LoadUserMetadata(std::io::Error), + #[error("Failed to decode user metadata: {0}")] + DecodeUserMetadata(serde_json::Error), + #[error("Failed to save user metadata: {0}")] + SaveUserMetadata(std::io::Error), + #[error("Failed to delete API token: {0}")] + DeleteToken(std::io::Error), + #[error("Failed to load API token: {0}")] + LoadApiToken(std::io::Error), + #[error("Failed to decode API token: {0}")] + DecodeApiToken(serde_json::Error), + #[error("API Token does not exists!")] + ApiTokenDoesNotExists, + #[error("Failed to save API token: {0}")] + SaveAPIToken(std::io::Error), +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +pub struct UserEmail(pub String); + +impl UserEmail { + pub fn is_valid(&self) -> bool { + mailchecker::is_valid(&self.0) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct APITokenID(pub uuid::Uuid); + +impl Default for APITokenID { + fn default() -> Self { + Self(uuid::Uuid::new_v4()) + } +} + +impl FromStr for APITokenID { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result { + Ok(Self(uuid::Uuid::from_str(s)?)) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct User { + pub email: UserEmail, + pub name: String, + pub time_create: u64, + pub last_login: u64, +} + +impl User { + /// Get a user by its mail + pub async fn get_by_mail(mail: &UserEmail) -> anyhow::Result { + let path = AppConfig::get().user_metadata_file_path(mail); + let data = std::fs::read_to_string(path).map_err(MatrixGWUserError::LoadUserMetadata)?; + Ok(serde_json::from_str(&data).map_err(MatrixGWUserError::DecodeUserMetadata)?) + } + + /// Update user metadata on disk + pub async fn write(&self) -> anyhow::Result<()> { + let path = AppConfig::get().user_metadata_file_path(&self.email); + std::fs::write(&path, serde_json::to_string(&self)?) + .map_err(MatrixGWUserError::SaveUserMetadata)?; + Ok(()) + } + + /// Create or update user information + pub async fn create_or_update_user(mail: &UserEmail, name: &str) -> anyhow::Result { + let storage_dir = AppConfig::get().user_directory(mail); + let mut user = if !storage_dir.exists() { + std::fs::create_dir_all(storage_dir)?; + + User { + email: mail.clone(), + name: name.to_string(), + time_create: time_secs(), + last_login: time_secs(), + } + } else { + Self::get_by_mail(mail).await? + }; + + // Update some user information + user.name = name.to_string(); + user.last_login = time_secs(); + user.write().await?; + + Ok(user) + } +} + +/// Single API client information +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct APIToken { + /// Token unique ID + pub id: APITokenID, + + /// Client description + pub description: String, + + /// Restricted API network for token + pub network: Option, + + /// Client secret + pub secret: String, + + /// Client creation time + pub created: u64, + + /// Client last usage time + pub last_used: u64, + + /// Read only access + pub read_only: bool, + + /// Token max inactivity + pub max_inactivity: u64, +} + +impl APIToken { + /// Get a token information + pub async fn load(email: &UserEmail, id: &APITokenID) -> anyhow::Result { + let token_file = AppConfig::get().user_api_token_metadata_file(email, id); + match token_file.exists() { + true => Ok(serde_json::from_str::( + &std::fs::read_to_string(&token_file).map_err(MatrixGWUserError::LoadApiToken)?, + ) + .map_err(MatrixGWUserError::DecodeApiToken)?), + false => Err(MatrixGWUserError::ApiTokenDoesNotExists.into()), + } + } + + /// Write this token information + pub async fn write(&self, mail: &UserEmail) -> anyhow::Result<()> { + let path = AppConfig::get().user_api_token_metadata_file(mail, &self.id); + std::fs::write(&path, serde_json::to_string(&self)?) + .map_err(MatrixGWUserError::SaveAPIToken)?; + Ok(()) + } + + /// Delete this token + pub async fn delete(self, email: &UserEmail) -> anyhow::Result<()> { + let token_file = AppConfig::get().user_api_token_metadata_file(email, &self.id); + std::fs::remove_file(&token_file).map_err(MatrixGWUserError::DeleteToken)?; + Ok(()) + } + + pub fn shall_update_time_used(&self) -> bool { + let refresh_interval = min(600, self.max_inactivity / 10); + + (self.last_used) < time_secs() - refresh_interval + } + + pub fn is_expired(&self) -> bool { + (self.last_used + self.max_inactivity) < time_secs() + } +} diff --git a/matrixgw_backend/src/utils/crypt_utils.rs b/matrixgw_backend/src/utils/crypt_utils.rs new file mode 100644 index 0000000..9a22fa9 --- /dev/null +++ b/matrixgw_backend/src/utils/crypt_utils.rs @@ -0,0 +1,6 @@ +use sha2::{Digest, Sha256}; + +/// Compute SHA256sum of a given string +pub fn sha256str(input: &str) -> String { + hex::encode(Sha256::digest(input.as_bytes())) +} diff --git a/matrixgw_backend/src/utils/mod.rs b/matrixgw_backend/src/utils/mod.rs new file mode 100644 index 0000000..ff5baec --- /dev/null +++ b/matrixgw_backend/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod crypt_utils; +pub mod rand_utils; +pub mod time_utils; diff --git a/matrixgw_backend/src/utils/rand_utils.rs b/matrixgw_backend/src/utils/rand_utils.rs new file mode 100644 index 0000000..aa40750 --- /dev/null +++ b/matrixgw_backend/src/utils/rand_utils.rs @@ -0,0 +1,6 @@ +use rand::distr::{Alphanumeric, SampleString}; + +/// Generate a random string of a given length +pub fn rand_string(len: usize) -> String { + Alphanumeric.sample_string(&mut rand::rng(), len) +} diff --git a/matrixgw_backend/src/utils/time_utils.rs b/matrixgw_backend/src/utils/time_utils.rs new file mode 100644 index 0000000..ac8c636 --- /dev/null +++ b/matrixgw_backend/src/utils/time_utils.rs @@ -0,0 +1,9 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get the current time since epoch +pub fn time_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} -- 2.49.1 From 1cdd3d9e6061cab0ca2b71dfb8ed8d04405b4f9a Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Nov 2025 22:28:38 +0100 Subject: [PATCH 05/21] Add backend README --- matrixgw_backend/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 matrixgw_backend/README.md diff --git a/matrixgw_backend/README.md b/matrixgw_backend/README.md new file mode 100644 index 0000000..9c11efa --- /dev/null +++ b/matrixgw_backend/README.md @@ -0,0 +1,2 @@ +# Matrix Gateway backend +Backend component, written in Rust using Actix. -- 2.49.1 From d05747e60e939725949164123e0923d17e07fd79 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 18:58:41 +0100 Subject: [PATCH 06/21] Add base web app --- matrixgw_backend/Cargo.lock | 16 + matrixgw_backend/Cargo.toml | 1 + matrixgw_backend/examples/api_curl.rs | 2 +- matrixgw_backend/src/main.rs | 25 +- matrixgw_frontend/.env | 1 + matrixgw_frontend/.env.production | 1 + matrixgw_frontend/package-lock.json | 763 +++++++++++++++++- matrixgw_frontend/package.json | 5 + matrixgw_frontend/src/App.css | 42 - matrixgw_frontend/src/App.tsx | 36 +- matrixgw_frontend/src/api/ApiClient.ts | 192 +++++ matrixgw_frontend/src/api/AuthApi.ts | 87 ++ matrixgw_frontend/src/api/ServerApi.ts | 48 ++ matrixgw_frontend/src/assets/react.svg | 1 - .../contexts_provider/AlertDialogProvider.tsx | 68 ++ .../ConfirmDialogProvider.tsx | 98 +++ .../LoadingMessageProvider.tsx | 62 ++ .../contexts_provider/SnackbarProvider.tsx | 41 + matrixgw_frontend/src/index.css | 67 +- matrixgw_frontend/src/main.tsx | 42 +- matrixgw_frontend/src/widgets/AsyncWidget.tsx | 94 +++ 21 files changed, 1511 insertions(+), 181 deletions(-) create mode 100644 matrixgw_frontend/.env create mode 100644 matrixgw_frontend/.env.production delete mode 100644 matrixgw_frontend/src/App.css create mode 100644 matrixgw_frontend/src/api/ApiClient.ts create mode 100644 matrixgw_frontend/src/api/AuthApi.ts create mode 100644 matrixgw_frontend/src/api/ServerApi.ts delete mode 100644 matrixgw_frontend/src/assets/react.svg create mode 100644 matrixgw_frontend/src/hooks/contexts_provider/AlertDialogProvider.tsx create mode 100644 matrixgw_frontend/src/hooks/contexts_provider/ConfirmDialogProvider.tsx create mode 100644 matrixgw_frontend/src/hooks/contexts_provider/LoadingMessageProvider.tsx create mode 100644 matrixgw_frontend/src/hooks/contexts_provider/SnackbarProvider.tsx create mode 100644 matrixgw_frontend/src/widgets/AsyncWidget.tsx diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index ad5c8da..f857cd7 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.11.2" @@ -1605,6 +1620,7 @@ dependencies = [ name = "matrixgw_backend" version = "0.1.0" dependencies = [ + "actix-cors", "actix-remote-ip", "actix-session", "actix-web", diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index dfb0d9e..ffe9d1e 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -14,6 +14,7 @@ tokio = { version = "1.48.0", features = ["full"] } actix-web = "4.11.0" actix-session = { version = "0.11.0", features = ["redis-session"] } actix-remote-ip = "0.1.0" +actix-cors = "0.7.1" light-openid = "1.0.4" bytes = "1.10.1" sha2 = "0.10.9" diff --git a/matrixgw_backend/examples/api_curl.rs b/matrixgw_backend/examples/api_curl.rs index eb57533..3b8b7ff 100644 --- a/matrixgw_backend/examples/api_curl.rs +++ b/matrixgw_backend/examples/api_curl.rs @@ -3,10 +3,10 @@ use jwt_simple::algorithms::HS256Key; use jwt_simple::prelude::{Clock, Duration, JWTClaims, MACLike}; use matrixgw_backend::constants; use matrixgw_backend::extractors::auth_extractor::TokenClaims; +use matrixgw_backend::utils::rand_utils::rand_string; use std::ops::Add; use std::os::unix::prelude::CommandExt; use std::process::Command; -use matrixgw_backend::utils::rand_utils::rand_string; /// cURL wrapper to query MatrixGW #[derive(Parser, Debug)] diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 3100299..a3fd905 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -1,10 +1,13 @@ +use actix_cors::Cors; use actix_remote_ip::RemoteIPConfig; use actix_session::SessionMiddleware; use actix_session::config::SessionLifecycle; use actix_session::storage::RedisSessionStore; use actix_web::cookie::Key; +use actix_web::middleware::Logger; use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; +use matrixgw_backend::constants; use matrixgw_backend::controllers::{auth_controller, server_controller}; use matrixgw_backend::users::User; @@ -32,13 +35,23 @@ async fn main() -> std::io::Result<()> { ); HttpServer::new(move || { + let session_mw = SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) + .cookie_name("matrixgw-session".to_string()) + .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) + .build(); + + let cors = Cors::default() + .allowed_origin(&AppConfig::get().website_origin) + .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) + .allowed_header(constants::API_AUTH_HEADER) + .allow_any_header() + .supports_credentials() + .max_age(3600); + App::new() - .wrap( - SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) - .cookie_name("matrixgw-session".to_string()) - .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) - .build(), - ) + .wrap(Logger::default()) + .wrap(session_mw) + .wrap(cors) .app_data(web::Data::new(RemoteIPConfig { proxy: AppConfig::get().proxy_ip.clone(), })) diff --git a/matrixgw_frontend/.env b/matrixgw_frontend/.env new file mode 100644 index 0000000..38c6b7b --- /dev/null +++ b/matrixgw_frontend/.env @@ -0,0 +1 @@ +VITE_APP_BACKEND=http://localhost:8000/api diff --git a/matrixgw_frontend/.env.production b/matrixgw_frontend/.env.production new file mode 100644 index 0000000..89f62f8 --- /dev/null +++ b/matrixgw_frontend/.env.production @@ -0,0 +1 @@ +VITE_APP_BACKEND=/api diff --git a/matrixgw_frontend/package-lock.json b/matrixgw_frontend/package-lock.json index 612a943..5f2de0a 100644 --- a/matrixgw_frontend/package-lock.json +++ b/matrixgw_frontend/package-lock.json @@ -8,6 +8,11 @@ "name": "matrixgw_frontend", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.8", + "@mui/icons-material": "^7.3.5", + "@mui/material": "^7.3.5", "react": "^19.1.1", "react-dom": "^19.1.1" }, @@ -30,7 +35,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -86,7 +90,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -120,7 +123,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -130,7 +132,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -172,7 +173,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -182,7 +182,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -216,7 +215,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -260,11 +258,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -279,7 +285,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -298,7 +303,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -342,6 +346,158 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -525,6 +681,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource/roboto": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.8.tgz", + "integrity": "sha512-oh9g4Cg3loVMz9MWeKWfDI+ooxxG1aRVetkiKIb2ESS2rrryGecQ/y4pAj4z5A5ebyw450dYRi/c4k/I3UBhHA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -581,7 +746,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -603,7 +767,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -613,20 +776,251 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", + "integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.5.tgz", + "integrity": "sha512-LciL1GLMZ+VlzyHAALSVAR22t8IST4LCXmljcUSx2NOutgO2XnxdIp8ilFbeNf9wpo0iUFbAuoQcB7h+HHIf3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", + "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/core-downloads-tracker": "^7.3.5", + "@mui/system": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz", + "integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz", + "integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz", + "integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/private-theming": "^7.3.5", + "@mui/styled-engine": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz", + "integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz", + "integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/types": "^7.4.8", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", @@ -698,6 +1092,16 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.41", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", @@ -1023,11 +1427,22 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1043,6 +1458,15 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", @@ -1408,6 +1832,21 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1487,7 +1926,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1531,6 +1969,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1565,6 +2012,31 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1584,14 +2056,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1622,6 +2092,16 @@ "node": ">=8" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.244", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", @@ -1629,6 +2109,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1643,7 +2132,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1916,6 +2404,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1969,6 +2463,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2022,6 +2525,33 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2036,7 +2566,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -2059,6 +2588,27 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2103,7 +2653,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -2123,7 +2672,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2139,6 +2687,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2451,6 +3005,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2474,6 +3034,18 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2525,7 +3097,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -2561,6 +3132,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2615,7 +3195,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -2624,6 +3203,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2644,11 +3241,25 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2703,6 +3314,23 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2755,6 +3383,12 @@ "react": "^19.2.0" } }, + "node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -2765,11 +3399,46 @@ "node": ">=0.10.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2890,6 +3559,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2913,6 +3591,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2926,6 +3610,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3248,6 +3944,21 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/matrixgw_frontend/package.json b/matrixgw_frontend/package.json index 33d4639..2025306 100644 --- a/matrixgw_frontend/package.json +++ b/matrixgw_frontend/package.json @@ -10,6 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.8", + "@mui/icons-material": "^7.3.5", + "@mui/material": "^7.3.5", "react": "^19.1.1", "react-dom": "^19.1.1" }, diff --git a/matrixgw_frontend/src/App.css b/matrixgw_frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/matrixgw_frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 3d7ded3..c664eaa 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -1,35 +1,3 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> - -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) +export function App(): React.ReactElement { + return <>hello; } - -export default App diff --git a/matrixgw_frontend/src/api/ApiClient.ts b/matrixgw_frontend/src/api/ApiClient.ts new file mode 100644 index 0000000..fdb7da3 --- /dev/null +++ b/matrixgw_frontend/src/api/ApiClient.ts @@ -0,0 +1,192 @@ +import { AuthApi } from "./AuthApi"; + +interface RequestParams { + uri: string; + method: "GET" | "POST" | "DELETE" | "PATCH" | "PUT"; + allowFail?: boolean; + jsonData?: any; + formData?: FormData; + upProgress?: (progress: number) => void; + downProgress?: (e: { progress: number; total: number }) => void; +} + +interface APIResponse { + data: any; + status: number; +} + +export class ApiError extends Error { + public code: number; + public data: number; + constructor(message: string, code: number, data: any) { + super(`HTTP status: ${code}\nMessage: ${message}\nData=${data}`); + this.code = code; + this.data = data; + } +} + +export class APIClient { + /** + * Get backend URL + */ + static backendURL(): string { + const URL = import.meta.env.VITE_APP_BACKEND ?? ""; + if (URL.length === 0) throw new Error("Backend URL undefined!"); + return URL; + } + + /** + * Get the full URL at which the backend can be contacted + */ + static ActualBackendURL(): string { + const backendURL = this.backendURL(); + if (backendURL.startsWith("/")) return `${location.origin}${backendURL}`; + else return backendURL; + } + + /** + * Check out whether the backend is accessed through + * HTTPS or not + */ + static IsBackendSecure(): boolean { + return this.ActualBackendURL().startsWith("https"); + } + + /** + * Perform a request on the backend + */ + static async exec(args: RequestParams): Promise { + let body: string | undefined | FormData = undefined; + const headers: any = {}; + + // JSON request + if (args.jsonData) { + headers["Content-Type"] = "application/json"; + body = JSON.stringify(args.jsonData); + } + + // Form data request + else if (args.formData) { + body = args.formData; + } + + const url = this.backendURL() + args.uri; + + let data; + let status: number; + + // Make the request with XMLHttpRequest + if (args.upProgress) { + const res: XMLHttpRequest = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.upload.addEventListener("progress", (e) => { + args.upProgress!(e.loaded / e.total); + }); + xhr.addEventListener("load", () => { + resolve(xhr); + }); + xhr.addEventListener("error", () => { + reject(new Error("File upload failed")); + }); + xhr.addEventListener("abort", () => { + reject(new Error("File upload aborted")); + }); + xhr.addEventListener("timeout", () => { + reject(new Error("File upload timeout")); + }); + xhr.open(args.method, url, true); + xhr.withCredentials = true; + for (const key in headers) { + if (Object.prototype.hasOwnProperty.call(headers, key)) + xhr.setRequestHeader(key, headers[key]); + } + xhr.send(body); + }); + + status = res.status; + if (res.responseType === "json") data = JSON.parse(res.responseText); + else data = res.response; + } + + // Make the request with fetch + else { + const res = await fetch(url, { + method: args.method, + body: body, + headers: headers, + credentials: "include", + }); + + // Process response + // JSON response + if (res.headers.get("content-type") === "application/json") + data = await res.json(); + // Text / XML response + else if ( + ["application/xml", "text/plain"].includes( + res.headers.get("content-type") ?? "" + ) + ) + data = await res.text(); + // Binary file, tracking download progress + else if (res.body !== null && args.downProgress) { + // Track download progress + const contentEncoding = res.headers.get("content-encoding"); + const contentLength = contentEncoding + ? null + : res.headers.get("content-length"); + + const total = parseInt(contentLength ?? "0", 10); + let loaded = 0; + + const resInt = new Response( + new ReadableStream({ + start(controller) { + const reader = res.body!.getReader(); + + const read = async () => { + try { + const ret = await reader.read(); + if (ret.done) { + controller.close(); + return; + } + loaded += ret.value.byteLength; + args.downProgress!({ progress: loaded, total }); + controller.enqueue(ret.value); + read(); + } catch (e) { + console.error(e); + controller.error(e); + } + }; + + read(); + }, + }) + ); + + data = await resInt.blob(); + } + + // Do not track progress (binary file) + else data = await res.blob(); + + status = res.status; + } + + // Handle expired tokens + if (status === 412) { + AuthApi.UnsetAuthenticated(); + window.location.href = "/"; + } + + if (!args.allowFail && (status < 200 || status > 299)) + throw new ApiError("Request failed!", status, data); + + return { + data: data, + status: status, + }; + } +} diff --git a/matrixgw_frontend/src/api/AuthApi.ts b/matrixgw_frontend/src/api/AuthApi.ts new file mode 100644 index 0000000..eb4b105 --- /dev/null +++ b/matrixgw_frontend/src/api/AuthApi.ts @@ -0,0 +1,87 @@ +import { APIClient } from "./ApiClient"; + +export interface AuthInfo { + id: number; + time_create: number; + time_update: number; + name: string; + email: string; +} + +const TokenStateKey = "auth-state"; + +export class AuthApi { + /** + * Check out whether user is signed in or not + */ + static get SignedIn(): boolean { + return localStorage.getItem(TokenStateKey) !== null; + } + + /** + * Mark user as authenticated + */ + static SetAuthenticated() { + localStorage.setItem(TokenStateKey, ""); + } + + /** + * Un-mark user as authenticated + */ + static UnsetAuthenticated() { + localStorage.removeItem(TokenStateKey); + } + + /** + * Start OpenID login + */ + static async StartOpenIDLogin(): Promise<{ url: string }> { + return ( + await APIClient.exec({ + uri: "/auth/start_oidc", + method: "GET", + }) + ).data; + } + + /** + * Finish OpenID login + */ + static async FinishOpenIDLogin(code: string, state: string): Promise { + await APIClient.exec({ + uri: "/auth/finish_oidc", + method: "POST", + jsonData: { code: code, state: state }, + }); + + this.SetAuthenticated(); + } + + /** + * Get auth information + */ + static async GetAuthInfo(): Promise { + return ( + await APIClient.exec({ + uri: "/auth/info", + method: "GET", + }) + ).data; + } + + /** + * Sign out + */ + static async SignOut(): Promise { + try { + await APIClient.exec({ + uri: "/auth/sign_out", + method: "GET", + }); + } catch (e) { + console.error("Failed to sign out user on API!", e); + } + + this.UnsetAuthenticated(); + } +} diff --git a/matrixgw_frontend/src/api/ServerApi.ts b/matrixgw_frontend/src/api/ServerApi.ts new file mode 100644 index 0000000..eb7d24d --- /dev/null +++ b/matrixgw_frontend/src/api/ServerApi.ts @@ -0,0 +1,48 @@ +import { APIClient } from "./ApiClient"; + +export interface ServerConfig { + auth_disabled: boolean; + oidc_provider_name: string; + constraints: ServerConstraints; +} + +export interface AccountType { + label: string; + code: string; + icon: string; +} + +export interface ServerConstraints { + token_name: LenConstraint; + token_ip_net: LenConstraint; + token_max_inactivity: LenConstraint; +} + +export interface LenConstraint { + min: number; + max: number; +} + +let config: ServerConfig | null = null; + +export class ServerApi { + /** + * Get server configuration + */ + static async LoadConfig(): Promise { + config = ( + await APIClient.exec({ + uri: "/server/config", + method: "GET", + }) + ).data; + } + + /** + * Get cached configuration + */ + static get Config(): ServerConfig { + if (config === null) throw new Error("Missing configuration!"); + return config; + } +} diff --git a/matrixgw_frontend/src/assets/react.svg b/matrixgw_frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/matrixgw_frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/matrixgw_frontend/src/hooks/contexts_provider/AlertDialogProvider.tsx b/matrixgw_frontend/src/hooks/contexts_provider/AlertDialogProvider.tsx new file mode 100644 index 0000000..cdda964 --- /dev/null +++ b/matrixgw_frontend/src/hooks/contexts_provider/AlertDialogProvider.tsx @@ -0,0 +1,68 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import React, { type PropsWithChildren } from "react"; + +type AlertContext = (message: string, title?: string) => Promise; + +const AlertContextK = React.createContext(null); + +export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [title, setTitle] = React.useState(undefined); + const [message, setMessage] = React.useState(""); + + const cb = React.useRef void)>(null); + + const handleClose = () => { + setOpen(false); + + if (cb.current !== null) cb.current(); + cb.current = null; + }; + + const hook: AlertContext = (message, title) => { + setTitle(title); + setMessage(message); + setOpen(true); + + return new Promise((res) => { + cb.current = res; + }); + }; + + return ( + <> + {p.children} + + + {title && {title}} + + + {message} + + + + + + + + ); +} + +export function useAlert(): AlertContext { + return React.use(AlertContextK)!; +} diff --git a/matrixgw_frontend/src/hooks/contexts_provider/ConfirmDialogProvider.tsx b/matrixgw_frontend/src/hooks/contexts_provider/ConfirmDialogProvider.tsx new file mode 100644 index 0000000..588dc06 --- /dev/null +++ b/matrixgw_frontend/src/hooks/contexts_provider/ConfirmDialogProvider.tsx @@ -0,0 +1,98 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import React, { type PropsWithChildren } from "react"; + +type ConfirmContext = ( + message: string | React.ReactElement, + title?: string, + confirmButton?: string +) => Promise; + +const ConfirmContextK = React.createContext(null); + +export function ConfirmDialogProvider( + p: PropsWithChildren +): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [title, setTitle] = React.useState(undefined); + const [message, setMessage] = React.useState(""); + const [confirmButton, setConfirmButton] = React.useState( + undefined + ); + + const cb = React.useRef void)>(null); + + const handleClose = (confirm: boolean) => { + setOpen(false); + + if (cb.current !== null) cb.current(confirm); + cb.current = null; + }; + + const hook: ConfirmContext = (message, title, confirmButton) => { + setTitle(title); + setMessage(message); + setConfirmButton(confirmButton); + setOpen(true); + + return new Promise((res) => { + cb.current = res; + }); + }; + + const keyUp = (e: React.KeyboardEvent) => { + if (e.code === "Enter") handleClose(true); + }; + + return ( + <> + {p.children} + + { + handleClose(false); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + onKeyUp={keyUp} + > + {title && {title}} + + + {message} + + + + + + + + + ); +} + +export function useConfirm(): ConfirmContext { + return React.use(ConfirmContextK)!; +} diff --git a/matrixgw_frontend/src/hooks/contexts_provider/LoadingMessageProvider.tsx b/matrixgw_frontend/src/hooks/contexts_provider/LoadingMessageProvider.tsx new file mode 100644 index 0000000..083a985 --- /dev/null +++ b/matrixgw_frontend/src/hooks/contexts_provider/LoadingMessageProvider.tsx @@ -0,0 +1,62 @@ +import { + CircularProgress, + Dialog, + DialogContent, + DialogContentText, +} from "@mui/material"; +import React, { type PropsWithChildren } from "react"; + +interface LoadingMessageContext { + show: (message: string) => void; + hide: () => void; +} + +const LoadingMessageContextK = + React.createContext(null); + +export function LoadingMessageProvider( + p: PropsWithChildren +): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [message, setMessage] = React.useState(""); + + const hook: LoadingMessageContext = { + show(message) { + setMessage(message); + setOpen(true); + }, + hide() { + setMessage(""); + setOpen(false); + }, + }; + + return ( + <> + {p.children} + + + + +
+ + + {message} +
+
+
+
+ + ); +} + +export function useLoadingMessage(): LoadingMessageContext { + return React.use(LoadingMessageContextK)!; +} diff --git a/matrixgw_frontend/src/hooks/contexts_provider/SnackbarProvider.tsx b/matrixgw_frontend/src/hooks/contexts_provider/SnackbarProvider.tsx new file mode 100644 index 0000000..8f9b861 --- /dev/null +++ b/matrixgw_frontend/src/hooks/contexts_provider/SnackbarProvider.tsx @@ -0,0 +1,41 @@ +import { Snackbar } from "@mui/material"; + +import React, { type PropsWithChildren } from "react"; + +type SnackbarContext = (message: string, duration?: number) => void; + +const SnackbarContextK = React.createContext(null); + +export function SnackbarProvider(p: PropsWithChildren): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [message, setMessage] = React.useState(""); + const [duration, setDuration] = React.useState(0); + + const handleClose = () => { + setOpen(false); + }; + + const hook: SnackbarContext = (message, duration) => { + setMessage(message); + setDuration(duration ?? 6000); + setOpen(true); + }; + + return ( + <> + {p.children} + + + + ); +} + +export function useSnackbar(): SnackbarContext { + return React.use(SnackbarContextK)!; +} diff --git a/matrixgw_frontend/src/index.css b/matrixgw_frontend/src/index.css index 08a3ac9..b303f80 100644 --- a/matrixgw_frontend/src/index.css +++ b/matrixgw_frontend/src/index.css @@ -1,68 +1,9 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - body { margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; } -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +html, +body, +#root { + height: 100%; } diff --git a/matrixgw_frontend/src/main.tsx b/matrixgw_frontend/src/main.tsx index bef5202..cc8c1bb 100644 --- a/matrixgw_frontend/src/main.tsx +++ b/matrixgw_frontend/src/main.tsx @@ -1,10 +1,36 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; -createRoot(document.getElementById('root')!).render( +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import { App } from "./App"; +import { AlertDialogProvider } from "./hooks/contexts_provider/AlertDialogProvider"; +import { ConfirmDialogProvider } from "./hooks/contexts_provider/ConfirmDialogProvider"; +import { SnackbarProvider } from "./hooks/contexts_provider/SnackbarProvider"; +import { LoadingMessageProvider } from "./hooks/contexts_provider/LoadingMessageProvider"; +import { AsyncWidget } from "./widgets/AsyncWidget"; +import { ServerApi } from "./api/ServerApi"; + +createRoot(document.getElementById("root")!).render( - - , -) + + + + + { + await ServerApi.LoadConfig(); + }} + errMsg="Failed to load static server configuration!" + build={() => } + /> + + + + + +); diff --git a/matrixgw_frontend/src/widgets/AsyncWidget.tsx b/matrixgw_frontend/src/widgets/AsyncWidget.tsx new file mode 100644 index 0000000..d642a7f --- /dev/null +++ b/matrixgw_frontend/src/widgets/AsyncWidget.tsx @@ -0,0 +1,94 @@ +import { Alert, Box, Button, CircularProgress } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; + +const State = { + Loading: 0, + Ready: 1, + Error: 2, +} as const; + +type State = keyof typeof State; + +export function AsyncWidget(p: { + loadKey: any; + load: () => Promise; + errMsg: string; + build: () => React.ReactElement; + ready?: boolean; + errAdditionalElement?: () => React.ReactElement; +}): React.ReactElement { + const [state, setState] = useState(State.Loading); + + const counter = useRef(null); + + const load = async () => { + try { + setState(State.Loading); + await p.load(); + setState(State.Ready); + } catch (e) { + console.error(e); + setState(State.Error); + } + }; + + useEffect(() => { + if (counter.current === p.loadKey) return; + counter.current = p.loadKey; + + load(); + }); + + if (state === State.Error) + return ( + + theme.palette.mode === "light" + ? theme.palette.grey[100] + : theme.palette.grey[900], + }} + > + + {p.errMsg} + + + + + {p.errAdditionalElement?.()} + + ); + + if (state === State.Loading || p.ready === false) + return ( + + theme.palette.mode === "light" + ? theme.palette.grey[100] + : theme.palette.grey[900], + }} + > + + + ); + + return p.build(); +} -- 2.49.1 From 20a42f3c554ac53e72754365efeb4960ad2569f1 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 19:20:17 +0100 Subject: [PATCH 07/21] Add custom theme --- matrixgw_frontend/src/App.tsx | 2 +- matrixgw_frontend/src/main.tsx | 37 +- matrixgw_frontend/src/theme/AppTheme.tsx | 46 ++ matrixgw_frontend/src/theme/README.md | 2 + .../src/theme/customizations/dataDisplay.tsx | 233 +++++++++ .../src/theme/customizations/feedback.tsx | 46 ++ .../src/theme/customizations/inputs.tsx | 452 ++++++++++++++++++ .../src/theme/customizations/navigation.tsx | 284 +++++++++++ .../src/theme/customizations/surfaces.ts | 113 +++++ .../src/theme/themePrimitives.ts | 414 ++++++++++++++++ matrixgw_frontend/src/widgets/AsyncWidget.tsx | 8 - 11 files changed, 1612 insertions(+), 25 deletions(-) create mode 100644 matrixgw_frontend/src/theme/AppTheme.tsx create mode 100644 matrixgw_frontend/src/theme/README.md create mode 100644 matrixgw_frontend/src/theme/customizations/dataDisplay.tsx create mode 100644 matrixgw_frontend/src/theme/customizations/feedback.tsx create mode 100644 matrixgw_frontend/src/theme/customizations/inputs.tsx create mode 100644 matrixgw_frontend/src/theme/customizations/navigation.tsx create mode 100644 matrixgw_frontend/src/theme/customizations/surfaces.ts create mode 100644 matrixgw_frontend/src/theme/themePrimitives.ts diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index c664eaa..20351d8 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -1,3 +1,3 @@ export function App(): React.ReactElement { - return <>hello; + return <>hello world; } diff --git a/matrixgw_frontend/src/main.tsx b/matrixgw_frontend/src/main.tsx index cc8c1bb..afd73ad 100644 --- a/matrixgw_frontend/src/main.tsx +++ b/matrixgw_frontend/src/main.tsx @@ -13,24 +13,29 @@ import { SnackbarProvider } from "./hooks/contexts_provider/SnackbarProvider"; import { LoadingMessageProvider } from "./hooks/contexts_provider/LoadingMessageProvider"; import { AsyncWidget } from "./widgets/AsyncWidget"; import { ServerApi } from "./api/ServerApi"; +import { AppTheme } from "./theme/AppTheme"; +import { CssBaseline } from "@mui/material"; createRoot(document.getElementById("root")!).render( - - - - - { - await ServerApi.LoadConfig(); - }} - errMsg="Failed to load static server configuration!" - build={() => } - /> - - - - + + + + + + + { + await ServerApi.LoadConfig(); + }} + errMsg="Failed to load static server configuration!" + build={() => } + /> + + + + + ); diff --git a/matrixgw_frontend/src/theme/AppTheme.tsx b/matrixgw_frontend/src/theme/AppTheme.tsx new file mode 100644 index 0000000..2c432a5 --- /dev/null +++ b/matrixgw_frontend/src/theme/AppTheme.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { ThemeProvider, createTheme } from "@mui/material/styles"; +import type { ThemeOptions } from "@mui/material/styles"; +import { inputsCustomizations } from "./customizations/inputs"; +import { dataDisplayCustomizations } from "./customizations/dataDisplay"; +import { feedbackCustomizations } from "./customizations/feedback"; +import { navigationCustomizations } from "./customizations/navigation"; +import { surfacesCustomizations } from "./customizations/surfaces"; +import { colorSchemes, typography, shadows, shape } from "./themePrimitives"; + +interface AppThemeProps { + themeComponents?: ThemeOptions["components"]; +} + +export function AppTheme( + props: React.PropsWithChildren +): React.ReactElement { + const { children, themeComponents } = props; + const theme = React.useMemo(() => { + return createTheme({ + // For more details about CSS variables configuration, see https://mui.com/material-ui/customization/css-theme-variables/configuration/ + cssVariables: { + colorSchemeSelector: "data-mui-color-scheme", + cssVarPrefix: "template", + }, + colorSchemes, // Recently added in v6 for building light & dark mode app, see https://mui.com/material-ui/customization/palette/#color-schemes + typography, + shadows, + shape, + components: { + ...inputsCustomizations, + ...dataDisplayCustomizations, + ...feedbackCustomizations, + ...navigationCustomizations, + ...surfacesCustomizations, + ...themeComponents, + }, + }); + }, [themeComponents]); + + return ( + + {children} + + ); +} diff --git a/matrixgw_frontend/src/theme/README.md b/matrixgw_frontend/src/theme/README.md new file mode 100644 index 0000000..e603e6a --- /dev/null +++ b/matrixgw_frontend/src/theme/README.md @@ -0,0 +1,2 @@ +# Application Theme +Taken from https://github.com/mui/material-ui/tree/v7.3.4/docs/data/material/getting-started/templates/shared-theme \ No newline at end of file diff --git a/matrixgw_frontend/src/theme/customizations/dataDisplay.tsx b/matrixgw_frontend/src/theme/customizations/dataDisplay.tsx new file mode 100644 index 0000000..c23166a --- /dev/null +++ b/matrixgw_frontend/src/theme/customizations/dataDisplay.tsx @@ -0,0 +1,233 @@ +import { buttonBaseClasses } from "@mui/material/ButtonBase"; +import { chipClasses } from "@mui/material/Chip"; +import { iconButtonClasses } from "@mui/material/IconButton"; +import { alpha, type Components, type Theme } from "@mui/material/styles"; +import { svgIconClasses } from "@mui/material/SvgIcon"; +import { typographyClasses } from "@mui/material/Typography"; +import { gray, green, red } from "../themePrimitives"; + +/* eslint-disable import/prefer-default-export */ +export const dataDisplayCustomizations: Components = { + MuiList: { + styleOverrides: { + root: { + padding: "8px", + display: "flex", + flexDirection: "column", + gap: 0, + }, + }, + }, + MuiListItem: { + styleOverrides: { + root: ({ theme }) => ({ + [`& .${svgIconClasses.root}`]: { + width: "1rem", + height: "1rem", + color: (theme.vars || theme).palette.text.secondary, + }, + [`& .${typographyClasses.root}`]: { + fontWeight: 500, + }, + [`& .${buttonBaseClasses.root}`]: { + display: "flex", + gap: 8, + padding: "2px 8px", + borderRadius: (theme.vars || theme).shape.borderRadius, + opacity: 0.7, + "&.Mui-selected": { + opacity: 1, + backgroundColor: alpha(theme.palette.action.selected, 0.3), + [`& .${svgIconClasses.root}`]: { + color: (theme.vars || theme).palette.text.primary, + }, + "&:focus-visible": { + backgroundColor: alpha(theme.palette.action.selected, 0.3), + }, + "&:hover": { + backgroundColor: alpha(theme.palette.action.selected, 0.5), + }, + }, + "&:focus-visible": { + backgroundColor: "transparent", + }, + }, + }), + }, + }, + MuiListItemText: { + styleOverrides: { + primary: ({ theme }) => ({ + fontSize: theme.typography.body2.fontSize, + fontWeight: 500, + lineHeight: theme.typography.body2.lineHeight, + }), + secondary: ({ theme }) => ({ + fontSize: theme.typography.caption.fontSize, + lineHeight: theme.typography.caption.lineHeight, + }), + }, + }, + MuiListSubheader: { + styleOverrides: { + root: ({ theme }) => ({ + backgroundColor: "transparent", + padding: "4px 8px", + fontSize: theme.typography.caption.fontSize, + fontWeight: 500, + lineHeight: theme.typography.caption.lineHeight, + }), + }, + }, + MuiListItemIcon: { + styleOverrides: { + root: { + minWidth: 0, + }, + }, + }, + MuiChip: { + defaultProps: { + size: "small", + }, + styleOverrides: { + root: ({ theme }) => ({ + border: "1px solid", + borderRadius: "999px", + [`& .${chipClasses.label}`]: { + fontWeight: 600, + }, + variants: [ + { + props: { + color: "default", + }, + style: { + borderColor: gray[200], + backgroundColor: gray[100], + [`& .${chipClasses.label}`]: { + color: gray[500], + }, + [`& .${chipClasses.icon}`]: { + color: gray[500], + }, + ...theme.applyStyles("dark", { + borderColor: gray[700], + backgroundColor: gray[800], + [`& .${chipClasses.label}`]: { + color: gray[300], + }, + [`& .${chipClasses.icon}`]: { + color: gray[300], + }, + }), + }, + }, + { + props: { + color: "success", + }, + style: { + borderColor: green[200], + backgroundColor: green[50], + [`& .${chipClasses.label}`]: { + color: green[500], + }, + [`& .${chipClasses.icon}`]: { + color: green[500], + }, + ...theme.applyStyles("dark", { + borderColor: green[800], + backgroundColor: green[900], + [`& .${chipClasses.label}`]: { + color: green[300], + }, + [`& .${chipClasses.icon}`]: { + color: green[300], + }, + }), + }, + }, + { + props: { + color: "error", + }, + style: { + borderColor: red[100], + backgroundColor: red[50], + [`& .${chipClasses.label}`]: { + color: red[500], + }, + [`& .${chipClasses.icon}`]: { + color: red[500], + }, + ...theme.applyStyles("dark", { + borderColor: red[800], + backgroundColor: red[900], + [`& .${chipClasses.label}`]: { + color: red[200], + }, + [`& .${chipClasses.icon}`]: { + color: red[300], + }, + }), + }, + }, + { + props: { size: "small" }, + style: { + maxHeight: 20, + [`& .${chipClasses.label}`]: { + fontSize: theme.typography.caption.fontSize, + }, + [`& .${svgIconClasses.root}`]: { + fontSize: theme.typography.caption.fontSize, + }, + }, + }, + { + props: { size: "medium" }, + style: { + [`& .${chipClasses.label}`]: { + fontSize: theme.typography.caption.fontSize, + }, + }, + }, + ], + }), + }, + }, + MuiTablePagination: { + styleOverrides: { + actions: { + display: "flex", + gap: 8, + marginRight: 6, + [`& .${iconButtonClasses.root}`]: { + minWidth: 0, + width: 36, + height: 36, + }, + }, + }, + }, + MuiIcon: { + defaultProps: { + fontSize: "small", + }, + styleOverrides: { + root: { + variants: [ + { + props: { + fontSize: "small", + }, + style: { + fontSize: "1rem", + }, + }, + ], + }, + }, + }, +}; diff --git a/matrixgw_frontend/src/theme/customizations/feedback.tsx b/matrixgw_frontend/src/theme/customizations/feedback.tsx new file mode 100644 index 0000000..b8dc04b --- /dev/null +++ b/matrixgw_frontend/src/theme/customizations/feedback.tsx @@ -0,0 +1,46 @@ +import { type Theme, alpha, type Components } from "@mui/material/styles"; +import { gray, orange } from "../themePrimitives"; + +/* eslint-disable import/prefer-default-export */ +export const feedbackCustomizations: Components = { + MuiAlert: { + styleOverrides: { + root: ({ theme }) => ({ + borderRadius: 10, + backgroundColor: orange[100], + color: (theme.vars || theme).palette.text.primary, + border: `1px solid ${alpha(orange[300], 0.5)}`, + "& .MuiAlert-icon": { + color: orange[500], + }, + ...theme.applyStyles("dark", { + backgroundColor: `${alpha(orange[900], 0.5)}`, + border: `1px solid ${alpha(orange[800], 0.5)}`, + }), + }), + }, + }, + MuiDialog: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiDialog-paper": { + borderRadius: "10px", + border: "1px solid", + borderColor: (theme.vars || theme).palette.divider, + }, + }), + }, + }, + MuiLinearProgress: { + styleOverrides: { + root: ({ theme }) => ({ + height: 8, + borderRadius: 8, + backgroundColor: gray[200], + ...theme.applyStyles("dark", { + backgroundColor: gray[800], + }), + }), + }, + }, +}; diff --git a/matrixgw_frontend/src/theme/customizations/inputs.tsx b/matrixgw_frontend/src/theme/customizations/inputs.tsx new file mode 100644 index 0000000..d30fcf2 --- /dev/null +++ b/matrixgw_frontend/src/theme/customizations/inputs.tsx @@ -0,0 +1,452 @@ +import { alpha, type Theme, type Components } from "@mui/material/styles"; +import { outlinedInputClasses } from "@mui/material/OutlinedInput"; +import { svgIconClasses } from "@mui/material/SvgIcon"; +import { toggleButtonGroupClasses } from "@mui/material/ToggleButtonGroup"; +import { toggleButtonClasses } from "@mui/material/ToggleButton"; +import CheckBoxOutlineBlankRoundedIcon from "@mui/icons-material/CheckBoxOutlineBlankRounded"; +import CheckRoundedIcon from "@mui/icons-material/CheckRounded"; +import RemoveRoundedIcon from "@mui/icons-material/RemoveRounded"; +import { gray, brand } from "../themePrimitives"; + +/* eslint-disable import/prefer-default-export */ +export const inputsCustomizations: Components = { + MuiButtonBase: { + defaultProps: { + disableTouchRipple: true, + disableRipple: true, + }, + styleOverrides: { + root: ({ theme }) => ({ + boxSizing: "border-box", + transition: "all 100ms ease-in", + "&:focus-visible": { + outline: `3px solid ${alpha(theme.palette.primary.main, 0.5)}`, + outlineOffset: "2px", + }, + }), + }, + }, + MuiButton: { + styleOverrides: { + root: ({ theme }) => ({ + boxShadow: "none", + borderRadius: (theme.vars || theme).shape.borderRadius, + textTransform: "none", + variants: [ + { + props: { + size: "small", + }, + style: { + height: "2.25rem", + padding: "8px 12px", + }, + }, + { + props: { + size: "medium", + }, + style: { + height: "2.5rem", // 40px + }, + }, + { + props: { + color: "primary", + variant: "contained", + }, + style: { + color: "white", + backgroundColor: gray[900], + backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`, + boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`, + border: `1px solid ${gray[700]}`, + "&:hover": { + backgroundImage: "none", + backgroundColor: gray[700], + boxShadow: "none", + }, + "&:active": { + backgroundColor: gray[800], + }, + ...theme.applyStyles("dark", { + color: "black", + backgroundColor: gray[50], + backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`, + boxShadow: "inset 0 -1px 0 hsl(220, 30%, 80%)", + border: `1px solid ${gray[50]}`, + "&:hover": { + backgroundImage: "none", + backgroundColor: gray[300], + boxShadow: "none", + }, + "&:active": { + backgroundColor: gray[400], + }, + }), + }, + }, + { + props: { + color: "secondary", + variant: "contained", + }, + style: { + color: "white", + backgroundColor: brand[300], + backgroundImage: `linear-gradient(to bottom, ${alpha( + brand[400], + 0.8 + )}, ${brand[500]})`, + boxShadow: `inset 0 2px 0 ${alpha( + brand[200], + 0.2 + )}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`, + border: `1px solid ${brand[500]}`, + "&:hover": { + backgroundColor: brand[700], + boxShadow: "none", + }, + "&:active": { + backgroundColor: brand[700], + backgroundImage: "none", + }, + }, + }, + { + props: { + variant: "outlined", + }, + style: { + color: (theme.vars || theme).palette.text.primary, + border: "1px solid", + borderColor: gray[200], + backgroundColor: alpha(gray[50], 0.3), + "&:hover": { + backgroundColor: gray[100], + borderColor: gray[300], + }, + "&:active": { + backgroundColor: gray[200], + }, + ...theme.applyStyles("dark", { + backgroundColor: gray[800], + borderColor: gray[700], + + "&:hover": { + backgroundColor: gray[900], + borderColor: gray[600], + }, + "&:active": { + backgroundColor: gray[900], + }, + }), + }, + }, + { + props: { + color: "secondary", + variant: "outlined", + }, + style: { + color: brand[700], + border: "1px solid", + borderColor: brand[200], + backgroundColor: brand[50], + "&:hover": { + backgroundColor: brand[100], + borderColor: brand[400], + }, + "&:active": { + backgroundColor: alpha(brand[200], 0.7), + }, + ...theme.applyStyles("dark", { + color: brand[50], + border: "1px solid", + borderColor: brand[900], + backgroundColor: alpha(brand[900], 0.3), + "&:hover": { + borderColor: brand[700], + backgroundColor: alpha(brand[900], 0.6), + }, + "&:active": { + backgroundColor: alpha(brand[900], 0.5), + }, + }), + }, + }, + { + props: { + variant: "text", + }, + style: { + color: gray[600], + "&:hover": { + backgroundColor: gray[100], + }, + "&:active": { + backgroundColor: gray[200], + }, + ...theme.applyStyles("dark", { + color: gray[50], + "&:hover": { + backgroundColor: gray[700], + }, + "&:active": { + backgroundColor: alpha(gray[700], 0.7), + }, + }), + }, + }, + { + props: { + color: "secondary", + variant: "text", + }, + style: { + color: brand[700], + "&:hover": { + backgroundColor: alpha(brand[100], 0.5), + }, + "&:active": { + backgroundColor: alpha(brand[200], 0.7), + }, + ...theme.applyStyles("dark", { + color: brand[100], + "&:hover": { + backgroundColor: alpha(brand[900], 0.5), + }, + "&:active": { + backgroundColor: alpha(brand[900], 0.3), + }, + }), + }, + }, + ], + }), + }, + }, + MuiIconButton: { + styleOverrides: { + root: ({ theme }) => ({ + boxShadow: "none", + borderRadius: (theme.vars || theme).shape.borderRadius, + textTransform: "none", + fontWeight: theme.typography.fontWeightMedium, + letterSpacing: 0, + color: (theme.vars || theme).palette.text.primary, + border: "1px solid ", + borderColor: gray[200], + backgroundColor: alpha(gray[50], 0.3), + "&:hover": { + backgroundColor: gray[100], + borderColor: gray[300], + }, + "&:active": { + backgroundColor: gray[200], + }, + ...theme.applyStyles("dark", { + backgroundColor: gray[800], + borderColor: gray[700], + "&:hover": { + backgroundColor: gray[900], + borderColor: gray[600], + }, + "&:active": { + backgroundColor: gray[900], + }, + }), + variants: [ + { + props: { + size: "small", + }, + style: { + width: "2.25rem", + height: "2.25rem", + padding: "0.25rem", + [`& .${svgIconClasses.root}`]: { fontSize: "1rem" }, + }, + }, + { + props: { + size: "medium", + }, + style: { + width: "2.5rem", + height: "2.5rem", + }, + }, + ], + }), + }, + }, + MuiToggleButtonGroup: { + styleOverrides: { + root: ({ theme }) => ({ + borderRadius: "10px", + boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`, + [`& .${toggleButtonGroupClasses.selected}`]: { + color: brand[500], + }, + ...theme.applyStyles("dark", { + [`& .${toggleButtonGroupClasses.selected}`]: { + color: "#fff", + }, + boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`, + }), + }), + }, + }, + MuiToggleButton: { + styleOverrides: { + root: ({ theme }) => ({ + padding: "12px 16px", + textTransform: "none", + borderRadius: "10px", + fontWeight: 500, + ...theme.applyStyles("dark", { + color: gray[400], + boxShadow: "0 4px 16px rgba(0, 0, 0, 0.5)", + [`&.${toggleButtonClasses.selected}`]: { + color: brand[300], + }, + }), + }), + }, + }, + MuiCheckbox: { + defaultProps: { + disableRipple: true, + icon: ( + + ), + checkedIcon: , + indeterminateIcon: , + }, + styleOverrides: { + root: ({ theme }) => ({ + margin: 10, + height: 16, + width: 16, + borderRadius: 5, + border: "1px solid ", + borderColor: alpha(gray[300], 0.8), + boxShadow: "0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset", + backgroundColor: alpha(gray[100], 0.4), + transition: "border-color, background-color, 120ms ease-in", + "&:hover": { + borderColor: brand[300], + }, + "&.Mui-focusVisible": { + outline: `3px solid ${alpha(brand[500], 0.5)}`, + outlineOffset: "2px", + borderColor: brand[400], + }, + "&.Mui-checked": { + color: "white", + backgroundColor: brand[500], + borderColor: brand[500], + boxShadow: `none`, + "&:hover": { + backgroundColor: brand[600], + }, + }, + ...theme.applyStyles("dark", { + borderColor: alpha(gray[700], 0.8), + boxShadow: "0 0 0 1.5px hsl(210, 0%, 0%) inset", + backgroundColor: alpha(gray[900], 0.8), + "&:hover": { + borderColor: brand[300], + }, + "&.Mui-focusVisible": { + borderColor: brand[400], + outline: `3px solid ${alpha(brand[500], 0.5)}`, + outlineOffset: "2px", + }, + }), + }), + }, + }, + MuiInputBase: { + styleOverrides: { + root: { + border: "none", + }, + input: { + "&::placeholder": { + opacity: 0.7, + color: gray[500], + }, + }, + }, + }, + MuiOutlinedInput: { + styleOverrides: { + input: { + padding: 0, + }, + root: ({ theme }) => ({ + padding: "8px 12px", + color: (theme.vars || theme).palette.text.primary, + borderRadius: (theme.vars || theme).shape.borderRadius, + border: `1px solid ${(theme.vars || theme).palette.divider}`, + backgroundColor: (theme.vars || theme).palette.background.default, + transition: "border 120ms ease-in", + "&:hover": { + borderColor: gray[400], + }, + [`&.${outlinedInputClasses.focused}`]: { + outline: `3px solid ${alpha(brand[500], 0.5)}`, + borderColor: brand[400], + }, + ...theme.applyStyles("dark", { + "&:hover": { + borderColor: gray[500], + }, + }), + variants: [ + { + props: { + size: "small", + }, + style: { + height: "2.25rem", + }, + }, + { + props: { + size: "medium", + }, + style: { + height: "2.5rem", + }, + }, + ], + }), + notchedOutline: { + border: "none", + }, + }, + }, + MuiInputAdornment: { + styleOverrides: { + root: ({ theme }) => ({ + color: (theme.vars || theme).palette.grey[500], + ...theme.applyStyles("dark", { + color: (theme.vars || theme).palette.grey[400], + }), + }), + }, + }, + MuiFormLabel: { + styleOverrides: { + root: ({ theme }) => ({ + typography: theme.typography.caption, + marginBottom: 8, + }), + }, + }, +}; diff --git a/matrixgw_frontend/src/theme/customizations/navigation.tsx b/matrixgw_frontend/src/theme/customizations/navigation.tsx new file mode 100644 index 0000000..2b1a584 --- /dev/null +++ b/matrixgw_frontend/src/theme/customizations/navigation.tsx @@ -0,0 +1,284 @@ +import * as React from "react"; +import { type Theme, alpha, type Components } from "@mui/material/styles"; +import { type SvgIconProps } from "@mui/material/SvgIcon"; +import { buttonBaseClasses } from "@mui/material/ButtonBase"; +import { dividerClasses } from "@mui/material/Divider"; +import { menuItemClasses } from "@mui/material/MenuItem"; +import { selectClasses } from "@mui/material/Select"; +import { tabClasses } from "@mui/material/Tab"; +import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded"; +import { gray, brand } from "../themePrimitives"; + +/* eslint-disable import/prefer-default-export */ +export const navigationCustomizations: Components = { + MuiMenuItem: { + styleOverrides: { + root: ({ theme }) => ({ + borderRadius: (theme.vars || theme).shape.borderRadius, + padding: "6px 8px", + [`&.${menuItemClasses.focusVisible}`]: { + backgroundColor: "transparent", + }, + [`&.${menuItemClasses.selected}`]: { + [`&.${menuItemClasses.focusVisible}`]: { + backgroundColor: alpha(theme.palette.action.selected, 0.3), + }, + }, + }), + }, + }, + MuiMenu: { + styleOverrides: { + list: { + gap: "0px", + [`&.${dividerClasses.root}`]: { + margin: "0 -8px", + }, + }, + paper: ({ theme }) => ({ + marginTop: "4px", + borderRadius: (theme.vars || theme).shape.borderRadius, + border: `1px solid ${(theme.vars || theme).palette.divider}`, + backgroundImage: "none", + background: "hsl(0, 0%, 100%)", + boxShadow: + "hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px", + [`& .${buttonBaseClasses.root}`]: { + "&.Mui-selected": { + backgroundColor: alpha(theme.palette.action.selected, 0.3), + }, + }, + ...theme.applyStyles("dark", { + background: gray[900], + boxShadow: + "hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px", + }), + }), + }, + }, + MuiSelect: { + defaultProps: { + IconComponent: React.forwardRef( + (props, ref) => ( + + ) + ), + }, + styleOverrides: { + root: ({ theme }) => ({ + borderRadius: (theme.vars || theme).shape.borderRadius, + border: "1px solid", + borderColor: gray[200], + backgroundColor: (theme.vars || theme).palette.background.paper, + boxShadow: `inset 0 1px 0 1px hsla(220, 0%, 100%, 0.6), inset 0 -1px 0 1px hsla(220, 35%, 90%, 0.5)`, + "&:hover": { + borderColor: gray[300], + backgroundColor: (theme.vars || theme).palette.background.paper, + boxShadow: "none", + }, + [`&.${selectClasses.focused}`]: { + outlineOffset: 0, + borderColor: gray[400], + }, + "&:before, &:after": { + display: "none", + }, + + ...theme.applyStyles("dark", { + borderRadius: (theme.vars || theme).shape.borderRadius, + borderColor: gray[700], + backgroundColor: (theme.vars || theme).palette.background.paper, + boxShadow: `inset 0 1px 0 1px ${alpha( + gray[700], + 0.15 + )}, inset 0 -1px 0 1px hsla(220, 0%, 0%, 0.7)`, + "&:hover": { + borderColor: alpha(gray[700], 0.7), + backgroundColor: (theme.vars || theme).palette.background.paper, + boxShadow: "none", + }, + [`&.${selectClasses.focused}`]: { + outlineOffset: 0, + borderColor: gray[900], + }, + "&:before, &:after": { + display: "none", + }, + }), + }), + select: ({ theme }) => ({ + display: "flex", + alignItems: "center", + ...theme.applyStyles("dark", { + display: "flex", + alignItems: "center", + "&:focus-visible": { + backgroundColor: gray[900], + }, + }), + }), + }, + }, + MuiLink: { + defaultProps: { + underline: "none", + }, + styleOverrides: { + root: ({ theme }) => ({ + color: (theme.vars || theme).palette.text.primary, + fontWeight: 500, + position: "relative", + textDecoration: "none", + width: "fit-content", + "&::before": { + content: '""', + position: "absolute", + width: "100%", + height: "1px", + bottom: 0, + left: 0, + backgroundColor: (theme.vars || theme).palette.text.secondary, + opacity: 0.3, + transition: "width 0.3s ease, opacity 0.3s ease", + }, + "&:hover::before": { + width: 0, + }, + "&:focus-visible": { + outline: `3px solid ${alpha(brand[500], 0.5)}`, + outlineOffset: "4px", + borderRadius: "2px", + }, + }), + }, + }, + MuiDrawer: { + styleOverrides: { + paper: ({ theme }) => ({ + backgroundColor: (theme.vars || theme).palette.background.default, + }), + }, + }, + MuiPaginationItem: { + styleOverrides: { + root: ({ theme }) => ({ + "&.Mui-selected": { + color: "white", + backgroundColor: (theme.vars || theme).palette.grey[900], + }, + ...theme.applyStyles("dark", { + "&.Mui-selected": { + color: "black", + backgroundColor: (theme.vars || theme).palette.grey[50], + }, + }), + }), + }, + }, + MuiTabs: { + styleOverrides: { + root: { minHeight: "fit-content" }, + indicator: ({ theme }) => ({ + backgroundColor: (theme.vars || theme).palette.grey[800], + ...theme.applyStyles("dark", { + backgroundColor: (theme.vars || theme).palette.grey[200], + }), + }), + }, + }, + MuiTab: { + styleOverrides: { + root: ({ theme }) => ({ + padding: "6px 8px", + marginBottom: "8px", + textTransform: "none", + minWidth: "fit-content", + minHeight: "fit-content", + color: (theme.vars || theme).palette.text.secondary, + borderRadius: (theme.vars || theme).shape.borderRadius, + border: "1px solid", + borderColor: "transparent", + ":hover": { + color: (theme.vars || theme).palette.text.primary, + backgroundColor: gray[100], + borderColor: gray[200], + }, + [`&.${tabClasses.selected}`]: { + color: gray[900], + }, + ...theme.applyStyles("dark", { + ":hover": { + color: (theme.vars || theme).palette.text.primary, + backgroundColor: gray[800], + borderColor: gray[700], + }, + [`&.${tabClasses.selected}`]: { + color: "#fff", + }, + }), + }), + }, + }, + MuiStepConnector: { + styleOverrides: { + line: ({ theme }) => ({ + borderTop: "1px solid", + borderColor: (theme.vars || theme).palette.divider, + flex: 1, + borderRadius: "99px", + }), + }, + }, + MuiStepIcon: { + styleOverrides: { + root: ({ theme }) => ({ + color: "transparent", + border: `1px solid ${gray[400]}`, + width: 12, + height: 12, + borderRadius: "50%", + "& text": { + display: "none", + }, + "&.Mui-active": { + border: "none", + color: (theme.vars || theme).palette.primary.main, + }, + "&.Mui-completed": { + border: "none", + color: (theme.vars || theme).palette.success.main, + }, + ...theme.applyStyles("dark", { + border: `1px solid ${gray[700]}`, + "&.Mui-active": { + border: "none", + color: (theme.vars || theme).palette.primary.light, + }, + "&.Mui-completed": { + border: "none", + color: (theme.vars || theme).palette.success.light, + }, + }), + variants: [ + { + props: { completed: true }, + style: { + width: 12, + height: 12, + }, + }, + ], + }), + }, + }, + MuiStepLabel: { + styleOverrides: { + label: ({ theme }) => ({ + "&.Mui-completed": { + opacity: 0.6, + ...theme.applyStyles("dark", { opacity: 0.5 }), + }, + }), + }, + }, +}; diff --git a/matrixgw_frontend/src/theme/customizations/surfaces.ts b/matrixgw_frontend/src/theme/customizations/surfaces.ts new file mode 100644 index 0000000..38bf7ed --- /dev/null +++ b/matrixgw_frontend/src/theme/customizations/surfaces.ts @@ -0,0 +1,113 @@ +import { alpha, type Theme, type Components } from "@mui/material/styles"; +import { gray } from "../themePrimitives"; + +/* eslint-disable import/prefer-default-export */ +export const surfacesCustomizations: Components = { + MuiAccordion: { + defaultProps: { + elevation: 0, + disableGutters: true, + }, + styleOverrides: { + root: ({ theme }) => ({ + padding: 4, + overflow: "clip", + backgroundColor: (theme.vars || theme).palette.background.default, + border: "1px solid", + borderColor: (theme.vars || theme).palette.divider, + ":before": { + backgroundColor: "transparent", + }, + "&:not(:last-of-type)": { + borderBottom: "none", + }, + "&:first-of-type": { + borderTopLeftRadius: (theme.vars || theme).shape.borderRadius, + borderTopRightRadius: (theme.vars || theme).shape.borderRadius, + }, + "&:last-of-type": { + borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius, + borderBottomRightRadius: (theme.vars || theme).shape.borderRadius, + }, + }), + }, + }, + MuiAccordionSummary: { + styleOverrides: { + root: ({ theme }) => ({ + border: "none", + borderRadius: 8, + "&:hover": { backgroundColor: gray[50] }, + "&:focus-visible": { backgroundColor: "transparent" }, + ...theme.applyStyles("dark", { + "&:hover": { backgroundColor: gray[800] }, + }), + }), + }, + }, + MuiAccordionDetails: { + styleOverrides: { + root: { mb: 20, border: "none" }, + }, + }, + MuiPaper: { + defaultProps: { + elevation: 0, + }, + }, + MuiCard: { + styleOverrides: { + root: ({ theme }) => { + return { + padding: 16, + gap: 16, + transition: "all 100ms ease", + backgroundColor: gray[50], + borderRadius: (theme.vars || theme).shape.borderRadius, + border: `1px solid ${(theme.vars || theme).palette.divider}`, + boxShadow: "none", + ...theme.applyStyles("dark", { + backgroundColor: gray[800], + }), + variants: [ + { + props: { + variant: "outlined", + }, + style: { + border: `1px solid ${(theme.vars || theme).palette.divider}`, + boxShadow: "none", + background: "hsl(0, 0%, 100%)", + ...theme.applyStyles("dark", { + background: alpha(gray[900], 0.4), + }), + }, + }, + ], + }; + }, + }, + }, + MuiCardContent: { + styleOverrides: { + root: { + padding: 0, + "&:last-child": { paddingBottom: 0 }, + }, + }, + }, + MuiCardHeader: { + styleOverrides: { + root: { + padding: 0, + }, + }, + }, + MuiCardActions: { + styleOverrides: { + root: { + padding: 0, + }, + }, + }, +}; diff --git a/matrixgw_frontend/src/theme/themePrimitives.ts b/matrixgw_frontend/src/theme/themePrimitives.ts new file mode 100644 index 0000000..1e95864 --- /dev/null +++ b/matrixgw_frontend/src/theme/themePrimitives.ts @@ -0,0 +1,414 @@ +import { + createTheme, + alpha, + type PaletteMode, + type Shadows, +} from "@mui/material/styles"; + +declare module "@mui/material/Paper" { + interface PaperPropsVariantOverrides { + highlighted: true; + } +} +declare module "@mui/material/styles" { + interface ColorRange { + 50: string; + 100: string; + 200: string; + 300: string; + 400: string; + 500: string; + 600: string; + 700: string; + 800: string; + 900: string; + } + + interface PaletteColor extends ColorRange {} + + interface Palette { + baseShadow: string; + } +} + +const defaultTheme = createTheme(); + +const customShadows: Shadows = [...defaultTheme.shadows]; + +export const brand = { + 50: "hsl(210, 100%, 95%)", + 100: "hsl(210, 100%, 92%)", + 200: "hsl(210, 100%, 80%)", + 300: "hsl(210, 100%, 65%)", + 400: "hsl(210, 98%, 48%)", + 500: "hsl(210, 98%, 42%)", + 600: "hsl(210, 98%, 55%)", + 700: "hsl(210, 100%, 35%)", + 800: "hsl(210, 100%, 16%)", + 900: "hsl(210, 100%, 21%)", +}; + +export const gray = { + 50: "hsl(220, 35%, 97%)", + 100: "hsl(220, 30%, 94%)", + 200: "hsl(220, 20%, 88%)", + 300: "hsl(220, 20%, 80%)", + 400: "hsl(220, 20%, 65%)", + 500: "hsl(220, 20%, 42%)", + 600: "hsl(220, 20%, 35%)", + 700: "hsl(220, 20%, 25%)", + 800: "hsl(220, 30%, 6%)", + 900: "hsl(220, 35%, 3%)", +}; + +export const green = { + 50: "hsl(120, 80%, 98%)", + 100: "hsl(120, 75%, 94%)", + 200: "hsl(120, 75%, 87%)", + 300: "hsl(120, 61%, 77%)", + 400: "hsl(120, 44%, 53%)", + 500: "hsl(120, 59%, 30%)", + 600: "hsl(120, 70%, 25%)", + 700: "hsl(120, 75%, 16%)", + 800: "hsl(120, 84%, 10%)", + 900: "hsl(120, 87%, 6%)", +}; + +export const orange = { + 50: "hsl(45, 100%, 97%)", + 100: "hsl(45, 92%, 90%)", + 200: "hsl(45, 94%, 80%)", + 300: "hsl(45, 90%, 65%)", + 400: "hsl(45, 90%, 40%)", + 500: "hsl(45, 90%, 35%)", + 600: "hsl(45, 91%, 25%)", + 700: "hsl(45, 94%, 20%)", + 800: "hsl(45, 95%, 16%)", + 900: "hsl(45, 93%, 12%)", +}; + +export const red = { + 50: "hsl(0, 100%, 97%)", + 100: "hsl(0, 92%, 90%)", + 200: "hsl(0, 94%, 80%)", + 300: "hsl(0, 90%, 65%)", + 400: "hsl(0, 90%, 40%)", + 500: "hsl(0, 90%, 30%)", + 600: "hsl(0, 91%, 25%)", + 700: "hsl(0, 94%, 18%)", + 800: "hsl(0, 95%, 12%)", + 900: "hsl(0, 93%, 6%)", +}; + +export const getDesignTokens = (mode: PaletteMode) => { + customShadows[1] = + mode === "dark" + ? "hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px" + : "hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px"; + + return { + palette: { + mode, + primary: { + light: brand[200], + main: brand[400], + dark: brand[700], + contrastText: brand[50], + ...(mode === "dark" && { + contrastText: brand[50], + light: brand[300], + main: brand[400], + dark: brand[700], + }), + }, + info: { + light: brand[100], + main: brand[300], + dark: brand[600], + contrastText: gray[50], + ...(mode === "dark" && { + contrastText: brand[300], + light: brand[500], + main: brand[700], + dark: brand[900], + }), + }, + warning: { + light: orange[300], + main: orange[400], + dark: orange[800], + ...(mode === "dark" && { + light: orange[400], + main: orange[500], + dark: orange[700], + }), + }, + error: { + light: red[300], + main: red[400], + dark: red[800], + ...(mode === "dark" && { + light: red[400], + main: red[500], + dark: red[700], + }), + }, + success: { + light: green[300], + main: green[400], + dark: green[800], + ...(mode === "dark" && { + light: green[400], + main: green[500], + dark: green[700], + }), + }, + grey: { + ...gray, + }, + divider: mode === "dark" ? alpha(gray[700], 0.6) : alpha(gray[300], 0.4), + background: { + default: "hsl(0, 0%, 99%)", + paper: "hsl(220, 35%, 97%)", + ...(mode === "dark" && { + default: gray[900], + paper: "hsl(220, 30%, 7%)", + }), + }, + text: { + primary: gray[800], + secondary: gray[600], + warning: orange[400], + ...(mode === "dark" && { + primary: "hsl(0, 0%, 100%)", + secondary: gray[400], + }), + }, + action: { + hover: alpha(gray[200], 0.2), + selected: `${alpha(gray[200], 0.3)}`, + ...(mode === "dark" && { + hover: alpha(gray[600], 0.2), + selected: alpha(gray[600], 0.3), + }), + }, + }, + typography: { + fontFamily: "Inter, sans-serif", + h1: { + fontSize: defaultTheme.typography.pxToRem(48), + fontWeight: 600, + lineHeight: 1.2, + letterSpacing: -0.5, + }, + h2: { + fontSize: defaultTheme.typography.pxToRem(36), + fontWeight: 600, + lineHeight: 1.2, + }, + h3: { + fontSize: defaultTheme.typography.pxToRem(30), + lineHeight: 1.2, + }, + h4: { + fontSize: defaultTheme.typography.pxToRem(24), + fontWeight: 600, + lineHeight: 1.5, + }, + h5: { + fontSize: defaultTheme.typography.pxToRem(20), + fontWeight: 600, + }, + h6: { + fontSize: defaultTheme.typography.pxToRem(18), + fontWeight: 600, + }, + subtitle1: { + fontSize: defaultTheme.typography.pxToRem(18), + }, + subtitle2: { + fontSize: defaultTheme.typography.pxToRem(14), + fontWeight: 500, + }, + body1: { + fontSize: defaultTheme.typography.pxToRem(14), + }, + body2: { + fontSize: defaultTheme.typography.pxToRem(14), + fontWeight: 400, + }, + caption: { + fontSize: defaultTheme.typography.pxToRem(12), + fontWeight: 400, + }, + }, + shape: { + borderRadius: 8, + }, + shadows: customShadows, + }; +}; + +export const colorSchemes = { + light: { + palette: { + primary: { + light: brand[200], + main: brand[400], + dark: brand[700], + contrastText: brand[50], + }, + info: { + light: brand[100], + main: brand[300], + dark: brand[600], + contrastText: gray[50], + }, + warning: { + light: orange[300], + main: orange[400], + dark: orange[800], + }, + error: { + light: red[300], + main: red[400], + dark: red[800], + }, + success: { + light: green[300], + main: green[400], + dark: green[800], + }, + grey: { + ...gray, + }, + divider: alpha(gray[300], 0.4), + background: { + default: "hsl(0, 0%, 99%)", + paper: "hsl(220, 35%, 97%)", + }, + text: { + primary: gray[800], + secondary: gray[600], + warning: orange[400], + }, + action: { + hover: alpha(gray[200], 0.2), + selected: `${alpha(gray[200], 0.3)}`, + }, + baseShadow: + "hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px", + }, + }, + dark: { + palette: { + primary: { + contrastText: brand[50], + light: brand[300], + main: brand[400], + dark: brand[700], + }, + info: { + contrastText: brand[300], + light: brand[500], + main: brand[700], + dark: brand[900], + }, + warning: { + light: orange[400], + main: orange[500], + dark: orange[700], + }, + error: { + light: red[400], + main: red[500], + dark: red[700], + }, + success: { + light: green[400], + main: green[500], + dark: green[700], + }, + grey: { + ...gray, + }, + divider: alpha(gray[700], 0.6), + background: { + default: gray[900], + paper: "hsl(220, 30%, 7%)", + }, + text: { + primary: "hsl(0, 0%, 100%)", + secondary: gray[400], + }, + action: { + hover: alpha(gray[600], 0.2), + selected: alpha(gray[600], 0.3), + }, + baseShadow: + "hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px", + }, + }, +}; + +export const typography = { + fontFamily: "Inter, sans-serif", + h1: { + fontSize: defaultTheme.typography.pxToRem(48), + fontWeight: 600, + lineHeight: 1.2, + letterSpacing: -0.5, + }, + h2: { + fontSize: defaultTheme.typography.pxToRem(36), + fontWeight: 600, + lineHeight: 1.2, + }, + h3: { + fontSize: defaultTheme.typography.pxToRem(30), + lineHeight: 1.2, + }, + h4: { + fontSize: defaultTheme.typography.pxToRem(24), + fontWeight: 600, + lineHeight: 1.5, + }, + h5: { + fontSize: defaultTheme.typography.pxToRem(20), + fontWeight: 600, + }, + h6: { + fontSize: defaultTheme.typography.pxToRem(18), + fontWeight: 600, + }, + subtitle1: { + fontSize: defaultTheme.typography.pxToRem(18), + }, + subtitle2: { + fontSize: defaultTheme.typography.pxToRem(14), + fontWeight: 500, + }, + body1: { + fontSize: defaultTheme.typography.pxToRem(14), + }, + body2: { + fontSize: defaultTheme.typography.pxToRem(14), + fontWeight: 400, + }, + caption: { + fontSize: defaultTheme.typography.pxToRem(12), + fontWeight: 400, + }, +}; + +export const shape = { + borderRadius: 8, +}; + +// @ts-ignore +const defaultShadows: Shadows = [ + "none", + "var(--template-palette-baseShadow)", + ...defaultTheme.shadows.slice(2), +]; +export const shadows = defaultShadows; diff --git a/matrixgw_frontend/src/widgets/AsyncWidget.tsx b/matrixgw_frontend/src/widgets/AsyncWidget.tsx index d642a7f..bc7e036 100644 --- a/matrixgw_frontend/src/widgets/AsyncWidget.tsx +++ b/matrixgw_frontend/src/widgets/AsyncWidget.tsx @@ -50,10 +50,6 @@ export function AsyncWidget(p: { height: "100%", flex: "1", flexDirection: "column", - backgroundColor: (theme) => - theme.palette.mode === "light" - ? theme.palette.grey[100] - : theme.palette.grey[900], }} > - theme.palette.mode === "light" - ? theme.palette.grey[100] - : theme.palette.grey[900], }} > -- 2.49.1 From 9ed711777c11e16c524685a8903c742ff26dfbc8 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 19:43:51 +0100 Subject: [PATCH 08/21] Add webapp skeletons --- matrixgw_frontend/package-lock.json | 40 +++++++++++- matrixgw_frontend/package.json | 3 +- matrixgw_frontend/src/App.tsx | 62 ++++++++++++++++++- matrixgw_frontend/src/routes/HomeRoute.tsx | 3 + .../src/routes/NotFoundRoute.tsx | 23 +++++++ .../src/routes/auth/LoginRoute.tsx | 3 + .../src/routes/auth/OIDCCbRoute.tsx | 53 ++++++++++++++++ .../src/widgets/BaseAuthenticatedPage.tsx | 3 + matrixgw_frontend/src/widgets/RouterLink.tsx | 16 +++++ .../src/widgets/auth/AuthSingleMessage.tsx | 13 ++++ .../src/widgets/auth/BaseLoginPage.tsx | 3 + 11 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 matrixgw_frontend/src/routes/HomeRoute.tsx create mode 100644 matrixgw_frontend/src/routes/NotFoundRoute.tsx create mode 100644 matrixgw_frontend/src/routes/auth/LoginRoute.tsx create mode 100644 matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx create mode 100644 matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx create mode 100644 matrixgw_frontend/src/widgets/RouterLink.tsx create mode 100644 matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx create mode 100644 matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx diff --git a/matrixgw_frontend/package-lock.json b/matrixgw_frontend/package-lock.json index 5f2de0a..e80d450 100644 --- a/matrixgw_frontend/package-lock.json +++ b/matrixgw_frontend/package-lock.json @@ -14,7 +14,8 @@ "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", @@ -2012,6 +2013,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3399,6 +3409,28 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", + "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -3536,6 +3568,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/matrixgw_frontend/package.json b/matrixgw_frontend/package.json index 2025306..3207e3c 100644 --- a/matrixgw_frontend/package.json +++ b/matrixgw_frontend/package.json @@ -16,7 +16,8 @@ "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 20351d8..4b0d983 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -1,3 +1,61 @@ -export function App(): React.ReactElement { - return <>hello world; +import React from "react"; +import { + createBrowserRouter, + createRoutesFromElements, + Route, + RouterProvider, +} from "react-router"; +import { AuthApi } from "./api/AuthApi"; +import { ServerApi } from "./api/ServerApi"; +import { LoginRoute } from "./routes/auth/LoginRoute"; +import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; +import { HomeRoute } from "./routes/HomeRoute"; +import { NotFoundRoute } from "./routes/NotFoundRoute"; +import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; +import { BaseLoginPage } from "./widgets/auth/BaseLoginPage"; + +interface AuthContext { + signedIn: boolean; + setSignedIn: (signedIn: boolean) => void; +} + +const AuthContextK = React.createContext(null); + +export function App(): React.ReactElement { + const [signedIn, setSignedIn] = React.useState(AuthApi.SignedIn); + + const context: AuthContext = { + signedIn: signedIn, + setSignedIn: (s) => { + setSignedIn(s); + location.reload(); + }, + }; + + const router = createBrowserRouter( + createRoutesFromElements( + signedIn || ServerApi.Config.auth_disabled ? ( + }> + } /> + } /> + + ) : ( + }> + } /> + } /> + } /> + + ) + ) + ); + + return ( + + + + ); +} + +export function useAuth(): AuthContext { + return React.use(AuthContextK)!; } diff --git a/matrixgw_frontend/src/routes/HomeRoute.tsx b/matrixgw_frontend/src/routes/HomeRoute.tsx new file mode 100644 index 0000000..b93b24f --- /dev/null +++ b/matrixgw_frontend/src/routes/HomeRoute.tsx @@ -0,0 +1,3 @@ +export function HomeRoute(): React.ReactElement { + return

Todo home route

; +} diff --git a/matrixgw_frontend/src/routes/NotFoundRoute.tsx b/matrixgw_frontend/src/routes/NotFoundRoute.tsx new file mode 100644 index 0000000..72812e6 --- /dev/null +++ b/matrixgw_frontend/src/routes/NotFoundRoute.tsx @@ -0,0 +1,23 @@ +import { Button } from "@mui/material"; +import { RouterLink } from "../widgets/RouterLink"; + +export function NotFoundRoute(): React.ReactElement { + return ( +
+

404 Not found

+

The page you requested was not found!

+ + + +
+ ); +} diff --git a/matrixgw_frontend/src/routes/auth/LoginRoute.tsx b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx new file mode 100644 index 0000000..6190120 --- /dev/null +++ b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx @@ -0,0 +1,3 @@ +export function LoginRoute(): React.ReactElement { + return <>LoginRoute; +} diff --git a/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx new file mode 100644 index 0000000..90c67f8 --- /dev/null +++ b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx @@ -0,0 +1,53 @@ +import { CircularProgress } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router"; +import { AuthApi } from "../../api/AuthApi"; +import { useAuth } from "../../App"; +import { AuthSingleMessage } from "../../widgets/auth/AuthSingleMessage"; + +/** + * OpenID login callback route + */ +export function OIDCCbRoute(): React.ReactElement { + const auth = useAuth(); + const navigate = useNavigate(); + + const [error, setError] = useState(false); + + const [searchParams] = useSearchParams(); + const code = searchParams.get("code"); + const state = searchParams.get("state"); + + const count = useRef(""); + + useEffect(() => { + const load = async () => { + try { + if (count.current === code) { + return; + } + count.current = code!; + + await AuthApi.FinishOpenIDLogin(code!, state!); + navigate("/"); + auth.setSignedIn(true); + } catch (e) { + console.error(e); + setError(true); + } + }; + + load(); + }); + + if (error) + return ( + + ); + + return ( + <> + + + ); +} diff --git a/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx new file mode 100644 index 0000000..79607a4 --- /dev/null +++ b/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx @@ -0,0 +1,3 @@ +export function BaseAuthenticatedPage(): React.ReactElement { + return

todo authenticated

; +} diff --git a/matrixgw_frontend/src/widgets/RouterLink.tsx b/matrixgw_frontend/src/widgets/RouterLink.tsx new file mode 100644 index 0000000..c946ed6 --- /dev/null +++ b/matrixgw_frontend/src/widgets/RouterLink.tsx @@ -0,0 +1,16 @@ +import { type PropsWithChildren } from "react"; +import { Link } from "react-router"; + +export function RouterLink( + p: PropsWithChildren<{ to: string; target?: React.HTMLAttributeAnchorTarget }> +): React.ReactElement { + return ( + + {p.children} + + ); +} diff --git a/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx b/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx new file mode 100644 index 0000000..02423df --- /dev/null +++ b/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx @@ -0,0 +1,13 @@ +import { Button } from "@mui/material"; +import { Link } from "react-router"; + +export function AuthSingleMessage(p: { message: string }): React.ReactElement { + return ( + <> +

{p.message}

+ + + + + ); +} diff --git a/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx new file mode 100644 index 0000000..866dd64 --- /dev/null +++ b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx @@ -0,0 +1,3 @@ +export function BaseLoginPage(): React.ReactElement { + return

Todo login page route

; +} -- 2.49.1 From d9c96e85f700d9e60b145b3f1a908abbea153d46 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 20:51:07 +0100 Subject: [PATCH 09/21] Authentication flow is functional --- matrixgw_frontend/index.html | 4 +- matrixgw_frontend/package-lock.json | 17 +++++ matrixgw_frontend/package.json | 2 + .../src/routes/auth/LoginRoute.tsx | 49 ++++++++++++- .../src/routes/auth/OIDCCbRoute.tsx | 4 +- .../src/widgets/auth/BaseLoginPage.tsx | 70 ++++++++++++++++++- 6 files changed, 140 insertions(+), 6 deletions(-) diff --git a/matrixgw_frontend/index.html b/matrixgw_frontend/index.html index 03c4e8f..5d7e649 100644 --- a/matrixgw_frontend/index.html +++ b/matrixgw_frontend/index.html @@ -2,9 +2,9 @@ - + - matrixgw_frontend + MatrixGW
diff --git a/matrixgw_frontend/package-lock.json b/matrixgw_frontend/package-lock.json index e80d450..152f32f 100644 --- a/matrixgw_frontend/package-lock.json +++ b/matrixgw_frontend/package-lock.json @@ -11,6 +11,8 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.8", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", @@ -789,6 +791,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "license": "Apache-2.0" + }, + "node_modules/@mdi/react": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz", + "integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", diff --git a/matrixgw_frontend/package.json b/matrixgw_frontend/package.json index 3207e3c..823193f 100644 --- a/matrixgw_frontend/package.json +++ b/matrixgw_frontend/package.json @@ -13,6 +13,8 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.8", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", diff --git a/matrixgw_frontend/src/routes/auth/LoginRoute.tsx b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx index 6190120..75b9457 100644 --- a/matrixgw_frontend/src/routes/auth/LoginRoute.tsx +++ b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx @@ -1,3 +1,50 @@ +import { Alert, Box, Button, CircularProgress } from "@mui/material"; +import Icon from "@mdi/react"; +import { mdiOpenid } from "@mdi/js"; +import { ServerApi } from "../../api/ServerApi"; +import React from "react"; +import { AuthApi } from "../../api/AuthApi"; + export function LoginRoute(): React.ReactElement { - return <>LoginRoute; + const [loading, setLoading] = React.useState(false); + const [error, setError] = React.useState(null); + + const authWithOpenID = async () => { + try { + setLoading(true); + + const res = await AuthApi.StartOpenIDLogin(); + window.location.href = res.url; + } catch (e) { + console.error(e); + setError("Failed to initialize OpenID login"); + } + }; + + if (loading) + return ( +
+ +
+ ); + + return ( + <> + {error && ( + + {error} + + )} + + + + + ); } diff --git a/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx index 90c67f8..82f80fe 100644 --- a/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx +++ b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx @@ -46,8 +46,8 @@ export function OIDCCbRoute(): React.ReactElement { ); return ( - <> +
- +
); } diff --git a/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx index 866dd64..a6d86da 100644 --- a/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx +++ b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx @@ -1,3 +1,71 @@ +import { mdiMessageTextFast } from "@mdi/js"; +import Icon from "@mdi/react"; +import { Typography } from "@mui/material"; +import MuiCard from "@mui/material/Card"; +import Stack from "@mui/material/Stack"; +import { styled } from "@mui/material/styles"; +import { Outlet } from "react-router"; + +const Card = styled(MuiCard)(({ theme }) => ({ + display: "flex", + flexDirection: "column", + alignSelf: "center", + width: "100%", + padding: theme.spacing(4), + gap: theme.spacing(2), + margin: "auto", + [theme.breakpoints.up("sm")]: { + maxWidth: "450px", + }, + boxShadow: + "hsla(220, 30%, 5%, 0.05) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.05) 0px 15px 35px -5px", + ...theme.applyStyles("dark", { + boxShadow: + "hsla(220, 30%, 5%, 0.5) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.08) 0px 15px 35px -5px", + }), +})); + +const SignInContainer = styled(Stack)(({ theme }) => ({ + height: "calc((1 - var(--template-frame-height, 0)) * 100dvh)", + minHeight: "100%", + padding: theme.spacing(2), + [theme.breakpoints.up("sm")]: { + padding: theme.spacing(4), + }, + "&::before": { + content: '""', + display: "block", + position: "absolute", + zIndex: -1, + inset: 0, + backgroundImage: + "radial-gradient(ellipse at 50% 50%, hsl(210, 100%, 97%), hsl(0, 0%, 100%))", + backgroundRepeat: "no-repeat", + ...theme.applyStyles("dark", { + backgroundImage: + "radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))", + }), + }, +})); + export function BaseLoginPage(): React.ReactElement { - return

Todo login page route

; + return ( + + + + {" "} + MatrixGW + + + + + ); } -- 2.49.1 From fdcd565431e3e2c5815f741966311200de251040 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 21:31:54 +0100 Subject: [PATCH 10/21] Add base authenticated route --- matrixgw_frontend/src/App.tsx | 2 +- .../src/widgets/BaseAuthenticatedPage.tsx | 3 - .../dashboard/BaseAuthenticatedPage.tsx | 96 ++++++ .../src/widgets/dashboard/DashboardHeader.tsx | 130 ++++++++ .../widgets/dashboard/DashboardSidebar.tsx | 281 ++++++++++++++++++ .../dashboard/DashboardSidebarContext.tsx | 5 + .../dashboard/DashboardSidebarDividerItem.tsx | 28 ++ .../dashboard/DashboardSidebarHeaderItem.tsx | 46 +++ .../dashboard/DashboardSidebarPageItem.tsx | 253 ++++++++++++++++ .../widgets/dashboard/SidebarDividerItem.tsx | 28 ++ .../src/widgets/dashboard/ThemeSwitcher.tsx | 59 ++++ .../src/widgets/dashboard/constants.ts | 2 + .../src/widgets/dashboard/mixins.ts | 23 ++ 13 files changed, 952 insertions(+), 4 deletions(-) delete mode 100644 matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebarDividerItem.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/SidebarDividerItem.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx create mode 100644 matrixgw_frontend/src/widgets/dashboard/constants.ts create mode 100644 matrixgw_frontend/src/widgets/dashboard/mixins.ts diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 4b0d983..22acab2 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -11,8 +11,8 @@ import { LoginRoute } from "./routes/auth/LoginRoute"; import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; import { HomeRoute } from "./routes/HomeRoute"; import { NotFoundRoute } from "./routes/NotFoundRoute"; -import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { BaseLoginPage } from "./widgets/auth/BaseLoginPage"; +import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage"; interface AuthContext { signedIn: boolean; diff --git a/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx deleted file mode 100644 index 79607a4..0000000 --- a/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function BaseAuthenticatedPage(): React.ReactElement { - return

todo authenticated

; -} diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx new file mode 100644 index 0000000..7973152 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -0,0 +1,96 @@ +import { mdiMessageTextFast } from "@mdi/js"; +import Icon from "@mdi/react"; +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +import Toolbar from "@mui/material/Toolbar"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import * as React from "react"; +import { Outlet } from "react-router"; +import DashboardHeader from "./DashboardHeader"; +import DashboardSidebar from "./DashboardSidebar"; + +export default function BaseAuthenticatedPage(): React.ReactElement { + const theme = useTheme(); + + const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] = + React.useState(true); + const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] = + React.useState(false); + + const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md")); + + const isNavigationExpanded = isOverMdViewport + ? isDesktopNavigationExpanded + : isMobileNavigationExpanded; + + const setIsNavigationExpanded = React.useCallback( + (newExpanded: boolean) => { + if (isOverMdViewport) { + setIsDesktopNavigationExpanded(newExpanded); + } else { + setIsMobileNavigationExpanded(newExpanded); + } + }, + [ + isOverMdViewport, + setIsDesktopNavigationExpanded, + setIsMobileNavigationExpanded, + ] + ); + + const handleToggleHeaderMenu = React.useCallback( + (isExpanded: boolean) => { + setIsNavigationExpanded(isExpanded); + }, + [setIsNavigationExpanded] + ); + + const layoutRef = React.useRef(null); + + return ( + + } + title="" + menuOpen={isNavigationExpanded} + onToggleMenu={handleToggleHeaderMenu} + /> + + + + + + + + + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx new file mode 100644 index 0000000..5c98db7 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx @@ -0,0 +1,130 @@ +import * as React from "react"; +import { styled, useTheme } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import MuiAppBar from "@mui/material/AppBar"; +import IconButton from "@mui/material/IconButton"; +import Toolbar from "@mui/material/Toolbar"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; +import MenuIcon from "@mui/icons-material/Menu"; +import MenuOpenIcon from "@mui/icons-material/MenuOpen"; +import Stack from "@mui/material/Stack"; +import { Link } from "react-router"; +import ThemeSwitcher from "./ThemeSwitcher"; + +const AppBar = styled(MuiAppBar)(({ theme }) => ({ + borderWidth: 0, + borderBottomWidth: 1, + borderStyle: "solid", + borderColor: (theme.vars ?? theme).palette.divider, + boxShadow: "none", + zIndex: theme.zIndex.drawer + 1, +})); + +const LogoContainer = styled("div")({ + position: "relative", + height: 40, + display: "flex", + alignItems: "center", + "& img": { + maxHeight: 40, + }, +}); + +export interface DashboardHeaderProps { + logo?: React.ReactNode; + title?: string; + menuOpen: boolean; + onToggleMenu: (open: boolean) => void; +} + +export default function DashboardHeader({ + logo, + title, + menuOpen, + onToggleMenu, +}: DashboardHeaderProps) { + const theme = useTheme(); + + const handleMenuOpen = React.useCallback(() => { + onToggleMenu(!menuOpen); + }, [menuOpen, onToggleMenu]); + + const getMenuIcon = React.useCallback( + (isExpanded: boolean) => { + const expandMenuActionText = "Expand"; + const collapseMenuActionText = "Collapse"; + + return ( + +
+ + {isExpanded ? : } + +
+
+ ); + }, + [handleMenuOpen] + ); + + return ( + + + + + {getMenuIcon(menuOpen)} + + + {logo ? {logo} : null} + {title ? ( + + {title} + + ) : null} + + + + + + + + + + + + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx new file mode 100644 index 0000000..26111f6 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx @@ -0,0 +1,281 @@ +import * as React from "react"; +import { useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import List from "@mui/material/List"; +import Toolbar from "@mui/material/Toolbar"; +import type {} from "@mui/material/themeCssVarsAugmentation"; +import PersonIcon from "@mui/icons-material/Person"; +import BarChartIcon from "@mui/icons-material/BarChart"; +import DescriptionIcon from "@mui/icons-material/Description"; +import LayersIcon from "@mui/icons-material/Layers"; +import { matchPath, useLocation } from "react-router"; +import DashboardSidebarContext from "./DashboardSidebarContext"; +import { DRAWER_WIDTH, MINI_DRAWER_WIDTH } from "./constants"; +import DashboardSidebarPageItem from "./DashboardSidebarPageItem"; +import DashboardSidebarHeaderItem from "./DashboardSidebarHeaderItem"; +import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem"; +import { + getDrawerSxTransitionMixin, + getDrawerWidthTransitionMixin, +} from "./mixins"; + +export interface DashboardSidebarProps { + expanded?: boolean; + setExpanded: (expanded: boolean) => void; + disableCollapsibleSidebar?: boolean; + container?: Element; +} + +export default function DashboardSidebar({ + expanded = true, + setExpanded, + disableCollapsibleSidebar = false, + container, +}: DashboardSidebarProps) { + const theme = useTheme(); + + const { pathname } = useLocation(); + + const [expandedItemIds, setExpandedItemIds] = React.useState([]); + + const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm")); + const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md")); + + const [isFullyExpanded, setIsFullyExpanded] = React.useState(expanded); + const [isFullyCollapsed, setIsFullyCollapsed] = React.useState(!expanded); + + React.useEffect(() => { + if (expanded) { + const drawerWidthTransitionTimeout = setTimeout(() => { + setIsFullyExpanded(true); + }, theme.transitions.duration.enteringScreen); + + return () => clearTimeout(drawerWidthTransitionTimeout); + } + + setIsFullyExpanded(false); + + return () => {}; + }, [expanded, theme.transitions.duration.enteringScreen]); + + React.useEffect(() => { + if (!expanded) { + const drawerWidthTransitionTimeout = setTimeout(() => { + setIsFullyCollapsed(true); + }, theme.transitions.duration.leavingScreen); + + return () => clearTimeout(drawerWidthTransitionTimeout); + } + + setIsFullyCollapsed(false); + + return () => {}; + }, [expanded, theme.transitions.duration.leavingScreen]); + + const mini = !disableCollapsibleSidebar && !expanded; + + const handleSetSidebarExpanded = React.useCallback( + (newExpanded: boolean) => () => { + setExpanded(newExpanded); + }, + [setExpanded] + ); + + const handlePageItemClick = React.useCallback( + (itemId: string, hasNestedNavigation: boolean) => { + if (hasNestedNavigation && !mini) { + setExpandedItemIds((previousValue) => + previousValue.includes(itemId) + ? previousValue.filter( + (previousValueItemId) => previousValueItemId !== itemId + ) + : [...previousValue, itemId] + ); + } else if (!isOverSmViewport && !hasNestedNavigation) { + setExpanded(false); + } + }, + [mini, setExpanded, isOverSmViewport] + ); + + const hasDrawerTransitions = + isOverSmViewport && (!disableCollapsibleSidebar || isOverMdViewport); + + const getDrawerContent = React.useCallback( + (viewport: "phone" | "tablet" | "desktop") => ( + + + + + Main items + } + href="/employees" + selected={ + !!matchPath("/employees/*", pathname) || pathname === "/" + } + /> + + + Example items + + } + href="/reports" + selected={!!matchPath("/reports", pathname)} + defaultExpanded={!!matchPath("/reports", pathname)} + expanded={expandedItemIds.includes("reports")} + nestedNavigation={ + + } + href="/reports/sales" + selected={!!matchPath("/reports/sales", pathname)} + /> + } + href="/reports/traffic" + selected={!!matchPath("/reports/traffic", pathname)} + /> + + } + /> + } + href="/integrations" + selected={!!matchPath("/integrations", pathname)} + /> + + + + ), + [mini, hasDrawerTransitions, isFullyExpanded, expandedItemIds, pathname] + ); + + const getDrawerSharedSx = React.useCallback( + (isTemporary: boolean) => { + const drawerWidth = mini ? MINI_DRAWER_WIDTH : DRAWER_WIDTH; + + return { + displayPrint: "none", + width: drawerWidth, + flexShrink: 0, + ...getDrawerWidthTransitionMixin(expanded), + ...(isTemporary ? { position: "absolute" } : {}), + [`& .MuiDrawer-paper`]: { + position: "absolute", + width: drawerWidth, + boxSizing: "border-box", + backgroundImage: "none", + ...getDrawerWidthTransitionMixin(expanded), + }, + }; + }, + [expanded, mini] + ); + + const sidebarContextValue = React.useMemo(() => { + return { + onPageItemClick: handlePageItemClick, + mini, + fullyExpanded: isFullyExpanded, + fullyCollapsed: isFullyCollapsed, + hasDrawerTransitions, + }; + }, [ + handlePageItemClick, + mini, + isFullyExpanded, + isFullyCollapsed, + hasDrawerTransitions, + ]); + + return ( + + + {getDrawerContent("phone")} + + + {getDrawerContent("tablet")} + + + {getDrawerContent("desktop")} + + + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx new file mode 100644 index 0000000..56efe06 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx @@ -0,0 +1,5 @@ +import * as React from "react"; + +const DashboardSidebarContext = React.createContext(null); + +export default DashboardSidebarContext; diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarDividerItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarDividerItem.tsx new file mode 100644 index 0000000..d5e3178 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarDividerItem.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import Divider from "@mui/material/Divider"; +import type {} from "@mui/material/themeCssVarsAugmentation"; +import DashboardSidebarContext from "./DashboardSidebarContext"; +import { getDrawerSxTransitionMixin } from "./mixins"; + +export default function DashboardSidebarDividerItem() { + const sidebarContext = React.useContext(DashboardSidebarContext); + if (!sidebarContext) { + throw new Error("Sidebar context was used without a provider."); + } + const { fullyExpanded = true, hasDrawerTransitions } = sidebarContext; + + return ( +
  • + +
  • + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx new file mode 100644 index 0000000..d4dc3ce --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import ListSubheader from "@mui/material/ListSubheader"; +import type {} from "@mui/material/themeCssVarsAugmentation"; +import DashboardSidebarContext from "./DashboardSidebarContext"; +import { DRAWER_WIDTH } from "./constants"; +import { getDrawerSxTransitionMixin } from "./mixins"; + +export interface DashboardSidebarHeaderItemProps { + children?: React.ReactNode; +} + +export default function DashboardSidebarHeaderItem({ + children, +}: DashboardSidebarHeaderItemProps) { + const sidebarContext = React.useContext(DashboardSidebarContext); + if (!sidebarContext) { + throw new Error("Sidebar context was used without a provider."); + } + const { + mini = false, + fullyExpanded = true, + hasDrawerTransitions, + } = sidebarContext; + + return ( + + {children} + + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx new file mode 100644 index 0000000..637db5b --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx @@ -0,0 +1,253 @@ +import * as React from "react"; +import { type Theme, type SxProps } from "@mui/material/styles"; +import Avatar from "@mui/material/Avatar"; +import Box from "@mui/material/Box"; +import Collapse from "@mui/material/Collapse"; +import Grow from "@mui/material/Grow"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Paper from "@mui/material/Paper"; +import Typography from "@mui/material/Typography"; +import type {} from "@mui/material/themeCssVarsAugmentation"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { Link } from "react-router"; +import DashboardSidebarContext from "./DashboardSidebarContext"; +import { MINI_DRAWER_WIDTH } from "./constants"; + +export interface DashboardSidebarPageItemProps { + id: string; + title: string; + icon?: React.ReactNode; + href: string; + action?: React.ReactNode; + defaultExpanded?: boolean; + expanded?: boolean; + selected?: boolean; + disabled?: boolean; + nestedNavigation?: React.ReactNode; +} + +export default function DashboardSidebarPageItem({ + id, + title, + icon, + href, + action, + defaultExpanded = false, + expanded = defaultExpanded, + selected = false, + disabled = false, + nestedNavigation, +}: DashboardSidebarPageItemProps) { + const sidebarContext = React.useContext(DashboardSidebarContext); + if (!sidebarContext) { + throw new Error("Sidebar context was used without a provider."); + } + const { + onPageItemClick, + mini = false, + fullyExpanded = true, + fullyCollapsed = false, + } = sidebarContext; + + const [isHovered, setIsHovered] = React.useState(false); + + const handleClick = React.useCallback(() => { + if (onPageItemClick) { + onPageItemClick(id, !!nestedNavigation); + } + }, [onPageItemClick, id, nestedNavigation]); + + let nestedNavigationCollapseSx: SxProps = { display: "none" }; + if (mini && fullyCollapsed) { + nestedNavigationCollapseSx = { + fontSize: 18, + position: "absolute", + top: "41.5%", + right: "2px", + transform: "translateY(-50%) rotate(-90deg)", + }; + } else if (!mini && fullyExpanded) { + nestedNavigationCollapseSx = { + ml: 0.5, + fontSize: 20, + transform: `rotate(${expanded ? 0 : -90}deg)`, + transition: (theme: Theme) => + theme.transitions.create("transform", { + easing: theme.transitions.easing.sharp, + duration: 100, + }), + }; + } + + const hasExternalHref = href + ? href.startsWith("http://") || href.startsWith("https://") + : false; + + const LinkComponent = hasExternalHref ? "a" : Link; + + const miniNestedNavigationSidebarContextValue = React.useMemo(() => { + return { + onPageItemClick: onPageItemClick ?? (() => {}), + mini: false, + fullyExpanded: true, + fullyCollapsed: false, + hasDrawerTransitions: false, + }; + }, [onPageItemClick]); + + return ( + + { + setIsHovered(true); + }, + onMouseLeave: () => { + setIsHovered(false); + }, + } + : {})} + sx={{ + display: "block", + py: 0, + px: 1, + overflowX: "hidden", + }} + > + + {icon || mini ? ( + + + {icon ?? null} + {!icon && mini ? ( + + {title + .split(" ") + .slice(0, 2) + .map((titleWord) => titleWord.charAt(0).toUpperCase())} + + ) : null} + + {mini ? ( + + {title} + + ) : null} + + ) : null} + {!mini ? ( + + ) : null} + {action && !mini && fullyExpanded ? action : null} + {nestedNavigation ? ( + + ) : null} + + {nestedNavigation && mini ? ( + + + + + {nestedNavigation} + + + + + ) : null} + + {nestedNavigation && !mini ? ( + + {nestedNavigation} + + ) : null} + + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/SidebarDividerItem.tsx b/matrixgw_frontend/src/widgets/dashboard/SidebarDividerItem.tsx new file mode 100644 index 0000000..d5e3178 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/SidebarDividerItem.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import Divider from "@mui/material/Divider"; +import type {} from "@mui/material/themeCssVarsAugmentation"; +import DashboardSidebarContext from "./DashboardSidebarContext"; +import { getDrawerSxTransitionMixin } from "./mixins"; + +export default function DashboardSidebarDividerItem() { + const sidebarContext = React.useContext(DashboardSidebarContext); + if (!sidebarContext) { + throw new Error("Sidebar context was used without a provider."); + } + const { fullyExpanded = true, hasDrawerTransitions } = sidebarContext; + + return ( +
  • + +
  • + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx b/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx new file mode 100644 index 0000000..0fd38c6 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { useTheme, useColorScheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import IconButton from "@mui/material/IconButton"; +import Tooltip from "@mui/material/Tooltip"; +import DarkModeIcon from "@mui/icons-material/DarkMode"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import type {} from "@mui/material/themeCssVarsAugmentation"; + +export default function ThemeSwitcher() { + const theme = useTheme(); + + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + const preferredMode = prefersDarkMode ? "dark" : "light"; + + const { mode, setMode } = useColorScheme(); + + const paletteMode = !mode || mode === "system" ? preferredMode : mode; + + const toggleMode = React.useCallback(() => { + setMode(paletteMode === "dark" ? "light" : "dark"); + }, [setMode, paletteMode]); + + return ( + +
    + + + + + + +
    +
    + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/constants.ts b/matrixgw_frontend/src/widgets/dashboard/constants.ts new file mode 100644 index 0000000..780f97a --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/constants.ts @@ -0,0 +1,2 @@ +export const DRAWER_WIDTH = 240; // px +export const MINI_DRAWER_WIDTH = 90; // px diff --git a/matrixgw_frontend/src/widgets/dashboard/mixins.ts b/matrixgw_frontend/src/widgets/dashboard/mixins.ts new file mode 100644 index 0000000..bee5cd9 --- /dev/null +++ b/matrixgw_frontend/src/widgets/dashboard/mixins.ts @@ -0,0 +1,23 @@ +import { type Theme } from "@mui/material/styles"; + +export function getDrawerSxTransitionMixin( + isExpanded: boolean, + property: string +) { + return { + transition: (theme: Theme) => + theme.transitions.create(property, { + easing: theme.transitions.easing.sharp, + duration: isExpanded + ? theme.transitions.duration.enteringScreen + : theme.transitions.duration.leavingScreen, + }), + }; +} + +export function getDrawerWidthTransitionMixin(isExpanded: boolean) { + return { + ...getDrawerSxTransitionMixin(isExpanded, "width"), + overflowX: "hidden", + }; +} -- 2.49.1 From 79b5a767f3780263341eabcdb09becc72e091bea Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 21:51:20 +0100 Subject: [PATCH 11/21] Improve sidebar --- .../dashboard/BaseAuthenticatedPage.tsx | 4 - .../src/widgets/dashboard/DashboardHeader.tsx | 59 ++++---- .../widgets/dashboard/DashboardSidebar.tsx | 84 ++--------- .../dashboard/DashboardSidebarContext.tsx | 8 +- .../dashboard/DashboardSidebarHeaderItem.tsx | 46 ------ .../dashboard/DashboardSidebarPageItem.tsx | 141 +++--------------- 6 files changed, 68 insertions(+), 274 deletions(-) delete mode 100644 matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx index 7973152..ff3ee64 100644 --- a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -1,5 +1,3 @@ -import { mdiMessageTextFast } from "@mdi/js"; -import Icon from "@mdi/react"; import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import Toolbar from "@mui/material/Toolbar"; @@ -59,8 +57,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement { }} > } - title="" menuOpen={isNavigationExpanded} onToggleMenu={handleToggleHeaderMenu} /> diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx index 5c98db7..cf669b9 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx @@ -1,15 +1,17 @@ -import * as React from "react"; -import { styled, useTheme } from "@mui/material/styles"; -import Box from "@mui/material/Box"; +import { mdiMessageTextFast } from "@mdi/js"; +import Icon from "@mdi/react"; +import MenuIcon from "@mui/icons-material/Menu"; +import MenuOpenIcon from "@mui/icons-material/MenuOpen"; import MuiAppBar from "@mui/material/AppBar"; +import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; +import Stack from "@mui/material/Stack"; +import { styled } from "@mui/material/styles"; import Toolbar from "@mui/material/Toolbar"; import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; -import MenuIcon from "@mui/icons-material/Menu"; -import MenuOpenIcon from "@mui/icons-material/MenuOpen"; -import Stack from "@mui/material/Stack"; -import { Link } from "react-router"; +import * as React from "react"; +import { RouterLink } from "../RouterLink"; import ThemeSwitcher from "./ThemeSwitcher"; const AppBar = styled(MuiAppBar)(({ theme }) => ({ @@ -32,20 +34,14 @@ const LogoContainer = styled("div")({ }); export interface DashboardHeaderProps { - logo?: React.ReactNode; - title?: string; menuOpen: boolean; onToggleMenu: (open: boolean) => void; } export default function DashboardHeader({ - logo, - title, menuOpen, onToggleMenu, }: DashboardHeaderProps) { - const theme = useTheme(); - const handleMenuOpen = React.useCallback(() => { onToggleMenu(!menuOpen); }, [menuOpen, onToggleMenu]); @@ -60,7 +56,7 @@ export default function DashboardHeader({ title={`${ isExpanded ? collapseMenuActionText : expandMenuActionText } menu`} - enterDelay={1000} + enterDelay={200} >
    - {getMenuIcon(menuOpen)} - + {getMenuIcon(menuOpen)} + - {logo ? {logo} : null} - {title ? ( - - {title} - - ) : null} + + + + + MatrixGW + - + ([]); - const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm")); const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md")); @@ -83,22 +77,11 @@ export default function DashboardSidebar({ [setExpanded] ); - const handlePageItemClick = React.useCallback( - (itemId: string, hasNestedNavigation: boolean) => { - if (hasNestedNavigation && !mini) { - setExpandedItemIds((previousValue) => - previousValue.includes(itemId) - ? previousValue.filter( - (previousValueItemId) => previousValueItemId !== itemId - ) - : [...previousValue, itemId] - ); - } else if (!isOverSmViewport && !hasNestedNavigation) { - setExpanded(false); - } - }, - [mini, setExpanded, isOverSmViewport] - ); + const handlePageItemClick = React.useCallback(() => { + if (!isOverSmViewport) { + setExpanded(false); + } + }, [mini, setExpanded, isOverSmViewport]); const hasDrawerTransitions = isOverSmViewport && (!disableCollapsibleSidebar || isOverMdViewport); @@ -132,67 +115,30 @@ export default function DashboardSidebar({ width: mini ? MINI_DRAWER_WIDTH : "auto", }} > - Main items } href="/employees" - selected={ - !!matchPath("/employees/*", pathname) || pathname === "/" - } /> - - Example items - } href="/reports" - selected={!!matchPath("/reports", pathname)} - defaultExpanded={!!matchPath("/reports", pathname)} - expanded={expandedItemIds.includes("reports")} - nestedNavigation={ - - } - href="/reports/sales" - selected={!!matchPath("/reports/sales", pathname)} - /> - } - href="/reports/traffic" - selected={!!matchPath("/reports/traffic", pathname)} - /> - - } /> } href="/integrations" - selected={!!matchPath("/integrations", pathname)} /> ), - [mini, hasDrawerTransitions, isFullyExpanded, expandedItemIds, pathname] + [mini, hasDrawerTransitions, isFullyExpanded] ); const getDrawerSharedSx = React.useCallback( diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx index 56efe06..eb0dafd 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx @@ -1,5 +1,11 @@ import * as React from "react"; -const DashboardSidebarContext = React.createContext(null); +const DashboardSidebarContext = React.createContext<{ + onPageItemClick: () => void; + mini: boolean; + fullyExpanded: boolean; + fullyCollapsed: boolean; + hasDrawerTransitions: boolean; +} | null>(null); export default DashboardSidebarContext; diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx deleted file mode 100644 index d4dc3ce..0000000 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarHeaderItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react"; -import ListSubheader from "@mui/material/ListSubheader"; -import type {} from "@mui/material/themeCssVarsAugmentation"; -import DashboardSidebarContext from "./DashboardSidebarContext"; -import { DRAWER_WIDTH } from "./constants"; -import { getDrawerSxTransitionMixin } from "./mixins"; - -export interface DashboardSidebarHeaderItemProps { - children?: React.ReactNode; -} - -export default function DashboardSidebarHeaderItem({ - children, -}: DashboardSidebarHeaderItemProps) { - const sidebarContext = React.useContext(DashboardSidebarContext); - if (!sidebarContext) { - throw new Error("Sidebar context was used without a provider."); - } - const { - mini = false, - fullyExpanded = true, - hasDrawerTransitions, - } = sidebarContext; - - return ( - - {children} - - ); -} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx index 637db5b..75eb1b4 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx @@ -1,18 +1,13 @@ -import * as React from "react"; -import { type Theme, type SxProps } from "@mui/material/styles"; import Avatar from "@mui/material/Avatar"; import Box from "@mui/material/Box"; -import Collapse from "@mui/material/Collapse"; -import Grow from "@mui/material/Grow"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; -import Paper from "@mui/material/Paper"; import Typography from "@mui/material/Typography"; import type {} from "@mui/material/themeCssVarsAugmentation"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { Link } from "react-router"; +import * as React from "react"; +import { Link, matchPath, useLocation } from "react-router"; import DashboardSidebarContext from "./DashboardSidebarContext"; import { MINI_DRAWER_WIDTH } from "./constants"; @@ -22,11 +17,7 @@ export interface DashboardSidebarPageItemProps { icon?: React.ReactNode; href: string; action?: React.ReactNode; - defaultExpanded?: boolean; - expanded?: boolean; - selected?: boolean; disabled?: boolean; - nestedNavigation?: React.ReactNode; } export default function DashboardSidebarPageItem({ @@ -35,12 +26,10 @@ export default function DashboardSidebarPageItem({ icon, href, action, - defaultExpanded = false, - expanded = defaultExpanded, - selected = false, disabled = false, - nestedNavigation, }: DashboardSidebarPageItemProps) { + const { pathname } = useLocation(); + const sidebarContext = React.useContext(DashboardSidebarContext); if (!sidebarContext) { throw new Error("Sidebar context was used without a provider."); @@ -49,38 +38,13 @@ export default function DashboardSidebarPageItem({ onPageItemClick, mini = false, fullyExpanded = true, - fullyCollapsed = false, } = sidebarContext; - const [isHovered, setIsHovered] = React.useState(false); - const handleClick = React.useCallback(() => { if (onPageItemClick) { - onPageItemClick(id, !!nestedNavigation); + onPageItemClick(); } - }, [onPageItemClick, id, nestedNavigation]); - - let nestedNavigationCollapseSx: SxProps = { display: "none" }; - if (mini && fullyCollapsed) { - nestedNavigationCollapseSx = { - fontSize: 18, - position: "absolute", - top: "41.5%", - right: "2px", - transform: "translateY(-50%) rotate(-90deg)", - }; - } else if (!mini && fullyExpanded) { - nestedNavigationCollapseSx = { - ml: 0.5, - fontSize: 20, - transform: `rotate(${expanded ? 0 : -90}deg)`, - transition: (theme: Theme) => - theme.transitions.create("transform", { - easing: theme.transitions.easing.sharp, - duration: 100, - }), - }; - } + }, [onPageItemClick, id]); const hasExternalHref = href ? href.startsWith("http://") || href.startsWith("https://") @@ -88,61 +52,28 @@ export default function DashboardSidebarPageItem({ const LinkComponent = hasExternalHref ? "a" : Link; - const miniNestedNavigationSidebarContextValue = React.useMemo(() => { - return { - onPageItemClick: onPageItemClick ?? (() => {}), - mini: false, - fullyExpanded: true, - fullyCollapsed: false, - hasDrawerTransitions: false, - }; - }, [onPageItemClick]); + const selected = !!matchPath(href, pathname); return ( - { - setIsHovered(true); - }, - onMouseLeave: () => { - setIsHovered(false); - }, - } - : {})} - sx={{ - display: "block", - py: 0, - px: 1, - overflowX: "hidden", - }} - > + {icon || mini ? ( ) : null} {action && !mini && fullyExpanded ? action : null} - {nestedNavigation ? ( - - ) : null} - {nestedNavigation && mini ? ( - - - - - {nestedNavigation} - - - - - ) : null} - {nestedNavigation && !mini ? ( - - {nestedNavigation} - - ) : null} ); } -- 2.49.1 From 3de26c0fffb6d15ea4641df14233205a86ea7702 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 22:02:56 +0100 Subject: [PATCH 12/21] Simplify dashboard code --- .../dashboard/BaseAuthenticatedPage.tsx | 2 +- .../widgets/dashboard/DashboardSidebar.tsx | 56 ++++++------------- .../dashboard/DashboardSidebarContext.tsx | 1 - .../dashboard/DashboardSidebarPageItem.tsx | 12 +--- 4 files changed, 20 insertions(+), 51 deletions(-) diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx index ff3ee64..60ae762 100644 --- a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -11,7 +11,7 @@ export default function BaseAuthenticatedPage(): React.ReactElement { const theme = useTheme(); const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] = - React.useState(true); + React.useState(false); const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] = React.useState(false); diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx index 7eee311..d1584a6 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx @@ -1,6 +1,5 @@ -import BarChartIcon from "@mui/icons-material/BarChart"; -import LayersIcon from "@mui/icons-material/Layers"; -import PersonIcon from "@mui/icons-material/Person"; +import { mdiBug, mdiForum, mdiKeyVariant, mdiLinkLock } from "@mdi/js"; +import Icon from "@mdi/react"; import Box from "@mui/material/Box"; import Drawer from "@mui/material/Drawer"; import List from "@mui/material/List"; @@ -9,7 +8,6 @@ import { useTheme } from "@mui/material/styles"; import type {} from "@mui/material/themeCssVarsAugmentation"; import useMediaQuery from "@mui/material/useMediaQuery"; import * as React from "react"; -import { useLocation } from "react-router"; import DashboardSidebarContext from "./DashboardSidebarContext"; import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem"; import DashboardSidebarPageItem from "./DashboardSidebarPageItem"; @@ -38,7 +36,6 @@ export default function DashboardSidebar({ const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md")); const [isFullyExpanded, setIsFullyExpanded] = React.useState(expanded); - const [isFullyCollapsed, setIsFullyCollapsed] = React.useState(!expanded); React.useEffect(() => { if (expanded) { @@ -54,20 +51,6 @@ export default function DashboardSidebar({ return () => {}; }, [expanded, theme.transitions.duration.enteringScreen]); - React.useEffect(() => { - if (!expanded) { - const drawerWidthTransitionTimeout = setTimeout(() => { - setIsFullyCollapsed(true); - }, theme.transitions.duration.leavingScreen); - - return () => clearTimeout(drawerWidthTransitionTimeout); - } - - setIsFullyCollapsed(false); - - return () => {}; - }, [expanded, theme.transitions.duration.leavingScreen]); - const mini = !disableCollapsibleSidebar && !expanded; const handleSetSidebarExpanded = React.useCallback( @@ -116,23 +99,25 @@ export default function DashboardSidebar({ }} > } - href="/employees" + title="Messages" + icon={} + href="/" /> } - href="/reports" + title="Matrix link" + icon={} + href="/matrixlink" /> } - href="/integrations" + title="API tokens" + icon={} + href="/tokens" + /> + } + href="/wsdebug" /> @@ -168,16 +153,9 @@ export default function DashboardSidebar({ onPageItemClick: handlePageItemClick, mini, fullyExpanded: isFullyExpanded, - fullyCollapsed: isFullyCollapsed, hasDrawerTransitions, }; - }, [ - handlePageItemClick, - mini, - isFullyExpanded, - isFullyCollapsed, - hasDrawerTransitions, - ]); + }, [handlePageItemClick, mini, isFullyExpanded, hasDrawerTransitions]); return ( diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx index eb0dafd..751b63b 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarContext.tsx @@ -4,7 +4,6 @@ const DashboardSidebarContext = React.createContext<{ onPageItemClick: () => void; mini: boolean; fullyExpanded: boolean; - fullyCollapsed: boolean; hasDrawerTransitions: boolean; } | null>(null); diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx index 75eb1b4..5d32561 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebarPageItem.tsx @@ -12,7 +12,6 @@ import DashboardSidebarContext from "./DashboardSidebarContext"; import { MINI_DRAWER_WIDTH } from "./constants"; export interface DashboardSidebarPageItemProps { - id: string; title: string; icon?: React.ReactNode; href: string; @@ -21,7 +20,6 @@ export interface DashboardSidebarPageItemProps { } export default function DashboardSidebarPageItem({ - id, title, icon, href, @@ -40,12 +38,6 @@ export default function DashboardSidebarPageItem({ fullyExpanded = true, } = sidebarContext; - const handleClick = React.useCallback(() => { - if (onPageItemClick) { - onPageItemClick(); - } - }, [onPageItemClick, id]); - const hasExternalHref = href ? href.startsWith("http://") || href.startsWith("https://") : false; @@ -56,7 +48,7 @@ export default function DashboardSidebarPageItem({ return ( - + {icon || mini ? ( -- 2.49.1 From f9fb99cdb58623e0cd9cbc6caa114831c55f0099 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 22:04:12 +0100 Subject: [PATCH 13/21] Add TODO marker --- matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx index cf669b9..44444bc 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx @@ -118,6 +118,7 @@ export default function DashboardHeader({ + Hi TODO USER SIGN OUT -- 2.49.1 From a44327ddb00d7385196b8a22fe15eecb4b3017d2 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 5 Nov 2025 08:31:47 +0100 Subject: [PATCH 14/21] Display user name in application header --- matrixgw_backend/src/main.rs | 1 + matrixgw_frontend/src/api/AuthApi.ts | 6 +- .../dashboard/BaseAuthenticatedPage.tsx | 132 ++++++++++++------ .../src/widgets/dashboard/DashboardHeader.tsx | 44 +++++- .../src/widgets/dashboard/ThemeSwitcher.tsx | 32 ++--- 5 files changed, 147 insertions(+), 68 deletions(-) diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index a3fd905..a7073fe 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -17,6 +17,7 @@ async fn main() -> std::io::Result<()> { let secret_key = Key::from(AppConfig::get().secret().as_bytes()); + log::info!("Connect to Redis session store..."); let redis_store = RedisSessionStore::new(AppConfig::get().redis_connection_string()) .await .expect("Failed to connect to Redis!"); diff --git a/matrixgw_frontend/src/api/AuthApi.ts b/matrixgw_frontend/src/api/AuthApi.ts index eb4b105..d3f60eb 100644 --- a/matrixgw_frontend/src/api/AuthApi.ts +++ b/matrixgw_frontend/src/api/AuthApi.ts @@ -1,6 +1,6 @@ import { APIClient } from "./ApiClient"; -export interface AuthInfo { +export interface UserInfo { id: number; time_create: number; time_update: number; @@ -58,9 +58,9 @@ export class AuthApi { } /** - * Get auth information + * Get user information */ - static async GetAuthInfo(): Promise { + static async GetUserInfo(): Promise { return ( await APIClient.exec({ uri: "/auth/info", diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx index 60ae762..47e8be3 100644 --- a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -3,13 +3,39 @@ import { useTheme } from "@mui/material/styles"; import Toolbar from "@mui/material/Toolbar"; import useMediaQuery from "@mui/material/useMediaQuery"; import * as React from "react"; -import { Outlet } from "react-router"; +import { Outlet, useNavigate } from "react-router"; import DashboardHeader from "./DashboardHeader"; import DashboardSidebar from "./DashboardSidebar"; +import { AuthApi, type UserInfo } from "../../api/AuthApi"; +import { AsyncWidget } from "../AsyncWidget"; +import { Button } from "@mui/material"; +import { useAuth } from "../../App"; + +interface UserInfoContext { + info: UserInfo; + reloadUserInfo: () => void; + signOut: () => void; +} + +const UserInfoContextK = React.createContext(null); export default function BaseAuthenticatedPage(): React.ReactElement { const theme = useTheme(); + const [userInfo, setuserInfo] = React.useState(null); + const loadUserInfo = async () => { + setuserInfo(await AuthApi.GetUserInfo()); + }; + + const auth = useAuth(); + const navigate = useNavigate(); + + const signOut = () => { + AuthApi.SignOut(); + navigate("/"); + auth.setSignedIn(false); + }; + const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] = React.useState(false); const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] = @@ -46,47 +72,71 @@ export default function BaseAuthenticatedPage(): React.ReactElement { const layoutRef = React.useRef(null); return ( - - - - - - ( + <> + + + )} + build={() => ( + - - - - + + + + + + + + + + + + )} + /> ); } + +export function userUserInfo(): UserInfoContext { + return React.use(UserInfoContextK)!; +} diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx index 44444bc..0f76b97 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx @@ -1,7 +1,9 @@ import { mdiMessageTextFast } from "@mdi/js"; import Icon from "@mdi/react"; +import LogoutIcon from "@mui/icons-material/Logout"; import MenuIcon from "@mui/icons-material/Menu"; import MenuOpenIcon from "@mui/icons-material/MenuOpen"; +import { Avatar } from "@mui/material"; import MuiAppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; @@ -12,6 +14,7 @@ import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; import * as React from "react"; import { RouterLink } from "../RouterLink"; +import { userUserInfo as useUserInfo } from "./BaseAuthenticatedPage"; import ThemeSwitcher from "./ThemeSwitcher"; const AppBar = styled(MuiAppBar)(({ theme }) => ({ @@ -42,6 +45,8 @@ export default function DashboardHeader({ menuOpen, onToggleMenu, }: DashboardHeaderProps) { + const user = useUserInfo(); + const handleMenuOpen = React.useCallback(() => { onToggleMenu(!menuOpen); }, [menuOpen, onToggleMenu]); @@ -101,6 +106,7 @@ export default function DashboardHeader({ ml: 1, whiteSpace: "nowrap", lineHeight: 1, + display: { xs: "none", sm: "block" }, }} > MatrixGW @@ -108,17 +114,41 @@ export default function DashboardHeader({ + + {/* User avatar */} - - - + + + + {user.info.name} + + + {user.info.email} + + + + + + + + - Hi TODO USER SIGN OUT diff --git a/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx b/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx index 0fd38c6..7c22dc3 100644 --- a/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx @@ -34,24 +34,22 @@ export default function ThemeSwitcher() { } mode`} onClick={toggleMode} > - - - - + }, + }} + /> +
    -- 2.49.1 From 3dab9f41d20bf74fc86281116a29dca555b3521c Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 5 Nov 2025 16:30:06 +0100 Subject: [PATCH 15/21] Start Matrix client authentication --- README.md | 4 +- matrixgw_backend/Cargo.lock | 2650 ++++++++++++++++- matrixgw_backend/Cargo.toml | 5 +- matrixgw_backend/src/app_config.rs | 50 + .../src/controllers/matrix_link_controller.rs | 14 + matrixgw_backend/src/controllers/mod.rs | 1 + .../src/extractors/matrix_client_extractor.rs | 37 + matrixgw_backend/src/extractors/mod.rs | 1 + matrixgw_backend/src/lib.rs | 1 + matrixgw_backend/src/main.rs | 28 +- .../src/matrix_connection/matrix_client.rs | 115 + .../src/matrix_connection/matrix_manager.rs | 62 + matrixgw_backend/src/matrix_connection/mod.rs | 2 + 13 files changed, 2960 insertions(+), 10 deletions(-) create mode 100644 matrixgw_backend/src/controllers/matrix_link_controller.rs create mode 100644 matrixgw_backend/src/extractors/matrix_client_extractor.rs create mode 100644 matrixgw_backend/src/matrix_connection/matrix_client.rs create mode 100644 matrixgw_backend/src/matrix_connection/matrix_manager.rs create mode 100644 matrixgw_backend/src/matrix_connection/mod.rs diff --git a/README.md b/README.md index 0c7620e..25667cd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ docker run --rm -it docker.io/pierre42100/matrix_gateway --help ### Dependencies ``` +sudo apt install -y libsqlite3-dev + cd matrixgw_backend mkdir -p storage/maspostgres storage/synapse docker compose up @@ -53,4 +55,4 @@ cargo fmt && cargo clippy && cargo run -- cd matrixgw_frontend npm install npm run dev -``` \ No newline at end of file +``` diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index f857cd7..ebe0055 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accessory" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "actix-codec" version = "0.5.2" @@ -294,6 +306,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -350,12 +377,38 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + [[package]] name = "arrayref" version = "0.3.9" @@ -367,6 +420,15 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "as_variant" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" [[package]] name = "ascii_utils" @@ -374,6 +436,70 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "assign" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -393,6 +519,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ "fastrand", + "gloo-timers", + "tokio", ] [[package]] @@ -436,6 +564,24 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + +[[package]] +name = "bitpacking" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" +dependencies = [ + "crunchy", +] [[package]] name = "blake2b_simd" @@ -448,6 +594,19 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -457,6 +616,63 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bon" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" +dependencies = [ + "bon-macros 2.3.0", + "rustversion", +] + +[[package]] +name = "bon" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1" +dependencies = [ + "bon-macros 3.8.1", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" +dependencies = [ + "darling 0.20.11", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bon-macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645" +dependencies = [ + "darling 0.21.3", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "brotli" version = "8.0.2" @@ -478,18 +694,39 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytesize" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" + [[package]] name = "bytestring" version = "1.5.0" @@ -499,6 +736,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.44" @@ -511,12 +757,56 @@ dependencies = [ "shlex", ] +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + [[package]] name = "cipher" version = "0.4.4" @@ -525,6 +815,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -598,12 +889,47 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_panic" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652" +dependencies = [ + "typewit", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -662,6 +988,46 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -700,6 +1066,187 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "date_header" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" + +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +dependencies = [ + "tokio", +] + +[[package]] +name = "deadpool-sqlite" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8510000b26f632483a35120c2ce280c29e1e14c2dcb27b5055dbdac276f63f58" +dependencies = [ + "deadpool", + "deadpool-sync", + "rusqlite", +] + +[[package]] +name = "deadpool-sync" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524bc3df0d57e98ecd022e21ba31166c2625e7d3e5bcc4510efaeeab4abcab04" +dependencies = [ + "deadpool-runtime", +] + +[[package]] +name = "decancer" +version = "3.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "delegate-display" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.10" @@ -718,6 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -764,6 +1312,12 @@ dependencies = [ "syn", ] +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "ecdsa" version = "0.16.9" @@ -778,6 +1332,17 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + [[package]] name = "ed25519-compact" version = "2.1.1" @@ -788,6 +1353,27 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -857,6 +1443,77 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eyeball" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93bd0ebf93d61d6332d3c09a96e97975968a44e19a64c947bde06e6baff383f" +dependencies = [ + "futures-core", + "readlock", + "readlock-tokio", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "eyeball-im" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e8e9d31591be508826b875d8fe6056aebcaec3281ac0e45434ff303686c566" +dependencies = [ + "futures-core", + "imbl", + "tokio", + "tracing", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fancy_constructor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fast_chemail" version = "0.9.6" @@ -866,6 +1523,12 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "fastdivide" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" + [[package]] name = "fastrand" version = "2.3.0" @@ -882,6 +1545,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -934,6 +1603,41 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -941,6 +1645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -949,6 +1654,23 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + [[package]] name = "futures-macro" version = "0.3.31" @@ -978,10 +1700,13 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1033,6 +1758,31 @@ dependencies = [ "polyval", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "group" version = "0.13.0" @@ -1044,6 +1794,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "growable-bloom-filter" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", +] + [[package]] name = "h2" version = "0.3.27" @@ -1082,18 +1844,74 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core", + "http 1.3.1", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.3.1", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1142,6 +1960,24 @@ dependencies = [ "digest", ] +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + [[package]] name = "http" version = "0.2.12" @@ -1164,6 +2000,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-auth" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" +dependencies = [ + "memchr", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1279,6 +2124,39 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "hyperloglogplus" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" +dependencies = [ + "serde", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -1360,6 +2238,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1381,12 +2265,72 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "imbl" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4308a675e4cfc1920f36a8f4d8fb62d5533b7da106844bd1ec51c6f1fa94a0c" +dependencies = [ + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.3", + "rand_xoshiro", + "serde", + "version_check", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + [[package]] name = "impl-more" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexed_db_futures" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c" +dependencies = [ + "accessory", + "cfg-if", + "delegate-display", + "fancy_constructor", + "js-sys", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -1394,7 +2338,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", + "serde", + "serde_core", ] [[package]] @@ -1403,6 +2349,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] @@ -1431,6 +2378,24 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1481,6 +2446,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "js_int" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" +dependencies = [ + "serde", +] + +[[package]] +name = "js_option" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c" +dependencies = [ + "serde", +] + [[package]] name = "jwt-simple" version = "0.12.13" @@ -1503,7 +2486,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror", + "thiserror 2.0.17", "zeroize", ] @@ -1521,6 +2504,26 @@ dependencies = [ "signature", ] +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1536,6 +2539,12 @@ dependencies = [ "spin", ] +[[package]] +name = "levenshtein_automata" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + [[package]] name = "libc" version = "0.2.177" @@ -1548,6 +2557,16 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "light-openid" version = "1.0.4" @@ -1606,6 +2625,74 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "macroific" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "macroific_macro", +] + +[[package]] +name = "macroific_attr_parse" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "macroific_core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "macroific_macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mailchecker" version = "6.0.19" @@ -1616,6 +2703,298 @@ dependencies = [ "once_cell", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "matrix-pickle" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c34e6db65145740459f2ca56623b40cd4e6000ffae2a7d91515fa82aa935dbf" +dependencies = [ + "matrix-pickle-derive", + "thiserror 2.0.17", +] + +[[package]] +name = "matrix-pickle-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a962fc9981f823f6555416dcb2ae9ae67ca412d767ee21ecab5150113ee6285b" +dependencies = [ + "proc-macro-crate", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "matrix-sdk" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa71339f867dcada2e7f1130f858fd8892088b1a4c123dd50a99ed2399ab22" +dependencies = [ + "anymap2", + "aquamarine", + "as_variant", + "async-channel", + "async-stream", + "async-trait", + "backon", + "bytes", + "bytesize", + "cfg-if", + "event-listener", + "eyeball", + "eyeball-im", + "futures-core", + "futures-util", + "gloo-timers", + "http 1.3.1", + "imbl", + "indexmap", + "itertools 0.14.0", + "js_int", + "language-tags", + "matrix-sdk-base", + "matrix-sdk-common", + "matrix-sdk-indexeddb", + "matrix-sdk-search", + "matrix-sdk-sqlite", + "mime", + "mime2ext", + "oauth2", + "once_cell", + "percent-encoding", + "pin-project-lite", + "reqwest", + "ruma", + "serde", + "serde_html_form", + "serde_json", + "sha2", + "tempfile", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "urlencoding", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-base" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b14659a7e902ea8a821ec217f36b168fb4c79020d91b912175ac188b6c364225" +dependencies = [ + "as_variant", + "async-trait", + "bitflags", + "decancer", + "eyeball", + "eyeball-im", + "futures-util", + "growable-bloom-filter", + "matrix-sdk-common", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "once_cell", + "regex", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "matrix-sdk-common" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb33a986495135e217f28cfe0918bf7d01a9800e42f4ef88afbd48e23b8cc53" +dependencies = [ + "eyeball-im", + "futures-core", + "futures-executor", + "futures-util", + "gloo-timers", + "imbl", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "matrix-sdk-crypto" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61bf6c3195de301c98339283413a4e9d9d63c4f214ef8955147643caab161256" +dependencies = [ + "aes", + "aquamarine", + "as_variant", + "async-trait", + "bs58", + "byteorder", + "cfg-if", + "ctr", + "eyeball", + "futures-core", + "futures-util", + "hkdf", + "hmac", + "itertools 0.14.0", + "js_option", + "matrix-sdk-common", + "pbkdf2", + "rand 0.8.5", + "rmp-serde", + "ruma", + "serde", + "serde_json", + "sha2", + "subtle", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-stream", + "tracing", + "ulid", + "url", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-indexeddb" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2752015e69b6b56a8df72e52f91a834771259f755eb1c65232b77348ffb16b" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "getrandom 0.2.16", + "gloo-utils", + "hkdf", + "indexed_db_futures", + "js-sys", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "rmp-serde", + "ruma", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tracing", + "wasm-bindgen", + "web-sys", + "zeroize", +] + +[[package]] +name = "matrix-sdk-search" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7474bd9045b6ce28fbe5c903a1cef2ba43264e51386c1d07f61699a178f3fec0" +dependencies = [ + "ruma", + "tantivy", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "matrix-sdk-sqlite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662a128004b553196365d88ea46917047d7350a24dfe7ce829e7b78636673604" +dependencies = [ + "as_variant", + "async-trait", + "deadpool-sqlite", + "itertools 0.14.0", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "num_cpus", + "rmp-serde", + "ruma", + "rusqlite", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror 2.0.17", + "tokio", + "tracing", + "vodozemac", +] + +[[package]] +name = "matrix-sdk-store-encryption" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0aac550e685306fbbd57faae8f98af9812f19bbf0f83da7559d67cf4789679" +dependencies = [ + "base64 0.22.1", + "blake3", + "chacha20poly1305", + "hmac", + "pbkdf2", + "rand 0.8.5", + "rmp-serde", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.17", + "zeroize", +] + [[package]] name = "matrixgw_backend" version = "0.1.0" @@ -1637,27 +3016,60 @@ dependencies = [ "light-openid", "log", "mailchecker", + "matrix-sdk", + "ractor", "rand 0.9.2", "serde", "sha2", - "thiserror", + "thiserror 2.0.17", "tokio", + "url", "urlencoding", "uuid", ] +[[package]] +name = "measure_time" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" +dependencies = [ + "log", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime2ext" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1680,6 +3092,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" + [[package]] name = "native-tls" version = "0.2.14" @@ -1697,6 +3115,31 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1759,6 +3202,36 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.16", + "http 1.3.1", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1771,6 +3244,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oneshot" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1821,6 +3300,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ownedbytes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "p256" version = "0.13.2" @@ -1845,6 +3333,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1868,6 +3362,22 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1883,6 +3393,44 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1922,6 +3470,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -1973,6 +3532,22 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -1982,6 +3557,36 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.7", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -1991,6 +3596,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.41" @@ -2006,6 +3634,26 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "ractor" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9500e0be6f12a0539cb1154d654ef2e888bf8529164e54aff4a097baad5bb001" +dependencies = [ + "bon 2.3.0", + "dashmap", + "futures", + "js-sys", + "once_cell", + "strum", + "tokio", + "tokio_with_wasm", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + [[package]] name = "rand" version = "0.8.5" @@ -2065,6 +3713,60 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "readlock" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188bbae3aa4739bd264e9204da5919b2c91dd87dcce5049cf04bdf6aa17c5012" + +[[package]] +name = "readlock-tokio" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b1800712c0d75de4b0bda5483d46eaf8df757b81df5ca2bde53d5ac2e2c5b2" +dependencies = [ + "tokio", +] + [[package]] name = "redis" version = "0.32.7" @@ -2139,10 +3841,12 @@ version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ + "async-compression", "base64 0.22.1", "bytes", "encoding_rs", "futures-core", + "futures-util", "h2 0.4.12", "http 1.3.1", "http-body", @@ -2164,12 +3868,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -2197,6 +3903,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rsa" version = "0.9.8" @@ -2218,6 +3946,221 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ruma" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7b698b728bc3747f564a9115c83b4f2e229b52377f6a1cca2e6add9cf4a13be" +dependencies = [ + "assign", + "js_int", + "js_option", + "ruma-client-api", + "ruma-common", + "ruma-events", + "ruma-federation-api", + "ruma-html", + "ruma-signatures", + "web-time", +] + +[[package]] +name = "ruma-client-api" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54e56c591f9ad686defb0bacbebba5c8882eb0c9f8734f6a080345b4e3dd941" +dependencies = [ + "as_variant", + "assign", + "bytes", + "date_header", + "http 1.3.1", + "js_int", + "js_option", + "maplit", + "ruma-common", + "ruma-events", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.17", + "url", + "web-time", +] + +[[package]] +name = "ruma-common" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac7f59b9f7639667d0d6ae3ae242c8912e9ed061cea1fbaf72710a402e83b53e" +dependencies = [ + "as_variant", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "getrandom 0.2.16", + "http 1.3.1", + "indexmap", + "js-sys", + "js_int", + "konst", + "percent-encoding", + "rand 0.8.5", + "regex", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.17", + "time", + "tracing", + "url", + "uuid", + "web-time", + "wildmatch", + "zeroize", +] + +[[package]] +name = "ruma-events" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34fa815769ed4fe1ef5b50aa0ba6f350317c13b5a9f1e008b014f4a3ddf14204" +dependencies = [ + "as_variant", + "indexmap", + "js_int", + "js_option", + "percent-encoding", + "regex", + "ruma-common", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", + "url", + "web-time", + "wildmatch", + "zeroize", +] + +[[package]] +name = "ruma-federation-api" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbc887ba1292e48e6363b29e0dec4571b52d2b5102ebf60068105efadaa6e0a" +dependencies = [ + "headers", + "http 1.3.1", + "http-auth", + "httparse", + "js_int", + "memchr", + "mime", + "ruma-common", + "ruma-events", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "ruma-html" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6124d74847ea788601477c89a44485894432a806824cae93885c5825a8ae9dbc" +dependencies = [ + "as_variant", + "html5ever", + "tracing", + "wildmatch", +] + +[[package]] +name = "ruma-identifiers-validation" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a7b93ac1e571c585f8fa5cef09c07bb8a15529775fd56b9a3eac4f9233dff2" +dependencies = [ + "js_int", + "thiserror 2.0.17", +] + +[[package]] +name = "ruma-macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9911c7188517f28505d2d513339511d00e0f50cec5c2dde820cd0ec7e6a833" +dependencies = [ + "cfg-if", + "proc-macro-crate", + "proc-macro2", + "quote", + "ruma-identifiers-validation", + "serde", + "syn", + "toml", +] + +[[package]] +name = "ruma-signatures" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47cd146d56ae6e7a4a8d912a30dfe57c70e5bf18806fdf617527d4d4f2dd2a4" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "pkcs8", + "rand 0.8.5", + "ruma-common", + "serde_json", + "sha2", + "thiserror 2.0.17", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -2328,6 +4271,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2338,6 +4287,27 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2358,6 +4328,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_html_form" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.145" @@ -2371,6 +4354,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2405,6 +4408,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2436,6 +4448,21 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.11" @@ -2490,12 +4517,59 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2567,6 +4641,152 @@ dependencies = [ "libc", ] +[[package]] +name = "tantivy" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502915c7381c5cb2d2781503962610cb880ad8f1a0ca95df1bae645d5ebf2545" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "bon 3.8.1", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "hyperloglogplus", + "itertools 0.14.0", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror 2.0.17", + "time", + "uuid", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b04eed5108d8283607da6710fe17a7663523440eaf7ea5a1a440d19a1448b6" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b628488ae936c83e92b5c4056833054ca56f76c0e616aee8339e24ac89119cd" +dependencies = [ + "downcast-rs", + "fastdivide", + "itertools 0.14.0", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880aa7cab0c063a47b62596d10991cdd0b6e0e0575d9c5eeb298b307a25de55" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "768fccdc84d60d86235d42d7e4c33acf43c418258ff5952abf07bd7837fcd26b" +dependencies = [ + "nom", + "serde", + "serde_json", +] + +[[package]] +name = "tantivy-sstable" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8292095d1a8a2c2b36380ec455f910ab52dde516af36321af332c93f20ab7d5" +dependencies = [ + "futures-util", + "itertools 0.14.0", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d38a379411169f0b3002c9cba61cdfe315f757e9d4f239c00c282497a0749d" +dependencies = [ + "murmurhash32", + "rand_distr", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23024f6aeb25ceb1a0e27740c84bdb0fae52626737b7e9a9de6ad5aa25c7b038" +dependencies = [ + "serde", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -2580,13 +4800,44 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2600,6 +4851,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.44" @@ -2641,6 +4901,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.48.0" @@ -2655,6 +4930,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.6.1", "tokio-macros", + "tracing", "windows-sys 0.61.2", ] @@ -2689,6 +4965,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -2702,6 +4990,94 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio_with_wasm" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfba9b946459940fb564dcf576631074cdfb0bfe4c962acd4c31f0dca7897e6" +dependencies = [ + "js-sys", + "tokio", + "tokio_with_wasm_proc", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "tokio_with_wasm_proc" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime 0.7.3", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "tower" version = "0.5.2" @@ -2777,6 +5153,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -2791,12 +5193,46 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "typewit" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.2", + "web-time", +] + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.6" @@ -2837,6 +5273,18 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2861,6 +5309,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2873,6 +5327,36 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vodozemac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c022a277687e4e8685d72b95a7ca3ccfec907daa946678e715f8badaa650883d" +dependencies = [ + "aes", + "arrayvec", + "base64 0.22.1", + "base64ct", + "cbc", + "chacha20poly1305", + "curve25519-dalek", + "ed25519-dalek", + "getrandom 0.2.16", + "hkdf", + "hmac", + "matrix-pickle", + "prost", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "subtle", + "thiserror 2.0.17", + "x25519-dalek", + "zeroize", +] + [[package]] name = "want" version = "0.3.1" @@ -2964,6 +5448,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.82" @@ -2974,6 +5471,79 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wildmatch" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b7d07a236abaef6607536ccfaf19b396dbe3f5110ddb73d39f4562902ed382" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.1.3" @@ -2993,8 +5563,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -3006,6 +5576,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -3015,6 +5594,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3024,6 +5612,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -3171,6 +5768,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -3183,6 +5789,24 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "yoke" version = "0.8.1" @@ -3252,6 +5876,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index ffe9d1e..94f3b2b 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -27,4 +27,7 @@ uuid = { version = "1.18.1", features = ["v4", "serde"] } ipnet = { version = "2.11.0", features = ["serde"] } rand = "0.9.2" hex = "0.4.3" -mailchecker = "6.0.19" \ No newline at end of file +mailchecker = "6.0.19" +matrix-sdk = "0.14.0" +url = "2.5.7" +ractor = "0.15.9" \ No newline at end of file diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index ed122d8..32fa673 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -1,7 +1,11 @@ use crate::users::{APITokenID, UserEmail}; use crate::utils::crypt_utils::sha256str; use clap::Parser; +use matrix_sdk::authentication::oauth::registration::{ + ApplicationType, ClientMetadata, Localized, OAuthGrantType, +}; use std::path::{Path, PathBuf}; +use url::Url; /// Matrix gateway backend API #[derive(Parser, Debug, Clone)] @@ -76,6 +80,10 @@ pub struct AppConfig { #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] oidc_redirect_url: String, + /// Matrix oauth redirect URL + #[arg(long, env, default_value = "APP_ORIGIN/matrix_auth_cb")] + matrix_oauth_redirect_url: String, + /// Application storage path #[arg(long, env, default_value = "app_storage")] storage_path: String, @@ -146,6 +154,38 @@ impl AppConfig { } } + /// Matrix OAuth redirect URL + pub fn matrix_oauth_redirect_url(&self) -> String { + self.matrix_oauth_redirect_url + .replace("APP_ORIGIN", &self.website_origin) + } + + /// Get Matrix client metadata information + pub fn matrix_client_metadata(&self) -> ClientMetadata { + let client_uri = Localized::new( + Url::parse(&self.website_origin).expect("Invalid website origin!"), + [], + ); + ClientMetadata { + application_type: ApplicationType::Native, + grant_types: vec![OAuthGrantType::AuthorizationCode { + redirect_uris: vec![ + Url::parse(&self.matrix_oauth_redirect_url()) + .expect("Failed to parse matrix auth redirect URI!"), + ], + }], + client_name: Some(Localized::new("MatrixGW".to_string(), [])), + logo_uri: Some(Localized::new( + Url::parse(&format!("{}/favicon.png", self.website_origin)) + .expect("Invalid website origin!"), + [], + )), + policy_uri: Some(client_uri.clone()), + tos_uri: Some(client_uri.clone()), + client_uri, + } + } + /// Get storage path pub fn storage_path(&self) -> &Path { Path::new(self.storage_path.as_str()) @@ -170,6 +210,16 @@ impl AppConfig { pub fn user_api_token_metadata_file(&self, mail: &UserEmail, id: &APITokenID) -> PathBuf { self.user_api_token_directory(mail).join(id.0.to_string()) } + + /// Get user Matrix database path + pub fn user_matrix_db_path(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("matrix-db") + } + + /// Get user Matrix database passphrase path + pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("matrix-db-passphrase") + } } #[derive(Debug, Clone, serde::Serialize)] diff --git a/matrixgw_backend/src/controllers/matrix_link_controller.rs b/matrixgw_backend/src/controllers/matrix_link_controller.rs new file mode 100644 index 0000000..38fea49 --- /dev/null +++ b/matrixgw_backend/src/controllers/matrix_link_controller.rs @@ -0,0 +1,14 @@ +use crate::controllers::HttpResult; +use crate::extractors::matrix_client_extractor::MatrixClientExtractor; +use actix_web::HttpResponse; + +#[derive(serde::Serialize)] +struct StartAuthResponse { + url: String, +} + +/// Start user authentication on Matrix server +pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult { + let url = client.client.initiate_login().await?.to_string(); + Ok(HttpResponse::Ok().json(StartAuthResponse { url })) +} diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs index 25104bf..8a581e9 100644 --- a/matrixgw_backend/src/controllers/mod.rs +++ b/matrixgw_backend/src/controllers/mod.rs @@ -3,6 +3,7 @@ use actix_web::{HttpResponse, ResponseError}; use std::error::Error; pub mod auth_controller; +pub mod matrix_link_controller; pub mod server_controller; #[derive(thiserror::Error, Debug)] diff --git a/matrixgw_backend/src/extractors/matrix_client_extractor.rs b/matrixgw_backend/src/extractors/matrix_client_extractor.rs new file mode 100644 index 0000000..fbe6d3c --- /dev/null +++ b/matrixgw_backend/src/extractors/matrix_client_extractor.rs @@ -0,0 +1,37 @@ +use crate::extractors::auth_extractor::AuthExtractor; +use crate::matrix_connection::matrix_client::MatrixClient; +use crate::matrix_connection::matrix_manager::MatrixManagerMsg; +use actix_web::dev::Payload; +use actix_web::{FromRequest, HttpRequest, web}; +use ractor::ActorRef; + +pub struct MatrixClientExtractor { + pub auth: AuthExtractor, + pub client: MatrixClient, +} + +impl FromRequest for MatrixClientExtractor { + type Error = actix_web::Error; + type Future = futures_util::future::LocalBoxFuture<'static, Result>; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let req = req.clone(); + let mut payload = payload.take(); + Box::pin(async move { + let auth = AuthExtractor::from_request(&req, &mut payload).await?; + + let matrix_manager_actor = + web::Data::>::from_request(&req, &mut Payload::None) + .await?; + let client = ractor::call!( + matrix_manager_actor, + MatrixManagerMsg::GetClient, + auth.user.email.clone() + ) + .expect("Failed to query manager actor!") + .expect("Failed to get client!"); + + Ok(Self { auth, client }) + }) + } +} diff --git a/matrixgw_backend/src/extractors/mod.rs b/matrixgw_backend/src/extractors/mod.rs index 4e4f77f..546e4d2 100644 --- a/matrixgw_backend/src/extractors/mod.rs +++ b/matrixgw_backend/src/extractors/mod.rs @@ -1,2 +1,3 @@ pub mod auth_extractor; +pub mod matrix_client_extractor; pub mod session_extractor; diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 82c2228..61b9e52 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -2,5 +2,6 @@ pub mod app_config; pub mod constants; pub mod controllers; pub mod extractors; +pub mod matrix_connection; pub mod users; pub mod utils; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index a7073fe..2571cdc 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -8,8 +8,10 @@ use actix_web::middleware::Logger; use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; use matrixgw_backend::constants; -use matrixgw_backend::controllers::{auth_controller, server_controller}; +use matrixgw_backend::controllers::{auth_controller, matrix_link_controller, server_controller}; +use matrixgw_backend::matrix_connection::matrix_manager::MatrixManagerActor; use matrixgw_backend::users::User; +use ractor::Actor; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -29,12 +31,22 @@ async fn main() -> std::io::Result<()> { .expect("Failed to create auto-login account!"); } + // Create matrix clients manager actor + let (manager_actor, manager_actor_handle) = Actor::spawn( + Some("matrix-clients-manager".to_string()), + MatrixManagerActor, + (), + ) + .await + .expect("Failed to start Matrix manager actor!"); + log::info!( "Starting to listen on {} for {}", AppConfig::get().listen_address, AppConfig::get().website_origin ); + let manager_actor_clone = manager_actor.clone(); HttpServer::new(move || { let session_mw = SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) .cookie_name("matrixgw-session".to_string()) @@ -53,6 +65,7 @@ async fn main() -> std::io::Result<()> { .wrap(Logger::default()) .wrap(session_mw) .wrap(cors) + .app_data(web::Data::new(manager_actor_clone.clone())) .app_data(web::Data::new(RemoteIPConfig { proxy: AppConfig::get().proxy_ip.clone(), })) @@ -76,9 +89,20 @@ async fn main() -> std::io::Result<()> { "/api/auth/sign_out", web::get().to(auth_controller::sign_out), ) + // Matrix link controller + .route( + "/api/matrix_link/start_auth", + web::post().to(matrix_link_controller::start_auth), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? .run() - .await + .await?; + + // Terminate manager actor + manager_actor.stop(None); + manager_actor_handle.await?; + + Ok(()) } diff --git a/matrixgw_backend/src/matrix_connection/matrix_client.rs b/matrixgw_backend/src/matrix_connection/matrix_client.rs new file mode 100644 index 0000000..f6dcc86 --- /dev/null +++ b/matrixgw_backend/src/matrix_connection/matrix_client.rs @@ -0,0 +1,115 @@ +use crate::app_config::AppConfig; +use crate::users::UserEmail; +use crate::utils::rand_utils::rand_string; +use matrix_sdk::authentication::oauth::OAuthError; +use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; +use matrix_sdk::ruma::serde::Raw; +use matrix_sdk::{Client, ClientBuildError}; +use url::Url; + +/// Matrix Gateway session errors +#[derive(thiserror::Error, Debug)] +enum MatrixClientError { + #[error("Failed to create Matrix database storage directory! {0}")] + CreateMatrixDbDir(std::io::Error), + #[error("Failed to create database passphrase! {0}")] + CreateDbPassphrase(std::io::Error), + #[error("Failed to read database passphrase! {0}")] + ReadDbPassphrase(std::io::Error), + #[error("Failed to build Matrix client! {0}")] + BuildMatrixClient(ClientBuildError), + #[error("Failed to clear Matrix database storage directory! {0}")] + ClearMatrixDbDir(std::io::Error), + #[error("Failed to remove database passphrase! {0}")] + ClearDbPassphrase(std::io::Error), + #[error("Failed to fetch server metadata! {0}")] + FetchServerMetadata(OAuthDiscoveryError), + #[error("Failed to parse auth redirect URL! {0}")] + ParseAuthRedirectURL(url::ParseError), + #[error("Failed to build auth request! {0}")] + BuildAuthRequest(OAuthError), +} + +#[derive(Clone)] +pub struct MatrixClient { + pub email: UserEmail, + pub client: Client, +} + +impl MatrixClient { + /// Start to build Matrix client to initiate user authentication + pub async fn build_client(email: &UserEmail) -> anyhow::Result { + let db_path = AppConfig::get().user_matrix_db_path(email); + std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?; + + // Generate or load passphrase + let passphrase_path = AppConfig::get().user_matrix_passphrase_path(email); + if !passphrase_path.exists() { + std::fs::write(&passphrase_path, rand_string(32)) + .map_err(MatrixClientError::CreateDbPassphrase)?; + } + let passphrase = std::fs::read_to_string(passphrase_path) + .map_err(MatrixClientError::ReadDbPassphrase)?; + + let client = Client::builder() + .server_name_or_homeserver_url(&AppConfig::get().matrix_homeserver) + // Automatically refresh tokens if needed + .handle_refresh_tokens() + .sqlite_store(&db_path, Some(&passphrase)) + .build() + .await + .map_err(MatrixClientError::BuildMatrixClient)?; + + // Check metadata + let server_metadata = client + .oauth() + .server_metadata() + .await + .map_err(MatrixClientError::FetchServerMetadata)?; + log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer); + + // TODO : restore client if client already existed + + Ok(Self { + email: email.clone(), + client, + }) + } + + /// Destroy this Matrix client instance + pub fn destroy(&self) -> anyhow::Result<()> { + let db_path = AppConfig::get().user_matrix_db_path(&self.email); + if db_path.is_file() { + std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?; + } + + let passphrase_path = AppConfig::get().user_matrix_passphrase_path(&self.email); + if passphrase_path.is_file() { + std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?; + } + + todo!() + } + + /// Initiate oauth authentication + pub async fn initiate_login(&self) -> anyhow::Result { + let oauth = self.client.oauth(); + + let metadata = AppConfig::get().matrix_client_metadata(); + let client_metadata = Raw::new(&metadata).expect("Couldn't serialize client metadata"); + + let auth = oauth + .login( + Url::parse(&AppConfig::get().matrix_oauth_redirect_url()) + .map_err(MatrixClientError::ParseAuthRedirectURL)?, + None, + Some(client_metadata.into()), + None, + ) + .build() + .await + .map_err(MatrixClientError::BuildAuthRequest)?; + + Ok(auth.url) + } +} diff --git a/matrixgw_backend/src/matrix_connection/matrix_manager.rs b/matrixgw_backend/src/matrix_connection/matrix_manager.rs new file mode 100644 index 0000000..d6f180a --- /dev/null +++ b/matrixgw_backend/src/matrix_connection/matrix_manager.rs @@ -0,0 +1,62 @@ +use crate::matrix_connection::matrix_client::MatrixClient; +use crate::users::UserEmail; +use ractor::{Actor, ActorProcessingErr, ActorRef, RpcReplyPort}; +use std::collections::HashMap; + +pub struct MatrixManagerState { + pub clients: HashMap, +} + +pub enum MatrixManagerMsg { + GetClient(UserEmail, RpcReplyPort>), +} + +pub struct MatrixManagerActor; + +impl Actor for MatrixManagerActor { + type Msg = MatrixManagerMsg; + type State = MatrixManagerState; + type Arguments = (); + + async fn pre_start( + &self, + _myself: ActorRef, + _args: Self::Arguments, + ) -> Result { + Ok(MatrixManagerState { + clients: HashMap::new(), + }) + } + + async fn handle( + &self, + _myself: ActorRef, + message: Self::Msg, + state: &mut Self::State, + ) -> Result<(), ActorProcessingErr> { + match message { + // Get client information + MatrixManagerMsg::GetClient(email, port) => { + let res = port.send(match state.clients.get(&email) { + None => { + // Generate client if required + log::info!("Building new client for {:?}", &email); + match MatrixClient::build_client(&email).await { + Ok(c) => { + state.clients.insert(email.clone(), c.clone()); + Ok(c) + } + Err(e) => Err(e), + } + } + Some(c) => Ok(c.clone()), + }); + + if let Err(e) = res { + log::warn!("Failed to send client information: {e}") + } + } + } + Ok(()) + } +} diff --git a/matrixgw_backend/src/matrix_connection/mod.rs b/matrixgw_backend/src/matrix_connection/mod.rs new file mode 100644 index 0000000..c929f1b --- /dev/null +++ b/matrixgw_backend/src/matrix_connection/mod.rs @@ -0,0 +1,2 @@ +pub mod matrix_client; +pub mod matrix_manager; -- 2.49.1 From 37fad9ff551f1a3d80f2a13fc992ae2bfe5bbd93 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 5 Nov 2025 18:27:41 +0100 Subject: [PATCH 16/21] Can start Matrix authentication from UI --- .../src/controllers/auth_controller.rs | 4 +- matrixgw_backend/src/users.rs | 16 +++ matrixgw_frontend/src/App.tsx | 2 + matrixgw_frontend/src/api/AuthApi.ts | 1 + matrixgw_frontend/src/api/MatrixLinkApi.ts | 15 +++ matrixgw_frontend/src/routes/HomeRoute.tsx | 7 ++ .../src/routes/MatrixLinkRoute.tsx | 98 +++++++++++++++++++ .../src/widgets/MatrixGWRouteContainer.tsx | 37 +++++++ .../src/widgets/NotLinkedAccountMessage.tsx | 14 +++ .../dashboard/BaseAuthenticatedPage.tsx | 3 +- .../src/widgets/dashboard/DashboardHeader.tsx | 2 +- .../widgets/dashboard/DashboardSidebar.tsx | 2 +- 12 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 matrixgw_frontend/src/api/MatrixLinkApi.ts create mode 100644 matrixgw_frontend/src/routes/MatrixLinkRoute.tsx create mode 100644 matrixgw_frontend/src/widgets/MatrixGWRouteContainer.tsx create mode 100644 matrixgw_frontend/src/widgets/NotLinkedAccountMessage.tsx diff --git a/matrixgw_backend/src/controllers/auth_controller.rs b/matrixgw_backend/src/controllers/auth_controller.rs index ab14ace..6cbab4c 100644 --- a/matrixgw_backend/src/controllers/auth_controller.rs +++ b/matrixgw_backend/src/controllers/auth_controller.rs @@ -2,7 +2,7 @@ use crate::app_config::AppConfig; use crate::controllers::{HttpFailure, HttpResult}; use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; use crate::extractors::session_extractor::MatrixGWSession; -use crate::users::{User, UserEmail}; +use crate::users::{ExtendedUserInfo, User, UserEmail}; use actix_remote_ip::RemoteIP; use actix_web::{HttpResponse, web}; use light_openid::primitives::OpenIDConfig; @@ -108,7 +108,7 @@ pub async fn finish_oidc( /// Get current user information pub async fn auth_info(auth: AuthExtractor) -> HttpResult { - Ok(HttpResponse::Ok().json(auth.user)) + Ok(HttpResponse::Ok().json(ExtendedUserInfo::from_user(auth.user).await?)) } /// Sign out user diff --git a/matrixgw_backend/src/users.rs b/matrixgw_backend/src/users.rs index 2cb1e0a..aab18ed 100644 --- a/matrixgw_backend/src/users.rs +++ b/matrixgw_backend/src/users.rs @@ -166,3 +166,19 @@ impl APIToken { (self.last_used + self.max_inactivity) < time_secs() } } + +#[derive(serde::Serialize, Debug, Clone)] +pub struct ExtendedUserInfo { + #[serde(flatten)] + pub user: User, + pub matrix_user_id: Option, +} + +impl ExtendedUserInfo { + pub async fn from_user(user: User) -> anyhow::Result { + Ok(Self { + user, + matrix_user_id: None, // TODO + }) + } +} diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 22acab2..9988388 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -10,6 +10,7 @@ import { ServerApi } from "./api/ServerApi"; import { LoginRoute } from "./routes/auth/LoginRoute"; import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; import { HomeRoute } from "./routes/HomeRoute"; +import { MatrixLinkRoute } from "./routes/MatrixLinkRoute"; import { NotFoundRoute } from "./routes/NotFoundRoute"; import { BaseLoginPage } from "./widgets/auth/BaseLoginPage"; import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage"; @@ -37,6 +38,7 @@ export function App(): React.ReactElement { signedIn || ServerApi.Config.auth_disabled ? ( }> } /> + } /> } /> ) : ( diff --git a/matrixgw_frontend/src/api/AuthApi.ts b/matrixgw_frontend/src/api/AuthApi.ts index d3f60eb..c0fc286 100644 --- a/matrixgw_frontend/src/api/AuthApi.ts +++ b/matrixgw_frontend/src/api/AuthApi.ts @@ -6,6 +6,7 @@ export interface UserInfo { time_update: number; name: string; email: string; + matrix_user_id?: string; } const TokenStateKey = "auth-state"; diff --git a/matrixgw_frontend/src/api/MatrixLinkApi.ts b/matrixgw_frontend/src/api/MatrixLinkApi.ts new file mode 100644 index 0000000..6a05a3c --- /dev/null +++ b/matrixgw_frontend/src/api/MatrixLinkApi.ts @@ -0,0 +1,15 @@ +import { APIClient } from "./ApiClient"; + +export class MatrixLinkApi { + /** + * Start Matrix Account login + */ + static async StartAuth(): Promise<{ url: string }> { + return ( + await APIClient.exec({ + uri: "/matrix_link/start_auth", + method: "POST", + }) + ).data; + } +} diff --git a/matrixgw_frontend/src/routes/HomeRoute.tsx b/matrixgw_frontend/src/routes/HomeRoute.tsx index b93b24f..ee0ddf1 100644 --- a/matrixgw_frontend/src/routes/HomeRoute.tsx +++ b/matrixgw_frontend/src/routes/HomeRoute.tsx @@ -1,3 +1,10 @@ +import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage"; +import { NotLinkedAccountMessage } from "../widgets/NotLinkedAccountMessage"; + export function HomeRoute(): React.ReactElement { + const user = useUserInfo(); + + if (!user.info.matrix_user_id) return ; + return

    Todo home route

    ; } diff --git a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx new file mode 100644 index 0000000..7a33ed7 --- /dev/null +++ b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx @@ -0,0 +1,98 @@ +import LinkIcon from "@mui/icons-material/Link"; +import LinkOffIcon from "@mui/icons-material/LinkOff"; +import { + Button, + Card, + CardActions, + CardContent, + Typography, +} from "@mui/material"; +import { MatrixLinkApi } from "../api/MatrixLinkApi"; +import { useAlert } from "../hooks/contexts_provider/AlertDialogProvider"; +import { useLoadingMessage } from "../hooks/contexts_provider/LoadingMessageProvider"; +import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage"; +import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer"; + +export function MatrixLinkRoute(): React.ReactElement { + const user = useUserInfo(); + return ( + + {user.info.matrix_user_id === null ? : } + + ); +} + +function ConnectCard(): React.ReactElement { + const alert = useAlert(); + const loadingMessage = useLoadingMessage(); + + const startMatrixConnection = async () => { + try { + loadingMessage.show("Initiating Matrix link..."); + + const res = await MatrixLinkApi.StartAuth(); + + window.location.href = res.url; + } catch (e) { + console.error(`Failed to connect to Matrix account! ${e}`); + alert(`Failed to connect to Matrix account! ${e}`); + } finally { + loadingMessage.hide(); + } + }; + + return ( + + + + Disconnected from your Matrix account + + + + You need to connect MatrixGW to your Matrix account to let it access + your messages. + + + + + + + ); +} + +function ConnectedCard(): React.ReactElement { + const user = useUserInfo(); + + return ( + + + + Connected to your Matrix account + + + +

    + MatrixGW is currently connected to your account with ID{" "} + {user.info.matrix_user_id}. +

    +

    + If you encounter issues with your Matrix account you can try to + disconnect and connect back again. +

    +
    +
    + + + +
    + ); +} diff --git a/matrixgw_frontend/src/widgets/MatrixGWRouteContainer.tsx b/matrixgw_frontend/src/widgets/MatrixGWRouteContainer.tsx new file mode 100644 index 0000000..26c6ba9 --- /dev/null +++ b/matrixgw_frontend/src/widgets/MatrixGWRouteContainer.tsx @@ -0,0 +1,37 @@ +import { Typography } from "@mui/material"; +import React, { type PropsWithChildren } from "react"; + +export function MatrixGWRouteContainer( + p: { + label: string | React.ReactElement; + actions?: React.ReactElement; + } & PropsWithChildren +): React.ReactElement { + return ( +
    +
    + {p.label} + {p.actions ?? <>} +
    + + {p.children} +
    + ); +} diff --git a/matrixgw_frontend/src/widgets/NotLinkedAccountMessage.tsx b/matrixgw_frontend/src/widgets/NotLinkedAccountMessage.tsx new file mode 100644 index 0000000..65412b3 --- /dev/null +++ b/matrixgw_frontend/src/widgets/NotLinkedAccountMessage.tsx @@ -0,0 +1,14 @@ +export function NotLinkedAccountMessage(): React.ReactElement { + return ( +
    + Your Matrix account is not linked yet! +
    + ); +} diff --git a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx index 47e8be3..0382979 100644 --- a/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/BaseAuthenticatedPage.tsx @@ -124,7 +124,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement { flexDirection: "column", flex: 1, overflow: "auto", - padding: "50px", }} > @@ -137,6 +136,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement { ); } -export function userUserInfo(): UserInfoContext { +export function useUserInfo(): UserInfoContext { return React.use(UserInfoContextK)!; } diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx index 0f76b97..6beaa76 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx @@ -14,7 +14,7 @@ import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; import * as React from "react"; import { RouterLink } from "../RouterLink"; -import { userUserInfo as useUserInfo } from "./BaseAuthenticatedPage"; +import { useUserInfo } from "./BaseAuthenticatedPage"; import ThemeSwitcher from "./ThemeSwitcher"; const AppBar = styled(MuiAppBar)(({ theme }) => ({ diff --git a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx index d1584a6..a2c7f3d 100644 --- a/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx +++ b/matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx @@ -107,7 +107,7 @@ export default function DashboardSidebar({ } - href="/matrixlink" + href="/matrix_link" /> Date: Wed, 5 Nov 2025 19:32:11 +0100 Subject: [PATCH 17/21] Can finalize Matrix authentication --- .../src/controllers/auth_controller.rs | 7 +- .../src/controllers/matrix_link_controller.rs | 12 +++ matrixgw_backend/src/controllers/mod.rs | 4 +- .../src/extractors/auth_extractor.rs | 12 +++ .../src/extractors/matrix_client_extractor.rs | 11 +++ matrixgw_backend/src/main.rs | 6 +- .../src/matrix_connection/matrix_client.rs | 31 +++++++- matrixgw_backend/src/users.rs | 10 +-- matrixgw_frontend/src/App.tsx | 2 + matrixgw_frontend/src/api/AuthApi.ts | 1 + matrixgw_frontend/src/api/MatrixLinkApi.ts | 11 +++ .../src/routes/MatrixAuthCallback.tsx | 79 +++++++++++++++++++ .../src/routes/MatrixLinkRoute.tsx | 12 ++- 13 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 matrixgw_frontend/src/routes/MatrixAuthCallback.tsx diff --git a/matrixgw_backend/src/controllers/auth_controller.rs b/matrixgw_backend/src/controllers/auth_controller.rs index 6cbab4c..42d759f 100644 --- a/matrixgw_backend/src/controllers/auth_controller.rs +++ b/matrixgw_backend/src/controllers/auth_controller.rs @@ -1,8 +1,9 @@ use crate::app_config::AppConfig; use crate::controllers::{HttpFailure, HttpResult}; use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; +use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::extractors::session_extractor::MatrixGWSession; -use crate::users::{ExtendedUserInfo, User, UserEmail}; +use crate::users::{User, UserEmail}; use actix_remote_ip::RemoteIP; use actix_web::{HttpResponse, web}; use light_openid::primitives::OpenIDConfig; @@ -107,8 +108,8 @@ pub async fn finish_oidc( } /// Get current user information -pub async fn auth_info(auth: AuthExtractor) -> HttpResult { - Ok(HttpResponse::Ok().json(ExtendedUserInfo::from_user(auth.user).await?)) +pub async fn auth_info(client: MatrixClientExtractor) -> HttpResult { + Ok(HttpResponse::Ok().json(client.to_extended_user_info().await?)) } /// Sign out user diff --git a/matrixgw_backend/src/controllers/matrix_link_controller.rs b/matrixgw_backend/src/controllers/matrix_link_controller.rs index 38fea49..084597a 100644 --- a/matrixgw_backend/src/controllers/matrix_link_controller.rs +++ b/matrixgw_backend/src/controllers/matrix_link_controller.rs @@ -1,6 +1,8 @@ use crate::controllers::HttpResult; use crate::extractors::matrix_client_extractor::MatrixClientExtractor; +use crate::matrix_connection::matrix_client::FinishMatrixAuth; use actix_web::HttpResponse; +use anyhow::Context; #[derive(serde::Serialize)] struct StartAuthResponse { @@ -12,3 +14,13 @@ pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult { let url = client.client.initiate_login().await?.to_string(); Ok(HttpResponse::Ok().json(StartAuthResponse { url })) } + +/// Finish user authentication on Matrix server +pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult { + client + .client + .finish_login(client.auth.decode_json_body::()?) + .await + .context("Failed to finalize Matrix authentication!")?; + Ok(HttpResponse::Accepted().finish()) +} diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs index 8a581e9..8ddf218 100644 --- a/matrixgw_backend/src/controllers/mod.rs +++ b/matrixgw_backend/src/controllers/mod.rs @@ -28,7 +28,9 @@ impl ResponseError for HttpFailure { } fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()).body(self.to_string()) + HttpResponse::build(self.status_code()) + .content_type("text/plain") + .body(self.to_string()) } } diff --git a/matrixgw_backend/src/extractors/auth_extractor.rs b/matrixgw_backend/src/extractors/auth_extractor.rs index 8c75a29..36003e6 100644 --- a/matrixgw_backend/src/extractors/auth_extractor.rs +++ b/matrixgw_backend/src/extractors/auth_extractor.rs @@ -11,6 +11,8 @@ use anyhow::Context; use bytes::Bytes; use jwt_simple::common::VerificationOptions; use jwt_simple::prelude::{Duration, HS256Key, MACLike}; +use jwt_simple::reexports::serde_json; +use serde::de::DeserializeOwned; use sha2::{Digest, Sha256}; use std::fmt::Display; use std::net::IpAddr; @@ -32,6 +34,16 @@ pub struct AuthExtractor { pub payload: Option>, } +impl AuthExtractor { + pub fn decode_json_body(&self) -> anyhow::Result { + let payload = self + .payload + .as_ref() + .context("Failed to decode request as json: missing payload!")?; + serde_json::from_slice(payload).context("Failed to decode request json!") + } +} + #[derive(Debug, Eq, PartialEq)] pub struct MatrixJWTKID { pub user_email: UserEmail, diff --git a/matrixgw_backend/src/extractors/matrix_client_extractor.rs b/matrixgw_backend/src/extractors/matrix_client_extractor.rs index fbe6d3c..5de4f7f 100644 --- a/matrixgw_backend/src/extractors/matrix_client_extractor.rs +++ b/matrixgw_backend/src/extractors/matrix_client_extractor.rs @@ -1,6 +1,7 @@ use crate::extractors::auth_extractor::AuthExtractor; use crate::matrix_connection::matrix_client::MatrixClient; use crate::matrix_connection::matrix_manager::MatrixManagerMsg; +use crate::users::ExtendedUserInfo; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpRequest, web}; use ractor::ActorRef; @@ -10,6 +11,16 @@ pub struct MatrixClientExtractor { pub client: MatrixClient, } +impl MatrixClientExtractor { + pub async fn to_extended_user_info(&self) -> anyhow::Result { + Ok(ExtendedUserInfo { + user: self.auth.user.clone(), + matrix_user_id: self.client.client.user_id().map(|id| id.to_string()), + matrix_device_id: self.client.client.device_id().map(|id| id.to_string()), + }) + } +} + impl FromRequest for MatrixClientExtractor { type Error = actix_web::Error; type Future = futures_util::future::LocalBoxFuture<'static, Result>; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 2571cdc..6c7e4fd 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -55,7 +55,7 @@ async fn main() -> std::io::Result<()> { let cors = Cors::default() .allowed_origin(&AppConfig::get().website_origin) - .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) + .allowed_methods(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) .allowed_header(constants::API_AUTH_HEADER) .allow_any_header() .supports_credentials() @@ -94,6 +94,10 @@ async fn main() -> std::io::Result<()> { "/api/matrix_link/start_auth", web::post().to(matrix_link_controller::start_auth), ) + .route( + "/api/matrix_link/finish_auth", + web::post().to(matrix_link_controller::finish_auth), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? diff --git a/matrixgw_backend/src/matrix_connection/matrix_client.rs b/matrixgw_backend/src/matrix_connection/matrix_client.rs index f6dcc86..c6e88cb 100644 --- a/matrixgw_backend/src/matrix_connection/matrix_client.rs +++ b/matrixgw_backend/src/matrix_connection/matrix_client.rs @@ -1,8 +1,8 @@ use crate::app_config::AppConfig; use crate::users::UserEmail; use crate::utils::rand_utils::rand_string; -use matrix_sdk::authentication::oauth::OAuthError; use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; +use matrix_sdk::authentication::oauth::{OAuthError, UrlOrQuery}; use matrix_sdk::ruma::serde::Raw; use matrix_sdk::{Client, ClientBuildError}; use url::Url; @@ -28,6 +28,14 @@ enum MatrixClientError { ParseAuthRedirectURL(url::ParseError), #[error("Failed to build auth request! {0}")] BuildAuthRequest(OAuthError), + #[error("Failed to finalize authentication! {0}")] + FinishLogin(matrix_sdk::Error), +} + +#[derive(serde::Deserialize)] +pub struct FinishMatrixAuth { + code: String, + state: String, } #[derive(Clone)] @@ -91,7 +99,7 @@ impl MatrixClient { todo!() } - /// Initiate oauth authentication + /// Initiate OAuth authentication pub async fn initiate_login(&self) -> anyhow::Result { let oauth = self.client.oauth(); @@ -112,4 +120,23 @@ impl MatrixClient { Ok(auth.url) } + + /// Finish OAuth authentication + pub async fn finish_login(&self, info: FinishMatrixAuth) -> anyhow::Result<()> { + let oauth = self.client.oauth(); + oauth + .finish_login(UrlOrQuery::Query(format!( + "state={}&code={}", + info.state, info.code + ))) + .await + .map_err(MatrixClientError::FinishLogin)?; + + log::info!( + "User successfully authenticated as {}!", + self.client.user_id().unwrap() + ); + + Ok(()) + } } diff --git a/matrixgw_backend/src/users.rs b/matrixgw_backend/src/users.rs index aab18ed..7eca242 100644 --- a/matrixgw_backend/src/users.rs +++ b/matrixgw_backend/src/users.rs @@ -172,13 +172,5 @@ pub struct ExtendedUserInfo { #[serde(flatten)] pub user: User, pub matrix_user_id: Option, -} - -impl ExtendedUserInfo { - pub async fn from_user(user: User) -> anyhow::Result { - Ok(Self { - user, - matrix_user_id: None, // TODO - }) - } + pub matrix_device_id: Option, } diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 9988388..7cd3001 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -14,6 +14,7 @@ import { MatrixLinkRoute } from "./routes/MatrixLinkRoute"; import { NotFoundRoute } from "./routes/NotFoundRoute"; import { BaseLoginPage } from "./widgets/auth/BaseLoginPage"; import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage"; +import { MatrixAuthCallback } from "./routes/MatrixAuthCallback"; interface AuthContext { signedIn: boolean; @@ -39,6 +40,7 @@ export function App(): React.ReactElement { }> } /> } /> + } /> } /> ) : ( diff --git a/matrixgw_frontend/src/api/AuthApi.ts b/matrixgw_frontend/src/api/AuthApi.ts index c0fc286..e2e10ac 100644 --- a/matrixgw_frontend/src/api/AuthApi.ts +++ b/matrixgw_frontend/src/api/AuthApi.ts @@ -7,6 +7,7 @@ export interface UserInfo { name: string; email: string; matrix_user_id?: string; + matrix_device_id?: string; } const TokenStateKey = "auth-state"; diff --git a/matrixgw_frontend/src/api/MatrixLinkApi.ts b/matrixgw_frontend/src/api/MatrixLinkApi.ts index 6a05a3c..3796189 100644 --- a/matrixgw_frontend/src/api/MatrixLinkApi.ts +++ b/matrixgw_frontend/src/api/MatrixLinkApi.ts @@ -12,4 +12,15 @@ export class MatrixLinkApi { }) ).data; } + + /** + * Finish Matrix Account login + */ + static async FinishAuth(code: string, state: string): Promise { + await APIClient.exec({ + uri: "/matrix_link/finish_auth", + method: "POST", + jsonData: { code, state }, + }); + } } diff --git a/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx b/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx new file mode 100644 index 0000000..ae9906c --- /dev/null +++ b/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx @@ -0,0 +1,79 @@ +import { Alert, Box, Button, CircularProgress } from "@mui/material"; +import React from "react"; +import { useNavigate, useSearchParams } from "react-router"; +import { MatrixLinkApi } from "../api/MatrixLinkApi"; +import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage"; +import { RouterLink } from "../widgets/RouterLink"; +import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider"; + +export function MatrixAuthCallback(): React.ReactElement { + const navigate = useNavigate(); + const snackbar = useSnackbar(); + + const info = useUserInfo(); + + const [error, setError] = React.useState(null); + + const [searchParams] = useSearchParams(); + const code = searchParams.get("code"); + const state = searchParams.get("state"); + + const count = React.useRef(""); + + React.useEffect(() => { + const load = async () => { + try { + if (count.current === code) { + return; + } + count.current = code!; + + await MatrixLinkApi.FinishAuth(code!, state!); + info.reloadUserInfo(); + snackbar("Successfully linked to Matrix account!"); + navigate("/matrix_link"); + } catch (e) { + console.error(e); + setError(String(e)); + } + }; + + load(); + }); + + if (error) + return ( + + + Failed to finalize Matrix authentication! +
    +
    + {error} +
    + + +
    + ); + + return ( +
    + +
    + ); +} diff --git a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx index 7a33ed7..23a3b27 100644 --- a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx +++ b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx @@ -79,9 +79,17 @@ function ConnectedCard(): React.ReactElement {

    - MatrixGW is currently connected to your account with ID{" "} - {user.info.matrix_user_id}. + MatrixGW is currently connected to your account with the following + information:

    +
      +
    • + User id: {user.info.matrix_user_id} +
    • +
    • + Device id: {user.info.matrix_device_id} +
    • +

    If you encounter issues with your Matrix account you can try to disconnect and connect back again. -- 2.49.1 From 1438e2de0ea9ae04b5942a57b8f2b6f42725ce97 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 5 Nov 2025 22:53:55 +0100 Subject: [PATCH 18/21] Can save Matrix session after authentication --- matrixgw_backend/Cargo.lock | 1 + matrixgw_backend/Cargo.toml | 3 +- matrixgw_backend/src/app_config.rs | 5 + .../src/controllers/matrix_link_controller.rs | 12 +- .../src/matrix_connection/matrix_client.rs | 115 ++++++++++++++++-- .../src/routes/MatrixAuthCallback.tsx | 6 +- 6 files changed, 124 insertions(+), 18 deletions(-) diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index ebe0055..59988bb 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -3020,6 +3020,7 @@ dependencies = [ "ractor", "rand 0.9.2", "serde", + "serde_json", "sha2", "thiserror 2.0.17", "tokio", diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index 94f3b2b..26e819c 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -30,4 +30,5 @@ hex = "0.4.3" mailchecker = "6.0.19" matrix-sdk = "0.14.0" url = "2.5.7" -ractor = "0.15.9" \ No newline at end of file +ractor = "0.15.9" +serde_json = "1.0.145" \ No newline at end of file diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index 32fa673..87c3b2c 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -220,6 +220,11 @@ impl AppConfig { pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf { self.user_directory(mail).join("matrix-db-passphrase") } + + /// Get user Matrix session file path + pub fn user_matrix_session_file_path(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("matrix-session.json") + } } #[derive(Debug, Clone, serde::Serialize)] diff --git a/matrixgw_backend/src/controllers/matrix_link_controller.rs b/matrixgw_backend/src/controllers/matrix_link_controller.rs index 084597a..1d769c1 100644 --- a/matrixgw_backend/src/controllers/matrix_link_controller.rs +++ b/matrixgw_backend/src/controllers/matrix_link_controller.rs @@ -2,7 +2,6 @@ use crate::controllers::HttpResult; use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::matrix_connection::matrix_client::FinishMatrixAuth; use actix_web::HttpResponse; -use anyhow::Context; #[derive(serde::Serialize)] struct StartAuthResponse { @@ -17,10 +16,15 @@ pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult { /// Finish user authentication on Matrix server pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult { - client + match client .client .finish_login(client.auth.decode_json_body::()?) .await - .context("Failed to finalize Matrix authentication!")?; - Ok(HttpResponse::Accepted().finish()) + { + Ok(_) => Ok(HttpResponse::Accepted().finish()), + Err(e) => { + log::error!("Failed to finish Matrix authentication: {e}"); + Err(e.into()) + } + } } diff --git a/matrixgw_backend/src/matrix_connection/matrix_client.rs b/matrixgw_backend/src/matrix_connection/matrix_client.rs index c6e88cb..dafb0c2 100644 --- a/matrixgw_backend/src/matrix_connection/matrix_client.rs +++ b/matrixgw_backend/src/matrix_connection/matrix_client.rs @@ -1,15 +1,29 @@ use crate::app_config::AppConfig; use crate::users::UserEmail; use crate::utils::rand_utils::rand_string; +use anyhow::Context; use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; -use matrix_sdk::authentication::oauth::{OAuthError, UrlOrQuery}; +use matrix_sdk::authentication::oauth::{ClientId, OAuthError, UrlOrQuery, UserSession}; use matrix_sdk::ruma::serde::Raw; use matrix_sdk::{Client, ClientBuildError}; +use serde::{Deserialize, Serialize}; use url::Url; +/// The full session to persist. +#[derive(Debug, Serialize, Deserialize, Clone)] +struct StoredSession { + /// The OAuth 2.0 user session. + user_session: UserSession, + + /// The OAuth 2.0 client ID. + client_id: ClientId, +} + /// Matrix Gateway session errors #[derive(thiserror::Error, Debug)] enum MatrixClientError { + #[error("Failed to destroy previous client data! {0}")] + DestroyPreviousData(Box), #[error("Failed to create Matrix database storage directory! {0}")] CreateMatrixDbDir(std::io::Error), #[error("Failed to create database passphrase! {0}")] @@ -18,6 +32,8 @@ enum MatrixClientError { ReadDbPassphrase(std::io::Error), #[error("Failed to build Matrix client! {0}")] BuildMatrixClient(ClientBuildError), + #[error("Failed to clear Matrix session file! {0}")] + ClearMatrixSessionFile(std::io::Error), #[error("Failed to clear Matrix database storage directory! {0}")] ClearMatrixDbDir(std::io::Error), #[error("Failed to remove database passphrase! {0}")] @@ -30,6 +46,8 @@ enum MatrixClientError { BuildAuthRequest(OAuthError), #[error("Failed to finalize authentication! {0}")] FinishLogin(matrix_sdk::Error), + #[error("Failed to write session file! {0}")] + WriteSessionFile(std::io::Error), } #[derive(serde::Deserialize)] @@ -47,6 +65,15 @@ pub struct MatrixClient { impl MatrixClient { /// Start to build Matrix client to initiate user authentication pub async fn build_client(email: &UserEmail) -> anyhow::Result { + // Check if we are restoring a previous state + let is_restoring = AppConfig::get() + .user_matrix_session_file_path(email) + .is_file(); + if !is_restoring { + Self::destroy_data(email).map_err(MatrixClientError::DestroyPreviousData)?; + } + + // Determine Matrix database path let db_path = AppConfig::get().user_matrix_db_path(email); std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?; @@ -69,34 +96,46 @@ impl MatrixClient { .map_err(MatrixClientError::BuildMatrixClient)?; // Check metadata - let server_metadata = client - .oauth() + let oauth = client.oauth(); + let server_metadata = oauth .server_metadata() .await .map_err(MatrixClientError::FetchServerMetadata)?; log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer); + // TODO : restore client ID to oauth if needed // TODO : restore client if client already existed - Ok(Self { + let client = Self { email: email.clone(), client, - }) + }; + + // Automatically save session when token gets refreshed + client.setup_background_session_save().await; + + Ok(client) } - /// Destroy this Matrix client instance - pub fn destroy(&self) -> anyhow::Result<()> { - let db_path = AppConfig::get().user_matrix_db_path(&self.email); - if db_path.is_file() { + /// Destroy Matrix client related data + fn destroy_data(email: &UserEmail) -> anyhow::Result<(), Box> { + let session_path = AppConfig::get().user_matrix_session_file_path(email); + if session_path.is_file() { + std::fs::remove_file(&session_path) + .map_err(MatrixClientError::ClearMatrixSessionFile)?; + } + + let db_path = AppConfig::get().user_matrix_db_path(email); + if db_path.is_dir() { std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?; } - let passphrase_path = AppConfig::get().user_matrix_passphrase_path(&self.email); + let passphrase_path = AppConfig::get().user_matrix_passphrase_path(email); if passphrase_path.is_file() { std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?; } - todo!() + Ok(()) } /// Initiate OAuth authentication @@ -137,6 +176,60 @@ impl MatrixClient { self.client.user_id().unwrap() ); + // Persist session tokens + self.save_stored_session().await?; + + Ok(()) + } + + /// Automatically persist session onto disk + pub async fn setup_background_session_save(&self) { + let this = self.clone(); + tokio::spawn(async move { + while let Ok(update) = this.client.subscribe_to_session_changes().recv().await { + match update { + matrix_sdk::SessionChange::UnknownToken { soft_logout } => { + log::warn!("Received an unknown token error; soft logout? {soft_logout:?}"); + } + matrix_sdk::SessionChange::TokensRefreshed => { + // The tokens have been refreshed, persist them to disk. + if let Err(err) = this.save_stored_session().await { + log::error!("Unable to store a session in the background: {err}"); + } + } + } + } + }); + } + + /// Update the session stored on the filesystem. + async fn save_stored_session(&self) -> anyhow::Result<()> { + log::debug!("Save the stored session for {:?}...", self.email); + + let user_session: UserSession = self + .client + .oauth() + .user_session() + .context("A logged in client must have a session")?; + + let stored_session = StoredSession { + user_session, + client_id: self + .client + .oauth() + .client_id() + .context("Client ID should be set at this point!")? + .clone(), + }; + + let serialized_session = serde_json::to_string(&stored_session)?; + std::fs::write( + AppConfig::get().user_matrix_session_file_path(&self.email), + serialized_session, + ) + .map_err(MatrixClientError::WriteSessionFile)?; + + log::debug!("Updating the stored session: done!"); Ok(()) } } diff --git a/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx b/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx index ae9906c..3eafadc 100644 --- a/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx +++ b/matrixgw_frontend/src/routes/MatrixAuthCallback.tsx @@ -29,17 +29,19 @@ export function MatrixAuthCallback(): React.ReactElement { count.current = code!; await MatrixLinkApi.FinishAuth(code!, state!); - info.reloadUserInfo(); + snackbar("Successfully linked to Matrix account!"); navigate("/matrix_link"); } catch (e) { console.error(e); setError(String(e)); + } finally { + info.reloadUserInfo(); } }; load(); - }); + }, [code, state]); if (error) return ( -- 2.49.1 From 1ba5372468a752fd8df53a6afb1110bf4d3be977 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 6 Nov 2025 18:58:43 +0100 Subject: [PATCH 19/21] Restore user session on restart --- .../src/matrix_connection/matrix_client.rs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/matrixgw_backend/src/matrix_connection/matrix_client.rs b/matrixgw_backend/src/matrix_connection/matrix_client.rs index dafb0c2..e682292 100644 --- a/matrixgw_backend/src/matrix_connection/matrix_client.rs +++ b/matrixgw_backend/src/matrix_connection/matrix_client.rs @@ -3,7 +3,9 @@ use crate::users::UserEmail; use crate::utils::rand_utils::rand_string; use anyhow::Context; use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; -use matrix_sdk::authentication::oauth::{ClientId, OAuthError, UrlOrQuery, UserSession}; +use matrix_sdk::authentication::oauth::{ + ClientId, OAuthError, OAuthSession, UrlOrQuery, UserSession, +}; use matrix_sdk::ruma::serde::Raw; use matrix_sdk::{Client, ClientBuildError}; use serde::{Deserialize, Serialize}; @@ -40,6 +42,12 @@ enum MatrixClientError { ClearDbPassphrase(std::io::Error), #[error("Failed to fetch server metadata! {0}")] FetchServerMetadata(OAuthDiscoveryError), + #[error("Failed to load stored session! {0}")] + LoadStoredSession(std::io::Error), + #[error("Failed to decode stored session! {0}")] + DecodeStoredSession(serde_json::Error), + #[error("Failed to restore stored session! {0}")] + RestoreSession(matrix_sdk::Error), #[error("Failed to parse auth redirect URL! {0}")] ParseAuthRedirectURL(url::ParseError), #[error("Failed to build auth request! {0}")] @@ -66,9 +74,8 @@ impl MatrixClient { /// Start to build Matrix client to initiate user authentication pub async fn build_client(email: &UserEmail) -> anyhow::Result { // Check if we are restoring a previous state - let is_restoring = AppConfig::get() - .user_matrix_session_file_path(email) - .is_file(); + let session_file_path = AppConfig::get().user_matrix_session_file_path(email); + let is_restoring = session_file_path.is_file(); if !is_restoring { Self::destroy_data(email).map_err(MatrixClientError::DestroyPreviousData)?; } @@ -103,8 +110,23 @@ impl MatrixClient { .map_err(MatrixClientError::FetchServerMetadata)?; log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer); - // TODO : restore client ID to oauth if needed - // TODO : restore client if client already existed + if is_restoring { + let session: StoredSession = serde_json::from_str( + std::fs::read_to_string(session_file_path) + .map_err(MatrixClientError::LoadStoredSession)? + .as_str(), + ) + .map_err(MatrixClientError::DecodeStoredSession)?; + + // Restore data + client + .restore_session(OAuthSession { + client_id: session.client_id, + user: session.user_session, + }) + .await + .map_err(MatrixClientError::RestoreSession)?; + } let client = Self { email: email.clone(), -- 2.49.1 From 8bbbe7022fe4665d7f69fe1a4756d875527e7037 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 6 Nov 2025 21:18:27 +0100 Subject: [PATCH 20/21] Automatically disconnect user when token is invalid --- matrixgw_backend/src/broadcast_messages.rs | 10 +++ matrixgw_backend/src/lib.rs | 1 + matrixgw_backend/src/main.rs | 6 +- .../src/matrix_connection/matrix_client.rs | 70 ++++++++++++++++--- .../src/matrix_connection/matrix_manager.rs | 27 +++++-- 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 matrixgw_backend/src/broadcast_messages.rs diff --git a/matrixgw_backend/src/broadcast_messages.rs b/matrixgw_backend/src/broadcast_messages.rs new file mode 100644 index 0000000..423114f --- /dev/null +++ b/matrixgw_backend/src/broadcast_messages.rs @@ -0,0 +1,10 @@ +use crate::users::UserEmail; + +pub type BroadcastSender = tokio::sync::broadcast::Sender; + +/// Broadcast messages +#[derive(Debug, Clone)] +pub enum BroadcastMessage { + /// User is or has been disconnected + UserDisconnected(UserEmail), +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 61b9e52..3eb69b6 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -1,4 +1,5 @@ pub mod app_config; +pub mod broadcast_messages; pub mod constants; pub mod controllers; pub mod extractors; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 6c7e4fd..7614ee8 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -7,6 +7,7 @@ use actix_web::cookie::Key; use actix_web::middleware::Logger; use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; +use matrixgw_backend::broadcast_messages::BroadcastMessage; use matrixgw_backend::constants; use matrixgw_backend::controllers::{auth_controller, matrix_link_controller, server_controller}; use matrixgw_backend::matrix_connection::matrix_manager::MatrixManagerActor; @@ -24,6 +25,8 @@ async fn main() -> std::io::Result<()> { .await .expect("Failed to connect to Redis!"); + let (ws_tx, _) = tokio::sync::broadcast::channel::(16); + // Auto create default account, if requested if let Some(mail) = &AppConfig::get().unsecure_auto_login_email() { User::create_or_update_user(mail, "Anonymous") @@ -35,7 +38,7 @@ async fn main() -> std::io::Result<()> { let (manager_actor, manager_actor_handle) = Actor::spawn( Some("matrix-clients-manager".to_string()), MatrixManagerActor, - (), + ws_tx.clone(), ) .await .expect("Failed to start Matrix manager actor!"); @@ -69,6 +72,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(RemoteIPConfig { proxy: AppConfig::get().proxy_ip.clone(), })) + .app_data(web::Data::new(ws_tx.clone())) // Server controller .route("/robots.txt", web::get().to(server_controller::robots_txt)) .route( diff --git a/matrixgw_backend/src/matrix_connection/matrix_client.rs b/matrixgw_backend/src/matrix_connection/matrix_client.rs index e682292..cfc23b1 100644 --- a/matrixgw_backend/src/matrix_connection/matrix_client.rs +++ b/matrixgw_backend/src/matrix_connection/matrix_client.rs @@ -1,13 +1,17 @@ use crate::app_config::AppConfig; +use crate::matrix_connection::matrix_manager::MatrixManagerMsg; use crate::users::UserEmail; use crate::utils::rand_utils::rand_string; use anyhow::Context; -use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError; +use matrix_sdk::authentication::oauth::error::{ + BasicErrorResponseType, OAuthDiscoveryError, RequestTokenError, +}; use matrix_sdk::authentication::oauth::{ ClientId, OAuthError, OAuthSession, UrlOrQuery, UserSession, }; use matrix_sdk::ruma::serde::Raw; -use matrix_sdk::{Client, ClientBuildError}; +use matrix_sdk::{Client, ClientBuildError, RefreshTokenError}; +use ractor::ActorRef; use serde::{Deserialize, Serialize}; use url::Url; @@ -48,6 +52,10 @@ enum MatrixClientError { DecodeStoredSession(serde_json::Error), #[error("Failed to restore stored session! {0}")] RestoreSession(matrix_sdk::Error), + #[error("Failed to disconnect user! {0}")] + DisconnectUser(anyhow::Error), + #[error("Failed to refresh access token! {0}")] + InitialRefreshToken(RefreshTokenError), #[error("Failed to parse auth redirect URL! {0}")] ParseAuthRedirectURL(url::ParseError), #[error("Failed to build auth request! {0}")] @@ -66,13 +74,17 @@ pub struct FinishMatrixAuth { #[derive(Clone)] pub struct MatrixClient { + manager: ActorRef, pub email: UserEmail, pub client: Client, } impl MatrixClient { /// Start to build Matrix client to initiate user authentication - pub async fn build_client(email: &UserEmail) -> anyhow::Result { + pub async fn build_client( + manager: ActorRef, + email: &UserEmail, + ) -> anyhow::Result { // Check if we are restoring a previous state let session_file_path = AppConfig::get().user_matrix_session_file_path(email); let is_restoring = session_file_path.is_file(); @@ -102,8 +114,14 @@ impl MatrixClient { .await .map_err(MatrixClientError::BuildMatrixClient)?; + let client = Self { + manager, + email: email.clone(), + client, + }; + // Check metadata - let oauth = client.oauth(); + let oauth = client.client.oauth(); let server_metadata = oauth .server_metadata() .await @@ -118,20 +136,33 @@ impl MatrixClient { ) .map_err(MatrixClientError::DecodeStoredSession)?; - // Restore data + // Restore session client + .client .restore_session(OAuthSession { client_id: session.client_id, user: session.user_session, }) .await .map_err(MatrixClientError::RestoreSession)?; - } - let client = Self { - email: email.clone(), - client, - }; + // Force token refresh to make sure session is still alive, otherwise disconnect user + if let Err(refresh_error) = client.client.oauth().refresh_access_token().await { + if let RefreshTokenError::OAuth(e) = &refresh_error + && let OAuthError::RefreshToken(RequestTokenError::ServerResponse(e)) = &**e + && e.error() == &BasicErrorResponseType::InvalidGrant + { + log::warn!( + "Refresh token rejected by server, token must have been invalidated! {refresh_error}" + ); + client + .disconnect() + .await + .map_err(MatrixClientError::DisconnectUser)?; + } + return Err(MatrixClientError::InitialRefreshToken(refresh_error).into()); + } + } // Automatically save session when token gets refreshed client.setup_background_session_save().await; @@ -212,6 +243,13 @@ impl MatrixClient { match update { matrix_sdk::SessionChange::UnknownToken { soft_logout } => { log::warn!("Received an unknown token error; soft logout? {soft_logout:?}"); + if let Err(e) = this + .manager + .cast(MatrixManagerMsg::DisconnectClient(this.email)) + { + log::warn!("Failed to propagate invalid token error: {e}"); + } + break; } matrix_sdk::SessionChange::TokensRefreshed => { // The tokens have been refreshed, persist them to disk. @@ -254,4 +292,16 @@ impl MatrixClient { log::debug!("Updating the stored session: done!"); Ok(()) } + + /// Disconnect user from client + pub async fn disconnect(self) -> anyhow::Result<()> { + if let Err(e) = self.client.logout().await { + log::warn!("Failed to send logout request: {e}"); + } + + // Destroy user associated data + Self::destroy_data(&self.email)?; + + Ok(()) + } } diff --git a/matrixgw_backend/src/matrix_connection/matrix_manager.rs b/matrixgw_backend/src/matrix_connection/matrix_manager.rs index d6f180a..d75cab6 100644 --- a/matrixgw_backend/src/matrix_connection/matrix_manager.rs +++ b/matrixgw_backend/src/matrix_connection/matrix_manager.rs @@ -1,14 +1,17 @@ +use crate::broadcast_messages::{BroadcastMessage, BroadcastSender}; use crate::matrix_connection::matrix_client::MatrixClient; use crate::users::UserEmail; use ractor::{Actor, ActorProcessingErr, ActorRef, RpcReplyPort}; use std::collections::HashMap; pub struct MatrixManagerState { + pub broadcast_sender: BroadcastSender, pub clients: HashMap, } pub enum MatrixManagerMsg { GetClient(UserEmail, RpcReplyPort>), + DisconnectClient(UserEmail), } pub struct MatrixManagerActor; @@ -16,21 +19,22 @@ pub struct MatrixManagerActor; impl Actor for MatrixManagerActor { type Msg = MatrixManagerMsg; type State = MatrixManagerState; - type Arguments = (); + type Arguments = BroadcastSender; async fn pre_start( &self, _myself: ActorRef, - _args: Self::Arguments, + args: Self::Arguments, ) -> Result { Ok(MatrixManagerState { + broadcast_sender: args, clients: HashMap::new(), }) } async fn handle( &self, - _myself: ActorRef, + myself: ActorRef, message: Self::Msg, state: &mut Self::State, ) -> Result<(), ActorProcessingErr> { @@ -41,7 +45,7 @@ impl Actor for MatrixManagerActor { None => { // Generate client if required log::info!("Building new client for {:?}", &email); - match MatrixClient::build_client(&email).await { + match MatrixClient::build_client(myself, &email).await { Ok(c) => { state.clients.insert(email.clone(), c.clone()); Ok(c) @@ -56,6 +60,21 @@ impl Actor for MatrixManagerActor { log::warn!("Failed to send client information: {e}") } } + MatrixManagerMsg::DisconnectClient(email) => { + if let Some(c) = state.clients.remove(&email) { + if let Err(e) = c.disconnect().await { + log::error!("Failed to disconnect client: {e}"); + } + if let Err(e) = state + .broadcast_sender + .send(BroadcastMessage::UserDisconnected(email)) + { + log::warn!( + "Failed to notify that user has been disconnected from Matrix! {e}" + ); + } + } + } } Ok(()) } -- 2.49.1 From 70a246355b538c06d58f315d1826ae452edcbdd7 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 6 Nov 2025 21:33:09 +0100 Subject: [PATCH 21/21] Can disconnect user from UI --- .../src/controllers/matrix_link_controller.rs | 17 +++++++++- matrixgw_backend/src/main.rs | 4 +++ matrixgw_frontend/src/api/MatrixLinkApi.ts | 10 ++++++ .../src/routes/MatrixLinkRoute.tsx | 33 ++++++++++++++++++- 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/matrixgw_backend/src/controllers/matrix_link_controller.rs b/matrixgw_backend/src/controllers/matrix_link_controller.rs index 1d769c1..16fe6cf 100644 --- a/matrixgw_backend/src/controllers/matrix_link_controller.rs +++ b/matrixgw_backend/src/controllers/matrix_link_controller.rs @@ -1,7 +1,10 @@ use crate::controllers::HttpResult; +use crate::extractors::auth_extractor::AuthExtractor; use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::matrix_connection::matrix_client::FinishMatrixAuth; -use actix_web::HttpResponse; +use crate::matrix_connection::matrix_manager::MatrixManagerMsg; +use actix_web::{HttpResponse, web}; +use ractor::ActorRef; #[derive(serde::Serialize)] struct StartAuthResponse { @@ -28,3 +31,15 @@ pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult { } } } + +/// Logout user from Matrix server +pub async fn logout( + auth: AuthExtractor, + manager: web::Data>, +) -> HttpResult { + manager + .cast(MatrixManagerMsg::DisconnectClient(auth.user.email)) + .expect("Failed to communicate with matrix manager!"); + + Ok(HttpResponse::Ok().finish()) +} diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 7614ee8..a239e35 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -102,6 +102,10 @@ async fn main() -> std::io::Result<()> { "/api/matrix_link/finish_auth", web::post().to(matrix_link_controller::finish_auth), ) + .route( + "/api/matrix_link/logout", + web::post().to(matrix_link_controller::logout), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? diff --git a/matrixgw_frontend/src/api/MatrixLinkApi.ts b/matrixgw_frontend/src/api/MatrixLinkApi.ts index 3796189..d5b3cb7 100644 --- a/matrixgw_frontend/src/api/MatrixLinkApi.ts +++ b/matrixgw_frontend/src/api/MatrixLinkApi.ts @@ -23,4 +23,14 @@ export class MatrixLinkApi { jsonData: { code, state }, }); } + + /** + * Disconnect from Matrix Account + */ + static async Disconnect(): Promise { + await APIClient.exec({ + uri: "/matrix_link/logout", + method: "POST", + }); + } } diff --git a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx index 23a3b27..3e9f394 100644 --- a/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx +++ b/matrixgw_frontend/src/routes/MatrixLinkRoute.tsx @@ -9,7 +9,9 @@ import { } from "@mui/material"; import { MatrixLinkApi } from "../api/MatrixLinkApi"; import { useAlert } from "../hooks/contexts_provider/AlertDialogProvider"; +import { useConfirm } from "../hooks/contexts_provider/ConfirmDialogProvider"; import { useLoadingMessage } from "../hooks/contexts_provider/LoadingMessageProvider"; +import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider"; import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage"; import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer"; @@ -68,8 +70,32 @@ function ConnectCard(): React.ReactElement { } function ConnectedCard(): React.ReactElement { + const snackbar = useSnackbar(); + const confirm = useConfirm(); + const alert = useAlert(); + const loadingMessage = useLoadingMessage(); + + const info = useUserInfo(); + const user = useUserInfo(); + const handleDisconnect = async () => { + if (!(await confirm("Do you really want to unlink your Matrix account?"))) + return; + + try { + loadingMessage.show("Unlinking Matrix account..."); + await MatrixLinkApi.Disconnect(); + snackbar("Successfully unlinked Matrix account!"); + } catch (e) { + console.error(`Failed to unlink user account! ${e}`); + alert(`Failed to unlink your account! ${e}`); + } finally { + info.reloadUserInfo(); + loadingMessage.hide(); + } + }; + return ( @@ -97,7 +123,12 @@ function ConnectedCard(): React.ReactElement { - -- 2.49.1