Compare commits

...

3 Commits

Author SHA1 Message Date
mbecker20
0873104b5a fmt 2025-08-31 19:13:14 -07:00
Maxwell Becker
9a7b6ebd51 1.19.2 (#764)
* 1.19.2-dev-0

* deploy 1.19.2-dev-1

* Add option to make run command detachable (#766)

* improve missing files log to include the missing paths

* bump mungos for urlencoding mongo creds

* Update permissioning.md - typo: "priviledges" -> "privileges" (#770)

* Add support for monaco-yaml and docker compose spec validatiaon (#772)

* deploy 1.19.2-dev-2

* on delete user, remove from all user groups

* fix Google login issues around `picture`

* unsafe_unsanitized_startup_config

* improve git provider support re #355

* should fix #468

* should fix exit code re #597

* deploy 1.19.2-dev-3

* fix container ports sorting (#776)

* missing serde default

* deploy 1.19.2-dev-4

* ensure git tokens trimmed in remote url

* Add link to Authentik support docs

* Fix incorrect commit branch when using linked repo re #634

* Better display container port ranges (#786)

* ensure build and sync also commit to correct branch. re #634

* deploy 1.19.2-dev-5

* Improve login form (#788)

* Use proper form for login, add autocomplete and names to input fields

* Do not return null if loading

* Remove unused function

* Cleanup and streamline

* improve login screen flash on reload

* first builder given same name as first server

* 1.19.2

---------

Co-authored-by: mbecker20 <max@mogh.tech>
Co-authored-by: Brian Bradley <brian.bradley.p@gmail.com>
Co-authored-by: Ravi Wolter-Krishan <rkn@gedikas.net>
Co-authored-by: Christopher Hoage <iam@chrishoage.com>
Co-authored-by: jack <45038833+jackra1n@users.noreply.github.com>
2025-08-31 19:08:45 -07:00
mbecker20
a4153fa28b fix UI showing Redeploy when its actually None 2025-08-24 14:30:43 -07:00
40 changed files with 2400 additions and 200 deletions

165
Cargo.lock generated
View File

@@ -118,10 +118,12 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "async-compression"
version = "0.4.27"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75"
dependencies = [
"compression-codecs",
"compression-core",
"flate2",
"futures-core",
"memchr",
@@ -267,9 +269,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.160.0"
version = "1.161.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4300d66be2c4e275d3a037576dacce6efaf9af24bfbd2460a848ea39299c979c"
checksum = "239393d2a430bd37a7a39d8467674b816f1c864e4bfe874b01a0c9e0d49be4ff"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -744,7 +746,7 @@ version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cexpr",
"clang-sys",
"itertools 0.12.1",
@@ -769,9 +771,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.2"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]]
name = "bitvec"
@@ -861,7 +863,7 @@ dependencies = [
"getrandom 0.2.16",
"getrandom 0.3.3",
"hex",
"indexmap 2.10.0",
"indexmap 2.11.0",
"js-sys",
"once_cell",
"rand 0.9.2",
@@ -905,7 +907,7 @@ dependencies = [
[[package]]
name = "cache"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"tokio",
@@ -913,9 +915,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.33"
version = "1.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
dependencies = [
"jobserver",
"libc",
@@ -1072,19 +1074,38 @@ dependencies = [
[[package]]
name = "command"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"komodo_client",
"run_command",
"svi",
]
[[package]]
name = "compression-codecs"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905"
dependencies = [
"compression-core",
"flate2",
"futures-core",
"memchr",
"pin-project-lite",
]
[[package]]
name = "compression-core"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01"
[[package]]
name = "config"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"colored",
"indexmap 2.10.0",
"indexmap 2.11.0",
"regex",
"serde",
"serde_json",
@@ -1193,7 +1214,7 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"crossterm_winapi",
"parking_lot 0.12.4",
"rustix",
@@ -1321,7 +1342,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "database"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"async-compression",
@@ -1620,7 +1641,7 @@ dependencies = [
[[package]]
name = "environment"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"formatting",
@@ -1630,7 +1651,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"thiserror 2.0.16",
]
@@ -1720,7 +1741,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"serror",
]
@@ -1882,7 +1903,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"cache",
@@ -1923,7 +1944,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.10.0",
"indexmap 2.11.0",
"slab",
"tokio",
"tokio-util",
@@ -1942,7 +1963,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.3.1",
"indexmap 2.10.0",
"indexmap 2.11.0",
"slab",
"tokio",
"tokio-util",
@@ -2458,9 +2479,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
@@ -2490,7 +2511,7 @@ dependencies = [
[[package]]
name = "interpolate"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"komodo_client",
@@ -2499,11 +2520,11 @@ dependencies = [
[[package]]
name = "io-uring"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cfg-if",
"libc",
]
@@ -2586,9 +2607,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.33"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"libc",
@@ -2621,7 +2642,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"chrono",
@@ -2646,7 +2667,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2658,7 +2679,7 @@ dependencies = [
"derive_variants",
"envy",
"futures",
"indexmap 2.10.0",
"indexmap 2.11.0",
"ipnetwork",
"mongo_indexed",
"partial_derive2",
@@ -2675,12 +2696,13 @@ dependencies = [
"tokio-util",
"tracing",
"typeshare",
"urlencoding",
"uuid",
]
[[package]]
name = "komodo_core"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"arc-swap",
@@ -2712,7 +2734,7 @@ dependencies = [
"git",
"hex",
"hmac",
"indexmap 2.10.0",
"indexmap 2.11.0",
"interpolate",
"jsonwebtoken",
"komodo_client",
@@ -2750,7 +2772,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"arc-swap",
@@ -2872,7 +2894,7 @@ dependencies = [
[[package]]
name = "logger"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"komodo_client",
@@ -3037,9 +3059,9 @@ dependencies = [
[[package]]
name = "mongodb"
version = "3.2.4"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f8c69f13acf07eae386a2974f48ffd9187ea2aba8defbea9aa34e7e272c5f3"
checksum = "f6e3788f35159bbcf461227af84711950525343b64451fdd90de4e237a0b8a13"
dependencies = [
"async-trait",
"base64 0.13.1",
@@ -3086,9 +3108,9 @@ dependencies = [
[[package]]
name = "mongodb-internal-macros"
version = "3.2.4"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9202de265a3a8bbb43f9fe56db27c93137d4f9fb04c093f47e9c7de0c61ac7d"
checksum = "fdfb13b7c0436b3e395f4ce5f4366431b9c9db47adc4cb33afc6b5b913d44969"
dependencies = [
"macro_magic",
"proc-macro2",
@@ -3098,9 +3120,9 @@ dependencies = [
[[package]]
name = "mungos"
version = "3.2.1"
version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc7db433516e852091e2ede8cd755a717223a359b519b22710e56225a5d6a9"
checksum = "f77d8548efe9a10b49e1c92fa719d2e4e27cc6b40936f05fbb0f6a7ac31d3c0f"
dependencies = [
"anyhow",
"envy",
@@ -3108,6 +3130,7 @@ dependencies = [
"mongodb",
"serde",
"serde_derive",
"urlencoding",
]
[[package]]
@@ -3116,7 +3139,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cfg-if",
"cfg_aliases 0.1.1",
"libc",
@@ -3259,7 +3282,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]
@@ -3614,7 +3637,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "periphery_client"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"komodo_client",
@@ -3947,7 +3970,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]
@@ -3972,9 +3995,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
@@ -3984,9 +4007,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
@@ -3995,15 +4018,15 @@ dependencies = [
[[package]]
name = "regex-lite"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30"
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "reqwest"
@@ -4145,7 +4168,7 @@ dependencies = [
[[package]]
name = "response"
version = "1.19.1"
version = "1.19.2"
dependencies = [
"anyhow",
"axum",
@@ -4259,7 +4282,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"errno",
"libc",
"linux-raw-sys",
@@ -4477,7 +4500,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
@@ -4490,7 +4513,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
@@ -4569,7 +4592,7 @@ version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [
"indexmap 2.10.0",
"indexmap 2.11.0",
"itoa",
"memchr",
"ryu",
@@ -4648,7 +4671,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.10.0",
"indexmap 2.11.0",
"schemars 0.9.0",
"schemars 1.0.4",
"serde",
@@ -4676,7 +4699,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
dependencies = [
"indexmap 2.10.0",
"indexmap 2.11.0",
"itoa",
"ryu",
"serde",
@@ -5005,7 +5028,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"core-foundation 0.9.4",
"system-configuration-sys",
]
@@ -5257,7 +5280,7 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"indexmap 2.10.0",
"indexmap 2.11.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -5338,7 +5361,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"indexmap 2.10.0",
"indexmap 2.11.0",
"pin-project-lite",
"slab",
"sync_wrapper",
@@ -5355,7 +5378,7 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"bytes",
"futures-core",
"futures-util",
@@ -5618,9 +5641,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.6"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
@@ -6242,9 +6265,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.12"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
[[package]]
name = "winreg"
@@ -6271,7 +6294,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "1.19.1"
version = "1.19.2"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -43,7 +43,7 @@ derive_variants = "1.0.0"
mongo_indexed = "2.0.2"
resolver_api = "3.0.0"
toml_pretty = "1.2.0"
mungos = "3.2.1"
mungos = "3.2.2"
svi = "1.2.0"
# ASYNC
@@ -65,9 +65,10 @@ axum = { version = "0.8.4", features = ["ws", "json", "macros"] }
# SER/DE
ipnetwork = { version = "0.21.1", features = ["serde"] }
indexmap = { version = "2.10.0", features = ["serde"] }
indexmap = { version = "2.11.0", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive"] }
strum = { version = "0.27.2", features = ["derive"] }
bson = { version = "2.15.0" } # must keep in sync with mongodb version
serde_yaml_ng = "0.10.0"
serde_json = "1.0.143"
serde_qs = "0.15.0"
@@ -112,7 +113,7 @@ sysinfo = "0.37.0"
# CLOUD
aws-config = "1.8.5"
aws-sdk-ec2 = "1.160.0"
aws-sdk-ec2 = "1.161.0"
aws-credential-types = "1.2.5"
## CRON
@@ -122,7 +123,7 @@ chrono = "0.4.41"
croner = "3.0.0"
# MISC
async-compression = { version = "0.4.27", features = ["tokio", "gzip"] }
async-compression = { version = "0.4.28", features = ["tokio", "gzip"] }
derive_builder = "0.20.2"
comfy-table = "7.1.4"
typeshare = "1.0.4"
@@ -130,7 +131,6 @@ octorust = "0.10.0"
dashmap = "6.1.0"
wildcard = "0.3.0"
colored = "3.0.0"
regex = "1.11.1"
regex = "1.11.2"
bytes = "1.10.1"
bson = "2.15.0"
shell-escape = "0.1.5"

View File

@@ -1033,6 +1033,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
command: self.command,
no_tty: self.no_tty,
no_deps: self.no_deps,
detach: self.detach,
service_ports: self.service_ports,
env: self.env,
workdir: self.workdir,

View File

@@ -186,7 +186,7 @@ async fn write_dockerfile_contents_git(
) -> serror::Result<Update> {
let WriteBuildFileContents { build: _, contents } = req;
let mut clone_args: RepoExecutionArgs = if !build
let mut repo_args: RepoExecutionArgs = if !build
.config
.files_on_host
&& !build.config.linked_repo.is_empty()
@@ -196,8 +196,8 @@ async fn write_dockerfile_contents_git(
} else {
(&build).into()
};
let root = clone_args.unique_path(&core_config().repo_directory)?;
clone_args.destination = Some(root.display().to_string());
let root = repo_args.unique_path(&core_config().repo_directory)?;
repo_args.destination = Some(root.display().to_string());
let build_path = build
.config
@@ -220,11 +220,11 @@ async fn write_dockerfile_contents_git(
})?;
}
let access_token = if let Some(account) = &clone_args.account {
git_token(&clone_args.provider, account, |https| clone_args.https = https)
let access_token = if let Some(account) = &repo_args.account {
git_token(&repo_args.provider, account, |https| repo_args.https = https)
.await
.with_context(
|| format!("Failed to get git token in call to db. Stopping run. | {} | {account}", clone_args.provider),
|| format!("Failed to get git token in call to db. Stopping run. | {} | {account}", repo_args.provider),
)?
} else {
None
@@ -235,7 +235,7 @@ async fn write_dockerfile_contents_git(
if !root.join(".git").exists() {
git::init_folder_as_repo(
&root,
&clone_args,
&repo_args,
access_token.as_deref(),
&mut update.logs,
)
@@ -249,9 +249,11 @@ async fn write_dockerfile_contents_git(
}
}
// Save this for later -- repo_args moved next.
let branch = repo_args.branch.clone();
// Pull latest changes to repo to ensure linear commit history
match git::pull_or_clone(
clone_args,
repo_args,
&core_config().repo_directory,
access_token,
)
@@ -298,7 +300,7 @@ async fn write_dockerfile_contents_git(
&format!("{}: Commit Dockerfile", args.user.username),
&root,
&build_path.join(&dockerfile_path),
&build.config.branch,
&branch,
)
.await;

View File

@@ -284,6 +284,8 @@ async fn write_stack_file_contents_git(
}
}
// Save this for later -- repo_args moved next.
let branch = repo_args.branch.clone();
// Pull latest changes to repo to ensure linear commit history
match git::pull_or_clone(
repo_args,
@@ -334,7 +336,7 @@ async fn write_stack_file_contents_git(
&format!("{username}: Write Stack File"),
&root,
&file_path,
&stack.config.branch,
&branch,
)
.await;

View File

@@ -323,6 +323,8 @@ async fn write_sync_file_contents_git(
}
}
// Save this for later -- repo_args moved next.
let branch = repo_args.branch.clone();
// Pull latest changes to repo to ensure linear commit history
match git::pull_or_clone(
repo_args,
@@ -373,7 +375,7 @@ async fn write_sync_file_contents_git(
&format!("{}: Commit Resource File", args.user.username),
&root,
&resource_path.join(&file_path),
&sync.config.branch,
&branch,
)
.await;

View File

@@ -220,6 +220,14 @@ impl Resolve<WriteArgs> for DeleteUser {
.delete_one(query)
.await
.context("Failed to delete user from database")?;
// Also remove user id from all user groups
if let Err(e) = db
.user_groups
.update_many(doc! {}, doc! { "$pull": { "users": &user.id } })
.await
{
warn!("Failed to remove deleted user from user groups | {e:?}");
};
Ok(user)
}
}

View File

@@ -187,8 +187,8 @@ impl GoogleOauthClient {
Ok(body)
} else {
let text = res.text().await.context(format!(
"method: POST | status: {status} | failed to get response text"
))?;
"method: POST | status: {status} | failed to get response text"
))?;
Err(anyhow!("method: POST | status: {status} | text: {text}"))
}
}
@@ -207,5 +207,6 @@ pub struct GoogleUser {
#[serde(rename = "sub")]
pub id: String,
pub email: String,
#[serde(default)]
pub picture: String,
}

View File

@@ -250,6 +250,7 @@ pub fn core_config() -> &'static CoreConfig {
.unwrap_or(config.logging.opentelemetry_service_name),
},
pretty_startup_config: env.komodo_pretty_startup_config.unwrap_or(config.pretty_startup_config),
unsafe_unsanitized_startup_config: env.komodo_unsafe_unsanitized_startup_config.unwrap_or(config.unsafe_unsanitized_startup_config),
internet_interface: env.komodo_internet_interface.unwrap_or(config.internet_interface),
ssl_enabled: env.komodo_ssl_enabled.unwrap_or(config.ssl_enabled),
ssl_key_file: env.komodo_ssl_key_file.unwrap_or(config.ssl_key_file),

View File

@@ -45,10 +45,14 @@ async fn app() -> anyhow::Result<()> {
info!("Komodo Core version: v{}", env!("CARGO_PKG_VERSION"));
if core_config().pretty_startup_config {
info!("{:#?}", config.sanitized());
} else {
info!("{:?}", config.sanitized());
match (
config.pretty_startup_config,
config.unsafe_unsanitized_startup_config,
) {
(true, true) => info!("{:#?}", config),
(true, false) => info!("{:#?}", config.sanitized()),
(false, true) => info!("{:?}", config),
(false, false) => info!("{:?}", config.sanitized()),
}
// Init jwt client to crash on failure

View File

@@ -81,7 +81,7 @@ pub async fn update_deployment_cache(
// If image already has tag, leave it,
// otherwise default the tag to latest
if image.contains(':') {
image
image.to_string()
} else {
format!("{image}:latest")
}
@@ -92,6 +92,9 @@ pub async fn update_deployment_cache(
..
}) = &container
{
// Docker will automatically strip `docker.io` from incoming image names re #468.
// Need to strip it in order to match by image name and find available updates.
let image = image.strip_prefix("docker.io/").unwrap_or(&image);
images
.iter()
.find(|i| i.name == image)
@@ -250,20 +253,21 @@ pub async fn update_stack_cache(
}
}.is_match(&container.name)
}).cloned();
// If image already has tag, leave it,
// otherwise default the tag to latest
let image = image.clone();
let image = if image.contains(':') {
image
image.to_string()
} else {
image + ":latest"
format!("{image}:latest")
};
let update_available = if let Some(ContainerListItem { image_id: Some(curr_image_id), .. }) = &container {
// Docker will automatically strip `docker.io` from incoming image names re #468.
// Need to strip it in order to match by image tag and find available update.
let image =
image.strip_prefix("docker.io/").unwrap_or(&image);
images
.iter()
.find(|i| i.name == image)
.map(|i| &i.id != curr_image_id)
.unwrap_or_default()
.iter()
.find(|i| i.name == image)
.map(|i| &i.id != curr_image_id)
.unwrap_or_default()
} else {
false
};

View File

@@ -242,7 +242,7 @@ async fn ensure_first_server_and_builder() {
return;
};
if let Err(e) = (CreateBuilder {
name: String::from("Local"),
name: config.first_server_name.clone(),
config: PartialBuilderConfig::Server(
PartialServerBuilderConfig {
server_id: Some(server.id),

View File

@@ -712,6 +712,7 @@ impl Resolve<super::Args> for ComposeRun {
command,
no_tty,
no_deps,
detach,
service_ports,
env,
workdir,
@@ -783,6 +784,9 @@ impl Resolve<super::Args> for ComposeRun {
}
let mut run_flags = String::from(" --rm");
if detach.unwrap_or_default() {
run_flags.push_str(" -d");
}
if no_tty.unwrap_or_default() {
run_flags.push_str(" --no-tty");
}

View File

@@ -347,7 +347,7 @@ async fn execute_command_on_terminal(
);
let full_command = format!(
"printf '\n{START_OF_OUTPUT}\n\n'; {command}; rc=$? printf '\n{KOMODO_EXIT_CODE}%d\n{END_OF_OUTPUT}\n' \"$rc\"\n"
"printf '\n{START_OF_OUTPUT}\n\n'; {command}; rc=$?; printf '\n{KOMODO_EXIT_CODE}%d\n{END_OF_OUTPUT}\n' \"$rc\"\n"
);
terminal

View File

@@ -43,8 +43,9 @@ pub async fn validate_files(
"Validate Files",
format_serror(
&anyhow!(
"Ensure the run_directory and all file paths are correct."
"Missing files: {}", res.missing_files.join(", ")
)
.context("Ensure the run_directory and all file paths are correct.")
.context("A file doesn't exist after writing stack.")
.into(),
),

View File

@@ -2,7 +2,8 @@ use std::{cmp::Ordering, sync::OnceLock};
use async_timing_util::wait_until_timelength;
use komodo_client::entities::stats::{
SingleDiskUsage, SystemInformation, SystemLoadAverage, SystemProcess, SystemStats,
SingleDiskUsage, SystemInformation, SystemLoadAverage,
SystemProcess, SystemStats,
};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
use tokio::sync::RwLock;

View File

@@ -28,6 +28,7 @@ resolver_api.workspace = true
# external
tokio-tungstenite.workspace = true
derive_builder.workspace = true
urlencoding.workspace = true
serde_json.workspace = true
tokio-util.workspace = true
thiserror.workspace = true

View File

@@ -379,6 +379,9 @@ pub struct RunStackService {
/// Do not start linked services
#[arg(long = "no-deps", action = SetTrue)]
pub no_deps: Option<bool>,
/// Detach container on run
#[arg(long = "detach", action = SetTrue)]
pub detach: Option<bool>,
/// Map service ports to the host
#[arg(long = "service-ports", action = SetTrue)]
pub service_ports: Option<bool>,

View File

@@ -125,6 +125,8 @@ pub struct Env {
pub komodo_logging_opentelemetry_service_name: Option<String>,
/// Override `pretty_startup_config`
pub komodo_pretty_startup_config: Option<bool>,
/// Override `unsafe_unsanitized_startup_config`
pub komodo_unsafe_unsanitized_startup_config: Option<bool>,
/// Override `transparent_mode`
pub komodo_transparent_mode: Option<bool>,
@@ -509,6 +511,12 @@ pub struct CoreConfig {
#[serde(default)]
pub pretty_startup_config: bool,
/// Unsafe: logs unsanitized config on startup,
/// in order to verify everything is being
/// passed correctly.
#[serde(default)]
pub unsafe_unsanitized_startup_config: bool,
// ===========
// = Pruning =
// ===========
@@ -729,6 +737,7 @@ impl Default for CoreConfig {
github_webhook_app: Default::default(),
logging: Default::default(),
pretty_startup_config: Default::default(),
unsafe_unsanitized_startup_config: Default::default(),
keep_stats_for_days: default_prune_days(),
keep_alerts_for_days: default_prune_days(),
resource_poll_interval: default_poll_interval(),
@@ -772,6 +781,8 @@ impl CoreConfig {
keep_alerts_for_days: config.keep_alerts_for_days,
logging: config.logging,
pretty_startup_config: config.pretty_startup_config,
unsafe_unsanitized_startup_config: config
.unsafe_unsanitized_startup_config,
transparent_mode: config.transparent_mode,
ui_write_disabled: config.ui_write_disabled,
disable_confirm_dialog: config.disable_confirm_dialog,

View File

@@ -511,8 +511,17 @@ impl RepoExecutionArgs {
&self,
access_token: Option<&str>,
) -> anyhow::Result<String> {
let access_token_at = match &access_token {
Some(token) => format!("token:{token}@"),
let access_token_at = match access_token {
Some(token) => match token.split_once(':') {
Some((username, token)) => format!(
"{}:{}@",
urlencoding::encode(username.trim()),
urlencoding::encode(token.trim())
),
None => {
format!("token:{}@", urlencoding::encode(token.trim()))
}
},
None => String::new(),
};
let protocol = if self.https { "https" } else { "http" };

View File

@@ -191,4 +191,4 @@ pub struct SystemLoadAverage {
pub five: f64,
/// 15m load average
pub fifteen: f64,
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "komodo_client",
"version": "1.19.1",
"version": "1.19.2",
"description": "Komodo client package",
"homepage": "https://komo.do",
"main": "dist/lib.js",

View File

@@ -7713,6 +7713,8 @@ export interface RunStackService {
no_tty?: boolean;
/** Do not start linked services */
no_deps?: boolean;
/** Detach container on run */
detach?: boolean;
/** Map service ports to the host */
service_ports?: boolean;
/** Extra environment variables for the run */

View File

@@ -272,6 +272,9 @@ pub struct ComposeRun {
/// Do not start linked services
#[serde(default)]
pub no_deps: Option<bool>,
/// Detach container on run
#[serde(default)]
pub detach: Option<bool>,
/// Map service ports to the host
#[serde(default)]
pub service_ports: Option<bool>,

View File

@@ -71,7 +71,7 @@ permissions = [
## Administration
Users can be given Admin priviledges by a `Super Admin` (only the first user is given this status, set with `super_admin: true` on a User document in database). Super admins will see the "Make Admin" button when on a User page `/users/${user_id}`.
Users can be given Admin privileges by a `Super Admin` (only the first user is given this status, set with `super_admin: true` on a User document in database). Super admins will see the "Make Admin" button when on a User page `/users/${user_id}`.
These users have unrestricted access to all Komodo Resources. Additionally, these users can update other (non-admin) user's permissions on resources.
@@ -82,4 +82,4 @@ Users also have some configurable global permissions, these are:
- create server permission
- create build permission
Only users with these permissions (as well as admins) can add additional servers to Komodo, and can create additional builds, respectively.
Only users with these permissions (as well as admins) can add additional servers to Komodo, and can create additional builds, respectively.

View File

@@ -14,6 +14,10 @@ Komodo also supports self hosted Oauth2 providers like [Authentik](https://docs.
- `<KOMODO_HOST>/auth/google/callback` for Google.
- `<KOMODO_HOST>/auth/oidc/callback` for OIDC.
### Authentik
Check out the [Authentik official support documentation](https://integrations.goauthentik.io/infrastructure/komodo/).
### Keycloak
- Create an [OIDC client](https://www.keycloak.org/docs/latest/server_admin/index.html#proc-creating-oidc-client_server_administration_guide) in Keycloak.
- Note down the `Client ID` that you enter (e.g.: "komodo"), you will need it for Komodo configuration

View File

@@ -40,6 +40,7 @@
"jotai": "2.12.5",
"lucide-react": "0.511.0",
"monaco-editor": "0.52.2",
"monaco-yaml": "5.4.0",
"prettier": "3.5.3",
"react": "19.1.0",
"react-charts": "3.0.0-beta.57",

View File

@@ -7306,6 +7306,8 @@ export interface RunStackService {
no_tty?: boolean;
/** Do not start linked services */
no_deps?: boolean;
/** Detach container on run */
detach?: boolean;
/** Map service ports to the host */
service_ports?: boolean;
/** Extra environment variables for the run */

File diff suppressed because it is too large Load Diff

View File

@@ -56,12 +56,14 @@ export const MonacoEditor = ({
onValueChange,
language: _language,
readOnly,
filename,
minHeight,
className,
}: {
value: string | undefined;
onValueChange?: (value: string) => void;
language: MonacoLanguage | undefined;
filename?: string;
readOnly?: boolean;
minHeight?: number;
className?: string;
@@ -170,6 +172,7 @@ export const MonacoEditor = ({
language={language}
value={value}
theme={theme}
defaultPath={filename ? `file:///${filename}` : undefined}
options={options}
onChange={(v) => onValueChange?.(v ?? "")}
onMount={(editor) => setEditor(editor)}

View File

@@ -1132,6 +1132,7 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
command: undefined,
no_tty: undefined,
no_deps: undefined,
detach: undefined,
service_ports: undefined,
env: undefined,
workdir: undefined,
@@ -1151,6 +1152,7 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
);
const [no_tty, setNoTty] = useState(!!params.no_tty);
const [no_deps, setNoDeps] = useState(!!params.no_deps);
const [detach, setDetach] = useState(!!params.detach);
const [service_ports, setServicePorts] = useState(!!params.service_ports);
const [workdir, setWorkdir] = useState(params.workdir ?? "");
const [user, setUser] = useState(params.user ?? "");
@@ -1175,6 +1177,7 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
);
setNoTty(!!params.no_tty);
setNoDeps(!!params.no_deps);
setDetach(!!params.detach);
setServicePorts(!!params.service_ports);
setWorkdir(params.workdir ?? "");
setUser(params.user ?? "");
@@ -1216,6 +1219,7 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
user: user || undefined,
entrypoint: entrypoint || undefined,
pull: pull ? true : undefined,
detach: detach ? true : undefined,
env,
} as any);
setOpen(false);
@@ -1273,6 +1277,10 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
<Switch checked={no_deps} onCheckedChange={setNoDeps} />
<span className="text-sm">No Dependencies</span>
</label>
<label className="flex items-center gap-2">
<Switch checked={detach} onCheckedChange={setDetach} />
<span className="text-sm">Detach</span>
</label>
<label className="flex items-center gap-2">
<Switch
checked={service_ports}

View File

@@ -879,6 +879,7 @@ export const StackConfig = ({
value={
show_default ? DEFAULT_STACK_FILE_CONTENTS : file_contents
}
filename="compose.yaml"
onValueChange={(file_contents) => set({ file_contents })}
language="yaml"
readOnly={disabled}
@@ -1004,7 +1005,7 @@ const ConfigFiles = ({
/>
<RequiresSelector
requires={requires ?? Types.StackFileRequires.Redeploy}
requires={requires ?? Types.StackFileRequires.None}
set={(requires) => {
values[i] = { ...values[i], requires };
set({ config_files: [...values] });

View File

@@ -263,6 +263,7 @@ export const StackInfo = ({
value={edits[content.path] ?? content.contents}
language={language_from_path(content.path)}
readOnly={!canEdit}
filename={content.path}
onValueChange={editFileCallback(content.path)}
/>
</CardContent>

View File

@@ -93,24 +93,6 @@ import {
} from "@ui/select";
import { useServer } from "./resources/server";
export const WithLoading = ({
children,
isLoading,
loading,
isError,
error,
}: {
children: ReactNode;
isLoading: boolean;
loading?: ReactNode;
isError: boolean;
error?: ReactNode;
}) => {
if (isLoading) return <>{loading ?? "loading"}</>;
if (isError) return <>{error ?? null}</>;
return <>{children}</>;
};
export const ActionButton = forwardRef<
HTMLButtonElement,
{
@@ -916,6 +898,23 @@ export const DockerContainersSection = ({
{
accessorKey: "ports.0",
size: 200,
sortingFn: (a, b) => {
const getMinHostPort = (row: typeof a) => {
const ports = row.original.ports ?? [];
if (!ports.length) return Number.POSITIVE_INFINITY;
const nums = ports
.map((p) => p.PublicPort)
.filter((p): p is number => typeof p === "number")
.map((n) => Number(n));
if (!nums.length || nums.some((n) => Number.isNaN(n))) {
return Number.POSITIVE_INFINITY;
}
return Math.min(...nums);
};
const pa = getMinHostPort(a);
const pb = getMinHostPort(b);
return pa === pb ? 0 : pa > pb ? 1 : -1;
},
header: ({ column }) => (
<SortableHeader column={column} title="Ports" />
),
@@ -1203,6 +1202,35 @@ export const TemplateQueryBehaviorSelector = () => {
);
};
export type ServerAddress = {
raw: string;
protocol: "http:" | "https:";
hostname: string;
};
export const useServerAddress = (
server_id: string | undefined
): ServerAddress | null => {
const server = useServer(server_id);
if (!server) return null;
const base = server.info.external_address || server.info.address;
const parsed = (() => {
try {
return new URL(base);
} catch {
return new URL("http://" + base);
}
})();
return {
raw: base,
protocol: parsed.protocol === "https:" ? "https:" : "http:",
hostname: parsed.hostname,
};
};
export const ContainerPortLink = ({
host_port,
ports,
@@ -1212,19 +1240,29 @@ export const ContainerPortLink = ({
ports: Types.Port[];
server_id: string | undefined;
}) => {
const server = useServer(server_id);
// Get the server address with periphery port removed
const server_address = server?.info.external_address
? server.info.external_address
: server?.info.address
.split(":")
// take just protocol and dns (indexes 0 and 1)
.filter((_, i) => i < 2)
.join(":");
const link =
host_port === "443"
? server_address
: server_address?.replace("https", "http") + ":" + host_port;
const server_address = useServerAddress(server_id);
if (!server_address) return null;
const isHttps = server_address.protocol === "https:";
const link = host_port === "443" && isHttps
? `https://${server_address.hostname}`
: `http://${server_address.hostname}:${host_port}`;
const uniqueHostPorts = Array.from(
new Set(
ports
.map((p) => p.PublicPort)
.filter((p): p is number => typeof p === "number")
.map((n) => Number(n))
.filter((n) => !Number.isNaN(n))
)
).sort((a, b) => a - b);
const display_text =
uniqueHostPorts.length <= 1
? String(uniqueHostPorts[0] ?? host_port)
: `${uniqueHostPorts[0]}-${uniqueHostPorts[uniqueHostPorts.length - 1]}`;
return (
<Tooltip>
<TooltipTrigger>
@@ -1236,7 +1274,7 @@ export const ContainerPortLink = ({
<EthernetPort
className={cn("w-4 h-4", stroke_color_class_by_intention("Good"))}
/>
{host_port}
{display_text}
</a>
</TooltipTrigger>
<TooltipContent className="flex flex-col gap-2 w-fit">
@@ -1248,12 +1286,18 @@ export const ContainerPortLink = ({
<LinkIcon className="w-3 h-3" />
{link}
</a>
{ports.map((port, i) => (
{ports.slice(0, 10).map((port, i) => (
<div key={i} className="flex gap-2 text-sm text-muted-foreground">
<div>-</div>
<span>-</span>
<div>{fmt_port_mount(port)}</div>
</div>
))}
{ports.length > 10 && (
<div className="flex gap-2 text-sm text-muted-foreground">
<span>+</span>
<div>{ports.length - 10} more</div>
</div>
)}
</TooltipContent>
</Tooltip>
);
@@ -1266,24 +1310,38 @@ export const ContainerPortsTableView = ({
ports: Types.Port[];
server_id: string | undefined;
}) => {
const map = useContainerPortsMap(ports);
const host_ports = Object.keys(map);
const portsMap = useContainerPortsMap(ports);
const sortedNumericPorts = Object.keys(portsMap)
.map(Number)
.filter((port) => !Number.isNaN(port))
.sort((a, b) => a - b);
type Group = { start: number; end: number; ports: Types.Port[] };
const groupedPorts = sortedNumericPorts.reduce<Group[]>((acc, port) => {
const lastGroup = acc[acc.length - 1];
const currentPorts = portsMap[String(port)] || [];
if (lastGroup && port === lastGroup.end + 1) {
lastGroup.end = port;
lastGroup.ports.push(...currentPorts);
} else {
acc.push({ start: port, end: port, ports: currentPorts });
}
return acc;
}, []);
return (
<div className="flex items-center gap-x-1 flex-wrap">
{host_ports.map((host_port, i) => {
return (
<Fragment key={host_port}>
<ContainerPortLink
host_port={host_port}
ports={map[host_port]}
server_id={server_id}
/>
{i !== host_ports.length - 1 && (
<div className="text-muted-foreground">|</div>
)}
</Fragment>
);
})}
{groupedPorts.map((group, i) => (
<Fragment key={group.start}>
{i > 0 && <span className="text-muted-foreground">|</span>}
<ContainerPortLink
host_port={String(group.start)}
ports={group.ports}
server_id={server_id}
/>
</Fragment>
))}
</div>
);
};

View File

@@ -4,6 +4,7 @@ import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import yamlWorker from "monaco-yaml/yaml.worker?worker";
self.MonacoEnvironment = {
getWorker(_, label) {
@@ -19,6 +20,9 @@ self.MonacoEnvironment = {
if (label === "typescript" || label === "javascript") {
return new tsWorker();
}
if (label === "yaml") {
return new yamlWorker();
}
return new editorWorker();
},
};
@@ -32,6 +36,6 @@ import "./theme";
import "./yaml";
import "./toml";
import "./fancy_toml";
import "./shell"
import "./shell";
import "./key_value";
import "./string_list";

View File

@@ -1,4 +1,5 @@
import * as monaco from "monaco-editor";
import { configureMonacoYaml } from "monaco-yaml";
// This is the one provided by Microsoft.
// https://github.com/microsoft/monaco-editor/blob/main/src/basic-languages/yaml/yaml.ts
@@ -252,11 +253,23 @@ const yaml_language = <monaco.languages.IMonarchLanguage>{
},
};
monaco.languages.register({ id: "yaml" });
configureMonacoYaml(monaco, {
enableSchemaRequest: true,
schemas: [
{
fileMatch: ["**/*compose.yml", "**/*compose.yaml"],
uri: new URL(
"/schema/compose-spec.json",
window.location.href
).toString(),
},
],
});
monaco.languages.register({ id: "yaml", aliases: ["yml"] });
monaco.languages.setMonarchTokensProvider("yaml", yaml_language);
monaco.languages.setLanguageConfiguration("yaml", yaml_conf);
/// V1
// const yaml_conf: monaco.languages.LanguageConfiguration = {
// comments: {
@@ -345,4 +358,4 @@ monaco.languages.setLanguageConfiguration("yaml", yaml_conf);
// monaco.languages.register({ id: "yaml" });
// monaco.languages.setMonarchTokensProvider("yaml", yaml_language);
// monaco.languages.setLanguageConfiguration("yaml", yaml_conf);
// monaco.languages.setLanguageConfiguration("yaml", yaml_conf);

View File

@@ -162,6 +162,23 @@ export default function ContainersPage() {
{
accessorKey: "ports.0",
size: 200,
sortingFn: (a, b) => {
const getMinHostPort = (row: typeof a) => {
const ports = row.original.ports ?? [];
if (!ports.length) return Number.POSITIVE_INFINITY;
const nums = ports
.map((p) => p.PublicPort)
.filter((p): p is number => typeof p === "number")
.map((n) => Number(n));
if (!nums.length || nums.some((n) => Number.isNaN(n))) {
return Number.POSITIVE_INFINITY;
}
return Math.min(...nums);
};
const pa = getMinHostPort(a);
const pb = getMinHostPort(b);
return pa === pb ? 0 : pa > pb ? 1 : -1;
},
header: ({ column }) => (
<SortableHeader column={column} title="Ports" />
),

View File

@@ -15,7 +15,7 @@ import {
useLoginOptions,
useUserInvalidate,
} from "@lib/hooks";
import { useState } from "react";
import { type FormEvent } from "react";
import { ThemeToggle } from "@ui/theme";
import { KOMODO_BASE_URL } from "@main";
import { KeyRound, X } from "lucide-react";
@@ -38,7 +38,6 @@ const login_with_oauth = (provider: OauthProvider) => {
export default function Login() {
const options = useLoginOptions().data;
const [creds, set] = useState({ username: "", password: "" });
const userInvalidate = useUserInvalidate();
const { toast } = useToast();
@@ -98,7 +97,18 @@ export default function Login() {
},
});
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
const username = String(fd.get("username") ?? "");
const password = String(fd.get("password") ?? "");
const action = String(fd.get("action") ?? "login");
if (action === "signup") {
signup({ username, password });
} else {
login({ username, password });
}
};
const no_auth_configured =
options !== undefined &&
@@ -110,7 +120,6 @@ export default function Login() {
return (
<div className="flex flex-col min-h-screen">
<div className="container flex justify-end items-center h-16">
{/* <img src="/komodo-512x512.png" className="w-[32px]" /> */}
<ThemeToggle />
</div>
<div
@@ -166,28 +175,28 @@ export default function Login() {
</div>
</CardHeader>
{options?.local && (
<>
<form
onSubmit={handleSubmit}
autoComplete="on"
>
<CardContent className="flex flex-col justify-center w-full gap-4">
<div className="flex flex-col gap-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
value={creds.username}
onChange={({ target }) =>
set((c) => ({ ...c, username: target.value }))
}
name="username"
autoComplete="username"
autoCapitalize="none"
autoCorrect="off"
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
name="password"
type="password"
value={creds.password}
onChange={({ target }) =>
set((c) => ({ ...c, password: target.value }))
}
onKeyDown={(e) => e.key === "Enter" && login(creds)}
autoComplete="current-password"
/>
</div>
</CardContent>
@@ -195,7 +204,9 @@ export default function Login() {
{show_sign_up && (
<Button
variant="outline"
onClick={() => signup(creds)}
type="submit"
name="action"
value="signup"
disabled={signupPending}
>
Sign Up
@@ -203,26 +214,27 @@ export default function Login() {
)}
<Button
variant="default"
onClick={() => login(creds)}
type="submit"
name="action"
value="login"
disabled={loginPending}
>
Log In
</Button>
</CardFooter>
</>
</form>
)}
{no_auth_configured && (
<CardContent className="w-full gap-2 text-muted-foreground text-sm">
No login methods have been configured. See the
<Button variant="link" className="text-sm py-0 px-1">
<a
href="https://github.com/moghtech/komodo/blob/main/config/core.config.toml"
target="_blank"
className="flex text-sm"
>
example config
</a>
</Button>
<a
href="https://github.com/moghtech/komodo/blob/main/config/core.config.toml"
target="_blank"
rel="noreferrer"
className="text-sm py-0 px-1 underline"
>
example config
</a>
for information on configuring auth.
</CardContent>
)}

View File

@@ -63,7 +63,7 @@ const useExchangeToken = () => {
};
export const Router = () => {
const { data: user, isLoading, error } = useUser();
const { data: user, error } = useUser();
// Handle exchange token loop to avoid showing login flash
const exchangeTokenPending = useExchangeToken();
@@ -75,9 +75,11 @@ export const Router = () => {
);
}
if (isLoading && !user) return null;
if (!user || error) return <Login />;
// Only how login once error indicating logged out state actually recieved
if (error) return <Login />;
// Don't display anything if !error and !user. This is loading state.
if (!user) return null;
// Don't try displaying pages if user disabled, will fail to load with many errors.
if (!user.enabled) return <UserDisabled />;
return (

View File

@@ -2197,6 +2197,11 @@ json5@^2.2.3:
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonc-parser@^3.0.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4"
integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==
keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
@@ -2288,6 +2293,49 @@ monaco-editor@0.52.2:
resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz"
integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==
monaco-languageserver-types@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz#8f3414c1ebba786b9c9db45857cc853e50e768e8"
integrity sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==
dependencies:
monaco-types "^0.1.0"
vscode-languageserver-protocol "^3.0.0"
vscode-uri "^3.0.0"
monaco-marker-data-provider@^1.0.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/monaco-marker-data-provider/-/monaco-marker-data-provider-1.2.4.tgz#56fbeede5bd830ec2ee15b2aea9a7df62fa447a7"
integrity sha512-4DsPgsAqpTyUDs3humXRBPUJoihTv+L6v9aupQWD80X2YXaCXUd11mWYeSCYHuPgdUmjFaNWCEOjQ6ewf/QA1Q==
dependencies:
monaco-types "^0.1.0"
monaco-types@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/monaco-types/-/monaco-types-0.1.0.tgz#3a3066aba499cb5923cd60efc736f3f14a169e10"
integrity sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==
monaco-worker-manager@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz#f67c54dfca34ed4b225d5de84e77b24b4e36de8a"
integrity sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==
monaco-yaml@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-5.4.0.tgz#c0ed6226c710c32c509e7c316c9a45f004862088"
integrity sha512-tuBVDy1KAPrgO905GHTItu8AaA5bIzF5S4X0JVRAE/D66FpRhkDUk7tKi5bwKMVTTugtpMLsXN4ewh4CgE/FtQ==
dependencies:
jsonc-parser "^3.0.0"
monaco-languageserver-types "^0.4.0"
monaco-marker-data-provider "^1.0.0"
monaco-types "^0.1.0"
monaco-worker-manager "^2.0.0"
path-browserify "^1.0.0"
prettier "^3.0.0"
vscode-languageserver-textdocument "^1.0.0"
vscode-languageserver-types "^3.0.0"
vscode-uri "^3.0.0"
yaml "^2.0.0"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
@@ -2380,6 +2428,11 @@ parse-srcset@^1.0.2:
resolved "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz"
integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==
path-browserify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
@@ -2486,6 +2539,11 @@ prettier@3.5.3:
resolved "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz"
integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
prettier@^3.0.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
@@ -2964,6 +3022,34 @@ vite@6.0.7:
optionalDependencies:
fsevents "~2.3.3"
vscode-jsonrpc@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
vscode-languageserver-protocol@^3.0.0:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
dependencies:
vscode-jsonrpc "8.2.0"
vscode-languageserver-types "3.17.5"
vscode-languageserver-textdocument@^1.0.0:
version "1.0.12"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"
integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==
vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.0.0:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
vscode-uri@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
@@ -2994,6 +3080,11 @@ yallist@^3.0.2:
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^2.0.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79"
integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
yaml@^2.3.4:
version "2.8.0"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz"