forked from github-starred/komodo
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8410ae64 | ||
|
|
0a2ee6ea43 | ||
|
|
fafae5d9d1 | ||
|
|
009221e288 | ||
|
|
b8022f279f | ||
|
|
2ca87c51f5 | ||
|
|
12601d49f4 | ||
|
|
284452e674 | ||
|
|
15ed767f65 | ||
|
|
9ec9f6e4a8 | ||
|
|
ff7bd5c96e | ||
|
|
4573881c9f | ||
|
|
4623f3ead2 | ||
|
|
3c2bbb0b52 | ||
|
|
b0f89a1b84 | ||
|
|
88c0e7e2e7 | ||
|
|
70af4a0b86 | ||
|
|
530b066717 | ||
|
|
7fbcee5dd1 | ||
|
|
bfef4725be | ||
|
|
05a9750f79 | ||
|
|
b006bef72c | ||
|
|
af17636137 | ||
|
|
d0571dcce0 | ||
|
|
cc9a534a8a | ||
|
|
112ae60264 | ||
|
|
fbc8fb3a58 | ||
|
|
ba85a5526d | ||
|
|
c5bc94b4d8 | ||
|
|
17f0dd4209 | ||
|
|
8528e4c1f7 | ||
|
|
10f82cdf2f | ||
|
|
ae7fb7f87f | ||
|
|
78451fc3e7 | ||
|
|
8b06ffb140 | ||
|
|
dab9aae61b | ||
|
|
7407f62cc5 | ||
|
|
627c56dbda | ||
|
|
829e8f360b | ||
|
|
38857cbc0b | ||
|
|
5bbd5510a1 | ||
|
|
8b1a3230c3 | ||
|
|
4fe84c17fb | ||
|
|
420c9c0569 | ||
|
|
6ee7a29f51 | ||
|
|
b7dab131fa | ||
|
|
0c30229e3e | ||
|
|
bcbe75ca5d |
Generated
+71
-94
@@ -118,12 +118,10 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.28"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75"
|
||||
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
@@ -269,9 +267,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ec2"
|
||||
version = "1.161.0"
|
||||
version = "1.160.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239393d2a430bd37a7a39d8467674b816f1c864e4bfe874b01a0c9e0d49be4ff"
|
||||
checksum = "4300d66be2c4e275d3a037576dacce6efaf9af24bfbd2460a848ea39299c979c"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -746,7 +744,7 @@ version = "0.69.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
@@ -771,9 +769,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -863,7 +861,7 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.3.3",
|
||||
"hex",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"rand 0.9.2",
|
||||
@@ -907,7 +905,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cache"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"tokio",
|
||||
@@ -915,9 +913,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.34"
|
||||
version = "1.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -1074,38 +1072,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
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.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1214,7 +1193,7 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"crossterm_winapi",
|
||||
"parking_lot 0.12.4",
|
||||
"rustix",
|
||||
@@ -1342,7 +1321,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "database"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -1641,7 +1620,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"formatting",
|
||||
@@ -1651,7 +1630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
@@ -1741,7 +1720,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1903,7 +1882,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cache",
|
||||
@@ -1944,7 +1923,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -1963,7 +1942,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2479,9 +2458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
@@ -2511,7 +2490,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interpolate"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2520,11 +2499,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@@ -2607,9 +2586,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
@@ -2642,7 +2621,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2667,7 +2646,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2679,7 +2658,7 @@ dependencies = [
|
||||
"derive_variants",
|
||||
"envy",
|
||||
"futures",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"ipnetwork",
|
||||
"mongo_indexed",
|
||||
"partial_derive2",
|
||||
@@ -2696,13 +2675,12 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"typeshare",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2734,7 +2712,7 @@ dependencies = [
|
||||
"git",
|
||||
"hex",
|
||||
"hmac",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"interpolate",
|
||||
"jsonwebtoken",
|
||||
"komodo_client",
|
||||
@@ -2772,7 +2750,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2894,7 +2872,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -3059,9 +3037,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongodb"
|
||||
version = "3.2.5"
|
||||
version = "3.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6e3788f35159bbcf461227af84711950525343b64451fdd90de4e237a0b8a13"
|
||||
checksum = "d0f8c69f13acf07eae386a2974f48ffd9187ea2aba8defbea9aa34e7e272c5f3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
@@ -3108,9 +3086,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongodb-internal-macros"
|
||||
version = "3.2.5"
|
||||
version = "3.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdfb13b7c0436b3e395f4ce5f4366431b9c9db47adc4cb33afc6b5b913d44969"
|
||||
checksum = "b9202de265a3a8bbb43f9fe56db27c93137d4f9fb04c093f47e9c7de0c61ac7d"
|
||||
dependencies = [
|
||||
"macro_magic",
|
||||
"proc-macro2",
|
||||
@@ -3120,9 +3098,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mungos"
|
||||
version = "3.2.2"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f77d8548efe9a10b49e1c92fa719d2e4e27cc6b40936f05fbb0f6a7ac31d3c0f"
|
||||
checksum = "d2cc7db433516e852091e2ede8cd755a717223a359b519b22710e56225a5d6a9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"envy",
|
||||
@@ -3130,7 +3108,6 @@ dependencies = [
|
||||
"mongodb",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3139,7 +3116,7 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
@@ -3282,7 +3259,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3637,7 +3614,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -3970,7 +3947,7 @@ version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3995,9 +3972,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4007,9 +3984,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4018,15 +3995,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
@@ -4168,7 +4145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "response"
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4282,7 +4259,7 @@ version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -4500,7 +4477,7 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -4513,7 +4490,7 @@ version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -4592,7 +4569,7 @@ version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -4671,7 +4648,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
@@ -4699,7 +4676,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -5028,7 +5005,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
@@ -5280,7 +5257,7 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -5361,7 +5338,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 2.11.0",
|
||||
"indexmap 2.10.0",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
@@ -5378,7 +5355,7 @@ version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -5641,9 +5618,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
version = "2.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -6265,9 +6242,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
@@ -6294,7 +6271,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+7
-7
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.19.3"
|
||||
version = "1.19.1-dev-14"
|
||||
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.2"
|
||||
mungos = "3.2.1"
|
||||
svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
@@ -65,10 +65,9 @@ axum = { version = "0.8.4", features = ["ws", "json", "macros"] }
|
||||
|
||||
# SER/DE
|
||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
||||
indexmap = { version = "2.11.0", features = ["serde"] }
|
||||
indexmap = { version = "2.10.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"
|
||||
@@ -113,7 +112,7 @@ sysinfo = "0.37.0"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.8.5"
|
||||
aws-sdk-ec2 = "1.161.0"
|
||||
aws-sdk-ec2 = "1.160.0"
|
||||
aws-credential-types = "1.2.5"
|
||||
|
||||
## CRON
|
||||
@@ -123,7 +122,7 @@ chrono = "0.4.41"
|
||||
croner = "3.0.0"
|
||||
|
||||
# MISC
|
||||
async-compression = { version = "0.4.28", features = ["tokio", "gzip"] }
|
||||
async-compression = { version = "0.4.27", features = ["tokio", "gzip"] }
|
||||
derive_builder = "0.20.2"
|
||||
comfy-table = "7.1.4"
|
||||
typeshare = "1.0.4"
|
||||
@@ -131,6 +130,7 @@ octorust = "0.10.0"
|
||||
dashmap = "6.1.0"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.0.0"
|
||||
regex = "1.11.2"
|
||||
regex = "1.11.1"
|
||||
bytes = "1.10.1"
|
||||
bson = "2.15.0"
|
||||
shell-escape = "0.1.5"
|
||||
@@ -549,20 +549,20 @@ async fn poll_update_until_complete(
|
||||
} else {
|
||||
format!("{}/updates/{}", cli_config().host, update.id)
|
||||
};
|
||||
println!("Link: '{}'", link.bold());
|
||||
info!("Link: '{}'", link.bold());
|
||||
|
||||
let client = super::komodo_client().await?;
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let update = client.poll_update_until_complete(&update.id).await?;
|
||||
if update.success {
|
||||
println!(
|
||||
info!(
|
||||
"FINISHED in {}: {}",
|
||||
format!("{:.1?}", timer.elapsed()).bold(),
|
||||
"EXECUTION SUCCESSFUL".green(),
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
warn!(
|
||||
"FINISHED in {}: {}",
|
||||
format!("{:.1?}", timer.elapsed()).bold(),
|
||||
"EXECUTION FAILED".red(),
|
||||
|
||||
@@ -29,12 +29,12 @@ pub async fn send_alert(
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | **{name}**{region} | Periphery version now matches Core version ✅\n{link}"
|
||||
"{level} | **{name}** ({region}) | Server version now matches core version ✅\n{link}"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | **{name}**{region} | Version mismatch detected ⚠️\nPeriphery: **{server_version}** | Core: **{core_version}**\n{link}"
|
||||
"{level} | **{name}** ({region}) | Version mismatch detected ⚠️\nServer: **{server_version}** | Core: **{core_version}**\n{link}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,12 +275,12 @@ fn standard_alert_content(alert: &Alert) -> String {
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | {name}{region} | Periphery version now matches Core version ✅\n{link}"
|
||||
"{level} | {name} ({region}) | Server version now matches core version ✅\n{link}"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | {name}{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}\n{link}"
|
||||
"{level} | {name} ({region}) | Version mismatch detected ⚠️\nServer: {server_version} | Core: {core_version}\n{link}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ pub async fn send_alert(
|
||||
let text = match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
|
||||
"{level} | {name} ({region}) | Server version now matches core version ✅"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
|
||||
"{level} | {name} ({region}) | Version mismatch detected ⚠️\nServer: {server_version} | Core: {core_version}"
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Context;
|
||||
use database::mungos::mongodb::bson::{
|
||||
doc, oid::ObjectId, to_bson, to_document,
|
||||
};
|
||||
use database::mungos::mongodb::bson::{doc, to_document};
|
||||
use formatting::format_serror;
|
||||
use interpolate::Interpolator;
|
||||
use komodo_client::{
|
||||
@@ -366,21 +364,23 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
// host before restart.
|
||||
maybe_pull_stack(&stack, Some(&mut update)).await?;
|
||||
|
||||
let mut update =
|
||||
restart_services(stack.name, Vec::new(), user).await?;
|
||||
|
||||
if update.success {
|
||||
// Need to update 'info.deployed_contents' with the
|
||||
// latest contents so next check doesn't read the same diff.
|
||||
update_deployed_contents_with_latest(
|
||||
&stack.id,
|
||||
stack.info.remote_contents,
|
||||
&mut update,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(update)
|
||||
// The existing update is initialized to DeployStack,
|
||||
// but also has not been created on database.
|
||||
// Setup a new update here.
|
||||
let req = ExecuteRequest::RestartStack(RestartStack {
|
||||
stack: stack.name,
|
||||
services: Vec::new(),
|
||||
});
|
||||
let update = init_execution_update(&req, user).await?;
|
||||
let ExecuteRequest::RestartStack(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
req
|
||||
.resolve(&ExecuteArgs {
|
||||
user: user.clone(),
|
||||
update,
|
||||
})
|
||||
.await
|
||||
}
|
||||
DeployIfChangedAction::Services { deploy, restart } => {
|
||||
match (deploy.is_empty(), restart.is_empty()) {
|
||||
@@ -401,22 +401,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
// PullStack in order to ensure latest repo contents on the
|
||||
// host before restart. Only necessary if no "deploys" (deploy already pulls stack).
|
||||
maybe_pull_stack(&stack, Some(&mut update)).await?;
|
||||
|
||||
let mut update =
|
||||
restart_services(stack.name, restart, user).await?;
|
||||
|
||||
if update.success {
|
||||
// Need to update 'info.deployed_contents' with the
|
||||
// latest contents so next check doesn't read the same diff.
|
||||
update_deployed_contents_with_latest(
|
||||
&stack.id,
|
||||
stack.info.remote_contents,
|
||||
&mut update,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(update)
|
||||
restart_services(stack.name, restart, user).await
|
||||
}
|
||||
// Only deploy
|
||||
(false, true) => {
|
||||
@@ -428,8 +413,6 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
"Execute Deploys",
|
||||
format!("Deploying: {}", deploy.join(", "),),
|
||||
);
|
||||
// This already updates 'stack.info.deployed_services',
|
||||
// restart doesn't require this again.
|
||||
let deploy_update =
|
||||
deploy_services(stack.name.clone(), deploy, user)
|
||||
.await?;
|
||||
@@ -513,59 +496,6 @@ async fn restart_services(
|
||||
.await
|
||||
}
|
||||
|
||||
/// This can safely be called in [DeployStackIfChanged]
|
||||
/// when there are ONLY changes to config files requiring restart,
|
||||
/// AFTER the restart has been successfully completed.
|
||||
///
|
||||
/// In the case the if changed action is not FullDeploy,
|
||||
/// the only file diff possible is to config files.
|
||||
/// Also note either full or service deploy will already update 'deployed_contents'
|
||||
/// making this method unnecessary in those cases.
|
||||
///
|
||||
/// Changes to config files after restart is applied should
|
||||
/// be taken as the deployed contents, otherwise next changed check
|
||||
/// will restart service again for no reason.
|
||||
async fn update_deployed_contents_with_latest(
|
||||
id: &str,
|
||||
contents: Option<Vec<StackRemoteFileContents>>,
|
||||
update: &mut Update,
|
||||
) {
|
||||
let Some(contents) = contents else {
|
||||
return;
|
||||
};
|
||||
let contents = contents
|
||||
.into_iter()
|
||||
.map(|f| FileContents {
|
||||
path: f.path,
|
||||
contents: f.contents,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if let Err(e) = (async {
|
||||
let contents = to_bson(&contents)
|
||||
.context("Failed to serialize contents to bson")?;
|
||||
let id =
|
||||
ObjectId::from_str(id).context("Id is not valid ObjectId")?;
|
||||
db_client()
|
||||
.stacks
|
||||
.update_one(
|
||||
doc! { "_id": id },
|
||||
doc! { "$set": { "info.deployed_contents": contents } },
|
||||
)
|
||||
.await
|
||||
.context("Failed to update stack 'deployed_contents'")?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Update content cache",
|
||||
format_serror(&e.into()),
|
||||
);
|
||||
update.finalize();
|
||||
let _ = update_update(update.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
enum DeployIfChangedAction {
|
||||
/// Changes to any compose or env files
|
||||
/// always lead to this.
|
||||
@@ -1033,7 +963,6 @@ 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,
|
||||
|
||||
@@ -186,7 +186,7 @@ async fn write_dockerfile_contents_git(
|
||||
) -> serror::Result<Update> {
|
||||
let WriteBuildFileContents { build: _, contents } = req;
|
||||
|
||||
let mut repo_args: RepoExecutionArgs = if !build
|
||||
let mut clone_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 = repo_args.unique_path(&core_config().repo_directory)?;
|
||||
repo_args.destination = Some(root.display().to_string());
|
||||
let root = clone_args.unique_path(&core_config().repo_directory)?;
|
||||
clone_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) = &repo_args.account {
|
||||
git_token(&repo_args.provider, account, |https| repo_args.https = https)
|
||||
let access_token = if let Some(account) = &clone_args.account {
|
||||
git_token(&clone_args.provider, account, |https| clone_args.https = https)
|
||||
.await
|
||||
.with_context(
|
||||
|| format!("Failed to get git token in call to db. Stopping run. | {} | {account}", repo_args.provider),
|
||||
|| format!("Failed to get git token in call to db. Stopping run. | {} | {account}", clone_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,
|
||||
&repo_args,
|
||||
&clone_args,
|
||||
access_token.as_deref(),
|
||||
&mut update.logs,
|
||||
)
|
||||
@@ -249,11 +249,9 @@ 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(
|
||||
repo_args,
|
||||
clone_args,
|
||||
&core_config().repo_directory,
|
||||
access_token,
|
||||
)
|
||||
@@ -300,7 +298,7 @@ async fn write_dockerfile_contents_git(
|
||||
&format!("{}: Commit Dockerfile", args.user.username),
|
||||
&root,
|
||||
&build_path.join(&dockerfile_path),
|
||||
&branch,
|
||||
&build.config.branch,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -284,8 +284,6 @@ 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,
|
||||
@@ -336,7 +334,7 @@ async fn write_stack_file_contents_git(
|
||||
&format!("{username}: Write Stack File"),
|
||||
&root,
|
||||
&file_path,
|
||||
&branch,
|
||||
&stack.config.branch,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -323,8 +323,6 @@ 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,
|
||||
@@ -375,7 +373,7 @@ async fn write_sync_file_contents_git(
|
||||
&format!("{}: Commit Resource File", args.user.username),
|
||||
&root,
|
||||
&resource_path.join(&file_path),
|
||||
&branch,
|
||||
&sync.config.branch,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -220,14 +220,6 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +207,5 @@ pub struct GoogleUser {
|
||||
#[serde(rename = "sub")]
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
#[serde(default)]
|
||||
pub picture: String,
|
||||
}
|
||||
|
||||
@@ -250,7 +250,6 @@ 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),
|
||||
|
||||
@@ -45,14 +45,10 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
info!("Komodo Core version: v{}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
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()),
|
||||
if core_config().pretty_startup_config {
|
||||
info!("{:#?}", config.sanitized());
|
||||
} else {
|
||||
info!("{:?}", config.sanitized());
|
||||
}
|
||||
|
||||
// Init jwt client to crash on failure
|
||||
|
||||
@@ -21,7 +21,6 @@ pub async fn record_server_stats(ts: i64) {
|
||||
ts,
|
||||
sid: status.id.clone(),
|
||||
cpu_perc: stats.cpu_perc,
|
||||
load_average: stats.load_average.clone(),
|
||||
mem_total_gb: stats.mem_total_gb,
|
||||
mem_used_gb: stats.mem_used_gb,
|
||||
disk_total_gb,
|
||||
|
||||
@@ -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.to_string()
|
||||
image
|
||||
} else {
|
||||
format!("{image}:latest")
|
||||
}
|
||||
@@ -92,9 +92,6 @@ 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)
|
||||
@@ -253,21 +250,20 @@ 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.to_string()
|
||||
image
|
||||
} else {
|
||||
format!("{image}:latest")
|
||||
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
|
||||
};
|
||||
|
||||
@@ -853,15 +853,9 @@ pub async fn delete<T: KomodoResource>(
|
||||
);
|
||||
update.push_simple_log("Deleted Toml", toml);
|
||||
|
||||
tokio::join!(
|
||||
async {
|
||||
if let Err(e) = T::post_delete(&resource, &mut update).await {
|
||||
update
|
||||
.push_error_log("post delete", format_serror(&e.into()));
|
||||
}
|
||||
},
|
||||
delete_from_alerters::<T>(&resource.id)
|
||||
);
|
||||
if let Err(e) = T::post_delete(&resource, &mut update).await {
|
||||
update.push_error_log("post delete", format_serror(&e.into()));
|
||||
}
|
||||
|
||||
refresh_all_resources_cache().await;
|
||||
|
||||
@@ -871,26 +865,6 @@ pub async fn delete<T: KomodoResource>(
|
||||
Ok(resource)
|
||||
}
|
||||
|
||||
async fn delete_from_alerters<T: KomodoResource>(id: &str) {
|
||||
let target_bson = doc! {
|
||||
"type": T::resource_type().as_ref(),
|
||||
"id": id,
|
||||
};
|
||||
if let Err(e) = db_client()
|
||||
.alerters
|
||||
.update_many(Document::new(), doc! {
|
||||
"$pull": {
|
||||
"config.resources": &target_bson,
|
||||
"config.except_resources": target_bson,
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Failed to clear deleted resource from alerter whitelist / blacklist")
|
||||
{
|
||||
warn!("{e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
// =======
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
|
||||
@@ -300,7 +300,6 @@ async fn get_repo_state_from_db(id: &str) -> RepoState {
|
||||
"$or": [
|
||||
{ "operation": "CloneRepo" },
|
||||
{ "operation": "PullRepo" },
|
||||
{ "operation": "BuildRepo" },
|
||||
],
|
||||
})
|
||||
.with_options(
|
||||
|
||||
@@ -242,7 +242,7 @@ async fn ensure_first_server_and_builder() {
|
||||
return;
|
||||
};
|
||||
if let Err(e) = (CreateBuilder {
|
||||
name: config.first_server_name.clone(),
|
||||
name: String::from("Local"),
|
||||
config: PartialBuilderConfig::Server(
|
||||
PartialServerBuilderConfig {
|
||||
server_id: Some(server.id),
|
||||
|
||||
@@ -712,7 +712,6 @@ impl Resolve<super::Args> for ComposeRun {
|
||||
command,
|
||||
no_tty,
|
||||
no_deps,
|
||||
detach,
|
||||
service_ports,
|
||||
env,
|
||||
workdir,
|
||||
@@ -784,9 +783,6 @@ 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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,9 +43,8 @@ pub async fn validate_files(
|
||||
"Validate Files",
|
||||
format_serror(
|
||||
&anyhow!(
|
||||
"Missing files: {}", res.missing_files.join(", ")
|
||||
"Ensure the run_directory and all file paths are correct."
|
||||
)
|
||||
.context("Ensure the run_directory and all file paths are correct.")
|
||||
.context("A file doesn't exist after writing stack.")
|
||||
.into(),
|
||||
),
|
||||
|
||||
@@ -2,8 +2,7 @@ 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, SystemProcess, SystemStats,
|
||||
};
|
||||
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
|
||||
use tokio::sync::RwLock;
|
||||
@@ -96,15 +95,8 @@ impl StatsClient {
|
||||
network_egress_bytes += network.transmitted();
|
||||
}
|
||||
|
||||
let load_avg = System::load_average();
|
||||
|
||||
SystemStats {
|
||||
cpu_perc: self.system.global_cpu_usage(),
|
||||
load_average: SystemLoadAverage {
|
||||
one: load_avg.one,
|
||||
five: load_avg.five,
|
||||
fifteen: load_avg.fifteen,
|
||||
},
|
||||
mem_free_gb: self.system.free_memory() as f64 / BYTES_PER_GB,
|
||||
mem_used_gb: (total_mem - available_mem) as f64 / BYTES_PER_GB,
|
||||
mem_total_gb: total_mem as f64 / BYTES_PER_GB,
|
||||
|
||||
@@ -28,7 +28,6 @@ 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
|
||||
|
||||
@@ -379,9 +379,6 @@ 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>,
|
||||
|
||||
@@ -125,8 +125,6 @@ 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>,
|
||||
@@ -511,12 +509,6 @@ 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 =
|
||||
// ===========
|
||||
@@ -737,7 +729,6 @@ 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(),
|
||||
@@ -781,8 +772,6 @@ 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,
|
||||
|
||||
@@ -511,17 +511,8 @@ impl RepoExecutionArgs {
|
||||
&self,
|
||||
access_token: Option<&str>,
|
||||
) -> anyhow::Result<String> {
|
||||
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()))
|
||||
}
|
||||
},
|
||||
let access_token_at = match &access_token {
|
||||
Some(token) => format!("token:{token}@"),
|
||||
None => String::new(),
|
||||
};
|
||||
let protocol = if self.https { "https" } else { "http" };
|
||||
|
||||
@@ -45,9 +45,6 @@ pub struct SystemStatsRecord {
|
||||
// basic stats
|
||||
/// Cpu usage percentage
|
||||
pub cpu_perc: f32,
|
||||
/// Load average (1m, 5m, 15m)
|
||||
#[serde(default)]
|
||||
pub load_average: SystemLoadAverage,
|
||||
/// Memory used in GB
|
||||
pub mem_used_gb: f64,
|
||||
/// Total memory in GB
|
||||
@@ -75,9 +72,6 @@ pub struct SystemStatsRecord {
|
||||
pub struct SystemStats {
|
||||
/// Cpu usage percentage
|
||||
pub cpu_perc: f32,
|
||||
/// Load average (1m, 5m, 15m)
|
||||
#[serde(default)]
|
||||
pub load_average: SystemLoadAverage,
|
||||
/// [1.15.9+]
|
||||
/// Free memory in GB.
|
||||
/// This is really the 'Free' memory, not the 'Available' memory.
|
||||
@@ -181,14 +175,3 @@ pub struct SystemProcess {
|
||||
/// Process disk write in KB/s
|
||||
pub disk_write_kb: f64,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct SystemLoadAverage {
|
||||
/// 1m load average
|
||||
pub one: f64,
|
||||
/// 5m load average
|
||||
pub five: f64,
|
||||
/// 15m load average
|
||||
pub fifteen: f64,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "komodo_client",
|
||||
"version": "1.19.3",
|
||||
"version": "1.19.1",
|
||||
"description": "Komodo client package",
|
||||
"homepage": "https://komo.do",
|
||||
"main": "dist/lib.js",
|
||||
|
||||
@@ -2402,15 +2402,6 @@ export interface SystemInformation {
|
||||
|
||||
export type GetSystemInformationResponse = SystemInformation;
|
||||
|
||||
export interface SystemLoadAverage {
|
||||
/** 1m load average */
|
||||
one: number;
|
||||
/** 5m load average */
|
||||
five: number;
|
||||
/** 15m load average */
|
||||
fifteen: number;
|
||||
}
|
||||
|
||||
/** Info for a single disk mounted on the system. */
|
||||
export interface SingleDiskUsage {
|
||||
/** The mount point of the disk */
|
||||
@@ -2472,8 +2463,6 @@ export enum Timelength {
|
||||
export interface SystemStats {
|
||||
/** Cpu usage percentage */
|
||||
cpu_perc: number;
|
||||
/** Load average (1m, 5m, 15m) */
|
||||
load_average?: SystemLoadAverage;
|
||||
/**
|
||||
* [1.15.9+]
|
||||
* Free memory in GB.
|
||||
@@ -6108,8 +6097,6 @@ export interface SystemStatsRecord {
|
||||
sid: string;
|
||||
/** Cpu usage percentage */
|
||||
cpu_perc: number;
|
||||
/** Load average (1m, 5m, 15m) */
|
||||
load_average?: SystemLoadAverage;
|
||||
/** Memory used in GB */
|
||||
mem_used_gb: number;
|
||||
/** Total memory in GB */
|
||||
@@ -7713,8 +7700,6 @@ 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 */
|
||||
|
||||
@@ -272,9 +272,6 @@ 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>,
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
## All fields with a "Default" provided are optional. If they are
|
||||
## left out of the file, the "Default" value will be used.
|
||||
|
||||
## This file is bundled into the official image, `ghcr.io/moghtech/komodo-core`,
|
||||
## This file is bundled into the official image, `ghcr.io/moghtech/komodo`,
|
||||
## as the default config at `/config/.default.config.toml`.
|
||||
## Komodo Core can start with no external config file mounted.
|
||||
## Komodo can start with no external config file mounted.
|
||||
|
||||
## Most fields can also be configured using environment variables.
|
||||
## Environment variables will override values set in this file.
|
||||
|
||||
## Can also use JSON or YAML if preferred. You can convert here:
|
||||
## Can also use JSON or YAML if preffered. You can convert here:
|
||||
## - YAML: https://it-tools.tech/toml-to-yaml
|
||||
## - JSON: https://it-tools.tech/toml-to-json
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
## Most fields can also be configured using cli arguments and environment variables.
|
||||
## These will will override values set in this file. (cli args > env > config files).
|
||||
|
||||
## You can also use JSON or YAML if preferred. You can convert here:
|
||||
## You can also use JSON or YAML if preffered. You can convert here:
|
||||
## - YAML: https://it-tools.tech/toml-to-yaml
|
||||
## - JSON: https://it-tools.tech/toml-to-json
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
## Most fields can also be configured using environment variables.
|
||||
## Environment variables will override values set in this file.
|
||||
|
||||
## You can also use JSON or YAML if preferred. You can convert here:
|
||||
## You can also use JSON or YAML if preffered. You can convert here:
|
||||
## - YAML: https://it-tools.tech/toml-to-yaml
|
||||
## - JSON: https://it-tools.tech/toml-to-json
|
||||
|
||||
@@ -75,10 +75,10 @@ disable_container_exec = false
|
||||
stats_polling_rate = "5-sec"
|
||||
|
||||
## How often Periphery polls the host for container stats,
|
||||
## Env: PERIPHERY_CONTAINER_STATS_POLLING_RATE
|
||||
## Env: PERIPHERY_STATS_POLLING_RATE
|
||||
## Options: https://docs.rs/komodo_client/latest/komodo_client/entities/enum.Timelength.html
|
||||
## Default: 30-sec
|
||||
container_stats_polling_rate = "30-sec"
|
||||
## Default: 5-sec
|
||||
container_stats_polling_rate = "1-min"
|
||||
|
||||
## Whether stack actions should use `docker-compose ...`
|
||||
## instead of `docker compose ...`.
|
||||
|
||||
@@ -71,7 +71,7 @@ permissions = [
|
||||
|
||||
## Administration
|
||||
|
||||
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}`.
|
||||
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}`.
|
||||
|
||||
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.
|
||||
@@ -14,10 +14,6 @@ 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
|
||||
|
||||
@@ -5,12 +5,10 @@ To run Komodo, you will need Docker. See [the docker install docs](https://docs.
|
||||
### Deploy with Docker Compose
|
||||
|
||||
- [**Using MongoDB**](./mongo.mdx)
|
||||
- Lower CPU usage, Higher RAM usage.
|
||||
- Some systems [do not support running the latest MongoDB versions](https://github.com/moghtech/komodo/issues/59).
|
||||
- [**Using FerretDB** (Postgres)](./ferretdb.mdx)
|
||||
|
||||
:::info
|
||||
Some systems [do not support running the latest MongoDB versions](https://github.com/moghtech/komodo/issues/59).
|
||||
Users with these systems should use FerretDB instead.
|
||||
:::
|
||||
- Lower RAM usage, Higher CPU usage.
|
||||
|
||||
:::info
|
||||
**FerretDB v1** users:
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"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",
|
||||
|
||||
Vendored
-14
@@ -2517,14 +2517,6 @@ export interface SystemInformation {
|
||||
container_exec_disabled: boolean;
|
||||
}
|
||||
export type GetSystemInformationResponse = SystemInformation;
|
||||
export interface SystemLoadAverage {
|
||||
/** 1m load average */
|
||||
one: number;
|
||||
/** 5m load average */
|
||||
five: number;
|
||||
/** 15m load average */
|
||||
fifteen: number;
|
||||
}
|
||||
/** Info for a single disk mounted on the system. */
|
||||
export interface SingleDiskUsage {
|
||||
/** The mount point of the disk */
|
||||
@@ -2584,8 +2576,6 @@ export declare enum Timelength {
|
||||
export interface SystemStats {
|
||||
/** Cpu usage percentage */
|
||||
cpu_perc: number;
|
||||
/** Load average (1m, 5m, 15m) */
|
||||
load_average?: SystemLoadAverage;
|
||||
/**
|
||||
* [1.15.9+]
|
||||
* Free memory in GB.
|
||||
@@ -5862,8 +5852,6 @@ export interface SystemStatsRecord {
|
||||
sid: string;
|
||||
/** Cpu usage percentage */
|
||||
cpu_perc: number;
|
||||
/** Load average (1m, 5m, 15m) */
|
||||
load_average?: SystemLoadAverage;
|
||||
/** Memory used in GB */
|
||||
mem_used_gb: number;
|
||||
/** Total memory in GB */
|
||||
@@ -7306,8 +7294,6 @@ 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
@@ -170,7 +170,6 @@ interface SectionProps {
|
||||
actions?: ReactNode;
|
||||
// otherwise items-start
|
||||
itemsCenterTitleRow?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Section = ({
|
||||
@@ -181,9 +180,8 @@ export const Section = ({
|
||||
actions,
|
||||
children,
|
||||
itemsCenterTitleRow,
|
||||
className,
|
||||
}: SectionProps) => (
|
||||
<div className={cn("flex flex-col gap-4", className)}>
|
||||
<div className="flex flex-col gap-4">
|
||||
{(title || icon || titleRight || titleOther || actions) && (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -224,7 +222,6 @@ export const NewLayout = ({
|
||||
}) => {
|
||||
const [open, set] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
@@ -251,14 +248,9 @@ export const NewLayout = ({
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
set(false);
|
||||
} catch (error) {
|
||||
console.error("Error creating resource:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
await onConfirm();
|
||||
setLoading(false);
|
||||
set(false);
|
||||
}}
|
||||
disabled={!enabled || loading}
|
||||
>
|
||||
|
||||
@@ -56,14 +56,12 @@ 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;
|
||||
@@ -172,7 +170,6 @@ export const MonacoEditor = ({
|
||||
language={language}
|
||||
value={value}
|
||||
theme={theme}
|
||||
defaultPath={defaultPath(filename)}
|
||||
options={options}
|
||||
onChange={(v) => onValueChange?.(v ?? "")}
|
||||
onMount={(editor) => setEditor(editor)}
|
||||
@@ -181,14 +178,6 @@ export const MonacoEditor = ({
|
||||
);
|
||||
};
|
||||
|
||||
const defaultPath = (filename?: string) => {
|
||||
if (!filename) return undefined;
|
||||
// Extract only the filename part of path,
|
||||
// avoiding critical issue when path starts with '/'
|
||||
const split = filename.split("/");
|
||||
return split[split.length - 1];
|
||||
};
|
||||
|
||||
const MIN_DIFF_HEIGHT = 100;
|
||||
const MAX_DIFF_HEIGHT = 400;
|
||||
|
||||
|
||||
@@ -1132,7 +1132,6 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
|
||||
command: undefined,
|
||||
no_tty: undefined,
|
||||
no_deps: undefined,
|
||||
detach: undefined,
|
||||
service_ports: undefined,
|
||||
env: undefined,
|
||||
workdir: undefined,
|
||||
@@ -1152,7 +1151,6 @@ 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 ?? "");
|
||||
@@ -1177,7 +1175,6 @@ 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 ?? "");
|
||||
@@ -1219,7 +1216,6 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
|
||||
user: user || undefined,
|
||||
entrypoint: entrypoint || undefined,
|
||||
pull: pull ? true : undefined,
|
||||
detach: detach ? true : undefined,
|
||||
env,
|
||||
} as any);
|
||||
setOpen(false);
|
||||
@@ -1277,10 +1273,6 @@ 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}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { file_contents_empty, sync_no_changes } from "@lib/utils";
|
||||
import { usePermissions } from "@lib/hooks";
|
||||
import { NotebookPen, RefreshCcw, SquarePlay } from "lucide-react";
|
||||
import { useFullResourceSync, useResourceSyncTabsView } from ".";
|
||||
import { useFullResourceSync, usePendingView } from ".";
|
||||
|
||||
export const RefreshSync = ({ id }: { id: string }) => {
|
||||
const inv = useInvalidate();
|
||||
@@ -34,10 +34,11 @@ export const ExecuteSync = ({ id }: { id: string }) => {
|
||||
{ refetchInterval: 5000 }
|
||||
).data?.syncing;
|
||||
const sync = useFullResourceSync(id);
|
||||
const { view } = useResourceSyncTabsView(sync);
|
||||
const [_pendingView] = usePendingView();
|
||||
const pendingView = sync?.config?.managed ? _pendingView : "Execute";
|
||||
|
||||
if (
|
||||
view !== "Execute" ||
|
||||
pendingView === "Commit" ||
|
||||
!sync ||
|
||||
sync_no_changes(sync) ||
|
||||
!sync.info?.remote_contents
|
||||
@@ -72,12 +73,11 @@ export const ExecuteSync = ({ id }: { id: string }) => {
|
||||
export const CommitSync = ({ id }: { id: string }) => {
|
||||
const { mutate, isPending } = useWrite("CommitSync");
|
||||
const sync = useFullResourceSync(id);
|
||||
const { view } = useResourceSyncTabsView(sync);
|
||||
const { canWrite } = usePermissions({ type: "ResourceSync", id });
|
||||
const [_pendingView] = usePendingView();
|
||||
const pendingView = sync?.config?.managed ? _pendingView : "Execute";
|
||||
|
||||
if (view !== "Commit" || !canWrite || !sync) {
|
||||
return null;
|
||||
}
|
||||
if (pendingView === "Execute" || !canWrite || !sync) return null;
|
||||
|
||||
const freshSync =
|
||||
!sync.config?.files_on_host &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { atomWithStorage, useRead, useUser } from "@lib/hooks";
|
||||
import { atomWithStorage, useLocalStorage, useRead, useUser } from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card } from "@ui/card";
|
||||
import { Clock, FolderSync } from "lucide-react";
|
||||
@@ -45,16 +45,23 @@ const ResourceSyncIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
return <FolderSync className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
type ResourceSyncTabsView = "Config" | "Info" | "Execute" | "Commit";
|
||||
const syncTabsViewAtom = atomWithStorage<ResourceSyncTabsView>(
|
||||
"sync-tabs-v4",
|
||||
"Config"
|
||||
const pendingViewAtom = atomWithStorage<"Execute" | "Commit">(
|
||||
"sync-view-v1",
|
||||
"Execute"
|
||||
);
|
||||
export const usePendingView = () => {
|
||||
return useAtom(pendingViewAtom) as [
|
||||
"Execute" | "Commit",
|
||||
(view: "Execute" | "Commit") => void,
|
||||
];
|
||||
};
|
||||
|
||||
export const useResourceSyncTabsView = (
|
||||
sync: Types.ResourceSync | undefined
|
||||
) => {
|
||||
const [_view, setView] = useAtom<ResourceSyncTabsView>(syncTabsViewAtom);
|
||||
const ConfigInfoPending = ({ id }: { id: string }) => {
|
||||
const [_view, setView] = useLocalStorage<"Config" | "Info" | "Pending">(
|
||||
"sync-tabs-v3",
|
||||
"Config"
|
||||
);
|
||||
const sync = useFullResourceSync(id);
|
||||
|
||||
const hideInfo = sync?.config?.files_on_host
|
||||
? false
|
||||
@@ -68,28 +75,13 @@ export const useResourceSyncTabsView = (
|
||||
const view =
|
||||
_view === "Info" && hideInfo
|
||||
? "Config"
|
||||
: (_view === "Execute" || _view === "Commit") && !showPending
|
||||
: _view === "Pending" && !showPending
|
||||
? sync?.config?.files_on_host ||
|
||||
sync?.config?.repo ||
|
||||
sync?.config?.linked_repo
|
||||
? "Info"
|
||||
: "Config"
|
||||
: _view === "Commit" && !sync?.config?.managed
|
||||
? "Execute"
|
||||
: _view;
|
||||
|
||||
return {
|
||||
view,
|
||||
setView,
|
||||
hideInfo,
|
||||
showPending,
|
||||
};
|
||||
};
|
||||
|
||||
const ConfigInfoPending = ({ id }: { id: string }) => {
|
||||
const sync = useFullResourceSync(id);
|
||||
const { view, setView, hideInfo, showPending } =
|
||||
useResourceSyncTabsView(sync);
|
||||
: _view;
|
||||
|
||||
const title = (
|
||||
<TabsList className="justify-start w-fit">
|
||||
@@ -104,21 +96,12 @@ const ConfigInfoPending = ({ id }: { id: string }) => {
|
||||
Info
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="Execute"
|
||||
value="Pending"
|
||||
className="w-[110px]"
|
||||
disabled={!showPending}
|
||||
>
|
||||
Execute
|
||||
Pending
|
||||
</TabsTrigger>
|
||||
{sync?.config?.managed && (
|
||||
<TabsTrigger
|
||||
value="Commit"
|
||||
className="w-[110px]"
|
||||
disabled={!showPending}
|
||||
>
|
||||
Commit
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
);
|
||||
return (
|
||||
@@ -129,10 +112,7 @@ const ConfigInfoPending = ({ id }: { id: string }) => {
|
||||
<TabsContent value="Info">
|
||||
<ResourceSyncInfo id={id} titleOther={title} />
|
||||
</TabsContent>
|
||||
<TabsContent value="Execute">
|
||||
<ResourceSyncPending id={id} titleOther={title} />
|
||||
</TabsContent>
|
||||
<TabsContent value="Commit">
|
||||
<TabsContent value="Pending">
|
||||
<ResourceSyncPending id={id} titleOther={title} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
@@ -184,7 +164,7 @@ export const ResourceSyncComponents: RequiredResourceComponents = {
|
||||
},
|
||||
|
||||
GroupActions: () => (
|
||||
<GroupActions type="ResourceSync" actions={["RunSync", "CommitSync"]} />
|
||||
<GroupActions type="ResourceSync" actions={["RunSync"]} />
|
||||
),
|
||||
|
||||
Table: ({ resources }) => (
|
||||
|
||||
@@ -10,7 +10,8 @@ import { cn, sanitizeOnlySpan } from "@lib/utils";
|
||||
import { ConfirmButton } from "@components/util";
|
||||
import { SquarePlay } from "lucide-react";
|
||||
import { usePermissions } from "@lib/hooks";
|
||||
import { useFullResourceSync, useResourceSyncTabsView } from ".";
|
||||
import { useFullResourceSync, usePendingView } from ".";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
import { ResourceDiff } from "komodo_client/dist/types";
|
||||
|
||||
export const ResourceSyncPending = ({
|
||||
@@ -23,16 +24,31 @@ export const ResourceSyncPending = ({
|
||||
const syncing = useRead("GetResourceSyncActionState", { sync: id }).data
|
||||
?.syncing;
|
||||
const sync = useFullResourceSync(id);
|
||||
const { view } = useResourceSyncTabsView(sync);
|
||||
const { canExecute } = usePermissions({ type: "ResourceSync", id });
|
||||
const [_pendingView, setPendingView] = usePendingView();
|
||||
const pendingView = sync?.config?.managed ? _pendingView : "Execute";
|
||||
const { mutate, isPending } = useExecute("RunSync");
|
||||
const loading = isPending || syncing;
|
||||
return (
|
||||
<Section titleOther={titleOther} className="min-h-[500px]">
|
||||
<div className="flex items-center gap-4 pl-1 py-2 flex-wrap">
|
||||
<div className="text-muted-foreground">{view} Mode:</div>
|
||||
<Section
|
||||
titleOther={titleOther}
|
||||
>
|
||||
<div className="flex items-center gap-4 py-2 flex-wrap">
|
||||
{sync?.config?.managed && (
|
||||
<Tabs value={pendingView} onValueChange={setPendingView as any}>
|
||||
<TabsList className="justify-start w-fit">
|
||||
<TabsTrigger value="Execute" className="w-[110px]">
|
||||
Execute
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Commit" className="w-[110px]">
|
||||
Commit
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)}
|
||||
<div className="text-muted-foreground">{pendingView} Mode:</div>
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{view === "Execute" && (
|
||||
{pendingView === "Execute" && (
|
||||
<>
|
||||
Update resources in the
|
||||
<div className="font-bold">UI</div>
|
||||
@@ -40,7 +56,7 @@ export const ResourceSyncPending = ({
|
||||
<div className="font-bold">file changes.</div>
|
||||
</>
|
||||
)}
|
||||
{view === "Commit" && (
|
||||
{pendingView === "Commit" && (
|
||||
<>
|
||||
Update resources in the
|
||||
<div className="font-bold">file</div>
|
||||
@@ -73,7 +89,7 @@ export const ResourceSyncPending = ({
|
||||
) : undefined}
|
||||
|
||||
{/* Pending Deploy */}
|
||||
{view === "Execute" && sync?.info?.pending_deploy?.to_deploy ? (
|
||||
{pendingView === "Execute" && sync?.info?.pending_deploy?.to_deploy ? (
|
||||
<Card>
|
||||
<CardHeader
|
||||
className={cn(
|
||||
@@ -102,10 +118,13 @@ export const ResourceSyncPending = ({
|
||||
<div className="flex items-center gap-4 font-mono">
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
diff_type_intention(update.data.type, view === "Commit")
|
||||
diff_type_intention(
|
||||
update.data.type,
|
||||
pendingView === "Commit"
|
||||
)
|
||||
)}
|
||||
>
|
||||
{view === "Commit"
|
||||
{pendingView === "Commit"
|
||||
? reverse_pending_type(update.data.type)
|
||||
: update.data.type}{" "}
|
||||
{update.target.type}
|
||||
@@ -120,7 +139,7 @@ export const ResourceSyncPending = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{canExecute && view === "Execute" && (
|
||||
{canExecute && pendingView === "Execute" && (
|
||||
<ConfirmButton
|
||||
title="Execute Change"
|
||||
icon={<SquarePlay className="w-4 h-4" />}
|
||||
@@ -149,7 +168,7 @@ export const ResourceSyncPending = ({
|
||||
)}
|
||||
{update.data.type === "Update" && (
|
||||
<>
|
||||
{view === "Execute" && (
|
||||
{pendingView === "Execute" && (
|
||||
<MonacoDiffEditor
|
||||
original={update.data.data.current}
|
||||
modified={update.data.data.proposed}
|
||||
@@ -157,7 +176,7 @@ export const ResourceSyncPending = ({
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
{view === "Commit" && (
|
||||
{pendingView === "Commit" && (
|
||||
<MonacoDiffEditor
|
||||
original={update.data.data.proposed}
|
||||
modified={update.data.data.current}
|
||||
@@ -186,11 +205,13 @@ export const ResourceSyncPending = ({
|
||||
className={cn(
|
||||
"font-mono pb-2",
|
||||
text_color_class_by_intention(
|
||||
diff_type_intention(data.type, view === "Commit")
|
||||
diff_type_intention(data.type, pendingView === "Commit")
|
||||
)
|
||||
)}
|
||||
>
|
||||
{view === "Commit" ? reverse_pending_type(data.type) : data.type}{" "}
|
||||
{pendingView === "Commit"
|
||||
? reverse_pending_type(data.type)
|
||||
: data.type}{" "}
|
||||
Variable
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -203,7 +224,7 @@ export const ResourceSyncPending = ({
|
||||
)}
|
||||
{data.type === "Update" && (
|
||||
<>
|
||||
{view === "Execute" && (
|
||||
{pendingView === "Execute" && (
|
||||
<MonacoDiffEditor
|
||||
original={data.data.current}
|
||||
modified={data.data.proposed}
|
||||
@@ -211,7 +232,7 @@ export const ResourceSyncPending = ({
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
{view === "Commit" && (
|
||||
{pendingView === "Commit" && (
|
||||
<MonacoDiffEditor
|
||||
original={data.data.proposed}
|
||||
modified={data.data.current}
|
||||
@@ -240,11 +261,13 @@ export const ResourceSyncPending = ({
|
||||
className={cn(
|
||||
"font-mono pb-2",
|
||||
text_color_class_by_intention(
|
||||
diff_type_intention(data.type, view === "Commit")
|
||||
diff_type_intention(data.type, pendingView === "Commit")
|
||||
)
|
||||
)}
|
||||
>
|
||||
{view === "Commit" ? reverse_pending_type(data.type) : data.type}{" "}
|
||||
{pendingView === "Commit"
|
||||
? reverse_pending_type(data.type)
|
||||
: data.type}{" "}
|
||||
User Group
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -257,7 +280,7 @@ export const ResourceSyncPending = ({
|
||||
)}
|
||||
{data.type === "Update" && (
|
||||
<>
|
||||
{view === "Execute" && (
|
||||
{pendingView === "Execute" && (
|
||||
<MonacoDiffEditor
|
||||
original={data.data.current}
|
||||
modified={data.data.proposed}
|
||||
@@ -265,7 +288,7 @@ export const ResourceSyncPending = ({
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
{view === "Commit" && (
|
||||
{pendingView === "Commit" && (
|
||||
<MonacoDiffEditor
|
||||
original={data.data.proposed}
|
||||
modified={data.data.current}
|
||||
|
||||
@@ -401,27 +401,6 @@ export const ServerComponents: RequiredResourceComponents = {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
LoadAvg: ({ id }) => {
|
||||
const server = useServer(id);
|
||||
const stats = useRead(
|
||||
"GetSystemStats",
|
||||
{ server: id },
|
||||
{
|
||||
enabled: server ? server.info.state !== "Disabled" : false,
|
||||
refetchInterval: 5000,
|
||||
}
|
||||
).data;
|
||||
|
||||
if (!stats?.load_average) return null;
|
||||
const one = stats.load_average?.one;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<Cpu className="w-4 h-4" />
|
||||
{one.toFixed(2)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
Mem: ({ id }) => {
|
||||
const server = useServer(id);
|
||||
const stats = useRead(
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { ResourceLink } from "@components/resources/common";
|
||||
import { ServerComponents } from "@components/resources/server";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const ServerMonitoringTable = ({ search = "" }: { search?: string }) => {
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const searchSplit = useMemo(
|
||||
() => search.toLowerCase().split(" ").filter((t) => t),
|
||||
[search]
|
||||
);
|
||||
const filtered = useMemo(
|
||||
() =>
|
||||
servers?.filter((s) =>
|
||||
searchSplit.length === 0
|
||||
? true
|
||||
: searchSplit.every((t) => s.name.toLowerCase().includes(t))
|
||||
) ?? [],
|
||||
[servers, searchSplit]
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<DataTable<any, any>
|
||||
tableKey="servers-monitoring-v1"
|
||||
data={filtered}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "name",
|
||||
size: 250,
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="System" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<ResourceLink type="Server" id={row.original.id} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "CPU",
|
||||
size: 200,
|
||||
cell: ({ row }) => <CpuCell id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "Memory",
|
||||
size: 200,
|
||||
cell: ({ row }) => <MemCell id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "Disk",
|
||||
size: 200,
|
||||
cell: ({ row }) => <DiskCell id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "Load Avg",
|
||||
size: 160,
|
||||
cell: ({ row }) => <LoadAvgCell id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "Net",
|
||||
size: 100,
|
||||
cell: ({ row }) => <NetCell id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "Agent",
|
||||
size: 160,
|
||||
cell: ({ row }) => <ServerComponents.Info.Version id={row.original.id} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useStats = (id: string) =>
|
||||
useRead("GetSystemStats", { server: id }, { refetchInterval: 10_000 }).data;
|
||||
|
||||
const useServerThresholds = (id: string) => {
|
||||
const config = useRead("GetServer", { server: id }).data?.config as any;
|
||||
return {
|
||||
cpuWarning: config?.cpu_warning ?? 75,
|
||||
cpuCritical: config?.cpu_critical ?? 90,
|
||||
memWarning: config?.mem_warning ?? 75,
|
||||
memCritical: config?.mem_critical ?? 90,
|
||||
diskWarning: config?.disk_warning ?? 75,
|
||||
diskCritical: config?.disk_critical ?? 90,
|
||||
};
|
||||
};
|
||||
|
||||
const Bar = ({ valuePerc, intent }: { valuePerc?: number; intent: "Good" | "Warning" | "Critical" }) => {
|
||||
const w = Math.max(0, Math.min(100, valuePerc ?? 0)) / 100;
|
||||
const color = intent === "Good" ? "bg-green-500" : intent === "Warning" ? "bg-orange-500" : "bg-red-500";
|
||||
return (
|
||||
<span className="grow min-w-8 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
|
||||
<span className={`absolute inset-0 w-full h-full origin-left ${color}`} style={{ transform: `scaleX(${w})` }} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const CpuCell = ({ id }: { id: string }) => {
|
||||
const stats = useStats(id);
|
||||
const cpu = stats?.cpu_perc ?? 0;
|
||||
const { cpuWarning: warning, cpuCritical: critical } = useServerThresholds(id);
|
||||
const intent: "Good" | "Warning" | "Critical" =
|
||||
cpu < warning ? "Good" : cpu < critical ? "Warning" : "Critical";
|
||||
return (
|
||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||
<span className="min-w-8">{cpu.toFixed(2)}%</span>
|
||||
<Bar valuePerc={cpu} intent={intent} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MemCell = ({ id }: { id: string }) => {
|
||||
const stats = useStats(id);
|
||||
const used = stats?.mem_used_gb ?? 0;
|
||||
const total = stats?.mem_total_gb ?? 0;
|
||||
const perc = total > 0 ? (used / total) * 100 : 0;
|
||||
const { memWarning: warning, memCritical: critical } = useServerThresholds(id);
|
||||
const intent: "Good" | "Warning" | "Critical" =
|
||||
perc < warning ? "Good" : perc < critical ? "Warning" : "Critical";
|
||||
return (
|
||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||
<span className="min-w-8">{perc.toFixed(1)}%</span>
|
||||
<Bar valuePerc={perc} intent={intent} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DiskCell = ({ id }: { id: string }) => {
|
||||
const stats = useStats(id);
|
||||
const used = stats?.disks?.reduce((acc, d) => acc + (d.used_gb || 0), 0) ?? 0;
|
||||
const total = stats?.disks?.reduce((acc, d) => acc + (d.total_gb || 0), 0) ?? 0;
|
||||
const perc = total > 0 ? (used / total) * 100 : 0;
|
||||
const { diskWarning: warning, diskCritical: critical } = useServerThresholds(id);
|
||||
const intent: "Good" | "Warning" | "Critical" =
|
||||
perc < warning ? "Good" : perc < critical ? "Warning" : "Critical";
|
||||
return (
|
||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||
<span className="min-w-8">{perc.toFixed(1)}%</span>
|
||||
<Bar valuePerc={perc} intent={intent} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const formatRate = (bytes?: number) => {
|
||||
const b = bytes ?? 0;
|
||||
const kb = 1024;
|
||||
const mb = kb * 1024;
|
||||
const gb = mb * 1024;
|
||||
if (b >= gb) return `${(b / gb).toFixed(2)} GB/s`;
|
||||
if (b >= mb) return `${(b / mb).toFixed(2)} MB/s`;
|
||||
if (b >= kb) return `${(b / kb).toFixed(2)} KB/s`;
|
||||
return `${b.toFixed(0)} B/s`;
|
||||
};
|
||||
|
||||
const NetCell = ({ id }: { id: string }) => {
|
||||
const stats = useStats(id);
|
||||
const ingress = stats?.network_ingress_bytes ?? 0;
|
||||
const egress = stats?.network_egress_bytes ?? 0;
|
||||
return (
|
||||
<span className="tabular-nums whitespace-nowrap">
|
||||
{formatRate(ingress + egress)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadAvgCell = ({ id }: { id: string }) => {
|
||||
const stats = useStats(id);
|
||||
const one = stats?.load_average?.one;
|
||||
const five = stats?.load_average?.five;
|
||||
const fifteen = stats?.load_average?.fifteen;
|
||||
if (one === undefined || five === undefined || fifteen === undefined) {
|
||||
return (
|
||||
<div className="w-full flex items-center gap-[.35em] tabular-nums text-muted-foreground tracking-tight">
|
||||
<span>N/A</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="w-full flex items-center gap-[.35em] tabular-nums tracking-tight">
|
||||
<span>{one.toFixed(2)}</span>
|
||||
<span>{five.toFixed(2)}</span>
|
||||
<span>{fifteen.toFixed(2)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,19 +3,13 @@ import { useRead } from "@lib/hooks";
|
||||
import { Types } from "komodo_client";
|
||||
import { useMemo } from "react";
|
||||
import { useStatsGranularity } from "./hooks";
|
||||
import { Loader2, OctagonAlert } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { AxisOptions, Chart } from "react-charts";
|
||||
import { convertTsMsToLocalUnixTsInMs } from "@lib/utils";
|
||||
import { useTheme } from "@ui/theme";
|
||||
import { fmt_utc_date } from "@lib/formatting";
|
||||
|
||||
type StatType =
|
||||
| "Cpu"
|
||||
| "Memory"
|
||||
| "Disk"
|
||||
| "Network Ingress"
|
||||
| "Network Egress"
|
||||
| "Load Average";
|
||||
type StatType = "Cpu" | "Memory" | "Disk" | "Network Ingress" | "Network Egress";
|
||||
|
||||
type StatDatapoint = { date: number; value: number };
|
||||
|
||||
@@ -35,34 +29,18 @@ export const StatChart = ({
|
||||
granularity,
|
||||
});
|
||||
|
||||
const seriesData = useMemo(() => {
|
||||
if (!data?.stats) return [] as { label: string; data: StatDatapoint[] }[];
|
||||
const records = [...data.stats].reverse();
|
||||
if (type === "Load Average") {
|
||||
const one = records.map((s) => ({
|
||||
date: convertTsMsToLocalUnixTsInMs(s.ts),
|
||||
value: s.load_average?.one ?? 0,
|
||||
}));
|
||||
const five = records.map((s) => ({
|
||||
date: convertTsMsToLocalUnixTsInMs(s.ts),
|
||||
value: s.load_average?.five ?? 0,
|
||||
}));
|
||||
const fifteen = records.map((s) => ({
|
||||
date: convertTsMsToLocalUnixTsInMs(s.ts),
|
||||
value: s.load_average?.fifteen ?? 0,
|
||||
}));
|
||||
return [
|
||||
{ label: "1m", data: one },
|
||||
{ label: "5m", data: five },
|
||||
{ label: "15m", data: fifteen },
|
||||
];
|
||||
}
|
||||
const single = records.map((stat) => ({
|
||||
date: convertTsMsToLocalUnixTsInMs(stat.ts),
|
||||
value: getStat(stat, type),
|
||||
}));
|
||||
return [{ label: type, data: single }];
|
||||
}, [data, type]);
|
||||
const stats = useMemo(
|
||||
() =>
|
||||
data?.stats
|
||||
.map((stat) => {
|
||||
return {
|
||||
date: convertTsMsToLocalUnixTsInMs(stat.ts),
|
||||
value: getStat(stat, type),
|
||||
};
|
||||
})
|
||||
.reverse(),
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@@ -72,11 +50,8 @@ export const StatChart = ({
|
||||
<Loader2 className="w-8 h-8 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<InnerStatChart
|
||||
type={type}
|
||||
stats={seriesData.flatMap((s) => s.data)}
|
||||
seriesData={seriesData}
|
||||
/>
|
||||
stats &&
|
||||
stats.length > 0 && <InnerStatChart type={type} stats={stats} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -89,11 +64,9 @@ const BYTES_PER_KB = 1024.0;
|
||||
export const InnerStatChart = ({
|
||||
type,
|
||||
stats,
|
||||
seriesData,
|
||||
}: {
|
||||
type: StatType;
|
||||
stats: StatDatapoint[] | undefined;
|
||||
seriesData?: { label: string; data: StatDatapoint[] }[];
|
||||
}) => {
|
||||
const { theme: _theme } = useTheme();
|
||||
const theme =
|
||||
@@ -123,13 +96,10 @@ export const InnerStatChart = ({
|
||||
cursor: (_value?: Date) => false,
|
||||
},
|
||||
};
|
||||
}, [min, max, diff]);
|
||||
}, []);
|
||||
|
||||
// Determine the dynamic scaling for network-related types
|
||||
const allValues = (seriesData ?? [{ data: stats ?? [] }]).flatMap((s) =>
|
||||
s.data.map((d) => d.value)
|
||||
);
|
||||
const maxStatValue = Math.max(...(allValues.length ? allValues : [0]));
|
||||
const maxStatValue = Math.max(...(stats?.map((d) => d.value) ?? [0]));
|
||||
|
||||
const { unit, maxUnitValue } = useMemo(() => {
|
||||
if (type === "Network Ingress" || type === "Network Egress") {
|
||||
@@ -143,13 +113,6 @@ export const InnerStatChart = ({
|
||||
return { unit: "TB", maxUnitValue: BYTES_PER_GB * 1024 }; // Larger scale for high values
|
||||
}
|
||||
}
|
||||
if (type === "Load Average") {
|
||||
// Leave unitless; set max slightly above observed
|
||||
return {
|
||||
unit: "",
|
||||
maxUnitValue: maxStatValue === 0 ? 1 : maxStatValue * 1.2,
|
||||
};
|
||||
}
|
||||
return { unit: "", maxUnitValue: 100 }; // Default for CPU, memory, disk
|
||||
}, [type, maxStatValue]);
|
||||
|
||||
@@ -157,8 +120,7 @@ export const InnerStatChart = ({
|
||||
(): AxisOptions<StatDatapoint>[] => [
|
||||
{
|
||||
getValue: (datum) => datum.value,
|
||||
elementType: type === "Load Average" ? "line" : "area",
|
||||
stacked: type !== "Load Average",
|
||||
elementType: "area",
|
||||
min: 0,
|
||||
max: maxUnitValue,
|
||||
formatters: {
|
||||
@@ -166,9 +128,7 @@ export const InnerStatChart = ({
|
||||
<div className="text-lg font-mono">
|
||||
{(type === "Network Ingress" || type === "Network Egress") && unit
|
||||
? `${(value ?? 0) / (maxUnitValue / 1024)} ${unit}`
|
||||
: type === "Load Average"
|
||||
? `${(value ?? 0).toFixed(2)}`
|
||||
: `${value?.toFixed(2)}%`}
|
||||
: `${value?.toFixed(2)}%`}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -176,30 +136,18 @@ export const InnerStatChart = ({
|
||||
],
|
||||
[type, maxUnitValue, unit]
|
||||
);
|
||||
|
||||
if ((seriesData?.[0]?.data.length ?? 0) < 2) {
|
||||
return (
|
||||
<div className="w-full h-full flex gap-4 justify-center items-center">
|
||||
<OctagonAlert className="w-6 h-6" />
|
||||
<h1>Not enough data yet, choose a smaller interval.</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Chart
|
||||
options={{
|
||||
data: seriesData ?? [{ label: type, data: stats ?? [] }],
|
||||
data: [
|
||||
{
|
||||
label: type,
|
||||
data: stats ?? [],
|
||||
},
|
||||
],
|
||||
primaryAxis: timeAxis,
|
||||
secondaryAxes: valueAxis,
|
||||
defaultColors:
|
||||
type === "Load Average"
|
||||
? [
|
||||
hex_color_by_intention("Good"),
|
||||
hex_color_by_intention("Neutral"),
|
||||
hex_color_by_intention("Unknown"),
|
||||
]
|
||||
: [getColor(type)],
|
||||
defaultColors: [getColor(type)],
|
||||
dark: theme === "dark",
|
||||
padding: {
|
||||
left: 10,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Section } from "@components/layouts";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
|
||||
import { Progress } from "@ui/progress";
|
||||
import { Cpu, Database, Loader2, MemoryStick, Search } from "lucide-react";
|
||||
import {
|
||||
Cpu,
|
||||
Database,
|
||||
Loader2,
|
||||
MemoryStick,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { useLocalStorage, usePermissions, useRead } from "@lib/hooks";
|
||||
import { Types } from "komodo_client";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
@@ -114,7 +120,6 @@ export const ServerStats = ({
|
||||
{/* Current Overview */}
|
||||
<Section title="Current">
|
||||
<div className="flex flex-col xl:flex-row gap-4">
|
||||
<LOAD_AVERAGE id={id} stats={stats} />
|
||||
<CPU stats={stats} />
|
||||
<RAM stats={stats} />
|
||||
<DISK stats={stats} />
|
||||
@@ -330,11 +335,6 @@ export const ServerStats = ({
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-8">
|
||||
<StatChart
|
||||
server_id={id}
|
||||
type="Load Average"
|
||||
className="w-full h-[250px]"
|
||||
/>
|
||||
<StatChart server_id={id} type="Cpu" className="w-full h-[250px]" />
|
||||
<StatChart
|
||||
server_id={id}
|
||||
@@ -495,82 +495,6 @@ const CPU = ({ stats }: { stats: Types.SystemStats | undefined }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const LOAD_AVERAGE = ({
|
||||
id,
|
||||
stats,
|
||||
}: {
|
||||
id: string;
|
||||
stats: Types.SystemStats | undefined;
|
||||
}) => {
|
||||
if (!stats?.load_average) return null;
|
||||
const { one = 0, five = 0, fifteen = 0 } = stats.load_average || {};
|
||||
const cores = useRead("GetSystemInformation", { server: id }).data
|
||||
?.core_count;
|
||||
|
||||
const pct = (load: number) =>
|
||||
cores && cores > 0 ? Math.min((load / cores) * 100, 100) : undefined;
|
||||
const textColor = (load: number) => {
|
||||
const p = pct(load);
|
||||
if (p === undefined) return "text-muted-foreground";
|
||||
return p <= 50
|
||||
? "text-green-600"
|
||||
: p <= 80
|
||||
? "text-yellow-600"
|
||||
: "text-red-600";
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>Load Average</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Current Load */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<span
|
||||
className={`text-3xl font-bold tabular-nums ${textColor(one)}`}
|
||||
>
|
||||
{one.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{cores && cores > 0
|
||||
? `${(pct(one) ?? 0).toFixed(0)}% of ${cores} cores`
|
||||
: "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={pct(one) ?? 0} className="h-2" />
|
||||
</div>
|
||||
|
||||
{/* Time Intervals */}
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
{[
|
||||
["1m", one],
|
||||
["5m", five],
|
||||
["15m", fifteen],
|
||||
].map(([label, value]) => (
|
||||
<div className="space-y-1" key={label as string}>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span
|
||||
className={`font-medium tabular-nums ${textColor(value as number)}`}
|
||||
>
|
||||
{(value as number).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={pct(value as number) ?? 0} className="h-1" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const RAM = ({ stats }: { stats: Types.SystemStats | undefined }) => {
|
||||
const used = stats?.mem_used_gb;
|
||||
const total = stats?.mem_total_gb;
|
||||
|
||||
@@ -879,7 +879,6 @@ 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}
|
||||
@@ -1005,7 +1004,7 @@ const ConfigFiles = ({
|
||||
/>
|
||||
|
||||
<RequiresSelector
|
||||
requires={requires ?? Types.StackFileRequires.None}
|
||||
requires={requires ?? Types.StackFileRequires.Redeploy}
|
||||
set={(requires) => {
|
||||
values[i] = { ...values[i], requires };
|
||||
set({ config_files: [...values] });
|
||||
@@ -1047,7 +1046,7 @@ const ServicesSelector = ({
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="text-xs text-muted-foreground">Services:</div>
|
||||
<div className="text-xs text-muted-foreground">Dependents:</div>
|
||||
{selected_services.length === 0
|
||||
? "All"
|
||||
: selected_services.join(", ")}
|
||||
|
||||
@@ -263,7 +263,6 @@ 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>
|
||||
|
||||
@@ -93,6 +93,24 @@ 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,
|
||||
{
|
||||
@@ -898,23 +916,6 @@ 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" />
|
||||
),
|
||||
@@ -1202,35 +1203,6 @@ 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,
|
||||
@@ -1240,29 +1212,19 @@ export const ContainerPortLink = ({
|
||||
ports: Types.Port[];
|
||||
server_id: string | undefined;
|
||||
}) => {
|
||||
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]}`;
|
||||
|
||||
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;
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
@@ -1274,7 +1236,7 @@ export const ContainerPortLink = ({
|
||||
<EthernetPort
|
||||
className={cn("w-4 h-4", stroke_color_class_by_intention("Good"))}
|
||||
/>
|
||||
{display_text}
|
||||
{host_port}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="flex flex-col gap-2 w-fit">
|
||||
@@ -1286,18 +1248,12 @@ export const ContainerPortLink = ({
|
||||
<LinkIcon className="w-3 h-3" />
|
||||
{link}
|
||||
</a>
|
||||
{ports.slice(0, 10).map((port, i) => (
|
||||
{ports.map((port, i) => (
|
||||
<div key={i} className="flex gap-2 text-sm text-muted-foreground">
|
||||
<span>-</span>
|
||||
<div>-</div>
|
||||
<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>
|
||||
);
|
||||
@@ -1310,38 +1266,24 @@ export const ContainerPortsTableView = ({
|
||||
ports: Types.Port[];
|
||||
server_id: string | undefined;
|
||||
}) => {
|
||||
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;
|
||||
}, []);
|
||||
|
||||
const map = useContainerPortsMap(ports);
|
||||
const host_ports = Object.keys(map);
|
||||
return (
|
||||
<div className="flex items-center gap-x-1 flex-wrap">
|
||||
{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>
|
||||
))}
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -805,24 +805,3 @@ export const useContainerPortsMap = (ports: Types.Port[]) => {
|
||||
return map;
|
||||
}, [ports]);
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom React hook that debounces a value, delaying its update until after
|
||||
* a specified period of inactivity. This is useful for performance optimization
|
||||
* in scenarios like search inputs, form validation, or API calls.
|
||||
*/
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ 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) {
|
||||
@@ -20,9 +19,6 @@ self.MonacoEnvironment = {
|
||||
if (label === "typescript" || label === "javascript") {
|
||||
return new tsWorker();
|
||||
}
|
||||
if (label === "yaml") {
|
||||
return new yamlWorker();
|
||||
}
|
||||
return new editorWorker();
|
||||
},
|
||||
};
|
||||
@@ -36,6 +32,6 @@ import "./theme";
|
||||
import "./yaml";
|
||||
import "./toml";
|
||||
import "./fancy_toml";
|
||||
import "./shell";
|
||||
import "./shell"
|
||||
import "./key_value";
|
||||
import "./string_list";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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
|
||||
@@ -253,23 +252,11 @@ const yaml_language = <monaco.languages.IMonarchLanguage>{
|
||||
},
|
||||
};
|
||||
|
||||
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.register({ id: "yaml" });
|
||||
monaco.languages.setMonarchTokensProvider("yaml", yaml_language);
|
||||
monaco.languages.setLanguageConfiguration("yaml", yaml_conf);
|
||||
|
||||
|
||||
/// V1
|
||||
// const yaml_conf: monaco.languages.LanguageConfiguration = {
|
||||
// comments: {
|
||||
@@ -358,4 +345,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);
|
||||
@@ -6,66 +6,33 @@ import {
|
||||
StatusBadge,
|
||||
} from "@components/util";
|
||||
import { container_state_intention } from "@lib/color";
|
||||
import { useDebounce, useRead } from "@lib/hooks";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { Input } from "@ui/input";
|
||||
import { MultiSelect } from "@ui/multi-select";
|
||||
import { Box, Search, RotateCcw } from "lucide-react";
|
||||
import { Button } from "@ui/button";
|
||||
import { Box, Search } from "lucide-react";
|
||||
import { Fragment, useCallback, useMemo, useState } from "react";
|
||||
|
||||
export default function ContainersPage() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedServers, setSelectedServers] = useState<string[]>([]);
|
||||
|
||||
const debouncedSearch = useDebounce(search, 300);
|
||||
|
||||
const searchSplit = debouncedSearch
|
||||
const searchSplit = search
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.filter((term) => term);
|
||||
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const serverOptions = useMemo(
|
||||
() =>
|
||||
servers?.map((server) => ({
|
||||
label: server.name,
|
||||
value: server.id,
|
||||
})) || [],
|
||||
[servers]
|
||||
);
|
||||
|
||||
const serverName = useCallback(
|
||||
(id: string) => servers?.find((server) => server.id === id)?.name,
|
||||
[servers]
|
||||
);
|
||||
|
||||
const _containers = useRead("ListAllDockerContainers", {}).data;
|
||||
|
||||
const containers = useMemo(
|
||||
() =>
|
||||
_containers?.filter((c) => {
|
||||
if (searchSplit.length > 0) {
|
||||
const lower = c.name.toLowerCase();
|
||||
const searchMatch = searchSplit.every((search) =>
|
||||
lower.includes(search)
|
||||
);
|
||||
if (!searchMatch) return false;
|
||||
}
|
||||
|
||||
if (selectedServers.length > 0) {
|
||||
return selectedServers.includes(c.server_id!);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (searchSplit.length === 0) return true;
|
||||
const lower = c.name.toLowerCase();
|
||||
return searchSplit.every((search) => lower.includes(search));
|
||||
}),
|
||||
[_containers, searchSplit, selectedServers]
|
||||
[_containers, searchSplit]
|
||||
);
|
||||
|
||||
const clearAllServers = useCallback(() => {
|
||||
setSelectedServers([]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title="Containers"
|
||||
@@ -77,45 +44,18 @@ export default function ContainersPage() {
|
||||
icon={<Box className="w-8 h-8" />}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Server Filter Multi-Select */}
|
||||
<div className="w-[280px]">
|
||||
<MultiSelect
|
||||
options={serverOptions}
|
||||
value={selectedServers}
|
||||
onChange={setSelectedServers}
|
||||
placeholder="Filter by server..."
|
||||
className="w-full h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reset Server Filter Button */}
|
||||
{selectedServers.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearAllServers}
|
||||
className="h-10 px-3"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4 mr-1" />
|
||||
Reset
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search Input */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div></div>
|
||||
<div className="relative">
|
||||
<Search className="w-4 absolute top-[50%] left-3 -translate-y-[50%] text-muted-foreground" />
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="search..."
|
||||
className="pl-8 w-[200px] lg:w-[300px] py-0 h-10"
|
||||
className="pl-8 w-[200px] lg:w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
data={containers ?? []}
|
||||
tableKey="containers-page-v1"
|
||||
@@ -222,23 +162,6 @@ 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" />
|
||||
),
|
||||
@@ -249,6 +172,29 @@ export default function ContainersPage() {
|
||||
/>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// accessorKey: "volumes.0",
|
||||
// minSize: 300,
|
||||
// header: ({ column }) => (
|
||||
// <SortableHeader column={column} title="Volumes" />
|
||||
// ),
|
||||
// cell: ({ row }) => (
|
||||
// <div className="flex items-center gap-x-2 flex-wrap">
|
||||
// {row.original.volumes.map((volume, i) => (
|
||||
// <Fragment key={volume}>
|
||||
// <DockerResourceLink
|
||||
// type="volume"
|
||||
// server_id={row.original.server_id!}
|
||||
// name={volume}
|
||||
// />
|
||||
// {i !== row.original.volumes.length - 1 && (
|
||||
// <div className="text-muted-foreground">|</div>
|
||||
// )}
|
||||
// </Fragment>
|
||||
// ))}
|
||||
// </div>
|
||||
// ),
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
useLoginOptions,
|
||||
useUserInvalidate,
|
||||
} from "@lib/hooks";
|
||||
import { useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import { ThemeToggle } from "@ui/theme";
|
||||
import { KOMODO_BASE_URL } from "@main";
|
||||
import { KeyRound, X } from "lucide-react";
|
||||
@@ -38,9 +38,9 @@ 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();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
// If signing in another user, need to redirect away from /login manually
|
||||
const maybeNavigate = location.pathname.startsWith("/login")
|
||||
@@ -64,13 +64,13 @@ export default function Login() {
|
||||
const message = e?.response?.data?.error as string | undefined;
|
||||
if (message) {
|
||||
toast({
|
||||
title: `Failed to sign up user. '${message}'`,
|
||||
title: `Failed to login user. '${message}'`,
|
||||
variant: "destructive",
|
||||
});
|
||||
console.error(e);
|
||||
} else {
|
||||
toast({
|
||||
title: "Failed to sign up user. See console log for details.",
|
||||
title: "Failed to login user. See console log for details.",
|
||||
variant: "destructive",
|
||||
});
|
||||
console.error(e);
|
||||
@@ -98,25 +98,7 @@ export default function Login() {
|
||||
},
|
||||
});
|
||||
|
||||
const getFormCredentials = () => {
|
||||
if (!formRef.current) return undefined;
|
||||
const fd = new FormData(formRef.current);
|
||||
const username = String(fd.get("username") ?? "");
|
||||
const password = String(fd.get("password") ?? "");
|
||||
return { username, password };
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
const creds = getFormCredentials();
|
||||
if (!creds) return;
|
||||
login(creds);
|
||||
};
|
||||
|
||||
const handleSignUp = () => {
|
||||
const creds = getFormCredentials();
|
||||
if (!creds) return;
|
||||
signup(creds);
|
||||
};
|
||||
|
||||
const no_auth_configured =
|
||||
options !== undefined &&
|
||||
@@ -128,6 +110,7 @@ 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
|
||||
@@ -183,28 +166,28 @@ export default function Login() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
{options?.local && (
|
||||
<form
|
||||
ref={formRef}
|
||||
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"
|
||||
name="username"
|
||||
autoComplete="username"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
value={creds.username}
|
||||
onChange={({ target }) =>
|
||||
set((c) => ({ ...c, username: target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={creds.password}
|
||||
onChange={({ target }) =>
|
||||
set((c) => ({ ...c, password: target.value }))
|
||||
}
|
||||
onKeyDown={(e) => e.key === "Enter" && login(creds)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -212,9 +195,7 @@ export default function Login() {
|
||||
{show_sign_up && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
value="signup"
|
||||
onClick={handleSignUp}
|
||||
onClick={() => signup(creds)}
|
||||
disabled={signupPending}
|
||||
>
|
||||
Sign Up
|
||||
@@ -222,27 +203,26 @@ export default function Login() {
|
||||
)}
|
||||
<Button
|
||||
variant="default"
|
||||
type="button"
|
||||
value="login"
|
||||
onClick={handleLogin}
|
||||
onClick={() => login(creds)}
|
||||
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
|
||||
<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>
|
||||
<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>
|
||||
for information on configuring auth.
|
||||
</CardContent>
|
||||
)}
|
||||
|
||||
@@ -10,17 +10,14 @@ import {
|
||||
useSetTitle,
|
||||
useTemplatesQueryBehavior,
|
||||
useUser,
|
||||
useLocalStorage,
|
||||
} from "@lib/hooks";
|
||||
import { Types } from "komodo_client";
|
||||
import { Input } from "@ui/input";
|
||||
import { Button } from "@ui/button";
|
||||
import { useState } from "react";
|
||||
import { Search, Eye, EyeOff } from "lucide-react";
|
||||
import { Search } from "lucide-react";
|
||||
import { NotFound, TemplateQueryBehaviorSelector } from "@components/util";
|
||||
import { Switch } from "@ui/switch";
|
||||
import { UsableResource } from "@types";
|
||||
import { ServerMonitoringTable } from "@components/resources/server/monitoring-table";
|
||||
|
||||
export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
const is_admin = useUser().data?.admin ?? false;
|
||||
@@ -31,10 +28,6 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
const name = type === "ResourceSync" ? "Resource Sync" : type;
|
||||
useSetTitle(name + "s");
|
||||
const [search, set] = useState("");
|
||||
const [monitoring, setMonitoring] = useLocalStorage<boolean>(
|
||||
"servers-monitoring-toggle-v1",
|
||||
false
|
||||
);
|
||||
const [filter_update_available, toggle_filter_update_available] =
|
||||
useFilterByUpdateAvailable();
|
||||
const query =
|
||||
@@ -63,7 +56,12 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
return <NotFound type={undefined} />;
|
||||
}
|
||||
|
||||
const targets = filtered?.map((resource) => ({ type, id: resource.id }));
|
||||
const targets = filtered?.map(
|
||||
(resource): Types.ResourceTarget => ({
|
||||
type,
|
||||
id: resource.id,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -74,36 +72,13 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
</div>
|
||||
}
|
||||
icon={<Components.BigIcon />}
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
{type === "Server" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => setMonitoring(!monitoring)}
|
||||
>
|
||||
{monitoring ? (
|
||||
<>
|
||||
<EyeOff className="w-4 h-4" />
|
||||
Hide Server Stats
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Eye className="w-4 h-4" />
|
||||
Show Server Stats
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<ExportButton targets={targets} />
|
||||
</div>
|
||||
}
|
||||
actions={<ExportButton targets={targets} />}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-wrap gap-4 items-center justify-between">
|
||||
<div className="flex gap-4">
|
||||
{(is_admin || !disable_non_admin_create) && <Components.New />}
|
||||
{!(type === "Server" && monitoring) && <Components.GroupActions />}
|
||||
<Components.GroupActions />
|
||||
</div>
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
{(type === "Stack" || type === "Deployment") && (
|
||||
@@ -115,10 +90,8 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
<Switch checked={filter_update_available} />
|
||||
</div>
|
||||
)}
|
||||
{!(type === "Server" && monitoring) && (
|
||||
<TemplateQueryBehaviorSelector />
|
||||
)}
|
||||
{!(type === "Server" && monitoring) && <TagsFilter />}
|
||||
<TemplateQueryBehaviorSelector />
|
||||
<TagsFilter />
|
||||
<div className="relative">
|
||||
<Search className="w-4 absolute top-[50%] left-3 -translate-y-[50%] text-muted-foreground" />
|
||||
<Input
|
||||
@@ -130,11 +103,7 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{type === "Server" && monitoring ? (
|
||||
<ServerMonitoringTable search={search} />
|
||||
) : (
|
||||
<Components.Table resources={filtered ?? []} />
|
||||
)}
|
||||
<Components.Table resources={filtered ?? []} />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
+36
-56
@@ -3,7 +3,7 @@ import { LOGIN_TOKENS, useAuth, useUser } from "@lib/hooks";
|
||||
import UpdatePage from "@pages/update";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { BrowserRouter, Navigate, Outlet, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
|
||||
// Lazy import pages
|
||||
const Resources = lazy(() => import("@pages/resources"));
|
||||
@@ -63,6 +63,8 @@ const useExchangeToken = () => {
|
||||
};
|
||||
|
||||
export const Router = () => {
|
||||
const { data: user, isLoading, error } = useUser();
|
||||
|
||||
// Handle exchange token loop to avoid showing login flash
|
||||
const exchangeTokenPending = useExchangeToken();
|
||||
if (exchangeTokenPending) {
|
||||
@@ -73,6 +75,11 @@ export const Router = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading && !user) return null;
|
||||
|
||||
if (!user || error) return <Login />;
|
||||
if (!user.enabled) return <UserDisabled />;
|
||||
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -84,36 +91,34 @@ export const Router = () => {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="login" element={<Login />} />
|
||||
<Route element={<RequireAuth />}>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route path="" element={<Home />} />
|
||||
<Route path="settings" element={<Settings />} />
|
||||
<Route path="tree" element={<Tree />} />
|
||||
<Route path="containers" element={<ContainersPage />} />
|
||||
<Route path="resources" element={<AllResources />} />
|
||||
<Route path="schedules" element={<SchedulesPage />} />
|
||||
<Route path="alerts" element={<AlertsPage />} />
|
||||
<Route path="user-groups/:id" element={<UserGroupPage />} />
|
||||
<Route path="users/:id" element={<UserPage />} />
|
||||
<Route path="updates">
|
||||
<Route path="" element={<UpdatesPage />} />
|
||||
<Route path=":id" element={<UpdatePage />} />
|
||||
</Route>
|
||||
<Route path=":type">
|
||||
<Route path="" element={<Resources />} />
|
||||
<Route path=":id" element={<Resource />} />
|
||||
<Route
|
||||
path=":id/service/:service"
|
||||
element={<StackServicePage />}
|
||||
/>
|
||||
<Route
|
||||
path=":id/container/:container"
|
||||
element={<ContainerPage />}
|
||||
/>
|
||||
<Route path=":id/network/:network" element={<NetworkPage />} />
|
||||
<Route path=":id/image/:image" element={<ImagePage />} />
|
||||
<Route path=":id/volume/:volume" element={<VolumePage />} />
|
||||
</Route>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route path="" element={<Home />} />
|
||||
<Route path="settings" element={<Settings />} />
|
||||
<Route path="tree" element={<Tree />} />
|
||||
<Route path="containers" element={<ContainersPage />} />
|
||||
<Route path="resources" element={<AllResources />} />
|
||||
<Route path="schedules" element={<SchedulesPage />} />
|
||||
<Route path="alerts" element={<AlertsPage />} />
|
||||
<Route path="user-groups/:id" element={<UserGroupPage />} />
|
||||
<Route path="users/:id" element={<UserPage />} />
|
||||
<Route path="updates">
|
||||
<Route path="" element={<UpdatesPage />} />
|
||||
<Route path=":id" element={<UpdatePage />} />
|
||||
</Route>
|
||||
<Route path=":type">
|
||||
<Route path="" element={<Resources />} />
|
||||
<Route path=":id" element={<Resource />} />
|
||||
<Route
|
||||
path=":id/service/:service"
|
||||
element={<StackServicePage />}
|
||||
/>
|
||||
<Route
|
||||
path=":id/container/:container"
|
||||
element={<ContainerPage />}
|
||||
/>
|
||||
<Route path=":id/network/:network" element={<NetworkPage />} />
|
||||
<Route path=":id/image/:image" element={<ImagePage />} />
|
||||
<Route path=":id/volume/:volume" element={<VolumePage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
@@ -124,29 +129,4 @@ export const Router = () => {
|
||||
// return <RouterProvider router={ROUTER} />;
|
||||
};
|
||||
|
||||
const RequireAuth = () => {
|
||||
const { data: user, error } = useUser();
|
||||
const location = useLocation();
|
||||
|
||||
if (error) {
|
||||
if (location.pathname === "/") {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
const backto = encodeURIComponent(location.pathname + location.search);
|
||||
return <Navigate to={`/login?backto=${backto}`} replace />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="w-screen h-screen flex justify-center items-center">
|
||||
<Loader2 className="w-8 h-8 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.enabled) return <UserDisabled />;
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Check, ChevronsUpDown, X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@/ui/badge";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/ui/command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/ui/popover";
|
||||
import { Skeleton } from "@/ui/skeleton";
|
||||
|
||||
interface MultiSelectProps {
|
||||
options?: { label: string; value: string }[];
|
||||
value: string[];
|
||||
onChange: (selected: string[]) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function MultiSelect({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Select items...",
|
||||
className,
|
||||
isLoading = false,
|
||||
disabled = false,
|
||||
}: MultiSelectProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleUnselect = (item: string) => {
|
||||
onChange(value.filter((i) => i !== item));
|
||||
};
|
||||
|
||||
const handleSelect = (item: string) => {
|
||||
if (value.includes(item)) {
|
||||
handleUnselect(item);
|
||||
} else {
|
||||
onChange([...value, item]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("w-full", className)}>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
className={cn(
|
||||
"flex h-full w-full transition-all items-center justify-between rounded-md border border-input bg-background text-sm",
|
||||
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"hover:bg-accent hover:text-accent-foreground"
|
||||
)}
|
||||
disabled={disabled}
|
||||
aria-expanded={open}
|
||||
>
|
||||
<div className="flex justify-between flex-1 overflow-hidden">
|
||||
<div
|
||||
className="flex gap-1 flex-1 py-2 px-3 overflow-x-auto"
|
||||
style={{
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "hsl(var(--border)) transparent",
|
||||
}}
|
||||
>
|
||||
{value.length === 0 ? (
|
||||
<span className="text-muted-foreground truncate">
|
||||
{placeholder}
|
||||
</span>
|
||||
) : (
|
||||
value.map((item) => {
|
||||
const option = options?.find((opt) => opt.value === item);
|
||||
return (
|
||||
<Badge key={item} variant="default" className="text-xs">
|
||||
{option?.label}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="ml-1 hover:bg-destructive transition-all hover:text-destructive-foreground rounded-full p-0.5"
|
||||
onKeyDown={(e) =>
|
||||
e.key === "Enter" && handleUnselect(item)
|
||||
}
|
||||
onClick={() => handleUnselect(item)}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
<hr className="border-l border-border bg-red-300 h-6 mx-0.5 my-auto" />
|
||||
<span
|
||||
role="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setOpen((prev) => !prev);
|
||||
}}
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
"p-1 mx-1.5 my-auto h-full outline-none",
|
||||
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"hover:bg-accent/50 rounded-sm cursor-pointer"
|
||||
)}
|
||||
>
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</span>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput autoFocus={false} placeholder="Search items..." />
|
||||
<CommandList>
|
||||
<CommandEmpty className="p-0">
|
||||
{isLoading ? (
|
||||
<div className="p-2">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={index}
|
||||
className="h-4 w-full mb-1 last:mb-0"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-sm py-4 text-muted-foreground">
|
||||
No items found.
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options?.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
onSelect={() => handleSelect(option.value)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value.includes(option.value)
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export {MultiSelect}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { cn } from "@lib/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-primary/10", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
@@ -2197,11 +2197,6 @@ 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"
|
||||
@@ -2293,49 +2288,6 @@ 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"
|
||||
@@ -2428,11 +2380,6 @@ 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"
|
||||
@@ -2539,11 +2486,6 @@ 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"
|
||||
@@ -3022,34 +2964,6 @@ 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"
|
||||
@@ -3080,11 +2994,6 @@ 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"
|
||||
|
||||
@@ -55,9 +55,8 @@ pub async fn copy(
|
||||
}
|
||||
}
|
||||
if !buffer.is_empty() {
|
||||
bulk_update_retry_too_big(&target_db, &collection, &buffer, true)
|
||||
.await
|
||||
.context("Failed to flush documents")?;
|
||||
bulk_update_retry_too_big(&target_db, &collection, &buffer, true).await.context("Failed to flush documents")?;
|
||||
|
||||
}
|
||||
anyhow::Ok(count)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user