This is an automated email from the ASF dual-hosted git repository.
piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new ff6695ba5 feat(server,web): embed Web UI into server behind iggy-web
feature (#2485)
ff6695ba5 is described below
commit ff6695ba589656acef68da534b4af55dda452c80
Author: Piotr Gankiewicz <[email protected]>
AuthorDate: Sun Dec 14 16:58:14 2025 +0100
feat(server,web): embed Web UI into server behind iggy-web feature (#2485)
Adds optional `iggy-web` feature to embed the Svelte dashboard at `/ui`
directly in the server binary using `rust-embed`.
- Added `iggy-web` feature with embedded static asset serving
- Refactored web app from SSR to SPA for static build support
- Added `build:static` script with `/ui` base path
- Client-side auth (cookies, JWT) instead of server-side
- Added `authorization` to CORS allowed headers
- Updated Rust & Web dependencies
---
Cargo.lock | 409 ++++++++++++---------
Cargo.toml | 2 +-
DEPENDENCIES.md | 81 ++--
bdd/rust/Cargo.toml | 2 +-
core/bench/dashboard/server/Cargo.toml | 2 +-
core/common/Cargo.toml | 2 +-
core/configs/server.toml | 2 +-
core/integration/Cargo.toml | 2 +-
core/server/Cargo.toml | 3 +
core/server/src/http/http_server.rs | 6 +
core/server/src/http/jwt/middleware.rs | 1 +
core/server/src/http/mod.rs | 2 +
core/server/src/http/web.rs | 83 +++++
web/package-lock.json | 111 +++---
web/package.json | 7 +-
web/src/hooks.client.ts | 11 +-
web/src/hooks.server.ts | 73 ----
web/src/lib/api/ApiSchema.ts | 6 +-
web/src/lib/api/clientApi.ts | 106 ++++++
web/src/lib/api/fetchRouteApi.ts | 35 +-
web/src/lib/api/handleFetchErrors.ts | 6 +-
web/src/lib/auth/authStore.svelte.ts | 125 +++++++
web/src/lib/components/Breadcrumbs.svelte | 2 +-
web/src/lib/components/Header.svelte | 21 +-
.../lib/components/Layouts/SettingsLayout.svelte | 10 +-
web/src/lib/components/Logo/Logo.svelte | 5 +-
web/src/lib/components/Navbar.svelte | 15 +-
web/src/lib/types/appRoutes.ts | 10 +-
web/src/routes/+layout.ts | 64 ++++
web/src/routes/api/proxy/+server.ts | 43 ---
.../auth/logout/+page.svelte} | 19 +-
.../auth/logout/{+page.server.ts => +page.ts} | 27 +-
web/src/routes/auth/sign-in/+page.server.ts | 77 ----
web/src/routes/auth/sign-in/+page.svelte | 100 +++--
.../{logout/+page.server.ts => sign-in/+page.ts} | 26 +-
.../dashboard/{+layout.server.ts => +layout.ts} | 45 ++-
.../overview/{+page.server.ts => +page.ts} | 13 +-
.../dashboard/settings/server/+page.server.ts | 39 --
.../settings/server/+page.ts} | 13 +-
.../settings/users/{+page.server.ts => +page.ts} | 31 +-
web/src/routes/dashboard/streams/+layout.server.ts | 37 --
web/src/routes/dashboard/streams/+layout.svelte | 5 +-
.../+page.server.ts => streams/+layout.ts} | 17 +-
.../streams/[streamId=i32]/+page.server.ts | 40 --
.../dashboard/streams/[streamId=i32]/+page.svelte | 3 +-
.../dashboard/streams/[streamId=i32]/+page.ts} | 15 +-
.../topics/[topicId=i32]/+page.server.ts | 40 --
.../topics/[topicId=i32]/+page.svelte | 6 +-
.../[streamId=i32]/topics/[topicId=i32]/+page.ts} | 17 +-
.../messages/{+page.server.ts => +page.ts} | 29 +-
web/svelte.config.js | 21 +-
51 files changed, 1048 insertions(+), 819 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 8e8525388..839063828 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -27,7 +27,7 @@ checksum =
"daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d"
dependencies = [
"actix-utils",
"actix-web",
- "derive_more 2.1.0",
+ "derive_more",
"futures-util",
"log",
"once_cell",
@@ -46,7 +46,7 @@ dependencies = [
"actix-web",
"bitflags 2.10.0",
"bytes",
- "derive_more 2.1.0",
+ "derive_more",
"futures-core",
"http-range",
"log",
@@ -72,7 +72,7 @@ dependencies = [
"brotli",
"bytes",
"bytestring",
- "derive_more 2.1.0",
+ "derive_more",
"encoding_rs",
"flate2",
"foldhash 0.1.5",
@@ -187,7 +187,7 @@ dependencies = [
"bytestring",
"cfg-if",
"cookie",
- "derive_more 2.1.0",
+ "derive_more",
"encoding_rs",
"foldhash 0.1.5",
"futures-core",
@@ -621,7 +621,7 @@ dependencies = [
"memchr",
"num",
"regex",
- "regex-syntax 0.8.8",
+ "regex-syntax",
]
[[package]]
@@ -630,6 +630,45 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063"
+[[package]]
+name = "asn1-rs"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 2.0.17",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
[[package]]
name = "assert_cmd"
version = "2.1.1"
@@ -687,9 +726,9 @@ dependencies = [
[[package]]
name = "async-compression"
-version = "0.4.34"
+version = "0.4.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
+checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
dependencies = [
"compression-codecs",
"compression-core",
@@ -992,9 +1031,9 @@ checksum =
"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
-version = "1.8.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
+checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
[[package]]
name = "bdd"
@@ -1050,7 +1089,7 @@ dependencies = [
"charming",
"colored",
"derive-new",
- "derive_more 2.1.0",
+ "derive_more",
"human-repr",
"rand 0.9.2",
"serde",
@@ -1464,11 +1503,12 @@ dependencies = [
[[package]]
name = "cargo-platform"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4"
+checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082"
dependencies = [
"serde",
+ "serde_core",
]
[[package]]
@@ -1492,7 +1532,7 @@ source =
"registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
dependencies = [
"camino",
- "cargo-platform 0.3.1",
+ "cargo-platform 0.3.2",
"semver",
"serde",
"serde_json",
@@ -1501,9 +1541,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.48"
+version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
+checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1619,7 +1659,7 @@ version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.111",
@@ -1640,9 +1680,9 @@ dependencies = [
[[package]]
name = "cmake"
-version = "0.1.54"
+version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586"
dependencies = [
"cc",
]
@@ -1889,9 +1929,9 @@ dependencies = [
[[package]]
name = "compression-codecs"
-version = "0.4.33"
+version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad"
+checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
dependencies = [
"bzip2",
"compression-core",
@@ -1930,15 +1970,15 @@ dependencies = [
[[package]]
name = "console"
-version = "0.15.11"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
+checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width 0.2.2",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2242,28 +2282,26 @@ dependencies = [
[[package]]
name = "cucumber"
-version = "0.21.1"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cd12917efc3a8b069a4975ef3cb2f2d835d42d04b3814d90838488f9dd9bf69"
+checksum = "18c09939b8de21501b829a3839fa8a01ef6cc226e6bc1f5f163f7104bd5e847d"
dependencies = [
"anyhow",
"clap",
"console",
"cucumber-codegen",
"cucumber-expressions",
- "derive_more 0.99.20",
- "drain_filter_polyfill",
+ "derive_more",
"either",
"futures",
"gherkin",
"globwalk",
"humantime",
"inventory",
- "itertools 0.13.0",
- "lazy-regex",
+ "itertools 0.14.0",
"linked-hash-map",
- "once_cell",
"pin-project",
+ "ref-cast",
"regex",
"sealed",
"smart-default",
@@ -2271,13 +2309,13 @@ dependencies = [
[[package]]
name = "cucumber-codegen"
-version = "0.21.1"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e19cd9e8e7cfd79fbf844eb6a7334117973c01f6bad35571262b00891e60f1c"
+checksum = "1f5afe541b5147a7b986816153ccfd502622bb37789420cfff412685f27c0a95"
dependencies = [
"cucumber-expressions",
"inflections",
- "itertools 0.13.0",
+ "itertools 0.14.0",
"proc-macro2",
"quote",
"regex",
@@ -2287,16 +2325,16 @@ dependencies = [
[[package]]
name = "cucumber-expressions"
-version = "0.3.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d794fed319eea24246fb5f57632f7ae38d61195817b7eb659455aa5bdd7c1810"
+checksum = "6401038de3af44fe74e6fccdb8a5b7db7ba418f480c8e9ad584c6f65c05a27a6"
dependencies = [
- "derive_more 0.99.20",
+ "derive_more",
"either",
- "nom",
+ "nom 8.0.0",
"nom_locate",
"regex",
- "regex-syntax 0.7.5",
+ "regex-syntax",
]
[[package]]
@@ -2376,9 +2414,9 @@ dependencies = [
[[package]]
name = "cyper-core"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e53f8cde9bdfe28b1bec664feb1825c237f1b3948c2dfdba019540be3f660b3f"
+checksum = "7f4b86aa741e422dab7f730aa1ec5ab6bc26569e577fe2b8fe0ebf6d779b2325"
dependencies = [
"compio",
"futures-util",
@@ -2484,9 +2522,9 @@ checksum =
"2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "dbus"
-version = "0.9.9"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9"
+checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4"
dependencies = [
"libc",
"libdbus-sys",
@@ -2521,6 +2559,20 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "der-parser"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
[[package]]
name = "deranged"
version = "0.5.5"
@@ -2584,17 +2636,6 @@ dependencies = [
"syn 2.0.111",
]
-[[package]]
-name = "derive_more"
-version = "0.99.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.111",
-]
-
[[package]]
name = "derive_more"
version = "2.1.0"
@@ -2711,9 +2752,9 @@ dependencies = [
[[package]]
name = "dlopen2_derive"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b"
+checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
dependencies = [
"proc-macro2",
"quote",
@@ -2761,12 +2802,6 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
-[[package]]
-name = "drain_filter_polyfill"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408"
-
[[package]]
name = "dtoa"
version = "1.0.10"
@@ -3154,9 +3189,9 @@ dependencies = [
[[package]]
name = "file-operation"
-version = "0.8.6"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e820507a8045dbe4861c925a91ca6989cce6c7c09b4c3d90c4941df84ffc0aeb"
+checksum = "34664772b984b86dbccb14ab56722778cca458b38f6f324db3331141ef441e05"
dependencies = [
"tokio",
]
@@ -3491,19 +3526,19 @@ dependencies = [
[[package]]
name = "gherkin"
-version = "0.14.0"
+version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20b79820c0df536d1f3a089a2fa958f61cb96ce9e0f3f8f507f5a31179567755"
+checksum = "70197ce7751bfe8bc828e3a855502d3a869a1e9416b58b10c4bde5cf8a0a3cb3"
dependencies = [
- "heck 0.4.1",
+ "heck",
"peg",
"quote",
"serde",
"serde_json",
"syn 2.0.111",
"textwrap",
- "thiserror 1.0.69",
- "typed-builder 0.15.2",
+ "thiserror 2.0.17",
+ "typed-builder 0.23.2",
]
[[package]]
@@ -3529,7 +3564,7 @@ dependencies = [
"bstr",
"log",
"regex-automata",
- "regex-syntax 0.8.8",
+ "regex-syntax",
]
[[package]]
@@ -3912,12 +3947,6 @@ dependencies = [
"stable_deref_trait",
]
-[[package]]
-name = "heck"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-
[[package]]
name = "heck"
version = "0.5.0"
@@ -4120,9 +4149,9 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.18"
+version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
+checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -4305,9 +4334,9 @@ checksum =
"7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
-version = "2.1.1"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -4319,9 +4348,9 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.1.1"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
name = "icu_provider"
@@ -4575,7 +4604,7 @@ dependencies = [
"compio-tls",
"compio-ws",
"crossbeam",
- "derive_more 2.1.0",
+ "derive_more",
"err_trail",
"fast-async-mutex",
"figment",
@@ -4905,7 +4934,7 @@ dependencies = [
"chrono",
"compio",
"ctor",
- "derive_more 2.1.0",
+ "derive_more",
"env_logger",
"futures",
"humantime",
@@ -5152,29 +5181,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
-[[package]]
-name = "lazy-regex"
-version = "3.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29"
-dependencies = [
- "lazy-regex-proc_macros",
- "once_cell",
- "regex",
-]
-
-[[package]]
-name = "lazy-regex-proc_macros"
-version = "3.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04"
-dependencies = [
- "proc-macro2",
- "quote",
- "regex",
- "syn 2.0.111",
-]
-
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -5280,9 +5286,9 @@ checksum =
"37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libdbus-sys"
-version = "0.2.6"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f"
+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
dependencies = [
"cc",
"pkg-config",
@@ -5383,9 +5389,9 @@ dependencies = [
[[package]]
name = "libz-rs-sys"
-version = "0.5.2"
+version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
+checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c"
dependencies = [
"zlib-rs",
]
@@ -5478,7 +5484,7 @@ dependencies = [
"lazy_static",
"proc-macro2",
"quote",
- "regex-syntax 0.8.8",
+ "regex-syntax",
"rustc_version",
"syn 2.0.111",
]
@@ -5647,9 +5653,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.1.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"log",
@@ -5738,15 +5744,24 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "nom_locate"
-version = "4.2.0"
+version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3"
+checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
dependencies = [
"bytecount",
"memchr",
- "nom",
+ "nom 8.0.0",
]
[[package]]
@@ -6037,6 +6052,15 @@ dependencies = [
"web-time",
]
+[[package]]
+name = "oid-registry"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
+dependencies = [
+ "asn1-rs",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -6381,7 +6405,7 @@ checksum =
"914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a"
dependencies = [
"parse-display-derive",
"regex",
- "regex-syntax 0.8.8",
+ "regex-syntax",
]
[[package]]
@@ -6393,7 +6417,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "regex-syntax 0.8.8",
+ "regex-syntax",
"structmeta",
"syn 2.0.111",
]
@@ -6784,7 +6808,7 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
- "toml_edit 0.23.7",
+ "toml_edit 0.23.9",
]
[[package]]
@@ -7165,14 +7189,15 @@ dependencies = [
[[package]]
name = "rcgen"
-version = "0.14.5"
+version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5"
+checksum = "3ec0a99f2de91c3cddc84b37e7db80e4d96b743e05607f647eb236fc0455907f"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
+ "x509-parser",
"yasna",
]
@@ -7225,7 +7250,7 @@ dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
- "regex-syntax 0.8.8",
+ "regex-syntax",
]
[[package]]
@@ -7236,7 +7261,7 @@ checksum =
"5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.8.8",
+ "regex-syntax",
]
[[package]]
@@ -7245,12 +7270,6 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
-[[package]]
-name = "regex-syntax"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
-
[[package]]
name = "regex-syntax"
version = "0.8.8"
@@ -7544,6 +7563,40 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rust-embed"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.111",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -7585,6 +7638,15 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom 7.1.3",
+]
+
[[package]]
name = "rustix"
version = "1.1.2"
@@ -7787,11 +7849,10 @@ checksum =
"1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "sealed"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d"
+checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [
- "heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.111",
@@ -8100,7 +8161,7 @@ dependencies = [
"cyper",
"cyper-axum",
"dashmap",
- "derive_more 2.1.0",
+ "derive_more",
"dotenvy",
"enum_dispatch",
"err_trail",
@@ -8115,6 +8176,7 @@ dependencies = [
"jsonwebtoken",
"lending-iterator",
"mimalloc",
+ "mime_guess",
"moka",
"nix",
"opentelemetry",
@@ -8128,6 +8190,7 @@ dependencies = [
"reqwest",
"ring",
"ringbuffer",
+ "rust-embed",
"rustls",
"rustls-pemfile",
"send_wrapper",
@@ -8212,9 +8275,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.7"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simd-json"
@@ -8301,7 +8364,7 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.111",
@@ -8433,7 +8496,7 @@ checksum =
"19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
- "heck 0.5.0",
+ "heck",
"hex",
"once_cell",
"proc-macro2",
@@ -8653,7 +8716,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"rustversion",
@@ -8666,7 +8729,7 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.111",
@@ -8722,9 +8785,9 @@ dependencies = [
[[package]]
name = "synthez"
-version = "0.3.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d"
+checksum = "6d8a928f38f1bc873f28e0d2ba8298ad65374a6ac2241dabd297271531a736cd"
dependencies = [
"syn 2.0.111",
"synthez-codegen",
@@ -8733,9 +8796,9 @@ dependencies = [
[[package]]
name = "synthez-codegen"
-version = "0.3.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746"
+checksum = "8fb83b8df4238e11746984dfb3819b155cd270de0e25847f45abad56b3671047"
dependencies = [
"syn 2.0.111",
"synthez-core",
@@ -8743,9 +8806,9 @@ dependencies = [
[[package]]
name = "synthez-core"
-version = "0.3.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bfa6ec52465e2425fd43ce5bbbe0f0b623964f7c63feb6b10980e816c654ea"
+checksum = "906fba967105d822e7c7ed60477b5e76116724d33de68a585681fb253fc30d5c"
dependencies = [
"proc-macro2",
"quote",
@@ -9210,9 +9273,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.23.7"
+version = "0.23.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
+checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
dependencies = [
"indexmap 2.12.1",
"toml_datetime 0.7.3",
@@ -9485,15 +9548,6 @@ dependencies = [
"rand 0.9.2",
]
-[[package]]
-name = "typed-builder"
-version = "0.15.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe83c85a85875e8c4cb9ce4a890f05b23d38cd0d47647db7895d3d2a79566d2"
-dependencies = [
- "typed-builder-macro 0.15.2",
-]
-
[[package]]
name = "typed-builder"
version = "0.19.1"
@@ -9513,14 +9567,12 @@ dependencies = [
]
[[package]]
-name = "typed-builder-macro"
-version = "0.15.2"
+name = "typed-builder"
+version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2"
+checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.111",
+ "typed-builder-macro 0.23.2",
]
[[package]]
@@ -9545,6 +9597,17 @@ dependencies = [
"syn 2.0.111",
]
+[[package]]
+name = "typed-builder-macro"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
[[package]]
name = "typenum"
version = "1.19.0"
@@ -10608,6 +10671,24 @@ dependencies = [
"tap",
]
+[[package]]
+name = "x509-parser"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom 7.1.3",
+ "oid-registry",
+ "ring",
+ "rusticata-macros",
+ "thiserror 2.0.17",
+ "time",
+]
+
[[package]]
name = "xattr"
version = "1.6.1"
@@ -10849,9 +10930,9 @@ dependencies = [
[[package]]
name = "zlib-rs"
-version = "0.5.2"
+version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
+checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235"
[[package]]
name = "zopfli"
diff --git a/Cargo.toml b/Cargo.toml
index 68679eada..87ecac08c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -161,7 +161,7 @@ reqwest = { version = "0.12.25", default-features = false,
features = [
reqwest-middleware = { version = "0.4.2", features = ["json"] }
reqwest-retry = "0.8.0"
reqwest-tracing = "0.5.8"
-rust-s3 = { version = "0.37.0", default-features = false, features = [
+rust-s3 = { version = "0.37.1", default-features = false, features = [
"tokio-rustls-tls",
"tags",
] }
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index c81f89d2d..51b3e5d83 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -50,11 +50,14 @@ arrow-schema: 55.2.0, "Apache-2.0",
arrow-select: 55.2.0, "Apache-2.0",
arrow-string: 55.2.0, "Apache-2.0",
as-any: 0.3.2, "Apache-2.0 OR MIT",
+asn1-rs: 0.7.1, "Apache-2.0 OR MIT",
+asn1-rs-derive: 0.6.0, "Apache-2.0 OR MIT",
+asn1-rs-impl: 0.2.0, "Apache-2.0 OR MIT",
assert_cmd: 2.1.1, "Apache-2.0 OR MIT",
astral-tokio-tar: 0.5.6, "Apache-2.0 OR MIT",
async-broadcast: 0.7.2, "Apache-2.0 OR MIT",
async-channel: 2.5.0, "Apache-2.0 OR MIT",
-async-compression: 0.4.34, "Apache-2.0 OR MIT",
+async-compression: 0.4.36, "Apache-2.0 OR MIT",
async-dropper: 0.3.1, "MIT",
async-dropper-derive: 0.3.1, "MIT",
async-dropper-simple: 0.2.6, "MIT",
@@ -80,7 +83,7 @@ backon: 1.6.0, "Apache-2.0",
base16ct: 0.2.0, "Apache-2.0 OR MIT",
base64: 0.21.7, "Apache-2.0 OR MIT",
base64: 0.22.1, "Apache-2.0 OR MIT",
-base64ct: 1.8.0, "Apache-2.0 OR MIT",
+base64ct: 1.8.1, "Apache-2.0 OR MIT",
bdd: 0.0.1, "Apache-2.0",
beef: 0.5.2, "Apache-2.0 OR MIT",
bench-dashboard-frontend: 0.4.1, "Apache-2.0",
@@ -123,10 +126,10 @@ bytestring: 1.5.0, "Apache-2.0 OR MIT",
bzip2: 0.6.1, "Apache-2.0 OR MIT",
camino: 1.2.1, "Apache-2.0 OR MIT",
cargo-platform: 0.1.9, "Apache-2.0 OR MIT",
-cargo-platform: 0.3.1, "Apache-2.0 OR MIT",
+cargo-platform: 0.3.2, "Apache-2.0 OR MIT",
cargo_metadata: 0.19.2, "MIT",
cargo_metadata: 0.23.1, "MIT",
-cc: 1.2.48, "Apache-2.0 OR MIT",
+cc: 1.2.49, "Apache-2.0 OR MIT",
cesu8: 1.1.0, "Apache-2.0 OR MIT",
cfg-if: 1.0.4, "Apache-2.0 OR MIT",
cfg_aliases: 0.2.1, "MIT",
@@ -140,7 +143,7 @@ clap_complete: 4.5.61, "Apache-2.0 OR MIT",
clap_derive: 4.5.49, "Apache-2.0 OR MIT",
clap_lex: 0.7.6, "Apache-2.0 OR MIT",
clock: 0.1.0, "N/A",
-cmake: 0.1.54, "Apache-2.0 OR MIT",
+cmake: 0.1.56, "Apache-2.0 OR MIT",
cobs: 0.3.0, "Apache-2.0 OR MIT",
colorchoice: 1.0.4, "Apache-2.0 OR MIT",
colored: 3.0.0, "MPL-2.0",
@@ -158,11 +161,11 @@ compio-quic: 0.6.0, "MIT",
compio-runtime: 0.10.1, "MIT",
compio-tls: 0.8.0, "MIT",
compio-ws: 0.2.0, "MIT",
-compression-codecs: 0.4.33, "Apache-2.0 OR MIT",
+compression-codecs: 0.4.35, "Apache-2.0 OR MIT",
compression-core: 0.4.31, "Apache-2.0 OR MIT",
concurrent-queue: 2.5.0, "Apache-2.0 OR MIT",
consensus: 0.1.0, "Apache-2.0",
-console: 0.15.11, "MIT",
+console: 0.16.1, "MIT",
console_error_panic_hook: 0.1.7, "Apache-2.0 OR MIT",
const-oid: 0.9.6, "Apache-2.0 OR MIT",
const-random: 0.1.18, "Apache-2.0 OR MIT",
@@ -196,14 +199,14 @@ ctor: 0.6.3, "Apache-2.0 OR MIT",
ctor-proc-macro: 0.0.7, "Apache-2.0 OR MIT",
ctr: 0.9.2, "Apache-2.0 OR MIT",
ctrlc: 3.5.1, "Apache-2.0 OR MIT",
-cucumber: 0.21.1, "Apache-2.0 OR MIT",
-cucumber-codegen: 0.21.1, "Apache-2.0 OR MIT",
-cucumber-expressions: 0.3.0, "Apache-2.0 OR MIT",
+cucumber: 0.22.0, "Apache-2.0 OR MIT",
+cucumber-codegen: 0.22.0, "Apache-2.0 OR MIT",
+cucumber-expressions: 0.5.0, "Apache-2.0 OR MIT",
curve25519-dalek: 4.1.3, "BSD-3-Clause",
curve25519-dalek-derive: 0.1.1, "Apache-2.0 OR MIT",
cyper: 0.7.1, "MIT",
cyper-axum: 0.7.1, "MIT",
-cyper-core: 0.7.0, "MIT",
+cyper-core: 0.7.1, "MIT",
darling: 0.20.11, "MIT",
darling: 0.21.3, "MIT",
darling_core: 0.20.11, "MIT",
@@ -213,17 +216,17 @@ darling_macro: 0.21.3, "MIT",
dary_heap: 0.3.8, "Apache-2.0 OR MIT",
dashmap: 6.1.0, "MIT",
data-encoding: 2.9.0, "MIT",
-dbus: 0.9.9, "Apache-2.0 OR MIT",
+dbus: 0.9.10, "Apache-2.0 OR MIT",
dbus-secret-service: 4.1.0, "Apache-2.0 OR MIT",
deflate64: 0.1.10, "MIT",
der: 0.7.10, "Apache-2.0 OR MIT",
+der-parser: 10.0.0, "Apache-2.0 OR MIT",
deranged: 0.5.5, "Apache-2.0 OR MIT",
derive-new: 0.7.0, "MIT",
derive_arbitrary: 1.4.2, "Apache-2.0 OR MIT",
derive_builder: 0.20.2, "Apache-2.0 OR MIT",
derive_builder_core: 0.20.2, "Apache-2.0 OR MIT",
derive_builder_macro: 0.20.2, "Apache-2.0 OR MIT",
-derive_more: 0.99.20, "MIT",
derive_more: 2.1.0, "MIT",
derive_more-impl: 2.1.0, "MIT",
difflib: 0.4.0, "MIT",
@@ -235,13 +238,12 @@ dispatch2: 0.3.0, "Apache-2.0 OR MIT OR Zlib",
displaydoc: 0.2.5, "Apache-2.0 OR MIT",
dissimilar: 1.0.10, "Apache-2.0",
dlopen2: 0.8.2, "MIT",
-dlopen2_derive: 0.4.2, "MIT",
+dlopen2_derive: 0.4.3, "MIT",
dlv-list: 0.5.2, "Apache-2.0 OR MIT",
docker_credential: 1.3.2, "Apache-2.0 OR MIT",
document-features: 0.2.12, "Apache-2.0 OR MIT",
dotenvy: 0.15.7, "MIT",
downcast: 0.11.0, "MIT",
-drain_filter_polyfill: 0.1.3, "Apache-2.0 OR MIT",
dtoa: 1.0.10, "Apache-2.0 OR MIT",
dtor: 0.1.1, "Apache-2.0 OR MIT",
dtor-proc-macro: 0.0.6, "Apache-2.0 OR MIT",
@@ -281,7 +283,7 @@ ff: 0.13.1, "Apache-2.0 OR MIT",
fiat-crypto: 0.2.9, "Apache-2.0 OR BSD-1-Clause OR MIT",
figlet-rs: 0.1.5, "Apache-2.0",
figment: 0.10.19, "Apache-2.0 OR MIT",
-file-operation: 0.8.6, "MIT",
+file-operation: 0.8.8, "MIT",
filetime: 0.2.26, "Apache-2.0 OR MIT",
find-msvc-tools: 0.1.5, "Apache-2.0 OR MIT",
flatbuffers: 25.9.23, "Apache-2.0",
@@ -317,7 +319,7 @@ generic-array: 0.14.7, "MIT",
getrandom: 0.2.16, "Apache-2.0 OR MIT",
getrandom: 0.3.4, "Apache-2.0 OR MIT",
ghash: 0.5.1, "Apache-2.0 OR MIT",
-gherkin: 0.14.0, "Apache-2.0 OR MIT",
+gherkin: 0.15.0, "Apache-2.0 OR MIT",
git2: 0.20.3, "Apache-2.0 OR MIT",
globset: 0.4.18, "MIT OR Unlicense",
globwalk: 0.9.1, "MIT",
@@ -349,7 +351,6 @@ hashbrown: 0.15.5, "Apache-2.0 OR MIT",
hashbrown: 0.16.1, "Apache-2.0 OR MIT",
hashlink: 0.10.0, "Apache-2.0 OR MIT",
heapless: 0.7.17, "Apache-2.0 OR MIT",
-heck: 0.4.1, "Apache-2.0 OR MIT",
heck: 0.5.0, "Apache-2.0 OR MIT",
hermit-abi: 0.5.2, "Apache-2.0 OR MIT",
hex: 0.4.3, "Apache-2.0 OR MIT",
@@ -370,7 +371,7 @@ hyper: 1.8.1, "MIT",
hyper-named-pipe: 0.1.0, "Apache-2.0",
hyper-rustls: 0.27.7, "Apache-2.0 OR ISC OR MIT",
hyper-timeout: 0.5.2, "Apache-2.0 OR MIT",
-hyper-util: 0.1.18, "MIT",
+hyper-util: 0.1.19, "MIT",
hyperlocal: 0.9.1, "MIT",
iana-time-zone: 0.1.64, "Apache-2.0 OR MIT",
iana-time-zone-haiku: 0.1.2, "Apache-2.0 OR MIT",
@@ -380,8 +381,8 @@ icu_collections: 2.1.1, "Unicode-3.0",
icu_locale_core: 2.1.1, "Unicode-3.0",
icu_normalizer: 2.1.1, "Unicode-3.0",
icu_normalizer_data: 2.1.1, "Unicode-3.0",
-icu_properties: 2.1.1, "Unicode-3.0",
-icu_properties_data: 2.1.1, "Unicode-3.0",
+icu_properties: 2.1.2, "Unicode-3.0",
+icu_properties_data: 2.1.2, "Unicode-3.0",
icu_provider: 2.1.1, "Unicode-3.0",
ident_case: 1.0.1, "Apache-2.0 OR MIT",
idna: 1.1.0, "Apache-2.0 OR MIT",
@@ -439,8 +440,6 @@ keyring: 3.6.3, "Apache-2.0 OR MIT",
kqueue: 1.1.1, "MIT",
kqueue-sys: 1.0.4, "MIT",
language-tags: 0.3.2, "Apache-2.0 OR MIT",
-lazy-regex: 3.4.2, "MIT",
-lazy-regex-proc_macros: 3.4.2, "MIT",
lazy_static: 1.5.0, "Apache-2.0 OR MIT",
lending-iterator: 0.1.7, "Apache-2.0 OR MIT OR Zlib",
lending-iterator-proc_macros: 0.1.7, "Apache-2.0 OR MIT OR Zlib",
@@ -452,7 +451,7 @@ lexical-write-float: 1.0.6, "Apache-2.0 OR MIT",
lexical-write-integer: 1.0.6, "Apache-2.0 OR MIT",
libbz2-rs-sys: 0.2.2, "bzip2-1.0.6",
libc: 0.2.178, "Apache-2.0 OR MIT",
-libdbus-sys: 0.2.6, "Apache-2.0 OR MIT",
+libdbus-sys: 0.2.7, "Apache-2.0 OR MIT",
libflate: 2.2.1, "MIT",
libflate_lz77: 2.2.0, "MIT",
libgit2-sys: 0.18.3+1.9.2, "Apache-2.0 OR MIT",
@@ -462,7 +461,7 @@ libm: 0.2.15, "MIT",
libmimalloc-sys: 0.1.44, "MIT",
libredox: 0.1.10, "MIT",
libsqlite3-sys: 0.30.1, "MIT",
-libz-rs-sys: 0.5.2, "Zlib",
+libz-rs-sys: 0.5.4, "Zlib",
libz-sys: 1.1.23, "Apache-2.0 OR MIT",
linked-hash-map: 0.5.6, "Apache-2.0 OR MIT",
linux-raw-sys: 0.11.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
@@ -493,7 +492,7 @@ mime: 0.3.17, "Apache-2.0 OR MIT",
mime_guess: 2.0.5, "MIT",
minimal-lexical: 0.2.1, "Apache-2.0 OR MIT",
miniz_oxide: 0.8.9, "Apache-2.0 OR MIT OR Zlib",
-mio: 1.1.0, "MIT",
+mio: 1.1.1, "MIT",
mockall: 0.14.0, "Apache-2.0 OR MIT",
mockall_derive: 0.14.0, "Apache-2.0 OR MIT",
moka: 0.12.11, "(Apache-2.0 OR MIT) AND Apache-2.0",
@@ -501,7 +500,8 @@ murmur3: 0.5.2, "Apache-2.0 OR MIT",
never-say-never: 6.6.666, "Apache-2.0 OR MIT OR Zlib",
nix: 0.30.1, "MIT",
nom: 7.1.3, "MIT",
-nom_locate: 4.2.0, "MIT",
+nom: 8.0.0, "MIT",
+nom_locate: 5.0.0, "MIT",
nonzero_ext: 0.3.0, "Apache-2.0",
nonzero_lit: 0.1.2, "Apache-2.0 OR CC0-1.0 OR MIT",
normalize-line-endings: 0.3.0, "Apache-2.0",
@@ -529,6 +529,7 @@ objc2-core-foundation: 0.3.2, "Apache-2.0 OR MIT OR Zlib",
objc2-encode: 4.1.0, "MIT",
objc2-io-kit: 0.3.2, "Apache-2.0 OR MIT OR Zlib",
octocrab: 0.48.1, "Apache-2.0 OR MIT",
+oid-registry: 0.8.1, "Apache-2.0 OR MIT",
once_cell: 1.21.3, "Apache-2.0 OR MIT",
once_cell_polyfill: 1.70.2, "Apache-2.0 OR MIT",
opaque-debug: 0.3.1, "Apache-2.0 OR MIT",
@@ -635,7 +636,7 @@ rand_xoshiro: 0.7.0, "Apache-2.0 OR MIT",
raw-cpuid: 11.6.0, "MIT",
rayon: 1.11.0, "Apache-2.0 OR MIT",
rayon-core: 1.13.0, "Apache-2.0 OR MIT",
-rcgen: 0.14.5, "Apache-2.0 OR MIT",
+rcgen: 0.14.6, "Apache-2.0 OR MIT",
redox_syscall: 0.5.18, "MIT",
redox_users: 0.5.2, "MIT",
ref-cast: 1.0.25, "Apache-2.0 OR MIT",
@@ -643,7 +644,6 @@ ref-cast-impl: 1.0.25, "Apache-2.0 OR MIT",
regex: 1.12.2, "Apache-2.0 OR MIT",
regex-automata: 0.4.13, "Apache-2.0 OR MIT",
regex-lite: 0.1.8, "Apache-2.0 OR MIT",
-regex-syntax: 0.7.5, "Apache-2.0 OR MIT",
regex-syntax: 0.8.8, "Apache-2.0 OR MIT",
rend: 0.4.2, "MIT",
reqsign: 0.16.5, "Apache-2.0",
@@ -663,10 +663,14 @@ rmcp-macros: 0.11.0, "MIT",
roaring: 0.10.12, "Apache-2.0 OR MIT",
route-recognizer: 0.3.1, "MIT",
rsa: 0.9.9, "Apache-2.0 OR MIT",
+rust-embed: 8.9.0, "MIT",
+rust-embed-impl: 8.9.0, "MIT",
+rust-embed-utils: 8.9.0, "MIT",
rust-ini: 0.21.3, "MIT",
rust_decimal: 1.39.0, "MIT",
rustc-hash: 2.1.1, "Apache-2.0 OR MIT",
rustc_version: 0.4.1, "Apache-2.0 OR MIT",
+rusticata-macros: 4.1.0, "Apache-2.0 OR MIT",
rustix: 1.1.2, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
rustls: 0.23.35, "Apache-2.0 OR ISC OR MIT",
rustls-native-certs: 0.8.2, "Apache-2.0 OR ISC OR MIT",
@@ -687,7 +691,7 @@ scoped-tls: 1.0.1, "Apache-2.0 OR MIT",
scopeguard: 1.2.0, "Apache-2.0 OR MIT",
sdd: 3.0.10, "Apache-2.0",
seahash: 4.1.0, "MIT",
-sealed: 0.5.0, "Apache-2.0 OR MIT",
+sealed: 0.6.0, "Apache-2.0 OR MIT",
sec1: 0.7.3, "Apache-2.0 OR MIT",
secrecy: 0.10.3, "Apache-2.0 OR MIT",
security-framework: 3.5.1, "Apache-2.0 OR MIT",
@@ -720,7 +724,7 @@ sharded-slab: 0.1.7, "MIT",
shlex: 1.3.0, "Apache-2.0 OR MIT",
signal-hook-registry: 1.4.7, "Apache-2.0 OR MIT",
signature: 2.2.0, "Apache-2.0 OR MIT",
-simd-adler32: 0.3.7, "MIT",
+simd-adler32: 0.3.8, "MIT",
simd-json: 0.17.0, "Apache-2.0 OR MIT",
simdutf8: 0.1.5, "Apache-2.0 OR MIT",
simple_asn1: 0.6.3, "ISC",
@@ -760,9 +764,9 @@ syn: 1.0.109, "Apache-2.0 OR MIT",
syn: 2.0.111, "Apache-2.0 OR MIT",
sync_wrapper: 1.0.2, "Apache-2.0",
synstructure: 0.13.2, "MIT",
-synthez: 0.3.1, "BlueOak-1.0.0",
-synthez-codegen: 0.3.1, "BlueOak-1.0.0",
-synthez-core: 0.3.1, "BlueOak-1.0.0",
+synthez: 0.4.0, "BlueOak-1.0.0",
+synthez-codegen: 0.4.0, "BlueOak-1.0.0",
+synthez-core: 0.4.0, "BlueOak-1.0.0",
sysinfo: 0.34.2, "MIT",
sysinfo: 0.37.2, "MIT",
tagptr: 0.2.0, "Apache-2.0 OR MIT",
@@ -803,7 +807,7 @@ toml_datetime: 0.6.11, "Apache-2.0 OR MIT",
toml_datetime: 0.7.3, "Apache-2.0 OR MIT",
toml_edit: 0.19.15, "Apache-2.0 OR MIT",
toml_edit: 0.22.27, "Apache-2.0 OR MIT",
-toml_edit: 0.23.7, "Apache-2.0 OR MIT",
+toml_edit: 0.23.9, "Apache-2.0 OR MIT",
toml_parser: 1.0.4, "Apache-2.0 OR MIT",
toml_write: 0.1.2, "Apache-2.0 OR MIT",
toml_writer: 1.0.4, "Apache-2.0 OR MIT",
@@ -825,12 +829,12 @@ trait-variant: 0.1.2, "Apache-2.0 OR MIT",
try-lock: 0.2.5, "MIT",
tungstenite: 0.28.0, "Apache-2.0 OR MIT",
twox-hash: 2.1.2, "MIT",
-typed-builder: 0.15.2, "Apache-2.0 OR MIT",
typed-builder: 0.19.1, "Apache-2.0 OR MIT",
typed-builder: 0.20.1, "Apache-2.0 OR MIT",
-typed-builder-macro: 0.15.2, "Apache-2.0 OR MIT",
+typed-builder: 0.23.2, "Apache-2.0 OR MIT",
typed-builder-macro: 0.19.1, "Apache-2.0 OR MIT",
typed-builder-macro: 0.20.1, "Apache-2.0 OR MIT",
+typed-builder-macro: 0.23.2, "Apache-2.0 OR MIT",
typenum: 1.19.0, "Apache-2.0 OR MIT",
ucd-trie: 0.1.7, "Apache-2.0 OR MIT",
ulid: 1.2.1, "MIT",
@@ -957,6 +961,7 @@ winnow: 0.7.14, "MIT",
wit-bindgen: 0.46.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
writeable: 0.6.2, "Unicode-3.0",
wyz: 0.5.1, "MIT",
+x509-parser: 0.18.0, "Apache-2.0 OR MIT",
xattr: 1.6.1, "Apache-2.0 OR MIT",
yansi: 1.0.1, "Apache-2.0 OR MIT",
yasna: 0.5.2, "Apache-2.0 OR MIT",
@@ -976,7 +981,7 @@ zerotrie: 0.2.3, "Unicode-3.0",
zerovec: 0.11.5, "Unicode-3.0",
zerovec-derive: 0.11.2, "Unicode-3.0",
zip: 6.0.0, "MIT",
-zlib-rs: 0.5.2, "Zlib",
+zlib-rs: 0.5.4, "Zlib",
zopfli: 0.8.3, "Apache-2.0",
zstd: 0.13.3, "MIT",
zstd-safe: 7.2.4, "Apache-2.0 OR MIT",
diff --git a/bdd/rust/Cargo.toml b/bdd/rust/Cargo.toml
index 4182a31d9..5f8fe779b 100644
--- a/bdd/rust/Cargo.toml
+++ b/bdd/rust/Cargo.toml
@@ -27,7 +27,7 @@ bdd = []
[dev-dependencies]
bytes = { workspace = true }
-cucumber = "0.21"
+cucumber = "0.22"
iggy = { workspace = true }
integration = { workspace = true }
tokio = { workspace = true }
diff --git a/core/bench/dashboard/server/Cargo.toml
b/core/bench/dashboard/server/Cargo.toml
index 217b8de00..7552ba3b1 100644
--- a/core/bench/dashboard/server/Cargo.toml
+++ b/core/bench/dashboard/server/Cargo.toml
@@ -30,7 +30,7 @@ bench-report = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
clap = { workspace = true }
dashmap = { workspace = true }
-file-operation = "0.8.6"
+file-operation = "0.8.8"
notify = "8.2.0"
octocrab = "0.48.1"
serde = { workspace = true, features = ["derive"] }
diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml
index 4da4e40b1..fbfe9b3a5 100644
--- a/core/common/Cargo.toml
+++ b/core/common/Cargo.toml
@@ -55,7 +55,7 @@ figment = { workspace = true }
human-repr = { workspace = true }
humantime = { workspace = true }
once_cell = { workspace = true }
-rcgen = "0.14.5"
+rcgen = "0.14.6"
rustls = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/core/configs/server.toml b/core/configs/server.toml
index 581763acf..b1a25b61d 100644
--- a/core/configs/server.toml
+++ b/core/configs/server.toml
@@ -53,7 +53,7 @@ allowed_origins = ["*"]
# Lists allowed headers that can be used in CORS requests.
# For example, ["content-type"] permits only the content-type header.
-allowed_headers = ["content-type"]
+allowed_headers = ["content-type", "authorization"]
# Headers that browsers are allowed to access in CORS responses.
# An empty array means no additional headers are exposed to browsers.
diff --git a/core/integration/Cargo.toml b/core/integration/Cargo.toml
index f7e5a1067..6d97946f4 100644
--- a/core/integration/Cargo.toml
+++ b/core/integration/Cargo.toml
@@ -47,7 +47,7 @@ libc = "0.2.178"
log = { workspace = true }
predicates = { workspace = true }
rand = { workspace = true }
-rcgen = "0.14.5"
+rcgen = "0.14.6"
reqwest = { workspace = true }
rmcp = { version = "0.11.0", features = [
"client",
diff --git a/core/server/Cargo.toml b/core/server/Cargo.toml
index 3688a4fb0..bb375bdf5 100644
--- a/core/server/Cargo.toml
+++ b/core/server/Cargo.toml
@@ -35,6 +35,7 @@ path = "src/main.rs"
default = ["mimalloc"]
disable-mimalloc = []
mimalloc = ["dep:mimalloc"]
+iggy-web = ["dep:rust-embed", "dep:mime_guess"]
[dependencies]
ahash = { workspace = true }
@@ -74,6 +75,7 @@ iggy_common = { workspace = true }
jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] }
lending-iterator = "0.1.7"
mimalloc = { workspace = true, optional = true }
+mime_guess = { version = "2.0", optional = true }
moka = { version = "0.12.11", features = ["future"] }
nix = { version = "0.30", features = ["fs", "resource"] }
opentelemetry = { version = "0.31.0", features = ["trace", "logs"] }
@@ -100,6 +102,7 @@ rand = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls-no-provider"] }
ring = "0.17.14"
ringbuffer = "0.16.0"
+rust-embed = { version = "8.9.0", optional = true }
rustls = { workspace = true }
rustls-pemfile = "2.2.0"
send_wrapper = "0.6.0"
diff --git a/core/server/src/http/http_server.rs
b/core/server/src/http/http_server.rs
index 21e7cbcdb..e83c2191d 100644
--- a/core/server/src/http/http_server.rs
+++ b/core/server/src/http/http_server.rs
@@ -117,6 +117,12 @@ pub async fn start_http_server(
app = app.layer(middleware::from_fn(request_diagnostics));
+ #[cfg(feature = "iggy-web")]
+ {
+ app = app.merge(web::router());
+ info!("Web UI enabled at /ui");
+ }
+
if !config.tls.enabled {
let listener = TcpListener::bind(config.address.clone())
.await
diff --git a/core/server/src/http/jwt/middleware.rs
b/core/server/src/http/jwt/middleware.rs
index b2ed614d5..a47d83ad6 100644
--- a/core/server/src/http/jwt/middleware.rs
+++ b/core/server/src/http/jwt/middleware.rs
@@ -41,6 +41,7 @@ const PUBLIC_PATHS: &[&str] = &[
"/users/login",
"/users/refresh-token",
"/personal-access-tokens/login",
+ "/ui",
];
pub async fn jwt_auth(
diff --git a/core/server/src/http/mod.rs b/core/server/src/http/mod.rs
index 4cf663d36..1d6046ebb 100644
--- a/core/server/src/http/mod.rs
+++ b/core/server/src/http/mod.rs
@@ -34,5 +34,7 @@ pub mod streams;
pub mod system;
pub mod topics;
pub mod users;
+#[cfg(feature = "iggy-web")]
+pub mod web;
pub const COMPONENT: &str = "HTTP";
diff --git a/core/server/src/http/web.rs b/core/server/src/http/web.rs
new file mode 100644
index 000000000..76e740e70
--- /dev/null
+++ b/core/server/src/http/web.rs
@@ -0,0 +1,83 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use axum::Router;
+use axum::body::Body;
+use axum::extract::Path;
+use axum::http::{Response, StatusCode, header};
+use axum::response::IntoResponse;
+use axum::routing::get;
+use rust_embed::{Embed, EmbeddedFile};
+
+#[derive(Embed)]
+#[folder = "../../web/build/static/"]
+#[allow_missing = true]
+struct WebAssets;
+
+impl WebAssets {
+ fn get_file(path: &str) -> Option<EmbeddedFile> {
+ <Self as Embed>::get(path)
+ }
+}
+
+pub fn router() -> Router {
+ Router::new()
+ .route("/ui/{*wildcard}", get(serve_web_asset))
+ .route("/ui", get(serve_index))
+ .route("/ui/", get(serve_index))
+}
+
+async fn serve_index() -> impl IntoResponse {
+ serve_file("index.html")
+}
+
+async fn serve_web_asset(Path(wildcard): Path<String>) -> impl IntoResponse {
+ if let Some(response) = try_serve_file(&wildcard) {
+ return response;
+ }
+
+ if !wildcard.contains('.') {
+ return serve_file("index.html");
+ }
+
+ Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .body(Body::from("Not Found"))
+ .unwrap()
+}
+
+fn try_serve_file(path: &str) -> Option<Response<Body>> {
+ let asset = WebAssets::get_file(path)?;
+ let mime = mime_guess::from_path(path).first_or_octet_stream();
+
+ Some(
+ Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, mime.as_ref())
+ .body(Body::from(asset.data.into_owned()))
+ .unwrap(),
+ )
+}
+
+fn serve_file(path: &str) -> Response<Body> {
+ try_serve_file(path).unwrap_or_else(|| {
+ Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .body(Body::from("Not Found"))
+ .unwrap()
+ })
+}
diff --git a/web/package-lock.json b/web/package-lock.json
index d8f1a6e49..d7876bcb5 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -25,6 +25,7 @@
"@eslint/compat": "^2.0.0",
"@eslint/js": "^9.39.1",
"@sveltejs/adapter-node": "^5.4.0",
+ "@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.48.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/d3-interpolate": "^3.0.4",
@@ -42,7 +43,7 @@
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.43.6",
"svelte-check": "^4.3.4",
- "sveltekit-superforms": "^2.28.1",
+ "sveltekit-superforms": "^2.14.0",
"tailwindcss": "^4.1.17",
"ts-toolbelt": "^9.6.0",
"tslib": "^2.8.1",
@@ -65,20 +66,20 @@
}
},
"node_modules/@ark/schema": {
- "version": "0.54.0",
- "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.54.0.tgz",
- "integrity":
"sha512-QloFou+ODfb5qXgPxX1EbJyRqZEwoElzvJ6VuuFVvWJQGoigBEUW3L0HejXG/B9v8K3VvDikuULp5ELSwZn8hg==",
+ "version": "0.56.0",
+ "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.56.0.tgz",
+ "integrity":
"sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@ark/util": "0.54.0"
+ "@ark/util": "0.56.0"
}
},
"node_modules/@ark/util": {
- "version": "0.54.0",
- "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.54.0.tgz",
- "integrity":
"sha512-ugTfpEDGA6d2uU2/3M7uRiuPNYckQMhg2wfNMw8zZHXjPhuVCbXWZzIcBojuXuCN8j4MrNWNqQ9z7f8W/JiE8w==",
+ "version": "0.56.0",
+ "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.56.0.tgz",
+ "integrity":
"sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==",
"dev": true,
"license": "MIT",
"optional": true
@@ -802,22 +803,6 @@
"esbuild-runner": ">= 2.2.2"
}
},
- "node_modules/@gcornut/valibot-json-schema/node_modules/valibot": {
- "version": "0.42.1",
- "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz",
- "integrity":
"sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peerDependencies": {
- "typescript": ">=5"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -1461,6 +1446,16 @@
"@sveltejs/kit": "^2.4.0"
}
},
+ "node_modules/@sveltejs/adapter-static": {
+ "version": "3.0.10",
+ "resolved":
"https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz",
+ "integrity":
"sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@sveltejs/kit": "^2.0.0"
+ }
+ },
"node_modules/@sveltejs/kit": {
"version": "2.49.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz",
@@ -1867,9 +1862,9 @@
"license": "MIT"
},
"node_modules/@types/validator": {
- "version": "13.15.9",
- "resolved":
"https://registry.npmjs.org/@types/validator/-/validator-13.15.9.tgz",
- "integrity":
"sha512-9ENIuq9PUX45M1QRtfJDprgfErED4fBiMPmjlPci4W9WiBelVtHYCjF3xkQNcSnmUeuruLS1kH6hSl5M1vz4Sw==",
+ "version": "13.15.10",
+ "resolved":
"https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
+ "integrity":
"sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
"dev": true,
"license": "MIT",
"optional": true
@@ -2271,35 +2266,27 @@
}
},
"node_modules/arkregex": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.2.tgz",
- "integrity":
"sha512-ttjDUICBVoXD/m8bf7eOjx8XMR6yIT2FmmW9vsN0FCcFOygEZvvIX8zK98tTdXkzi0LkRi5CmadB44jFEIyDNA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.5.tgz",
+ "integrity":
"sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@ark/util": "0.53.0"
+ "@ark/util": "0.56.0"
}
},
- "node_modules/arkregex/node_modules/@ark/util": {
- "version": "0.53.0",
- "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.53.0.tgz",
- "integrity":
"sha512-TGn4gLlA6dJcQiqrtCtd88JhGb2XBHo6qIejsDre+nxpGuUVW4G3YZGVrwjNBTO0EyR+ykzIo4joHJzOj+/cpA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/arktype": {
- "version": "2.1.26",
- "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.26.tgz",
- "integrity":
"sha512-zDwukKV6uTElKCAbIoQ9OU6shXE5ALjvZAqHErOSv6l0iLKlubELZ7AcevYLaWFYr5rmIN4Uv9+dIzInktSO1A==",
+ "version": "2.1.29",
+ "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.29.tgz",
+ "integrity":
"sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@ark/schema": "0.54.0",
- "@ark/util": "0.54.0",
- "arkregex": "0.0.2"
+ "@ark/schema": "0.56.0",
+ "@ark/util": "0.56.0",
+ "arkregex": "0.0.5"
}
},
"node_modules/autoprefixer": {
@@ -2520,16 +2507,16 @@
}
},
"node_modules/class-validator": {
- "version": "0.14.2",
- "resolved":
"https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
- "integrity":
"sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
+ "version": "0.14.3",
+ "resolved":
"https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
+ "integrity":
"sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@types/validator": "^13.11.8",
+ "@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1",
- "validator": "^13.9.0"
+ "validator": "^13.15.20"
}
},
"node_modules/clsx": {
@@ -2712,9 +2699,9 @@
"optional": true
},
"node_modules/effect": {
- "version": "3.19.3",
- "resolved": "https://registry.npmjs.org/effect/-/effect-3.19.3.tgz",
- "integrity":
"sha512-LodiPXiyUJWQ5LoMhUGbu0acD2ff5A5teJtUlLKDPVfoeWEBcZLlzK8BeVXpVa0f30UsdHouVCf0C/E0TxYMrA==",
+ "version": "3.19.12",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.19.12.tgz",
+ "integrity":
"sha512-7F9RGTrCTC3D7nh9Zw+3VlJWwZgo5k33KA+476BAaD0rKIXKZsY/jQ+ipyhR/Avo239Fi6GqAVFs1mqM1IJ7yg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -3591,9 +3578,9 @@
}
},
"node_modules/libphonenumber-js": {
- "version": "1.12.26",
- "resolved":
"https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.26.tgz",
- "integrity":
"sha512-MagMOuqEXB2Pa90cWE+BoCmcKJx+de5uBIicaUkQ+uiEslZ0OBMNOkSZT/36syXNHu68UeayTxPm3DYM2IHoLQ==",
+ "version": "1.12.31",
+ "resolved":
"https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.31.tgz",
+ "integrity":
"sha512-Z3IhgVgrqO1S5xPYM3K5XwbkDasU67/Vys4heW+lfSBALcUZjeIIzI8zCLifY+OCzSq+fpDdywMDa7z+4srJPQ==",
"dev": true,
"license": "MIT",
"optional": true
@@ -5026,9 +5013,9 @@
}
},
"node_modules/typebox": {
- "version": "1.0.53",
- "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.0.53.tgz",
- "integrity":
"sha512-fCi3wnKP4owdhs+LRIUfkPzR4p5RLa3tM4O6gqO7TeAo6SsamvqA59yPb1GyKDcCgB/ClmifVJ9BqQ128a4uvQ==",
+ "version": "1.0.62",
+ "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.0.62.tgz",
+ "integrity":
"sha512-iWxUfvzC5tGZWFrhoPLUKQJgFif4aDVgyaBbCURFIswex8to1JsnwDtenM2GqNI1VC/vqNlHtGc0ZR5eh0vbww==",
"dev": true,
"license": "MIT",
"optional": true
@@ -5140,9 +5127,9 @@
}
},
"node_modules/valibot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz",
- "integrity":
"sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity":
"sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
"dev": true,
"license": "MIT",
"optional": true,
diff --git a/web/package.json b/web/package.json
index 63800cb17..95e232734 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,6 +5,7 @@
"scripts": {
"dev": "vite dev --port 3050",
"build": "vite build",
+ "build:static": "STATIC_BUILD=true PUBLIC_IGGY_API_URL= vite build",
"preview": "vite preview",
"server": "vite server",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -16,6 +17,7 @@
"@eslint/compat": "^2.0.0",
"@eslint/js": "^9.39.1",
"@sveltejs/adapter-node": "^5.4.0",
+ "@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.48.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/d3-interpolate": "^3.0.4",
@@ -33,7 +35,7 @@
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.43.6",
"svelte-check": "^4.3.4",
- "sveltekit-superforms": "^2.28.1",
+ "sveltekit-superforms": "^2.14.0",
"tailwindcss": "^4.1.17",
"ts-toolbelt": "^9.6.0",
"tslib": "^2.8.1",
@@ -57,7 +59,8 @@
"uuid": "^13.0.0"
},
"overrides": {
- "cookie": "^0.7.0"
+ "cookie": "^0.7.0",
+ "valibot": "^1.2.0"
},
"type": "module"
}
diff --git a/web/src/hooks.client.ts b/web/src/hooks.client.ts
index d3f825db2..e5fb6b240 100644
--- a/web/src/hooks.client.ts
+++ b/web/src/hooks.client.ts
@@ -17,12 +17,7 @@
* under the License.
*/
-import type { HandleClientError } from '@sveltejs/kit';
+import { authStore } from '$lib/auth/authStore.svelte';
-export const handleError: HandleClientError = async ({ error }) => {
- console.log('client error handler', error);
- return {
- message: 'Whoops!',
- errorId: 1
- };
-};
+// Initialize auth store when the app loads
+authStore.initialize();
diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts
deleted file mode 100644
index 6285ec07a..000000000
--- a/web/src/hooks.server.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { env } from '$env/dynamic/public';
-import { checkIfPathnameIsPublic, typedRoute } from '$lib/types/appRoutes';
-import { tokens } from '$lib/utils/constants/tokens';
-import type { Handle } from '@sveltejs/kit';
-import { sequence } from '@sveltejs/kit/hooks';
-import { redirect } from '@sveltejs/kit';
-
-console.log(`Iggy API URL: ${env.PUBLIC_IGGY_API_URL}`);
-
-const accessTokenOkRedirects = [
- {
- from: '/dashboard/',
- to: typedRoute('/dashboard/overview')
- },
- {
- from: '/dashboard',
- to: typedRoute('/dashboard/overview')
- },
- {
- from: '/',
- to: typedRoute('/dashboard/overview')
- },
- {
- from: '/auth',
- to: typedRoute('/dashboard/overview')
- },
- {
- from: typedRoute('/auth/sign-in'),
- to: typedRoute('/dashboard/overview')
- }
-];
-
-const handleAuth: Handle = async ({ event, resolve }) => {
- const cookies = event.cookies;
- const isPublicPath = checkIfPathnameIsPublic(event.url.pathname);
- const accessToken = cookies.get(tokens.accessToken);
-
- if (!accessToken) {
- if (isPublicPath) {
- return resolve(event);
- } else {
- redirect(302, typedRoute('/auth/sign-in'));
- }
- }
-
- const invalidPathRedirect = accessTokenOkRedirects.find((r) => r.from ===
event.url.pathname);
- if (invalidPathRedirect) {
- redirect(302, invalidPathRedirect.to);
- }
-
- return resolve(event);
-};
-
-export const handle = sequence(handleAuth);
diff --git a/web/src/lib/api/ApiSchema.ts b/web/src/lib/api/ApiSchema.ts
index 1b2dcbab2..a18f2dce5 100644
--- a/web/src/lib/api/ApiSchema.ts
+++ b/web/src/lib/api/ApiSchema.ts
@@ -183,4 +183,8 @@ type Stats = {
path: '/stats';
};
-export type ApiSchema = Users | Streams | Stats | Topics | Auth;
+type BaseApiSchema = Users | Streams | Stats | Topics | Auth;
+
+export type ApiSchema = BaseApiSchema & {
+ queryParams?: Record<string, string | number | boolean>;
+};
diff --git a/web/src/lib/api/clientApi.ts b/web/src/lib/api/clientApi.ts
new file mode 100644
index 000000000..fb1372371
--- /dev/null
+++ b/web/src/lib/api/clientApi.ts
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { resolve } from '$app/paths';
+import { env } from '$env/dynamic/public';
+import { authStore } from '$lib/auth/authStore.svelte';
+import { typedRoute } from '$lib/types/appRoutes';
+import { error } from '@sveltejs/kit';
+import { getJson } from './getJson';
+
+export interface ApiRequest {
+ path: string;
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+ body?: unknown;
+ queryParams?: Record<string, string>;
+}
+
+export async function clientApi<T = unknown>(args: ApiRequest): Promise<T> {
+ const { path, method, queryParams, body } = args;
+
+ const headers = new Headers();
+ headers.set('Content-Type', 'application/json');
+
+ const token = authStore.getAccessToken();
+ if (token) {
+ headers.set('Authorization', `Bearer ${token}`);
+ }
+
+ // Use PUBLIC_IGGY_API_URL if set, otherwise use relative path (for embedded
mode)
+ const baseUrl = env.PUBLIC_IGGY_API_URL || '';
+ let fullUrl = `${baseUrl}${path}`;
+
+ if (queryParams) {
+ const query = new URLSearchParams(Object.entries(queryParams));
+ fullUrl += '?' + query.toString();
+ }
+
+ const response = await fetch(fullUrl, {
+ headers,
+ method,
+ ...(body ? { body: JSON.stringify(body) } : {})
+ });
+
+ const data = await getJson(response);
+
+ if (response.ok) {
+ return data as T;
+ }
+
+ // Handle errors
+ if (response.status === 401 || response.status === 403) {
+ if (browser) {
+ authStore.logout();
+ }
+ error(401, { message: 'Unauthorized' });
+ }
+
+ if (response.status === 404) {
+ error(404, { message: 'Not Found' });
+ }
+
+ if (response.status === 400) {
+ // Return the error data for form validation
+ throw { status: 400, data };
+ }
+
+ error(500, { message: 'Internal server error' });
+}
+
+/**
+ * Wrapper that handles errors gracefully for load functions
+ */
+export async function clientApiSafe<T = unknown>(
+ args: ApiRequest
+): Promise<{ data: T | null; error: string | null }> {
+ try {
+ const data = await clientApi<T>(args);
+ return { data, error: null };
+ } catch (e: any) {
+ if (e?.status === 401 || e?.status === 403) {
+ if (browser) {
+ goto(resolve(typedRoute('/auth/sign-in')));
+ }
+ return { data: null, error: 'Unauthorized' };
+ }
+ return { data: null, error: e?.message || 'Unknown error' };
+ }
+}
diff --git a/web/src/lib/api/fetchRouteApi.ts b/web/src/lib/api/fetchRouteApi.ts
index 894c094e2..fd461262e 100644
--- a/web/src/lib/api/fetchRouteApi.ts
+++ b/web/src/lib/api/fetchRouteApi.ts
@@ -17,15 +17,46 @@
* under the License.
*/
+import { env } from '$env/dynamic/public';
+import { authStore } from '$lib/auth/authStore.svelte';
import type { ApiSchema } from './ApiSchema';
+import { convertBigIntsToStrings } from './convertBigIntsToStrings';
import { getJson } from './getJson';
export const fetchRouteApi = async (
arg: ApiSchema
): Promise<{ data: any; status: number; ok: boolean }> => {
try {
- const res = await fetch('/api/proxy', { body: JSON.stringify(arg), method:
'POST' });
- return (await getJson(res)) as any;
+ const { path, method, queryParams } = arg;
+
+ const headers = new Headers();
+ headers.set('Content-Type', 'application/json');
+
+ const token = authStore.getAccessToken();
+ if (token) {
+ headers.set('Authorization', `Bearer ${token}`);
+ }
+
+ // Use PUBLIC_IGGY_API_URL if set, otherwise use relative path (for
embedded mode)
+ const baseUrl = env.PUBLIC_IGGY_API_URL || '';
+ let fullUrl = `${baseUrl}${path}`;
+
+ if (queryParams) {
+ const params = Object.entries(queryParams).map(([k, v]) => [k,
String(v)]);
+ const query = new URLSearchParams(params);
+ fullUrl += '?' + query.toString();
+ }
+
+ const res = await fetch(fullUrl, {
+ headers,
+ method,
+ ...('body' in arg && arg.body ? { body: JSON.stringify(arg.body) } : {})
+ });
+
+ const data = await getJson(res);
+ const safeData = convertBigIntsToStrings(data);
+
+ return { data: safeData, status: res.status, ok: res.ok };
} catch (err) {
throw new Error('fetchRouteApi error');
}
diff --git a/web/src/lib/api/handleFetchErrors.ts
b/web/src/lib/api/handleFetchErrors.ts
index b3bb75f66..3ef5559fc 100644
--- a/web/src/lib/api/handleFetchErrors.ts
+++ b/web/src/lib/api/handleFetchErrors.ts
@@ -18,6 +18,7 @@
*/
import { error, type Cookies, redirect } from '@sveltejs/kit';
+import { base } from '$app/paths';
import { getJson } from './getJson';
import { tokens } from '$lib/utils/constants/tokens';
import { typedRoute } from '$lib/types/appRoutes';
@@ -57,15 +58,14 @@ export const handleFetchErrors = async (
};
},
401: () => {
- // TODO: Refresh token
console.log(`handleErrorStatus: 401 ${response.url}`);
removeCookies();
- redirect(302, typedRoute('/auth/sign-in'));
+ redirect(302, `${base}${typedRoute('/auth/sign-in')}`);
},
403: () => {
console.log(`handleErrorStatus: 403 ${response.url}`);
removeCookies();
- redirect(302, typedRoute('/auth/sign-in'));
+ redirect(302, `${base}${typedRoute('/auth/sign-in')}`);
},
404: () => {
console.log(`handleErrorStatus: 404 ${response.url}`);
diff --git a/web/src/lib/auth/authStore.svelte.ts
b/web/src/lib/auth/authStore.svelte.ts
new file mode 100644
index 000000000..fdd5511e0
--- /dev/null
+++ b/web/src/lib/auth/authStore.svelte.ts
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { resolve } from '$app/paths';
+import { typedRoute } from '$lib/types/appRoutes';
+import { tokens } from '$lib/utils/constants/tokens';
+import { jwtDecode } from 'jwt-decode';
+
+export interface AuthState {
+ isAuthenticated: boolean;
+ accessToken: string | null;
+ userId: number | null;
+}
+
+function createAuthStore() {
+ let state = $state<AuthState>({
+ isAuthenticated: false,
+ accessToken: null,
+ userId: null
+ });
+
+ function getTokenFromCookie(): string | null {
+ if (!browser) return null;
+ const match = document.cookie.match(new RegExp(`(^|
)${tokens.accessToken}=([^;]+)`));
+ return match ? match[2] : null;
+ }
+
+ function setTokenCookie(token: string, expiry: number): void {
+ if (!browser) return;
+ const expires = new Date(expiry * 1000);
+ document.cookie = `${tokens.accessToken}=${token}; path=/;
expires=${expires.toUTCString()}; SameSite=Lax`;
+ }
+
+ function clearTokenCookie(): void {
+ if (!browser) return;
+ document.cookie = `${tokens.accessToken}=; path=/; expires=Thu, 01 Jan
1970 00:00:00 GMT`;
+ }
+
+ function initialize(): void {
+ const token = getTokenFromCookie();
+ if (token) {
+ try {
+ const decoded = jwtDecode(token);
+ const userId = decoded.sub ? parseInt(decoded.sub, 10) : null;
+ state = {
+ isAuthenticated: true,
+ accessToken: token,
+ userId
+ };
+ } catch {
+ clearTokenCookie();
+ state = { isAuthenticated: false, accessToken: null, userId: null };
+ }
+ }
+ }
+
+ function login(token: string, expiry: number): void {
+ setTokenCookie(token, expiry);
+ try {
+ const decoded = jwtDecode(token);
+ const userId = decoded.sub ? parseInt(decoded.sub, 10) : null;
+ state = {
+ isAuthenticated: true,
+ accessToken: token,
+ userId
+ };
+ } catch {
+ state = { isAuthenticated: true, accessToken: token, userId: null };
+ }
+ }
+
+ function logout(): void {
+ clearTokenCookie();
+ state = { isAuthenticated: false, accessToken: null, userId: null };
+ goto(resolve(typedRoute('/auth/sign-in')));
+ }
+
+ function getAccessToken(): string | null {
+ return state.accessToken || getTokenFromCookie();
+ }
+
+ // Initialize on creation if in browser
+ if (browser) {
+ initialize();
+ }
+
+ return {
+ get state() {
+ return state;
+ },
+ get isAuthenticated() {
+ return state.isAuthenticated;
+ },
+ get accessToken() {
+ return state.accessToken;
+ },
+ get userId() {
+ return state.userId;
+ },
+ initialize,
+ login,
+ logout,
+ getAccessToken
+ };
+}
+
+export const authStore = createAuthStore();
diff --git a/web/src/lib/components/Breadcrumbs.svelte
b/web/src/lib/components/Breadcrumbs.svelte
index b90f70e2d..d83adef32 100644
--- a/web/src/lib/components/Breadcrumbs.svelte
+++ b/web/src/lib/components/Breadcrumbs.svelte
@@ -65,7 +65,7 @@
return { path, label: segment };
}
- let parts = $derived(page.url.pathname.split('/').filter(Boolean).slice(1));
+ let parts = $derived(page.url.pathname.replace(/^\/ui/,
'').split('/').filter(Boolean).slice(1));
let crumbs = $derived(parts.map((segment, index) =>
formatPathSegment(segment, index, parts)));
</script>
diff --git a/web/src/lib/components/Header.svelte
b/web/src/lib/components/Header.svelte
index 4f4388565..4906aba8f 100644
--- a/web/src/lib/components/Header.svelte
+++ b/web/src/lib/components/Header.svelte
@@ -26,11 +26,12 @@
import DropdownMenu from './DropdownMenu/DropdownMenu.svelte';
import { theme } from './ThemeController.svelte';
import { typedRoute } from '$lib/types/appRoutes';
+ import { resolve } from '$app/paths';
import Button from './Button.svelte';
import StopPropagation from './StopPropagation.svelte';
interface Props {
- user: User;
+ user: User | null;
}
let { user }: Props = $props();
@@ -63,7 +64,7 @@
{#snippet children({ close: _close })}
<div class="p-1 min-w-[150px] transition-all duration-200 flex
flex-col text-sm text-color">
<span class="flex items-center justify-between gap-2 border-b px-2
py-3">
- <span>{user.username}</span>
+ <span>{user?.username ?? 'User'}</span>
<Icon
name="user"
class="w-[24px] h-[24px] stroke-black dark:stroke-white"
@@ -98,15 +99,13 @@
/>
</label>
</div>
- <form class="w-full" method="POST"
action={typedRoute('/auth/logout')}>
- <button
- class="flex w-full items-center justify-between gap-2 px-2 py-2
hover:bg-shade-l300 dark:hover:bg-shade-d1000 rounded-md my-1
dark:hover:text-white"
- >
- <span>Log Out</span>
-
- <Icon name="logout" />
- </button>
- </form>
+ <a
+ href={resolve(typedRoute('/auth/logout'))}
+ class="flex w-full items-center justify-between gap-2 px-2 py-2
hover:bg-shade-l300 dark:hover:bg-shade-d1000 rounded-md my-1
dark:hover:text-white"
+ >
+ <span>Log Out</span>
+ <Icon name="logout" />
+ </a>
</div>
{/snippet}
</DropdownMenu>
diff --git a/web/src/lib/components/Layouts/SettingsLayout.svelte
b/web/src/lib/components/Layouts/SettingsLayout.svelte
index a2b28ba4a..ee7fd6912 100644
--- a/web/src/lib/components/Layouts/SettingsLayout.svelte
+++ b/web/src/lib/components/Layouts/SettingsLayout.svelte
@@ -41,25 +41,25 @@
tab: 'server',
icon: 'adjustments',
name: 'Server',
- href: typedRoute('/dashboard/settings/server')
+ href: resolve(typedRoute('/dashboard/settings/server'))
},
{
tab: 'webUI',
icon: 'settings',
name: 'Web UI',
- href: typedRoute('/dashboard/settings/webUI')
+ href: resolve(typedRoute('/dashboard/settings/webUI'))
},
{
tab: 'users',
icon: 'usersGroup',
name: 'Users',
- href: typedRoute('/dashboard/settings/users')
+ href: resolve(typedRoute('/dashboard/settings/users'))
}
// {
// name: 'Terminal',
// icon: 'terminal',
// tab: 'terminal',
- // href: typedRoute('/dashboard/settings/terminal')
+ // href: resolve(typedRoute('/dashboard/settings/terminal'))
// }
] satisfies { tab: Tabs; name: string; icon: iconType; href: string }[];
</script>
@@ -74,7 +74,7 @@
{#each tabs as { icon, name, href }, idx (idx)}
{@const isActive = activeTab === href.split('/').slice(-1)[0]}
<a
- href={resolve(href)}
+ {href}
class={twMerge('pb-3 relative group flex items-center justify-start
gap-2 text-color')}
>
<Icon name={icon} class="w-[15px] h-[15px]" />
diff --git a/web/src/lib/components/Logo/Logo.svelte
b/web/src/lib/components/Logo/Logo.svelte
index dd561ef0a..002456b2d 100644
--- a/web/src/lib/components/Logo/Logo.svelte
+++ b/web/src/lib/components/Logo/Logo.svelte
@@ -18,10 +18,11 @@
-->
<script lang="ts">
+ import { base } from '$app/paths';
let className = '';
export { className as class };
- const lightLogo = '/iggy-apache-lightbg.svg';
- const darkLogo = '/iggy-apache-darkbg.svg';
+ const lightLogo = `${base}/iggy-apache-lightbg.svg`;
+ const darkLogo = `${base}/iggy-apache-darkbg.svg`;
</script>
<img src={lightLogo} class="{className} block dark:hidden" alt="Apache Iggy" />
diff --git a/web/src/lib/components/Navbar.svelte
b/web/src/lib/components/Navbar.svelte
index fd0c1c307..55524b703 100644
--- a/web/src/lib/components/Navbar.svelte
+++ b/web/src/lib/components/Navbar.svelte
@@ -21,42 +21,41 @@
import Icon from './Icon.svelte';
import type { iconType } from './Icon.svelte';
import { page } from '$app/state';
+ import { resolve } from '$app/paths';
import { twMerge } from 'tailwind-merge';
import { tooltip } from '$lib/actions/tooltip';
import { typedRoute } from '$lib/types/appRoutes';
import LogoType from '$lib/components/Logo/LogoType.svelte';
import LogoMark from '$lib/components/Logo/LogoMark.svelte';
- import { resolve } from '$app/paths';
-
let navItems = $derived([
{
name: 'Overview',
icon: 'home',
- href: typedRoute('/dashboard/overview'),
+ href: resolve(typedRoute('/dashboard/overview')),
active: page.url.pathname.includes(typedRoute('/dashboard/overview'))
},
{
name: 'Streams',
icon: 'stream',
- href: typedRoute('/dashboard/streams'),
+ href: resolve(typedRoute('/dashboard/streams')),
active: page.url.pathname.includes(typedRoute('/dashboard/streams'))
},
// {
// name: 'Clients',
// icon: 'clients',
- // href: typedRoute('/dashboard/clients'),
+ // href: resolve(typedRoute('/dashboard/clients')),
// active: page.url.pathname.includes(typedRoute('/dashboard/clients'))
// },
// {
// name: 'Logs',
// icon: 'logs',
- // href: typedRoute('/dashboard/logs'),
+ // href: resolve(typedRoute('/dashboard/logs')),
// active: page.url.pathname.includes(typedRoute('/dashboard/logs'))
// },
{
name: 'Settings',
icon: 'settings',
- href: typedRoute('/dashboard/settings/webUI'),
+ href: resolve(typedRoute('/dashboard/settings/webUI')),
active: page.url.pathname.includes('/dashboard/settings')
}
] satisfies { name: string; icon: iconType; href: string; active: boolean
}[]);
@@ -78,7 +77,7 @@
<li>
<div use:tooltip={{ placement: 'right' }}>
<a
- href={resolve(href)}
+ {href}
data-trigger
class={twMerge(
'p-2 block rounded-xl transition-colors ring-2
ring-transparent',
diff --git a/web/src/lib/types/appRoutes.ts b/web/src/lib/types/appRoutes.ts
index 17823b40a..2078467db 100644
--- a/web/src/lib/types/appRoutes.ts
+++ b/web/src/lib/types/appRoutes.ts
@@ -32,7 +32,11 @@ type DashboardRoutes = `/dashboard/${
type AuthRoutes = `/auth/${'sign-in' | 'logout'}`;
export const typedRoute = <const T extends DashboardRoutes |
AuthRoutes>(route: T) => route;
-export const publicRoutes = [typedRoute('/auth/sign-in'), '/auth/test'] as
const;
+export const publicRoutes = ['/auth/sign-in', '/auth/logout', '/auth/test'] as
const;
-export const checkIfPathnameIsPublic = (pathname: string) =>
- (publicRoutes as ReadonlyArray<string>).includes(pathname);
+export const checkIfPathnameIsPublic = (pathname: string) => {
+ const pathWithoutBase = pathname.replace(/^\/ui/, '') || '/';
+ return (publicRoutes as ReadonlyArray<string>).some(
+ (route) => pathWithoutBase === route || pathWithoutBase.startsWith(route +
'/')
+ );
+};
diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts
new file mode 100644
index 000000000..7a0657895
--- /dev/null
+++ b/web/src/routes/+layout.ts
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { base, resolve } from '$app/paths';
+import { authStore } from '$lib/auth/authStore.svelte';
+import { checkIfPathnameIsPublic, typedRoute } from '$lib/types/appRoutes';
+import type { LayoutLoad } from './$types';
+
+// Enable client-side rendering for SPA mode
+export const ssr = false;
+export const prerender = false;
+
+export const load: LayoutLoad = async ({ url }) => {
+ if (browser) {
+ authStore.initialize();
+ }
+
+ const pathname = url.pathname;
+ const isPublicPath = checkIfPathnameIsPublic(pathname);
+ const isAuthenticated = authStore.getAccessToken() !== null;
+
+ const authRedirects = [
+ base,
+ `${base}/`,
+ `${base}/dashboard`,
+ `${base}/dashboard/`,
+ `${base}/auth`,
+ `${base}/auth/sign-in`
+ ];
+
+ if (browser) {
+ if (!isAuthenticated && !isPublicPath) {
+ goto(resolve(typedRoute('/auth/sign-in')));
+ return { isAuthenticated: false };
+ }
+
+ if (isAuthenticated && authRedirects.includes(pathname)) {
+ goto(resolve(typedRoute('/dashboard/overview')));
+ return { isAuthenticated: true };
+ }
+ }
+
+ return {
+ isAuthenticated
+ };
+};
diff --git a/web/src/routes/api/proxy/+server.ts
b/web/src/routes/api/proxy/+server.ts
deleted file mode 100644
index 70d091b0e..000000000
--- a/web/src/routes/api/proxy/+server.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { error, json, type RequestHandler } from '@sveltejs/kit';
-import { convertBigIntsToStrings } from '$lib/api/convertBigIntsToStrings';
-
-export const POST: RequestHandler = async ({ request, cookies }) => {
- const { path, body, method, queryParams } = await request.json();
-
- if (!path || !method) {
- const message = `routes/api/proxy/+server.ts no path or body or method
provided`;
- console.error(message);
- error(500, {
- message
- });
- }
-
- const result = await fetchIggyApi({ body, path, method, cookies, queryParams
});
-
- const { data, response } = await handleFetchErrors(result, cookies);
-
- const safeData = convertBigIntsToStrings(data);
-
- return json({ data: safeData, ok: response.ok, status: response.status });
-};
diff --git a/web/src/lib/components/Logo/Logo.svelte
b/web/src/routes/auth/logout/+page.svelte
similarity index 64%
copy from web/src/lib/components/Logo/Logo.svelte
copy to web/src/routes/auth/logout/+page.svelte
index dd561ef0a..6401055af 100644
--- a/web/src/lib/components/Logo/Logo.svelte
+++ b/web/src/routes/auth/logout/+page.svelte
@@ -18,11 +18,18 @@
-->
<script lang="ts">
- let className = '';
- export { className as class };
- const lightLogo = '/iggy-apache-lightbg.svg';
- const darkLogo = '/iggy-apache-darkbg.svg';
+ import { onMount } from 'svelte';
+ import { goto } from '$app/navigation';
+ import { resolve } from '$app/paths';
+ import { authStore } from '$lib/auth/authStore.svelte';
+ import { typedRoute } from '$lib/types/appRoutes';
+
+ onMount(() => {
+ authStore.logout();
+ goto(resolve(typedRoute('/auth/sign-in')));
+ });
</script>
-<img src={lightLogo} class="{className} block dark:hidden" alt="Apache Iggy" />
-<img src={darkLogo} class="{className} hidden dark:block" alt="Apache Iggy" />
+<div class="flex items-center justify-center h-full">
+ <span class="text-color">Logging out...</span>
+</div>
diff --git a/web/src/routes/auth/logout/+page.server.ts
b/web/src/routes/auth/logout/+page.ts
similarity index 67%
copy from web/src/routes/auth/logout/+page.server.ts
copy to web/src/routes/auth/logout/+page.ts
index e2d7ac8af..5de770f46 100644
--- a/web/src/routes/auth/logout/+page.server.ts
+++ b/web/src/routes/auth/logout/+page.ts
@@ -17,21 +17,18 @@
* under the License.
*/
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { resolve } from '$app/paths';
+import { authStore } from '$lib/auth/authStore.svelte';
import { typedRoute } from '$lib/types/appRoutes';
-import { tokens } from '$lib/utils/constants/tokens.js';
-import { redirect, type Actions } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
-export const actions = {
- default({ cookies }) {
- // eat the cookie
- cookies.set(tokens.accessToken, '', {
- path: '/',
- expires: new Date(0)
- });
-
- console.log('deleting cookie');
-
- // redirect the user
- redirect(302, typedRoute('/auth/sign-in'));
+export const load: PageLoad = async () => {
+ if (browser) {
+ authStore.logout();
+ goto(resolve(typedRoute('/auth/sign-in')));
}
-} satisfies Actions;
+
+ return {};
+};
diff --git a/web/src/routes/auth/sign-in/+page.server.ts
b/web/src/routes/auth/sign-in/+page.server.ts
deleted file mode 100644
index f840df92f..000000000
--- a/web/src/routes/auth/sign-in/+page.server.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fail, redirect } from '@sveltejs/kit';
-import type { Actions } from './$types';
-import { message, superValidate } from 'sveltekit-superforms/server';
-import { zod4 } from 'sveltekit-superforms/adapters';
-import { z } from 'zod';
-import { typedRoute } from '$lib/types/appRoutes';
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { tokens } from '$lib/utils/constants/tokens';
-import { getJson } from '$lib/api/getJson';
-
-const schema = z.object({
- username: z.string().min(1),
- password: z.string().min(4)
-});
-
-type FormSchema = z.infer<typeof schema>;
-
-export const load = async () => {
- const form = await superValidate(zod4(schema));
-
- return { form };
-};
-
-export const actions = {
- default: async ({ request, cookies }) => {
- const form = await superValidate(request, zod4(schema));
-
- if (!form.valid) {
- return fail(400, { form });
- }
-
- const { password, username } = form.data;
-
- const result = await fetchIggyApi({
- method: 'POST',
- path: '/users/login',
- body: { username, password }
- });
-
- console.log(result);
-
- if (!(result instanceof Response) || !result.ok) {
- return message(form, 'Username or password is not valid', { status: 403
});
- }
-
- const { access_token } = (await getJson(result)) as any;
-
- cookies.set(tokens.accessToken, access_token.token, {
- path: '/',
- httpOnly: true,
- sameSite: 'lax',
- secure: true,
- expires: new Date(1000 * access_token.expiry)
- });
-
- redirect(302, typedRoute('/dashboard/overview'));
- }
-} satisfies Actions;
diff --git a/web/src/routes/auth/sign-in/+page.svelte
b/web/src/routes/auth/sign-in/+page.svelte
index 0183cf15e..31e0aca1b 100644
--- a/web/src/routes/auth/sign-in/+page.svelte
+++ b/web/src/routes/auth/sign-in/+page.svelte
@@ -18,40 +18,87 @@
-->
<script lang="ts">
+ import { goto } from '$app/navigation';
+ import { resolve } from '$app/paths';
+ import { env } from '$env/dynamic/public';
+ import { authStore } from '$lib/auth/authStore.svelte';
import Button from '$lib/components/Button.svelte';
import Checkbox from '$lib/components/Checkbox.svelte';
import Icon from '$lib/components/Icon.svelte';
import Input from '$lib/components/Input.svelte';
import PasswordInput from '$lib/components/PasswordInput.svelte';
+ import { typedRoute } from '$lib/types/appRoutes';
import { persistedStore } from '$lib/utils/persistedStore.js';
import { onMount } from 'svelte';
- import { superForm } from 'sveltekit-superforms/client';
- interface Props {
- data: any;
- }
-
- let { data }: Props = $props();
- const { form, constraints, errors, message } = superForm(data.form, {});
+ let username = $state('');
+ let password = $state('');
+ let errorMessage = $state('');
+ let isLoading = $state(false);
+ let usernameError = $state('');
+ let passwordError = $state('');
const remember = persistedStore('rememberMe', { rememberMe: true, username:
'' });
onMount(() => {
if ($remember.rememberMe) {
- $form.username = $remember.username;
+ username = $remember.username;
}
});
-</script>
-<form
- method="POST"
- onsubmit={() => {
+ async function handleSubmit(event: SubmitEvent) {
+ event.preventDefault();
+ errorMessage = '';
+ usernameError = '';
+ passwordError = '';
+
+ // Validate
+ if (!username) {
+ usernameError = 'Username is required';
+ return;
+ }
+ if (!password || password.length < 4) {
+ passwordError = 'Password must be at least 4 characters';
+ return;
+ }
+
+ // Remember username if checked
if ($remember.rememberMe) {
- $remember.username = $form.username;
+ $remember.username = username;
} else {
$remember.username = '';
}
- }}
+
+ isLoading = true;
+
+ try {
+ const baseUrl = env.PUBLIC_IGGY_API_URL || '';
+ const response = await fetch(`${baseUrl}/users/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password })
+ });
+
+ if (!response.ok) {
+ errorMessage = 'Username or password is not valid';
+ isLoading = false;
+ return;
+ }
+
+ const data = await response.json();
+ const { access_token } = data;
+
+ authStore.login(access_token.token, access_token.expiry);
+ goto(resolve(typedRoute('/dashboard/overview')));
+ } catch (e) {
+ errorMessage = 'Failed to connect to server';
+ isLoading = false;
+ }
+ }
+</script>
+
+<form
+ onsubmit={handleSubmit}
class="min-w-[350px] max-w-[400px] bg-white dark:bg-shade-d700 border
text-color p-5 rounded-2xl card-shadow dark:shadow-lg flex flex-col gap-5"
>
<span class="mx-auto font-semibold">Admin sign in</span>
@@ -60,26 +107,23 @@
label="Username"
name="username"
autocomplete="username"
- errorMessage={Array.isArray($errors?.username)
- ? $errors.username.join(',')
- : String($errors?.username || '')}
- bind:value={$form.username}
- {...$constraints.username}
+ errorMessage={usernameError}
+ bind:value={username}
+ required
/>
<PasswordInput
label="Password"
name="password"
autocomplete="current-password"
- errorMessage={Array.isArray($errors?.password)
- ? $errors.password.join(',')
- : String($errors?.password || '')}
- bind:value={$form.password}
- {...$constraints.password}
+ errorMessage={passwordError}
+ bind:value={password}
+ required
+ minlength={4}
/>
- {#if $message}
- <span class="text-sm mx-auto text-red">{$message}</span>
+ {#if errorMessage}
+ <span class="text-sm mx-auto text-red-500">{errorMessage}</span>
{/if}
<div class="flex justify-between items-center">
@@ -89,8 +133,8 @@
</label>
</div>
- <Button variant="contained" size="lg" class="mt-7" type="submit">
- <span> Login </span>
+ <Button variant="contained" size="lg" class="mt-7" type="submit"
disabled={isLoading}>
+ <span>{isLoading ? 'Logging in...' : 'Login'}</span>
<Icon name="login" class="w-[23px] h-[23px]" />
</Button>
diff --git a/web/src/routes/auth/logout/+page.server.ts
b/web/src/routes/auth/sign-in/+page.ts
similarity index 67%
rename from web/src/routes/auth/logout/+page.server.ts
rename to web/src/routes/auth/sign-in/+page.ts
index e2d7ac8af..5ca57c99b 100644
--- a/web/src/routes/auth/logout/+page.server.ts
+++ b/web/src/routes/auth/sign-in/+page.ts
@@ -17,21 +17,17 @@
* under the License.
*/
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { resolve } from '$app/paths';
+import { authStore } from '$lib/auth/authStore.svelte';
import { typedRoute } from '$lib/types/appRoutes';
-import { tokens } from '$lib/utils/constants/tokens.js';
-import { redirect, type Actions } from '@sveltejs/kit';
+import type { PageLoad } from './$types';
-export const actions = {
- default({ cookies }) {
- // eat the cookie
- cookies.set(tokens.accessToken, '', {
- path: '/',
- expires: new Date(0)
- });
-
- console.log('deleting cookie');
-
- // redirect the user
- redirect(302, typedRoute('/auth/sign-in'));
+export const load: PageLoad = async () => {
+ if (browser && authStore.getAccessToken()) {
+ goto(resolve(typedRoute('/dashboard/overview')));
}
-} satisfies Actions;
+
+ return {};
+};
diff --git a/web/src/routes/dashboard/+layout.server.ts
b/web/src/routes/dashboard/+layout.ts
similarity index 50%
rename from web/src/routes/dashboard/+layout.server.ts
rename to web/src/routes/dashboard/+layout.ts
index 5d0c105f2..30b80d565 100644
--- a/web/src/routes/dashboard/+layout.server.ts
+++ b/web/src/routes/dashboard/+layout.ts
@@ -17,22 +17,47 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { resolve } from '$app/paths';
+import { clientApi } from '$lib/api/clientApi';
+import { authStore } from '$lib/auth/authStore.svelte';
import { userDetailsMapper } from '$lib/domain/UserDetails';
-import type { LayoutServerLoad } from './$types';
+import { typedRoute } from '$lib/types/appRoutes';
import { jwtDecode } from 'jwt-decode';
+import type { LayoutLoad } from './$types';
+
+export const load: LayoutLoad = async () => {
+ if (browser) {
+ authStore.initialize();
+ }
+
+ const token = authStore.getAccessToken();
+ if (browser && !token) {
+ goto(resolve(typedRoute('/auth/sign-in')));
+ return { user: null };
+ }
-export const load: LayoutServerLoad = async ({ cookies }) => {
const getDetailedUser = async () => {
- //always available here, auth hook prevents rendering this page without
access_token
- const accessToken = cookies.get('access_token')!;
- const userId = jwtDecode(accessToken).sub!;
+ let userId = authStore.userId;
+
+ if (!userId && token) {
+ try {
+ const decoded = jwtDecode(token);
+ userId = decoded.sub ? parseInt(decoded.sub, 10) : null;
+ } catch {
+ return null;
+ }
+ }
- const userResult = await fetchIggyApi({ method: 'GET', path:
`/users/${+userId}`, cookies });
- const { data } = await handleFetchErrors(userResult, cookies);
+ if (!userId) return null;
- return userDetailsMapper(data);
+ try {
+ const data = await clientApi({ method: 'GET', path: `/users/${userId}`
});
+ return userDetailsMapper(data);
+ } catch {
+ return null;
+ }
};
return {
diff --git a/web/src/routes/dashboard/overview/+page.server.ts
b/web/src/routes/dashboard/overview/+page.ts
similarity index 76%
copy from web/src/routes/dashboard/overview/+page.server.ts
copy to web/src/routes/dashboard/overview/+page.ts
index 3eac60a15..5f88bb588 100644
--- a/web/src/routes/dashboard/overview/+page.server.ts
+++ b/web/src/routes/dashboard/overview/+page.ts
@@ -17,19 +17,16 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
+import { clientApi } from '$lib/api/clientApi';
import { statsMapper } from '$lib/domain/Stats';
+import type { PageLoad } from './$types';
-export const load = async ({ cookies }) => {
- const result = await fetchIggyApi({
+export const load: PageLoad = async () => {
+ const data = await clientApi({
path: '/stats',
- method: 'GET',
- cookies
+ method: 'GET'
});
- const { data } = await handleFetchErrors(result, cookies);
-
return {
stats: statsMapper(data)
};
diff --git a/web/src/routes/dashboard/settings/server/+page.server.ts
b/web/src/routes/dashboard/settings/server/+page.server.ts
deleted file mode 100644
index bff846f2c..000000000
--- a/web/src/routes/dashboard/settings/server/+page.server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { statsMapper } from '$lib/domain/Stats';
-
-export const load = async ({ cookies }) => {
- const getStats = async () => {
- const result = await fetchIggyApi({
- method: 'GET',
- path: '/stats',
- cookies
- });
-
- const { data } = await handleFetchErrors(result, cookies);
- return statsMapper(data);
- };
-
- return {
- serverStats: await getStats()
- };
-};
diff --git a/web/src/routes/+layout.server.ts
b/web/src/routes/dashboard/settings/server/+page.ts
similarity index 73%
rename from web/src/routes/+layout.server.ts
rename to web/src/routes/dashboard/settings/server/+page.ts
index 27bb9c06f..5bf4759bc 100644
--- a/web/src/routes/+layout.server.ts
+++ b/web/src/routes/dashboard/settings/server/+page.ts
@@ -17,10 +17,17 @@
* under the License.
*/
-import type { LayoutServerLoad } from './$types';
+import { clientApi } from '$lib/api/clientApi';
+import { statsMapper } from '$lib/domain/Stats';
+import type { PageLoad } from './$types';
+
+export const load: PageLoad = async () => {
+ const data = await clientApi({
+ method: 'GET',
+ path: '/stats'
+ });
-export const load: LayoutServerLoad = async ({ locals }) => {
return {
- user: locals.user
+ serverStats: statsMapper(data)
};
};
diff --git a/web/src/routes/dashboard/settings/users/+page.server.ts
b/web/src/routes/dashboard/settings/users/+page.ts
similarity index 63%
rename from web/src/routes/dashboard/settings/users/+page.server.ts
rename to web/src/routes/dashboard/settings/users/+page.ts
index 2f2344585..73fc12615 100644
--- a/web/src/routes/dashboard/settings/users/+page.server.ts
+++ b/web/src/routes/dashboard/settings/users/+page.ts
@@ -17,33 +17,28 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
+import { clientApi } from '$lib/api/clientApi';
import { streamListMapper } from '$lib/domain/Stream';
-import { streamDetailsMapper } from '$lib/domain/StreamDetails.js';
+import { streamDetailsMapper } from '$lib/domain/StreamDetails';
+import { userMapper, type User } from '$lib/domain/User';
+import type { PageLoad } from './$types';
-import { userMapper, type User } from '$lib/domain/User.js';
-
-export const load = async ({ cookies }) => {
+export const load: PageLoad = async () => {
const getUsers = async () => {
- const result = await fetchIggyApi({
+ const data = await clientApi<any[]>({
method: 'GET',
- path: '/users',
- cookies
+ path: '/users'
});
- const { data } = await handleFetchErrors(result, cookies);
- return (data as any).map((item: any) => userMapper(item)) as User[];
+ return data.map((item: any) => userMapper(item)) as User[];
};
const getStreams = async () => {
- const result = await fetchIggyApi({
+ const data = await clientApi<any[]>({
method: 'GET',
- path: '/streams',
- cookies
+ path: '/streams'
});
- const { data } = await handleFetchErrors(result, cookies);
const streams = streamListMapper(data);
if (streams.length === 0) {
@@ -53,12 +48,10 @@ export const load = async ({ cookies }) => {
};
}
- const streamDetailResult = await fetchIggyApi({
+ const streamDetailsData = await clientApi({
method: 'GET',
- path: `/streams/${streams[0].id}`,
- cookies
+ path: `/streams/${streams[0].id}`
});
- const { data: streamDetailsData } = await
handleFetchErrors(streamDetailResult, cookies);
return {
streams,
diff --git a/web/src/routes/dashboard/streams/+layout.server.ts
b/web/src/routes/dashboard/streams/+layout.server.ts
deleted file mode 100644
index 0e0650ea2..000000000
--- a/web/src/routes/dashboard/streams/+layout.server.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { streamMapper, type Stream } from '$lib/domain/Stream';
-import type { LayoutServerLoad } from './$types';
-
-export const load: LayoutServerLoad = async ({ cookies }) => {
- const result = await fetchIggyApi({
- method: 'GET',
- path: '/streams',
- cookies
- });
-
- const { data } = await handleFetchErrors(result, cookies);
-
- return {
- streams: ((data as any).map(streamMapper) as Stream[]).sort((a, b) =>
b.createdAt - a.createdAt)
- };
-};
diff --git a/web/src/routes/dashboard/streams/+layout.svelte
b/web/src/routes/dashboard/streams/+layout.svelte
index bc66222b4..99b8d91d0 100644
--- a/web/src/routes/dashboard/streams/+layout.svelte
+++ b/web/src/routes/dashboard/streams/+layout.svelte
@@ -41,7 +41,10 @@
let filteredData = $derived(data.streams.filter((stream) =>
stream.name.includes(searchQuery)));
onMount(() => {
- if (data.streams.length > 0 && page.url.pathname ===
typedRoute('/dashboard/streams')) {
+ if (
+ data.streams.length > 0 &&
+ page.url.pathname === resolve(typedRoute('/dashboard/streams'))
+ ) {
goto(resolve(typedRoute(`/dashboard/streams/${data.streams[0].id}`)));
}
});
diff --git a/web/src/routes/dashboard/overview/+page.server.ts
b/web/src/routes/dashboard/streams/+layout.ts
similarity index 69%
copy from web/src/routes/dashboard/overview/+page.server.ts
copy to web/src/routes/dashboard/streams/+layout.ts
index 3eac60a15..711b9977c 100644
--- a/web/src/routes/dashboard/overview/+page.server.ts
+++ b/web/src/routes/dashboard/streams/+layout.ts
@@ -17,20 +17,17 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { statsMapper } from '$lib/domain/Stats';
+import { clientApi } from '$lib/api/clientApi';
+import { streamMapper, type Stream } from '$lib/domain/Stream';
+import type { LayoutLoad } from './$types';
-export const load = async ({ cookies }) => {
- const result = await fetchIggyApi({
- path: '/stats',
+export const load: LayoutLoad = async () => {
+ const data = await clientApi<any[]>({
method: 'GET',
- cookies
+ path: '/streams'
});
- const { data } = await handleFetchErrors(result, cookies);
-
return {
- stats: statsMapper(data)
+ streams: (data.map(streamMapper) as Stream[]).sort((a, b) => b.createdAt -
a.createdAt)
};
};
diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/+page.server.ts
b/web/src/routes/dashboard/streams/[streamId=i32]/+page.server.ts
deleted file mode 100644
index 3b22b2cbf..000000000
--- a/web/src/routes/dashboard/streams/[streamId=i32]/+page.server.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fetchIggyApi } from '$lib/api/fetchApi.js';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { streamDetailsMapper } from '$lib/domain/StreamDetails';
-
-export const load = async ({ params, cookies }) => {
- const getStreamDetails = async () => {
- const result = await fetchIggyApi({
- method: 'GET',
- path: `/streams/${+params.streamId}`,
- cookies
- });
-
- const { data } = await handleFetchErrors(result, cookies);
-
- return streamDetailsMapper(data);
- };
-
- return {
- streamDetails: await getStreamDetails()
- };
-};
diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte
b/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte
index ee2da4431..a4869cea5 100644
--- a/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte
+++ b/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte
@@ -19,6 +19,7 @@
<script lang="ts">
import { page } from '$app/state';
+ import { resolve } from '$app/paths';
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
import { openModal } from '$lib/components/Modals/AppModals.svelte';
@@ -85,7 +86,7 @@
rowClass="grid grid-cols-[150px_3fr_2fr_2fr_2fr_2fr_3fr]"
data={stream.topics}
hrefBuilder={(topic) =>
- typedRoute(`/dashboard/streams/${+(page.params.streamId ||
'')}/topics/${topic.id}`)}
+ resolve(typedRoute(`/dashboard/streams/${+(page.params.streamId ||
'')}/topics/${topic.id}`))}
colNames={{
id: 'ID',
name: 'Name',
diff --git a/web/src/hooks.client.ts
b/web/src/routes/dashboard/streams/[streamId=i32]/+page.ts
similarity index 70%
copy from web/src/hooks.client.ts
copy to web/src/routes/dashboard/streams/[streamId=i32]/+page.ts
index d3f825db2..8ac07677d 100644
--- a/web/src/hooks.client.ts
+++ b/web/src/routes/dashboard/streams/[streamId=i32]/+page.ts
@@ -17,12 +17,17 @@
* under the License.
*/
-import type { HandleClientError } from '@sveltejs/kit';
+import { clientApi } from '$lib/api/clientApi';
+import { streamDetailsMapper } from '$lib/domain/StreamDetails';
+import type { PageLoad } from './$types';
+
+export const load: PageLoad = async ({ params }) => {
+ const data = await clientApi({
+ method: 'GET',
+ path: `/streams/${+params.streamId}`
+ });
-export const handleError: HandleClientError = async ({ error }) => {
- console.log('client error handler', error);
return {
- message: 'Whoops!',
- errorId: 1
+ streamDetails: streamDetailsMapper(data)
};
};
diff --git
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.server.ts
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.server.ts
deleted file mode 100644
index 5b0c49451..000000000
---
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.server.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { topicDetailsMapper } from '$lib/domain/TopicDetails';
-
-export const load = async ({ params, cookies }) => {
- const getTopic = async () => {
- const result = await fetchIggyApi({
- method: 'GET',
- path: `/streams/${+params.streamId}/topics/${+params.topicId}`,
- cookies
- });
-
- const { data } = await handleFetchErrors(result, cookies);
-
- return topicDetailsMapper(data);
- };
-
- return {
- topic: await getTopic()
- };
-};
diff --git
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte
index 6cb4c4673..0a4601ede 100644
---
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte
+++
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte
@@ -92,8 +92,10 @@
rowClass="grid grid-cols-[150px_1fr_1fr_1fr_1fr_1fr]"
data={topic.partitions}
hrefBuilder={(partition) =>
- typedRoute(
- `/dashboard/streams/${+(page.params.streamId ||
'')}/topics/${topic.id}/partitions/${partition.id}/messages`
+ resolve(
+ typedRoute(
+ `/dashboard/streams/${+(page.params.streamId ||
'')}/topics/${topic.id}/partitions/${partition.id}/messages`
+ )
)}
colNames={{
id: 'ID',
diff --git a/web/src/routes/dashboard/overview/+page.server.ts
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.ts
similarity index 69%
rename from web/src/routes/dashboard/overview/+page.server.ts
rename to
web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.ts
index 3eac60a15..78626a88a 100644
--- a/web/src/routes/dashboard/overview/+page.server.ts
+++
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.ts
@@ -17,20 +17,17 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
-import { statsMapper } from '$lib/domain/Stats';
+import { clientApi } from '$lib/api/clientApi';
+import { topicDetailsMapper } from '$lib/domain/TopicDetails';
+import type { PageLoad } from './$types';
-export const load = async ({ cookies }) => {
- const result = await fetchIggyApi({
- path: '/stats',
+export const load: PageLoad = async ({ params }) => {
+ const data = await clientApi({
method: 'GET',
- cookies
+ path: `/streams/${+params.streamId}/topics/${+params.topicId}`
});
- const { data } = await handleFetchErrors(result, cookies);
-
return {
- stats: statsMapper(data)
+ topic: topicDetailsMapper(data)
};
};
diff --git
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.ts
similarity index 79%
rename from
web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts
rename to
web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.ts
index 7bf5ba315..b5bdcec87 100644
---
a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts
+++
b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.ts
@@ -17,22 +17,21 @@
* under the License.
*/
-import { fetchIggyApi } from '$lib/api/fetchApi';
-import { handleFetchErrors } from '$lib/api/handleFetchErrors';
+import { clientApi } from '$lib/api/clientApi';
import { partitionMessagesDetailsMapper } from '$lib/domain/MessageDetails';
import { topicDetailsMapper } from '$lib/domain/TopicDetails';
+import type { PageLoad } from './$types';
const MESSAGES_PER_PAGE = 20;
-export const load = async ({ params, cookies, url }) => {
- const offset = url.searchParams.get('offset') || '0';
+export const load: PageLoad = async ({ params, url }) => {
const direction = url.searchParams.get('direction') || 'desc';
const getPartitionMessages = async () => {
- const initialResult = await fetchIggyApi({
+ // First, get the initial offset to determine total messages
+ const initialData = await clientApi<any>({
method: 'GET',
path: `/streams/${+params.streamId}/topics/${+params.topicId}/messages`,
- cookies,
queryParams: {
kind: 'offset',
value: '0',
@@ -42,18 +41,15 @@ export const load = async ({ params, cookies, url }) => {
}
});
- const { data: initialData } = await handleFetchErrors(initialResult,
cookies);
- const initialMessages = partitionMessagesDetailsMapper(initialData as any);
-
+ const initialMessages = partitionMessagesDetailsMapper(initialData);
const totalMessages = initialMessages.currentOffset + 1;
const offset =
url.searchParams.get('offset') ??
(direction === 'desc' ? Math.max(0, totalMessages -
MESSAGES_PER_PAGE).toString() : '0');
- const result = await fetchIggyApi({
+ const data = await clientApi<any>({
method: 'GET',
path: `/streams/${+params.streamId}/topics/${+params.topicId}/messages`,
- cookies,
queryParams: {
kind: 'offset',
value: offset.toString(),
@@ -63,23 +59,22 @@ export const load = async ({ params, cookies, url }) => {
}
});
- const { data } = await handleFetchErrors(result, cookies);
- return partitionMessagesDetailsMapper(data as any);
+ return partitionMessagesDetailsMapper(data);
};
const getTopic = async () => {
- const result = await fetchIggyApi({
+ const data = await clientApi({
method: 'GET',
- path: `/streams/${+params.streamId}/topics/${+params.topicId}`,
- cookies
+ path: `/streams/${+params.streamId}/topics/${+params.topicId}`
});
- const { data } = await handleFetchErrors(result, cookies);
return topicDetailsMapper(data);
};
const [partitionMessages, topic] = await
Promise.all([getPartitionMessages(), getTopic()]);
+ const offset = url.searchParams.get('offset') || '0';
+
return {
partitionMessages,
topic,
diff --git a/web/svelte.config.js b/web/svelte.config.js
index 34cdf47c8..0685bbe46 100644
--- a/web/svelte.config.js
+++ b/web/svelte.config.js
@@ -17,15 +17,28 @@
* under the License.
*/
-import adapter from '@sveltejs/adapter-node';
+import adapterNode from '@sveltejs/adapter-node';
+import adapterStatic from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+// Use static adapter when STATIC_BUILD env is set (for embedding in Rust
server)
+const useStaticAdapter = process.env.STATIC_BUILD === 'true';
+
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
- adapter: adapter({
- out: 'build'
- }),
+ adapter: useStaticAdapter
+ ? adapterStatic({
+ pages: 'build/static',
+ assets: 'build/static',
+ fallback: 'index.html'
+ })
+ : adapterNode({
+ out: 'build'
+ }),
+ paths: {
+ base: useStaticAdapter ? '/ui' : ''
+ },
csrf: {
trustedOrigins: ['*']
}