Compare commits

..

26 Commits

Author SHA1 Message Date
mbecker20
f0697e812a shift + N open new variable dialog 2025-11-11 14:22:56 -08:00
mbecker20
78766463d6 create variable Enter submit 2025-11-11 14:18:28 -08:00
mbecker20
0fa1edba2c deploy 2.0.0-dev-90 2025-11-11 14:13:55 -08:00
mbecker20
bbd968cac3 bump toml pretty with fix syncing procedure executions with multiline batch patterns 2025-11-11 14:13:25 -08:00
mbecker20
5f24fc1be3 deploy 2.0.0-dev-89 2025-11-11 00:44:49 -08:00
mbecker20
7ecd2b0b0b improve cmd wrapper with comment removal support 2025-11-11 00:43:54 -08:00
mbecker20
7bf44d2e04 fix some broken tabs 2025-11-11 00:35:24 -08:00
mbecker20
24e0672384 dashboard resets page title 2025-11-11 00:18:16 -08:00
mbecker20
04f081631f deploy 2.0.0-dev-88 2025-11-11 00:16:07 -08:00
mbecker20
b1af956b63 fix dashboard pie chart code splitting issue 2025-11-11 00:05:32 -08:00
mbecker20
370712b29f gen served client types 2025-11-11 00:05:02 -08:00
mbecker20
2b6c552964 canius lite update 2025-11-11 00:04:52 -08:00
mbecker20
434a1d8ea9 clippy lint 2025-11-11 00:04:39 -08:00
ChanningHe
0b7f28360f Add optional command wrapper for Docker Compose in StackConfig (#973) 2025-11-10 23:59:09 -08:00
ChanningHe
3c8ef0ab29 Add track option for Additional Env Files (#955) 2025-11-10 23:47:07 -08:00
mbecker20
930b2423c3 deploy 2.0.0-dev-87 2025-11-07 10:33:23 -08:00
mbecker20
546747b5f2 add timeout to dns ip resolve, only use ipv4 2025-11-07 10:32:55 -08:00
mbecker20
c6df866755 better aws builder config organization 2025-11-07 10:04:45 -08:00
mbecker20
ea5e684915 better useUserTargetPermissions 2025-11-06 22:18:31 -08:00
mbecker20
64db8933de RefreshBuildCache after build 2025-11-04 00:27:34 -08:00
mbecker20
7a5580de57 builder uppercase login 2025-11-04 00:06:46 -08:00
mbecker20
b1656bb174 log about enabling user linger 2025-11-03 10:29:50 -08:00
Badal Singh
559ce103da Update setup-periphery.py (#958) 2025-11-03 09:57:49 -08:00
mbecker20
75e278a57b builder fix partial_default 2025-10-30 00:41:27 -07:00
mbecker20
430f3ddc34 fix omni search container double select on same name 2025-10-29 00:02:32 -07:00
mbecker20
6c30c202e9 add Terminals to omni search 2025-10-28 23:59:41 -07:00
34 changed files with 801 additions and 441 deletions

235
Cargo.lock generated
View File

@@ -65,6 +65,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -138,9 +144,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "async-compression"
version = "0.4.32"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0"
checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2"
dependencies = [
"compression-codecs",
"compression-core",
@@ -187,9 +193,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-config"
version = "1.8.8"
version = "1.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8"
checksum = "1856b1b48b65f71a4dd940b1c0931f9a7b646d4a924b9828ffefc1454714668a"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -217,9 +223,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
version = "1.2.8"
version = "1.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc"
checksum = "86590e57ea40121d47d3f2e131bfd873dea15d78dc2f4604f4734537ad9e56c4"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -254,9 +260,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.5.12"
version = "1.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d"
checksum = "8fe0fd441565b0b318c76e7206c8d1d0b0166b3e986cf30e890b61feb6192045"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -278,9 +284,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.176.0"
version = "1.184.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171b12a84d9c7b43b75bf2ae99e86ce40ff0b5ecc8194a67d547e55c1ad2438e"
checksum = "f78a1031a701f3f82e3e9d7087cd09e7ef69bd7e592719521c873d8f02c464d6"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -301,9 +307,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.86.0"
version = "1.89.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0abbfab841446cce6e87af853a3ba2cc1bc9afcd3f3550dd556c43d434c86d"
checksum = "a9c1b1af02288f729e95b72bd17988c009aa72e26dcb59b3200f86d7aea726c9"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -323,9 +329,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.88.0"
version = "1.91.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a68d675582afea0e94d38b6ca9c5aaae4ca14f1d36faa6edb19b42e687e70d7"
checksum = "4e8122301558dc7c6c68e878af918880b82ff41897a60c8c4e18e4dc4d93e9f1"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -345,9 +351,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.88.0"
version = "1.92.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d30990923f4f675523c51eb1c0dec9b752fb267b36a61e83cbc219c9d86da715"
checksum = "a0c7808adcff8333eaa76a849e6de926c6ac1a1268b9fd6afe32de9c29ef29d2"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -368,9 +374,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.3.5"
version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68"
checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -401,15 +407,16 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
version = "0.62.4"
version = "0.62.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3feafd437c763db26aa04e0cc7591185d0961e64c61885bece0fb9d50ceac671"
checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"bytes",
"bytes-utils",
"futures-core",
"futures-util",
"http 0.2.12",
"http 1.3.1",
"http-body 0.4.6",
@@ -421,9 +428,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http-client"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1053b5e587e6fa40ce5a79ea27957b04ba660baa02b28b7436f64850152234f1"
checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -440,7 +447,7 @@ dependencies = [
"hyper-util",
"pin-project-lite",
"rustls 0.21.12",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -451,9 +458,9 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
version = "0.61.6"
version = "0.61.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff418fc8ec5cadf8173b10125f05c2e7e1d46771406187b2c878557d4503390"
checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54"
dependencies = [
"aws-smithy-types",
]
@@ -479,9 +486,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.9.3"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ab99739082da5347660c556689256438defae3bcefd66c52b095905730e404"
checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -503,9 +510,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3683c5b152d2ad753607179ed71988e8cfd52964443b4f74fd8e552d0bbfeb46"
checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -520,9 +527,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.3.3"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5b3a7486f6690ba25952cabf1e7d75e34d69eaff5081904a47bc79074d6457"
checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e"
dependencies = [
"base64-simd",
"bytes",
@@ -546,18 +553,18 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
version = "0.60.11"
version = "0.60.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c34127e8c624bc2999f3b657e749c1393bedc9cd97b92a804db8ced4d2e163"
checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
version = "1.3.9"
version = "1.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1"
checksum = "d79fb68e3d7fe5d4833ea34dc87d2e97d26d3086cb3da660bb6b1f76d98680b6"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -625,13 +632,14 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.10.3"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
checksum = "5136e6c5e7e7978fe23e9876fb924af2c0f84c72127ac6ac17e7c46f457d362c"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-core",
"futures-util",
"headers",
"http 1.3.1",
@@ -639,8 +647,6 @@ dependencies = [
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"serde_core",
"tower-layer",
"tower-service",
"tracing",
@@ -671,7 +677,7 @@ dependencies = [
"hyper 1.7.0",
"hyper-util",
"pin-project-lite",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"tokio",
@@ -806,9 +812,9 @@ dependencies = [
[[package]]
name = "bollard"
version = "0.19.3"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7646ee90964aa59e9f832a67182791396a19a5b1d76eb17599a8310a7e2e09"
checksum = "87a52479c9237eb04047ddb94788c41ca0d26eaff8b697ecfbb4c32f7fdc3b1b"
dependencies = [
"base64 0.22.1",
"bollard-stubs",
@@ -902,7 +908,7 @@ dependencies = [
[[package]]
name = "cache"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"tokio",
@@ -1019,9 +1025,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1029,9 +1035,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -1094,7 +1100,7 @@ dependencies = [
[[package]]
name = "command"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"komodo_client",
"shlex",
@@ -1104,9 +1110,9 @@ dependencies = [
[[package]]
name = "compression-codecs"
version = "0.4.31"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23"
checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b"
dependencies = [
"compression-core",
"flate2",
@@ -1115,13 +1121,13 @@ dependencies = [
[[package]]
name = "compression-core"
version = "0.4.29"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb"
checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582"
[[package]]
name = "config"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"colored",
"indexmap 2.12.0",
@@ -1227,9 +1233,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "croner"
version = "3.0.0"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c007081651a19b42931f86f7d4f74ee1c2a7d0cd2c6636a81695b5ffd4e9990"
checksum = "4aa42bcd3d846ebf66e15bd528d1087f75d1c6c1c66ebff626178a106353c576"
dependencies = [
"chrono",
"derive_builder",
@@ -1443,7 +1449,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "database"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"async-compression",
@@ -1466,7 +1472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"pem-rfc7468",
"pem-rfc7468 0.7.0",
"zeroize",
]
@@ -1732,7 +1738,7 @@ dependencies = [
"generic-array",
"group",
"hkdf",
"pem-rfc7468",
"pem-rfc7468 0.7.0",
"pkcs8",
"rand_core 0.6.4",
"sec1",
@@ -1742,7 +1748,7 @@ dependencies = [
[[package]]
name = "encoding"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"bytes",
@@ -1784,7 +1790,7 @@ dependencies = [
[[package]]
name = "environment"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"formatting",
@@ -1794,7 +1800,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"thiserror 2.0.17",
]
@@ -1879,6 +1885,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -1890,7 +1902,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"serror",
]
@@ -2056,7 +2068,7 @@ dependencies = [
[[package]]
name = "git"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"cache",
@@ -2127,21 +2139,23 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
@@ -2454,7 +2468,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.7.0",
"hyper-util",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -2688,7 +2702,7 @@ dependencies = [
[[package]]
name = "interpolate"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"komodo_client",
@@ -2793,9 +2807,9 @@ dependencies = [
[[package]]
name = "jsonwebtoken"
version = "10.1.0"
version = "10.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b"
checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e"
dependencies = [
"aws-lc-rs",
"base64 0.22.1",
@@ -2810,7 +2824,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"chrono",
@@ -2838,7 +2852,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2874,7 +2888,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"arc-swap",
@@ -2921,7 +2935,7 @@ dependencies = [
"reqwest",
"resolver_api",
"response",
"rustls 0.23.34",
"rustls 0.23.35",
"secret_file",
"serde",
"serde_json",
@@ -2948,7 +2962,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"arc-swap",
@@ -2979,7 +2993,7 @@ dependencies = [
"periphery_client",
"portable-pty",
"resolver_api",
"rustls 0.23.34",
"rustls 0.23.35",
"secret_file",
"serde",
"serde_json",
@@ -3069,7 +3083,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "logger"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"komodo_client",
@@ -3300,7 +3314,7 @@ dependencies = [
"percent-encoding",
"rand 0.8.5",
"rustc_version_runtime",
"rustls 0.23.34",
"rustls 0.23.35",
"rustversion",
"serde",
"serde_bytes",
@@ -3361,7 +3375,7 @@ dependencies = [
[[package]]
name = "noise"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"arc-swap",
@@ -3369,7 +3383,7 @@ dependencies = [
"colored",
"der",
"komodo_client",
"pem-rfc7468",
"pem-rfc7468 1.0.0",
"pkcs8",
"secret_file",
"serde_json",
@@ -3659,11 +3673,11 @@ dependencies = [
[[package]]
name = "ordered_hash_map"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176"
checksum = "a6c699f8a30f345785be969deed7eee4c73a5de58c7faf61d6a3251ef798ff61"
dependencies = [
"hashbrown 0.13.2",
"hashbrown 0.15.5",
"serde",
]
@@ -3768,6 +3782,15 @@ dependencies = [
"base64ct",
]
[[package]]
name = "pem-rfc7468"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -3776,7 +3799,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "periphery_client"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"derive_variants",
@@ -3996,7 +4019,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.34",
"rustls 0.23.35",
"socket2 0.6.1",
"thiserror 2.0.17",
"tokio",
@@ -4016,7 +4039,7 @@ dependencies = [
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pki-types",
"slab",
"thiserror 2.0.17",
@@ -4208,7 +4231,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"serde",
@@ -4257,7 +4280,7 @@ dependencies = [
[[package]]
name = "response"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"axum",
@@ -4362,9 +4385,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.34"
version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"aws-lc-rs",
"log",
@@ -4527,7 +4550,7 @@ dependencies = [
[[package]]
name = "secret_file"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"tokio",
]
@@ -5273,7 +5296,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls 0.23.34",
"rustls 0.23.35",
"tokio",
]
@@ -5297,7 +5320,7 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
"tokio",
@@ -5307,9 +5330,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.16"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -5354,9 +5377,9 @@ dependencies = [
[[package]]
name = "toml_pretty"
version = "1.2.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd195be7600b4b26e951a25b382b67a976d2d44bb967a1a5b573a3e63d18c29"
checksum = "abc44e8fe3b5ec4cce2db87a3c11e0ebc396d4c2d59da64d6f0c00e78f9b9296"
dependencies = [
"ordered_hash_map",
"serde",
@@ -5560,7 +5583,7 @@ dependencies = [
[[package]]
name = "transport"
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
dependencies = [
"anyhow",
"axum",
@@ -5573,7 +5596,7 @@ dependencies = [
"periphery_client",
"pin-project-lite",
"rand 0.9.2",
"rustls 0.23.34",
"rustls 0.23.35",
"serde",
"serror",
"sha1",
@@ -5604,7 +5627,7 @@ dependencies = [
"httparse",
"log",
"rand 0.9.2",
"rustls 0.23.34",
"rustls 0.23.35",
"rustls-pki-types",
"sha1",
"thiserror 2.0.17",

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "2.0.0-dev-86"
version = "2.0.0-dev-90"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -48,14 +48,14 @@ partial_derive2 = "0.4.3"
derive_variants = "1.0.0"
mongo_indexed = "2.0.2"
resolver_api = "3.0.0"
toml_pretty = "1.2.0"
toml_pretty = "2.0.0"
mungos = "3.2.2"
svi = "1.2.0"
# ASYNC
reqwest = { version = "0.12.24", default-features = false, features = ["json", "stream", "rustls-tls-native-roots"] }
tokio = { version = "1.48.0", features = ["full"] }
tokio-util = { version = "0.7.16", features = ["io", "codec"] }
tokio-util = { version = "0.7.17", features = ["io", "codec"] }
tokio-stream = { version = "0.1.17", features = ["sync"] }
pin-project-lite = "0.2.16"
futures-util = "0.3.31"
@@ -63,7 +63,7 @@ arc-swap = "1.7.1"
# SERVER
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] }
axum-extra = { version = "0.10.3", features = ["typed-header"] }
axum-extra = { version = "0.12.1", features = ["typed-header"] }
tower-http = { version = "0.6.6", features = ["fs", "cors"] }
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
axum = { version = "0.8.6", features = ["ws", "json", "macros"] }
@@ -94,15 +94,15 @@ opentelemetry = "0.31.0"
tracing = "0.1.41"
# CONFIG
clap = { version = "4.5.50", features = ["derive"] }
clap = { version = "4.5.51", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
# CRYPTO / AUTH
uuid = { version = "1.18.1", features = ["v4", "fast-rng", "serde"] }
jsonwebtoken = { version = "10.1.0", features = ["aws_lc_rs"] } # locked back with octorust
rustls = { version = "0.23.34", features = ["aws-lc-rs"] }
pem-rfc7468 = { version = "0.7.0", features = ["alloc"] }
jsonwebtoken = { version = "10.2.0", features = ["aws_lc_rs"] } # locked back with octorust
rustls = { version = "0.23.35", features = ["aws-lc-rs"] }
pem-rfc7468 = { version = "1.0.0", features = ["alloc"] }
openidconnect = "4.0.1"
urlencoding = "2.1.3"
bcrypt = "0.17.1"
@@ -122,23 +122,23 @@ hickory-resolver = "0.25.2"
portable-pty = "0.9.0"
shell-escape = "0.1.5"
crossterm = "0.29.0"
bollard = "0.19.3"
bollard = "0.19.4"
sysinfo = "0.37.1"
shlex = "1.3.0"
# CLOUD
aws-config = "1.8.8"
aws-sdk-ec2 = "1.176.0"
aws-credential-types = "1.2.8"
aws-config = "1.8.10"
aws-sdk-ec2 = "1.184.0"
aws-credential-types = "1.2.9"
## CRON
english-to-cron = "0.1.6"
chrono-tz = "0.10.4"
chrono = "0.4.42"
croner = "3.0.0"
croner = "3.0.1"
# MISC
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
async-compression = { version = "0.4.33", features = ["tokio", "gzip"] }
derive_builder = "0.20.2"
comfy-table = "7.2.1"
typeshare = "1.0.4"

View File

@@ -17,9 +17,12 @@ use formatting::format_serror;
use futures_util::future::join_all;
use interpolate::Interpolator;
use komodo_client::{
api::execute::{
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
RunBuild,
api::{
execute::{
BatchExecutionResponse, BatchRunBuild, CancelBuild, Deploy,
RunBuild,
},
write::RefreshBuildCache,
},
entities::{
alert::{Alert, AlertData, SeverityLevel},
@@ -41,6 +44,7 @@ use uuid::Uuid;
use crate::{
alert::send_alerts,
api::write::WriteArgs,
helpers::{
build_git_token,
builder::{cleanup_builder_instance, connect_builder_periphery},
@@ -186,7 +190,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
update.finalize();
let id = update.id.clone();
if let Err(e) = update_update(update).await {
warn!("failed to modify Update {id} on db | {e:#}");
warn!("Failed to modify Update {id} on db | {e:#}");
}
if !is_server_builder {
cancel_clone.cancel();
@@ -215,12 +219,12 @@ impl Resolve<ExecuteArgs> for RunBuild {
Ok(builder) => builder,
Err(e) => {
warn!(
"failed to get builder for build {} | {e:#}",
"Failed to get Builder for Build {} | {e:#}",
build.name
);
update.logs.push(Log::error(
"get builder",
format_serror(&e.context("failed to get builder").into()),
"Get Builder",
format_serror(&e.context("Failed to get Builder").into()),
));
return handle_early_return(
update, build.id, build.name, false,
@@ -276,7 +280,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
let commit_message = match res {
Ok(res) => {
debug!("finished repo clone");
debug!("Finished repo clone");
update.logs.extend(res.res.logs);
update.commit_hash =
res.res.commit_hash.unwrap_or_default().to_string();
@@ -312,10 +316,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
commit_hash: optional_string(&update.commit_hash),
// Unused for now
additional_tags: Default::default(),
}) => res.context("failed at call to periphery to build"),
}) => res.context("Failed at call to Periphery to build"),
_ = cancel.cancelled() => {
info!("Build cancelled during build, cleaning up builder");
update.push_error_log("Build cancelled", String::from("user cancelled build during docker build"));
update.push_error_log("Build cancelled", String::from("User cancelled build during docker build"));
cleanup_builder_instance(periphery, cleanup_data, &mut update)
.await;
return handle_early_return(update, build.id, build.name, true).await
@@ -328,10 +332,10 @@ impl Resolve<ExecuteArgs> for RunBuild {
update.logs.extend(logs);
}
Err(e) => {
warn!("error in build | {e:#}");
warn!("Error in build | {e:#}");
update.push_error_log(
"Build Error",
format_serror(&e.context("failed to build").into()),
format_serror(&e.context("Failed to build").into()),
)
}
};
@@ -382,12 +386,15 @@ impl Resolve<ExecuteArgs> for RunBuild {
update_update(update.clone()).await?;
let Build { id, name, .. } = build;
if update.success {
// don't hold response up for user
tokio::spawn(async move {
handle_post_build_redeploy(&build.id).await;
handle_post_build_redeploy(&id).await;
});
} else {
let name = name.clone();
let target = update.target.clone();
let version = update.version;
tokio::spawn(async move {
@@ -398,16 +405,22 @@ impl Resolve<ExecuteArgs> for RunBuild {
resolved_ts: Some(komodo_timestamp()),
resolved: true,
level: SeverityLevel::Warning,
data: AlertData::BuildFailed {
id: build.id,
name: build.name,
version,
},
data: AlertData::BuildFailed { id, name, version },
};
send_alerts(&[alert]).await
});
}
if let Err(e) = (RefreshBuildCache { build: name })
.resolve(&WriteArgs { user: user.clone() })
.await
{
update.push_error_log(
"Refresh build cache",
format_serror(&e.error.into()),
);
}
Ok(update.clone())
}
}
@@ -565,7 +578,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
.await
{
warn!(
"failed to set CancelBuild Update status Complete after timeout | {e:#}"
"Failed to set CancelBuild Update status Complete after timeout | {e:#}"
)
}
});

View File

@@ -9,7 +9,7 @@ use formatting::format_serror;
use komodo_client::entities::{
FileContents, RepoExecutionArgs,
repo::Repo,
stack::{Stack, StackRemoteFileContents},
stack::{AdditionalEnvFile, Stack, StackRemoteFileContents},
to_path_compatible_name,
update::Log,
};
@@ -60,24 +60,22 @@ pub async fn maybe_login_registry(
pub fn env_file_args(
env_file_path: Option<&str>,
additional_env_files: &[String],
additional_env_files: &[AdditionalEnvFile],
) -> anyhow::Result<String> {
let mut res = String::new();
for file in additional_env_files.iter().filter(|&path| {
let Some(komodo_path) = env_file_path else {
return true;
};
// Filter komodo env out of additional env file if its also in there.
// It will be always be added last / have highest priority.
path != komodo_path
}) {
write!(res, " --env-file {file}").with_context(|| {
format!("Failed to write --env-file arg for {file}")
// Add additional env files (except komodo's own, which comes last)
for file in additional_env_files
.iter()
.filter(|f| env_file_path != Some(f.path.as_str()))
{
let path = &file.path;
write!(res, " --env-file {path}").with_context(|| {
format!("Failed to write --env-file arg for {path}")
})?;
}
// Add this last, so it is applied on top
// Add komodo's env file last for highest priority
if let Some(file) = env_file_path {
write!(res, " --env-file {file}").with_context(|| {
format!("Failed to write --env-file arg for {file}")

View File

@@ -8,14 +8,18 @@ use command::{
use formatting::format_serror;
use git::write_commit_file;
use interpolate::Interpolator;
use komodo_client::entities::{
FileContents, RepoExecutionResponse, all_logs_success,
stack::{
ComposeFile, ComposeProject, ComposeService,
ComposeServiceDeploy, StackRemoteFileContents, StackServiceNames,
use komodo_client::{
entities::{
FileContents, RepoExecutionResponse, all_logs_success,
stack::{
ComposeFile, ComposeProject, ComposeService,
ComposeServiceDeploy, StackRemoteFileContents,
StackServiceNames,
},
to_path_compatible_name,
update::Log,
},
to_path_compatible_name,
update::Log,
parsers::parse_multiline_command,
};
use periphery_client::api::compose::*;
use resolver_api::Resolve;
@@ -361,7 +365,7 @@ impl Resolve<super::Args> for ComposePull {
)?;
let file_paths = stack
.all_file_paths()
.all_tracked_file_paths()
.into_iter()
.map(|path| {
(
@@ -658,10 +662,18 @@ impl Resolve<super::Args> for ComposeUp {
// Run compose up
let extra_args = format_extra_args(&stack.config.extra_args);
let command = format!(
let mut command = format!(
"{docker_compose} -p {project_name} -f {file_args}{env_file_args} up -d{extra_args}{service_args}",
);
// Apply compose cmd wrapper if configured
let compose_cmd_wrapper =
parse_multiline_command(&stack.config.compose_cmd_wrapper);
if !compose_cmd_wrapper.is_empty() {
command =
compose_cmd_wrapper.replace("[[COMPOSE_COMMAND]]", &command);
}
let span = info_span!("RunComposeUp");
let Some(log) = run_komodo_command_with_sanitization(
"Compose Up",

View File

@@ -1,5 +1,6 @@
use std::{
net::IpAddr, path::PathBuf, str::FromStr as _, sync::OnceLock,
time::Duration,
};
use anyhow::Context;
@@ -221,12 +222,10 @@ fn opendns_resolver() -> &'static OpenDNSResolver {
static OPENDNS_RESOLVER: OnceLock<OpenDNSResolver> =
OnceLock::new();
OPENDNS_RESOLVER.get_or_init(|| {
// OpenDNS resolver ips
// OpenDNS resolver ipv4s
let ips = [
IpAddr::from_str("208.67.222.222").unwrap(),
IpAddr::from_str("208.67.220.220").unwrap(),
IpAddr::from_str("2620:119:35::35").unwrap(),
IpAddr::from_str("2620:119:53::53").unwrap(),
IpAddr::from_str("208.67.222.222").unwrap(),
];
// trust_negative_responses=true means NXDOMAIN/empty NOERROR from an
@@ -249,15 +248,23 @@ fn opendns_resolver() -> &'static OpenDNSResolver {
})
}
/// Includes 1s timeout
pub async fn resolve_host_public_ip() -> anyhow::Result<String> {
opendns_resolver()
.lookup_ip("myip.opendns.com.")
.await
.context("Failed to query OpenDNS resolvers for host public IP")?
.into_iter()
.map(|ip| ip.to_string())
.next()
.context("OpenDNS call for public IP didn't return anything")
tokio::time::timeout(Duration::from_secs(1), async {
opendns_resolver()
.lookup_ip("myip.opendns.com.")
.await
.context(
"Failed to query OpenDNS resolvers for host public IP",
)?
.into_iter()
.map(|ip| ip.to_string())
.next()
.context("OpenDNS call for public IP didn't return anything")
})
.await
.context("OpenDNS call for public IP timed out")
.flatten()
}
// =====

View File

@@ -327,7 +327,7 @@ pub struct UrlBuilderConfig {
/// The address of the Periphery agent
#[serde(default = "default_address")]
#[builder(default = default_address())]
#[partial(default(default_address()))]
#[partial_default(default_address())]
pub address: String,
/// An expected public key associated with Periphery private key.
/// If empty, doesn't validate Periphery public key.
@@ -337,7 +337,7 @@ pub struct UrlBuilderConfig {
/// Whether to validate the Periphery tls certificates.
#[serde(default = "default_insecure_tls")]
#[builder(default = default_insecure_tls())]
#[partial(default(default_insecure_tls()))]
#[partial_default(default_insecure_tls())]
pub insecure_tls: bool,
/// Deprecated. Use private / public keys instead.
/// An optional override passkey to use
@@ -423,18 +423,6 @@ pub struct AwsBuilderConfig {
#[partial_default(aws_default_volume_gb())]
pub volume_gb: i32,
/// The port periphery will be running on.
/// Default: `8120`
#[serde(default = "default_port")]
#[builder(default = "default_port()")]
#[partial_default(default_port())]
pub port: i32,
#[serde(default = "default_use_https")]
#[builder(default = "default_use_https()")]
#[partial_default(default_use_https())]
pub use_https: bool,
/// The EC2 ami id to create.
/// The ami should have the periphery client configured to start on startup,
/// and should have the necessary github / dockerhub accounts configured.
@@ -473,14 +461,27 @@ pub struct AwsBuilderConfig {
#[builder(default)]
pub user_data: String,
/// The port periphery will be running on.
/// Default: `8120`
#[serde(default = "default_port")]
#[builder(default = "default_port()")]
#[partial_default(default_port())]
pub port: i32,
#[serde(default = "default_use_https")]
#[builder(default = "default_use_https()")]
#[partial_default(default_use_https())]
pub use_https: bool,
/// An expected public key associated with Periphery private key.
/// If empty, doesn't validate Periphery public key.
#[serde(default)]
pub periphery_public_key: String,
/// Whether to validate the Periphery tls certificates.
#[serde(default = "default_insecure_tls")]
#[builder(default = default_insecure_tls())]
#[partial(default(default_insecure_tls()))]
#[partial_default(default_insecure_tls())]
pub insecure_tls: bool,
/// Which git providers are available on the AMI

View File

@@ -79,14 +79,24 @@ impl Stack {
false
}
pub fn all_file_paths(&self) -> Vec<String> {
/// Get tracked additional env files (those that Komodo should manage)
fn tracked_env_files(&self) -> impl Iterator<Item = &str> {
self
.config
.additional_env_files
.iter()
.filter(|f| f.track)
.map(|f| f.path.as_str())
}
pub fn all_tracked_file_paths(&self) -> Vec<String> {
let mut res = self
.compose_file_paths()
.iter()
.cloned()
// Makes sure to dedup them, while maintaining ordering
.collect::<IndexSet<_>>();
res.extend(self.config.additional_env_files.clone());
res.extend(self.tracked_env_files().map(str::to_string));
res.extend(
self.config.config_files.iter().map(|f| f.path.clone()),
);
@@ -103,11 +113,8 @@ impl Stack {
.collect::<IndexSet<_>>();
res.extend(
self
.config
.additional_env_files
.iter()
.cloned()
.map(StackFileDependency::full_redeploy),
.tracked_env_files()
.map(|p| StackFileDependency::full_redeploy(p.to_string())),
);
res.extend(self.config.config_files.clone());
res.into_iter().collect()
@@ -450,13 +457,10 @@ pub struct StackConfig {
///
/// Note. It is already included as an `additional_file`.
/// Don't add it again there.
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[serde(default)]
#[partial_attr(serde(default))]
#[builder(default)]
pub additional_env_files: Vec<String>,
pub additional_env_files: Vec<AdditionalEnvFile>,
/// Add additional config files either in repo or on host to track.
/// Can add any files associated with the stack to enable editing them in the UI.
@@ -517,6 +521,17 @@ pub struct StackConfig {
#[builder(default)]
pub build_extra_args: Vec<String>,
/// Optional command wrapper for secrets management tools.
/// Wraps the docker compose up command with a prefix command.
/// Use [[COMPOSE_COMMAND]] as placeholder for the full compose command.
///
/// Examples:
/// - "op run -- [[COMPOSE_COMMAND]]" (1password CLI)
/// - "sops exec-file --no-fifo /path/to/secret.env '[[COMPOSE_COMMAND]]'" (sops)
#[serde(default)]
#[builder(default)]
pub compose_cmd_wrapper: String,
/// Ignore certain services declared in the compose file when checking
/// the stack status. For example, an init service might be exited, but the
/// stack should be healthy. This init service should be in `ignore_services`
@@ -619,6 +634,7 @@ impl Default for StackConfig {
run_build: Default::default(),
destroy_before_deploy: Default::default(),
build_extra_args: Default::default(),
compose_cmd_wrapper: Default::default(),
skip_secret_interp: Default::default(),
linked_repo: Default::default(),
git_provider: default_git_provider(),
@@ -818,6 +834,86 @@ pub enum StackFileRequires {
None,
}
/// Additional env file configuration for Stack.
/// Supports backward compatibility with string-only format.
#[typeshare]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct AdditionalEnvFile {
/// File path relative to run directory
pub path: String,
/// Whether Komodo should track this file's contents.
/// If true (default), Komodo will read, display, diff, and validate.
/// If false, only passed to docker compose via --env-file.
/// Useful for externally managed files (e.g., sops decrypted files).
#[serde(default = "default_true")]
pub track: bool,
}
fn default_true() -> bool {
true
}
/// Used with custom de/serializer for [AdditionalEnvFile]
#[derive(Deserialize)]
struct __AdditionalEnvFile {
path: String,
#[serde(default = "default_true")]
track: bool,
}
impl<'de> Deserialize<'de> for AdditionalEnvFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct AdditionalEnvFileVisitor;
impl<'de> Visitor<'de> for AdditionalEnvFileVisitor {
type Value = AdditionalEnvFile;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
write!(formatter, "string or AdditionalEnvFile (object)")
}
fn visit_string<E>(self, path: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(AdditionalEnvFile {
path,
track: default_true(),
})
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::visit_string(self, v.to_string())
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
__AdditionalEnvFile::deserialize(
MapAccessDeserializer::new(map).into_deserializer(),
)
.map(|v| AdditionalEnvFile {
path: v.path,
track: v.track,
})
}
}
deserializer.deserialize_any(AdditionalEnvFileVisitor)
}
}
/// Configure additional file dependencies of the Stack.
#[typeshare]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]

View File

@@ -2205,6 +2205,22 @@ export type GetStackActionStateResponse = StackActionState;
export type GetStackLogResponse = Log;
/**
* Additional env file configuration for Stack.
* Supports backward compatibility with string-only format.
*/
export interface AdditionalEnvFile {
/** File path relative to run directory */
path: string;
/**
* Whether Komodo should track this file's contents.
* If true (default), Komodo will read, display, diff, and validate.
* If false, only passed to docker compose via --env-file.
* Useful for externally managed files (e.g., sops decrypted files).
*/
track: boolean;
}
export enum StackFileRequires {
/** Diff requires service redeploy. */
Redeploy = "Redeploy",
@@ -2342,7 +2358,7 @@ export interface StackConfig {
* Note. It is already included as an `additional_file`.
* Don't add it again there.
*/
additional_env_files?: string[];
additional_env_files?: AdditionalEnvFile[];
/**
* Add additional config files either in repo or on host to track.
* Can add any files associated with the stack to enable editing them in the UI.
@@ -2374,6 +2390,16 @@ export interface StackConfig {
* Only used if `run_build: true`
*/
build_extra_args?: string[];
/**
* Optional command wrapper for secrets management tools.
* Wraps the docker compose up command with a prefix command.
* Use [[COMPOSE_COMMAND]] as placeholder for the full compose command.
*
* Examples:
* - "op run -- [[COMPOSE_COMMAND]]" (1password CLI)
* - "sops exec-file --no-fifo /path/to/secret.env '[[COMPOSE_COMMAND]]'" (sops)
*/
compose_cmd_wrapper?: string;
/**
* Ignore certain services declared in the compose file when checking
* the stack status. For example, an init service might be exited, but the
@@ -4283,12 +4309,6 @@ export interface AwsBuilderConfig {
instance_type: string;
/** The size of the builder volume in gb */
volume_gb: number;
/**
* The port periphery will be running on.
* Default: `8120`
*/
port: number;
use_https: boolean;
/**
* The EC2 ami id to create.
* The ami should have the periphery client configured to start on startup,
@@ -4316,6 +4336,12 @@ export interface AwsBuilderConfig {
security_group_ids?: string[];
/** The user data to deploy the instance with. */
user_data?: string;
/**
* The port periphery will be running on.
* Default: `8120`
*/
port: number;
use_https: boolean;
/**
* An expected public key associated with Periphery private key.
* If empty, doesn't validate Periphery public key.

View File

@@ -49,9 +49,9 @@
"react-router-dom": "7.6.1",
"react-xtermjs": "1.0.10",
"sanitize-html": "2.17.0",
"shell-quote": "1.8.1",
"tailwind-merge": "2.6.0",
"tailwindcss-animate": "1.0.7",
"shell-quote": "1.8.1"
"tailwindcss-animate": "1.0.7"
},
"devDependencies": {
"@types/react": "19.1.6",

View File

@@ -2339,6 +2339,21 @@ export interface StackActionState {
}
export type GetStackActionStateResponse = StackActionState;
export type GetStackLogResponse = Log;
/**
* Additional env file configuration for Stack.
* Supports backward compatibility with string-only format.
*/
export interface AdditionalEnvFile {
/** File path relative to run directory */
path: string;
/**
* Whether Komodo should track this file's contents.
* If true (default), Komodo will read, display, diff, and validate.
* If false, only passed to docker compose via --env-file.
* Useful for externally managed files (e.g., sops decrypted files).
*/
track: boolean;
}
export declare enum StackFileRequires {
/** Diff requires service redeploy. */
Redeploy = "Redeploy",
@@ -2474,7 +2489,7 @@ export interface StackConfig {
* Note. It is already included as an `additional_file`.
* Don't add it again there.
*/
additional_env_files?: string[];
additional_env_files?: AdditionalEnvFile[];
/**
* Add additional config files either in repo or on host to track.
* Can add any files associated with the stack to enable editing them in the UI.
@@ -2506,6 +2521,16 @@ export interface StackConfig {
* Only used if `run_build: true`
*/
build_extra_args?: string[];
/**
* Optional command wrapper for secrets management tools.
* Wraps the docker compose up command with a prefix command.
* Use [[COMPOSE_COMMAND]] as placeholder for the full compose command.
*
* Examples:
* - "op run -- [[COMPOSE_COMMAND]]" (1password CLI)
* - "sops exec-file --no-fifo /path/to/secret.env '[[COMPOSE_COMMAND]]'" (sops)
*/
compose_cmd_wrapper?: string;
/**
* Ignore certain services declared in the compose file when checking
* the stack status. For example, an init service might be exited, but the
@@ -4204,12 +4229,6 @@ export interface AwsBuilderConfig {
instance_type: string;
/** The size of the builder volume in gb */
volume_gb: number;
/**
* The port periphery will be running on.
* Default: `8120`
*/
port: number;
use_https: boolean;
/**
* The EC2 ami id to create.
* The ami should have the periphery client configured to start on startup,
@@ -4237,6 +4256,12 @@ export interface AwsBuilderConfig {
security_group_ids?: string[];
/** The user data to deploy the instance with. */
user_data?: string;
/**
* The port periphery will be running on.
* Default: `8120`
*/
port: number;
use_https: boolean;
/**
* An expected public key associated with Periphery private key.
* If empty, doesn't validate Periphery public key.

View File

@@ -18,7 +18,12 @@ import {
import { Box, CalendarDays, Home, Search, Terminal, User } from "lucide-react";
import { Fragment, ReactNode, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { cn, RESOURCE_TARGETS, usableResourcePath } from "@lib/utils";
import {
cn,
RESOURCE_TARGETS,
terminalLink,
usableResourcePath,
} from "@lib/utils";
import { Badge } from "@ui/badge";
import { ResourceComponents } from "./resources";
import { Switch } from "@ui/switch";
@@ -121,6 +126,8 @@ export const OmniDialog = ({
{showContainers && (
<OmniContainers search={search} closeSearch={() => setOpen(false)} />
)}
<OmniTerminals search={search} closeSearch={() => setOpen(false)} />
</CommandList>
</CommandDialog>
);
@@ -265,25 +272,77 @@ const OmniContainers = ({
<>
<CommandSeparator />
<CommandGroup heading="Containers">
{containers?.map((container) => (
<CommandItem
key={container.id}
value={container.name}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(
`/servers/${container.server_id!}/container/${container.name}`
);
}}
>
<DOCKER_LINK_ICONS.container
server_id={container.server_id!}
name={container.name}
/>
{container.name}
</CommandItem>
))}
{containers?.map((container) => {
const key = container.server_id + container.name;
return (
<CommandItem
key={key}
value={key}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(
`/servers/${container.server_id!}/container/${container.name}`
);
}}
>
<DOCKER_LINK_ICONS.container
server_id={container.server_id!}
name={container.name}
/>
{container.name}
</CommandItem>
);
})}
</CommandGroup>
</>
);
};
const OmniTerminals = ({
search,
closeSearch,
}: {
search: string;
closeSearch: () => void;
}) => {
const _terminals = useRead("ListTerminals", {}).data;
const terminals = useMemo(() => {
return _terminals?.filter((c) => {
const searchTerms = search
.toLowerCase()
.split(" ")
.filter((term) => term);
if (searchTerms.length === 0) return true;
const lower = c.name.toLowerCase();
return searchTerms.every(
(term) => lower.includes(term) || "terminals".includes(term)
);
});
}, [_terminals, search]);
const navigate = useNavigate();
if ((terminals?.length ?? 0) < 1) return null;
return (
<>
<CommandSeparator />
<CommandGroup heading="Terminals">
{terminals?.map((terminal) => {
const key = JSON.stringify(terminal.target) + terminal.name;
return (
<CommandItem
key={key}
value={key}
className="flex items-center gap-2 cursor-pointer"
onSelect={() => {
closeSearch();
navigate(terminalLink(terminal));
}}
>
<Terminal className="w-4 h-4" />
{terminal.name}
</CommandItem>
);
})}
</CommandGroup>
</>
);

View File

@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";

View File

@@ -17,7 +17,7 @@ import {
} from "@lib/color";
import { cn } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { Card } from "@ui/card";
import { Badge } from "@ui/badge";

View File

@@ -104,13 +104,6 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
description:
"Whether to connect to the instance over the public IP. Otherwise, will use the internal IP.",
},
port: {
description: "Configure the port to connect to Periphery on.",
placeholder: "Input port",
},
use_https: {
description: "Whether to connect to Periphery using HTTPS.",
},
},
},
{
@@ -132,17 +125,24 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
],
additional: [
{
label: "Auth",
label: "Connection",
labelHidden: true,
components: {
periphery_public_key: {
label: "Periphery Public Key",
description:
"If provided, the associated private key must be set as Periphery 'private_key'. For Periphery -> Core connection, either this or using 'periphery_public_key' in Core config is required for Periphery to be able to connect.",
"If provided, the associated private key must be set as Periphery 'private_key'.",
placeholder: "custom-public-key",
},
port: {
description: "Configure the port to connect to Periphery on.",
placeholder: "Input port",
},
use_https: {
description: "Whether to connect to Periphery using HTTPS.",
},
insecure_tls: {
description: "Skip Periphery TLS certificate validation.",
description: "Skip Periphery TLS certificate validation when HTTPS is enabled.",
},
},
},
@@ -352,7 +352,7 @@ const UrlBuilderConfig = ({ id }: { id: string }) => {
},
},
{
label: "Auth",
label: "Connection",
labelHidden: true,
components: {
periphery_public_key: {

View File

@@ -24,7 +24,7 @@ import {
ResourcePageHeader,
} from "../common";
import { RunBuild } from "../build/actions";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import {
ContainerPortsTableView,
DockerResourceLink,

View File

@@ -3,7 +3,7 @@ import { useDeployment } from ".";
import { useLocalStorage, usePermissions } from "@lib/hooks";
import { useServer } from "../server";
import { useMemo } from "react";
import { MobileFriendlyTabsSelector } from "@ui/mobile-friendly-tabs";
import { MobileFriendlyTabsSelector, TabNoContent } from "@ui/mobile-friendly-tabs";
import { DeploymentConfig } from "./config";
import { DeploymentLogs } from "./log";
import { DeploymentInspect } from "./inspect";
@@ -34,16 +34,12 @@ const DeploymentTabsInner = ({
useServer(deployment.info.server_id)?.info.container_terminals_disabled ??
true;
const state = deployment.info.state;
const logsDisabled =
!specificLogs ||
state === undefined ||
state === Types.DeploymentState.Unknown ||
state === Types.DeploymentState.NotDeployed;
const inspectDisabled =
!specificInspect ||
const downOrUnknown =
state === undefined ||
state === Types.DeploymentState.Unknown ||
state === Types.DeploymentState.NotDeployed;
const logsDisabled = !specificLogs || downOrUnknown;
const inspectDisabled = !specificInspect || downOrUnknown;
const terminalDisabled =
!specificTerminal ||
container_terminals_disabled ||
@@ -55,34 +51,36 @@ const DeploymentTabsInner = ({
? "Config"
: _view;
const Selector = useMemo(
() => (
<MobileFriendlyTabsSelector
tabs={[
{
value: "Config",
},
{
value: "Log",
disabled: logsDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
]}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
),
const tabs = useMemo<TabNoContent<DeploymentTabsView>[]>(
() => [
{
value: "Config",
},
{
value: "Log",
disabled: logsDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
],
[logsDisabled, inspectDisabled, terminalDisabled]
);
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
);
const target: Types.TerminalTarget = useMemo(
() => ({
type: "Deployment",

View File

@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";

View File

@@ -18,7 +18,7 @@ import {
import { cn } from "@lib/utils";
import { useServer } from "../server";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { RepoLink, StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { useToast } from "@ui/use-toast";

View File

@@ -23,7 +23,7 @@ import {
import { ServerTable } from "./table";
import { DeleteResource, NewResource, ResourcePageHeader } from "../common";
import { ActionWithDialog, ConfirmButton, StatusBadge } from "@components/util";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { ServerStatsMini } from "./stats-mini";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";

View File

@@ -1,7 +1,10 @@
import { useLocalStorage, usePermissions, useRead, useUser } from "@lib/hooks";
import { useServer } from ".";
import { ReactNode, useMemo } from "react";
import { MobileFriendlyTabsSelector } from "@ui/mobile-friendly-tabs";
import {
MobileFriendlyTabsSelector,
TabNoContent,
} from "@ui/mobile-friendly-tabs";
import { ServerStats } from "./stats";
import { ServerInfo } from "./info";
import { ServerConfig } from "./config";
@@ -14,10 +17,10 @@ import { ServerTerminals } from "@components/terminal/server";
import { Card, CardHeader, CardTitle } from "@ui/card";
import { Types } from "komodo_client";
type ServerTabView = "Config" | "Stats" | "Docker" | "Resources" | "Terminals";
type ServerTabsView = "Config" | "Stats" | "Docker" | "Resources" | "Terminals";
export const ServerTabs = ({ id }: { id: string }) => {
const [view, setView] = useLocalStorage<ServerTabView>(
const [view, setView] = useLocalStorage<ServerTabsView>(
`server-${id}-tab`,
"Config"
);
@@ -45,36 +48,38 @@ export const ServerTabs = ({ id }: { id: string }) => {
const noResources = noDeployments && noRepos && noStacks;
const Selector = useMemo(
() => (
<MobileFriendlyTabsSelector
tabs={[
{
value: "Config",
},
{
value: "Stats",
},
{
value: "Docker",
},
{
value: "Resources",
disabled: noResources,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
]}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
),
const tabs = useMemo<TabNoContent<ServerTabsView>[]>(
() => [
{
value: "Config",
},
{
value: "Stats",
},
{
value: "Docker",
},
{
value: "Resources",
disabled: noResources,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
],
[noResources, terminalDisabled]
);
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
);
switch (view) {
case "Config":
return <ServerConfig id={id} titleOther={Selector} />;

View File

@@ -42,6 +42,8 @@ import {
import { LinkedRepoConfig } from "@components/config/linked_repo";
import { Button } from "@ui/button";
import { Input } from "@ui/input";
import { Label } from "@ui/label";
import { Switch } from "@ui/switch";
import { useStack } from ".";
import { filterBySplit } from "@lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
@@ -244,19 +246,86 @@ export const StackConfig = ({
},
additional_env_files:
(mode === "Files On Server" || mode === "Git Repo") &&
((values, set) => (
<ConfigList
label="Additional Env Files"
boldLabel
addLabel="Add Env File"
description="Add additional env files to pass with '--env-file'. Relative to the 'Run Directory'."
field="additional_env_files"
values={values ?? []}
set={set}
disabled={disabled}
placeholder=".env"
/>
)),
((values, set) => {
const files = (values ?? []).map((v: any) =>
typeof v === "string" ? { path: v, track: true } : v
);
return (
<ConfigItem label="Additional Env Files" boldLabel>
<div className="flex flex-col gap-2 w-full">
{files.map((file: any, i: number) => (
<div key={i} className="flex items-center gap-4 w-full">
<Input
value={file.path || ""}
onChange={(e) => {
const newFiles = [...files];
newFiles[i] = {
path: e.target.value,
track: file.track ?? true,
};
set({ additional_env_files: newFiles });
}}
placeholder=".env"
disabled={disabled}
className="w-[400px] max-w-full"
/>
<div className="flex items-center gap-1.5">
<Switch
checked={file.track ?? true}
onCheckedChange={(track) => {
const newFiles = [...files];
newFiles[i] = { ...newFiles[i], track };
set({ additional_env_files: newFiles });
}}
disabled={disabled}
id={`track-${i}`}
/>
<Label htmlFor={`track-${i}`} className="text-sm">
Track
</Label>
</div>
{!disabled && (
<Button
variant="secondary"
onClick={() => {
set({
additional_env_files: files.filter(
(_: any, idx: number) => idx !== i
),
});
}}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<Button
variant="secondary"
onClick={() => {
set({
additional_env_files: [
...files,
{ path: "", track: true },
],
});
}}
className="w-fit"
>
<PlusCircle className="w-4 h-4 mr-2" />
Add Env File
</Button>
)}
<div className="text-sm text-muted-foreground">
Add additional env files to pass with '--env-file'. Relative
to the 'Run Directory'. Uncheck 'Track' for externally managed
files (e.g., sops decrypted).
</div>
</div>
</ConfigItem>
);
}),
},
};
@@ -368,6 +437,26 @@ export const StackConfig = ({
),
},
},
{
label: "Command Wrapper",
description:
"Optional wrapper to execute 'docker compose up -d' as a subcommand of tools like secrets management.",
components: {
compose_cmd_wrapper: (value, set) => (
<MonacoEditor
value={
value ??
"# sops exec-env .encrypted.env '[[COMPOSE_COMMAND]]'\n"
}
language="shell"
onValueChange={(compose_cmd_wrapper) =>
set({ compose_cmd_wrapper })
}
readOnly={disabled}
/>
),
},
},
{
label: "Extra Args",
labelHidden: true,

View File

@@ -35,7 +35,7 @@ import {
import { Badge } from "@ui/badge";
import { Button } from "@ui/button";
import { useToast } from "@ui/use-toast";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipTrigger, TooltipContent } from "@ui/tooltip";

View File

@@ -36,7 +36,7 @@ export const StackTabs = ({ id }: { id: string }) => {
? "Config"
: _view;
const tabsNoContent = useMemo<TabNoContent<StackTabsView>[]>(
const tabs = useMemo<TabNoContent<StackTabsView>[]>(
() => [
{
value: "Config",
@@ -59,7 +59,7 @@ export const StackTabs = ({ id }: { id: string }) => {
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabsNoContent}
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"

View File

@@ -18,7 +18,7 @@ import {
} from "@lib/color";
import { cn } from "@lib/utils";
import { fmt_date } from "@lib/formatting";
import { DashboardPieChart } from "@pages/dashboard";
import { DashboardPieChart } from "@components/util";
import { StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { GroupActions } from "@components/group-actions";

View File

@@ -1,4 +1,4 @@
import { useRead } from "@lib/hooks";
import { useAllResources, useRead } from "@lib/hooks";
import { Types } from "komodo_client";
import { UsableResource } from "@types";
@@ -6,25 +6,17 @@ export const useUserTargetPermissions = (user_target: Types.UserTarget) => {
const permissions = useRead("ListUserTargetPermissions", {
user_target,
}).data;
const servers = useRead("ListServers", {}).data;
const stacks = useRead("ListStacks", {}).data;
const deployments = useRead("ListDeployments", {}).data;
const builds = useRead("ListBuilds", {}).data;
const repos = useRead("ListRepos", {}).data;
const procedures = useRead("ListProcedures", {}).data;
const builders = useRead("ListBuilders", {}).data;
const alerters = useRead("ListAlerters", {}).data;
const syncs = useRead("ListResourceSyncs", {}).data;
const allResources = useAllResources();
const perms: (Types.Permission & { name: string })[] = [];
addPerms(user_target, permissions, "Server", servers, perms);
addPerms(user_target, permissions, "Stack", stacks, perms);
addPerms(user_target, permissions, "Deployment", deployments, perms);
addPerms(user_target, permissions, "Build", builds, perms);
addPerms(user_target, permissions, "Repo", repos, perms);
addPerms(user_target, permissions, "Procedure", procedures, perms);
addPerms(user_target, permissions, "Builder", builders, perms);
addPerms(user_target, permissions, "Alerter", alerters, perms);
addPerms(user_target, permissions, "ResourceSync", syncs, perms);
for (const [resource_type, resources] of Object.entries(allResources)) {
addPerms(
user_target,
permissions,
resource_type as UsableResource,
resources,
perms
);
}
return perms;
};

View File

@@ -93,6 +93,7 @@ import {
SelectValue,
} from "@ui/select";
import { useServer } from "./resources/server";
import { PieChart } from "react-minimal-pie-chart";
export const ActionButton = forwardRef<
HTMLButtonElement,
@@ -1634,3 +1635,46 @@ export const ContainerTerminalModeSelector = ({
/>
);
};
export type DashboardPieChartItem = {
title: string;
intention: ColorIntention;
value: number;
};
export const DashboardPieChart = ({
data: _data,
}: {
data: Array<DashboardPieChartItem | false | undefined>;
}) => {
const data = _data.filter((d) => d) as Array<DashboardPieChartItem>;
return (
<div className="flex items-center gap-8">
<div className="flex flex-col gap-2 w-28">
{data.map(({ title, value, intention }) => (
<p key={title} className="flex gap-2 text-xs text-muted-foreground">
<span
className={cn(
"font-bold",
text_color_class_by_intention(intention)
)}
>
{value}
</span>
{title}
</p>
))}
</div>
<PieChart
className="w-32 h-32"
radius={42}
lineWidth={30}
data={data.map(({ title, value, intention }) => ({
title,
value,
color: hex_color_by_intention(intention),
}))}
/>
</div>
);
};

View File

@@ -284,3 +284,22 @@ export const resourceTargetFromTerminalTarget = (
return { type: "Deployment", id: target.params.deployment };
}
};
export const terminalLink = ({
target,
name,
}: {
target: Types.TerminalTarget;
name: string;
}) => {
switch (target.type) {
case "Server":
return `/servers/${target.params.server}/terminal/${name}`;
case "Container":
return `/servers/${target.params.server}/container/${target.params.container}/terminal/${name}`;
case "Stack":
return `/stacks/${target.params.stack}/service/${target.params.service}/terminal/${name}`;
case "Deployment":
return `/deployments/${target.params.deployment}/terminal/${name}`;
}
};

View File

@@ -11,16 +11,14 @@ import { Eye, EyeOff, Settings, Table } from "lucide-react";
import {
action_state_intention,
build_state_intention,
ColorIntention,
hex_color_by_intention,
procedure_state_intention,
repo_state_intention,
text_color_class_by_intention,
} from "@lib/color";
import {
useFilterResources,
useNoResources,
useRead,
useSetTitle,
useUser,
} from "@lib/hooks";
import { cn, usableResourcePath } from "@lib/utils";
@@ -28,7 +26,6 @@ import { Types } from "komodo_client";
import { RequiredResourceComponents, UsableResource } from "@types";
import { DataTable, SortableHeader } from "@ui/data-table";
import { AlertTriangle, Box, Circle, History } from "lucide-react";
import { PieChart } from "react-minimal-pie-chart";
import { Link } from "react-router-dom";
import { UpdateAvailable as StackUpdateAvailable } from "@components/resources/stack";
import { UpdateAvailable as DeploymentUpdateAvailable } from "@components/resources/deployment";
@@ -37,6 +34,7 @@ import { Input } from "@ui/input";
export default function Dashboard() {
const { preferences } = useDashboardPreferences();
useSetTitle(undefined);
return (
<>
<ActiveResources />
@@ -272,49 +270,6 @@ const RecentCard = ({
);
};
export type DashboardPieChartItem = {
title: string;
intention: ColorIntention;
value: number;
};
export const DashboardPieChart = ({
data: _data,
}: {
data: Array<DashboardPieChartItem | false | undefined>;
}) => {
const data = _data.filter((d) => d) as Array<DashboardPieChartItem>;
return (
<div className="flex items-center gap-8">
<div className="flex flex-col gap-2 w-28">
{data.map(({ title, value, intention }) => (
<p key={title} className="flex gap-2 text-xs text-muted-foreground">
<span
className={cn(
"font-bold",
text_color_class_by_intention(intention)
)}
>
{value}
</span>
{title}
</p>
))}
</div>
<PieChart
className="w-32 h-32"
radius={42}
lineWidth={30}
data={data.map(({ title, value, intention }) => ({
title,
value,
color: hex_color_by_intention(intention),
}))}
/>
</div>
);
};
const ActiveResources = () => {
const builds =
useRead("ListBuilds", {}).data?.filter(

View File

@@ -7,6 +7,7 @@ import {
useInvalidate,
useRead,
useSetTitle,
useShiftKeyListener,
useUser,
useWrite,
} from "@lib/hooks";
@@ -237,6 +238,7 @@ const CreateVariable = () => {
const user = useUser().data;
const disabled = !user?.admin;
const submit = () => mutate({ name });
useShiftKeyListener("N", () => !open && setOpen(true));
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
@@ -261,6 +263,11 @@ const CreateVariable = () => {
onChange={(e) =>
setName(e.target.value.toUpperCase().replaceAll(" ", "_"))
}
onKeyDown={(e) => {
if (e.key === "Enter" && name) {
submit();
}
}}
placeholder="Input variable name"
/>
</div>

View File

@@ -43,31 +43,33 @@ export const StackServiceTabs = ({
? "Log"
: _view;
const Selector = useMemo(
() => (
<MobileFriendlyTabsSelector
tabs={[
{
value: "Log",
disabled: logDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
]}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
),
const tabs = useMemo(
() => [
{
value: "Log",
disabled: logDisabled,
},
{
value: "Inspect",
disabled: inspectDisabled,
},
{
value: "Terminals",
disabled: terminalDisabled,
},
],
[logDisabled, inspectDisabled, terminalDisabled]
);
const Selector = (
<MobileFriendlyTabsSelector
tabs={tabs}
value={view}
onValueChange={setView as any}
tabsTriggerClassname="w-[110px]"
/>
);
const target: Types.TerminalTarget = useMemo(
() => ({
type: "Stack",

View File

@@ -17,7 +17,7 @@ import {
useTerminalTargetPermissions,
useWrite,
} from "@lib/hooks";
import { filterBySplit } from "@lib/utils";
import { filterBySplit, terminalLink } from "@lib/utils";
import { Button } from "@ui/button";
import { DataTable, SortableHeader } from "@ui/data-table";
import { Input } from "@ui/input";
@@ -86,7 +86,7 @@ export default function TerminalsPage() {
),
cell: ({ row }) => (
<Link
to={terminal_link(row.original.name, row.original.target)}
to={terminalLink(row.original)}
onClick={(e) => {
e.stopPropagation();
}}
@@ -162,19 +162,6 @@ export default function TerminalsPage() {
);
}
const terminal_link = (name: string, target: Types.TerminalTarget) => {
switch (target.type) {
case "Server":
return `/servers/${target.params.server}/terminal/${name}`;
case "Container":
return `/servers/${target.params.server}/container/${target.params.container}/terminal/${name}`;
case "Stack":
return `/stacks/${target.params.stack}/service/${target.params.service}/terminal/${name}`;
case "Deployment":
return `/deployments/${target.params.deployment}/terminal/${name}`;
}
};
const TerminalTargetResourceLink = ({
target,
}: {

View File

@@ -1464,9 +1464,9 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001718:
version "1.0.30001721"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz"
integrity sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==
version "1.0.30001754"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz"
integrity sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==
chalk@^4.0.0:
version "4.1.2"

View File

@@ -232,7 +232,7 @@ def main():
write_service_file(args, home_dir, bin_dir, config_dir, service_dir)
user = ""
if user_install:
if args.user:
user = " --user"
print("Starting Periphery...")
@@ -241,5 +241,7 @@ def main():
print("Finished Periphery setup.\n")
print(f'Note. Use "systemctl{user} status periphery" to make sure Periphery is running')
print(f'Note. Use "systemctl{user} enable periphery" to have Periphery start on system boot')
if args.user:
print(f'Note. Use "sudo loginctl enable-linger $USER" to make sure Periphery keeps runnning after user logs out')
main()