mirror of
https://github.com/moghtech/komodo.git
synced 2026-08-13 01:09:27 -05:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aebd5ed92 | ||
|
|
a9352ad90b | ||
|
|
37bd221609 | ||
|
|
bf5975bb93 | ||
|
|
d7706a32c1 | ||
|
|
dea9c49772 | ||
|
|
95666e69d8 | ||
|
|
cbc008fc89 | ||
|
|
445fa42a01 | ||
|
|
2c2b0eda8f | ||
|
|
15eefa3385 | ||
|
|
cb9bc80346 | ||
|
|
8c682c091f | ||
|
|
ca021a3979 | ||
|
|
1480c3b020 | ||
|
|
42f452fdda | ||
|
|
8ded07dfe5 | ||
|
|
d51c1cb1e7 | ||
|
|
b5f184b286 | ||
|
|
294ef20019 | ||
|
|
3efcaa0740 | ||
|
|
b5b680dd1d | ||
|
|
bf5ac8b6ac | ||
|
|
dcccc878c8 | ||
|
|
1c87fba8f5 | ||
|
|
2fc35b3c2d | ||
|
|
2012fd1dd9 | ||
|
|
550c0339d6 | ||
|
|
50cf2f2d50 | ||
|
|
b223fefec6 | ||
|
|
7fe56f72ae | ||
|
|
0a9bc397ca | ||
|
|
a03fceba7f | ||
|
|
1bf1574c2a | ||
|
|
83d90e0a16 | ||
|
|
304ffbf01d | ||
|
|
07787d6fa1 | ||
|
|
5d04142a99 | ||
|
|
aecba3be9f | ||
|
|
4c4e5b62e0 | ||
|
|
62f0ca9093 | ||
|
|
3d43e2419f | ||
|
|
d4081c2d6b | ||
|
|
d397cb4ea4 | ||
|
|
0c96e24cd4 | ||
|
|
1c90e768ef | ||
|
|
be73f20fd5 | ||
|
|
330178dbb8 | ||
|
|
c8c01307a0 | ||
|
|
2e80adff2d | ||
|
|
ef5a0982cb | ||
|
|
9ffa40022d | ||
|
|
15eaeab68d | ||
|
|
89ea5ad5a4 | ||
|
|
c05dde3678 | ||
|
|
a17a1005c7 | ||
|
|
d13314eb98 | ||
|
|
f41a61116a | ||
|
|
13fbcae105 | ||
|
|
71c437962f | ||
|
|
e4147ccdaf | ||
|
|
5089375211 | ||
|
|
e489812af4 | ||
|
|
7373ff8d0e | ||
|
|
4e1cd32f3f | ||
|
|
57211746f1 | ||
|
|
5e153fb02b | ||
|
|
b28a413005 | ||
|
|
691671abbd | ||
|
|
504e81c2f7 |
Generated
+96
-71
@@ -234,9 +234,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.8.14"
|
||||
version = "1.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2"
|
||||
checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -254,7 +254,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"hex",
|
||||
"http 1.4.0",
|
||||
"ring",
|
||||
"sha1",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -264,9 +264,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-credential-types"
|
||||
version = "1.2.13"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d203b0bf2626dcba8665f5cd0871d7c2c0930223d6b6be9097592fea21242d0"
|
||||
checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -299,9 +299,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede2ddc593e6c8acc6ce3358c28d6677a6dc49b65ba4b37a2befe14a11297e75"
|
||||
checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
@@ -324,9 +324,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ec2"
|
||||
version = "1.214.0"
|
||||
version = "1.216.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5011a07318505db9ffe5a03e667653b44ee85d392f0e5f36ebd1fff2f2cbfa8"
|
||||
checksum = "0bcafb17cfb3978dbf5b5a637d101a6212bc080cb33b5881da65f87967aedf67"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -349,9 +349,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.95.0"
|
||||
version = "1.96.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00c5ff27c6ba2cbd95e6e26e2e736676fdf6bcf96495b187733f521cfe4ce448"
|
||||
checksum = "f64a6eded248c6b453966e915d32aeddb48ea63ad17932682774eb026fbef5b1"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -373,9 +373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.97.0"
|
||||
version = "1.98.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d186f1e5a3694a188e5a0640b3115ccc6e084d104e16fd6ba968dca072ffef8"
|
||||
checksum = "db96d720d3c622fcbe08bae1c4b04a72ce6257d8b0584cb5418da00ae20a344f"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -397,9 +397,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.99.0"
|
||||
version = "1.100.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9acba7c62f3d4e2408fa998a3a8caacd8b9a5b5549cf36e2372fbdae329d5449"
|
||||
checksum = "fafbdda43b93f57f699c5dfe8328db590b967b8a820a13ccdd6687355dfcc7ca"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -422,9 +422,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sigv4"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37411f8e0f4bea0c3ca0958ce7f18f6439db24d555dbd809787262cd00926aa9"
|
||||
checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-http",
|
||||
@@ -444,9 +444,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-async"
|
||||
version = "1.2.13"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc50d0f63e714784b84223abd7abbc8577de8c35d699e0edd19f0a88a08ae13"
|
||||
checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
@@ -455,9 +455,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http"
|
||||
version = "0.63.5"
|
||||
version = "0.63.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d619373d490ad70966994801bc126846afaa0d1ee920697a031f0cf63f2568e7"
|
||||
checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -476,9 +476,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-http-client"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00ccbb08c10f6bcf912f398188e42ee2eab5f1767ce215a02a73bc5df1bbdd95"
|
||||
checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -506,27 +506,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.62.4"
|
||||
version = "0.62.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb"
|
||||
checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a"
|
||||
dependencies = [
|
||||
"aws-smithy-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-observability"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b"
|
||||
checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-query"
|
||||
version = "0.60.14"
|
||||
version = "0.60.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0"
|
||||
checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd"
|
||||
dependencies = [
|
||||
"aws-smithy-types",
|
||||
"urlencoding",
|
||||
@@ -534,9 +534,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.10.2"
|
||||
version = "1.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ccf7f6eba8b2dcf8ce9b74806c6c185659c311665c4bf8d6e71ebd454db6bf"
|
||||
checksum = "028999056d2d2fd58a697232f9eec4a643cf73a71cf327690a7edad1d2af2110"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
@@ -559,9 +559,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime-api"
|
||||
version = "1.11.5"
|
||||
version = "1.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4af6e5def28be846479bbeac55aa4603d6f7986fc5da4601ba324dd5d377516"
|
||||
checksum = "876ab3c9c29791ba4ba02b780a3049e21ec63dabda09268b175272c3733a79e6"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-types",
|
||||
@@ -576,9 +576,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.4.5"
|
||||
version = "1.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca2734c16913a45343b37313605d84e7d8b34a4611598ce1d25b35860a2bed3"
|
||||
checksum = "d2b1117b3b2bbe166d11199b540ceed0d0f7676e36e7b962b5a437a9971eac75"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
@@ -602,18 +602,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-xml"
|
||||
version = "0.60.14"
|
||||
version = "0.60.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa"
|
||||
checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-types"
|
||||
version = "1.3.13"
|
||||
version = "1.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0470cc047657c6e286346bdf10a8719d26efd6a91626992e0e64481e44323e96"
|
||||
checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-async",
|
||||
@@ -787,13 +787,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a0f5948f30df5f43ac29d310b7476793be97c50787e6ef4a63d960a0d0be827"
|
||||
checksum = "523ab528ce3a7ada6597f8ccf5bd8d85ebe26d5edf311cad4d1d3cfb2d357ac6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"blowfish",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1149,7 +1149,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"shlex",
|
||||
@@ -1489,7 +1489,7 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "database"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@@ -1759,7 +1759,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1801,7 +1801,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"formatting",
|
||||
@@ -1930,7 +1930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"mogh_error",
|
||||
]
|
||||
@@ -2109,7 +2109,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2709,7 +2709,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interpolate"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2835,7 +2835,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2852,6 +2852,7 @@ dependencies = [
|
||||
"mogh_logger",
|
||||
"mogh_pki",
|
||||
"mogh_secret_file",
|
||||
"rustls 0.23.37",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
@@ -2863,7 +2864,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2902,7 +2903,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -2960,7 +2961,7 @@ dependencies = [
|
||||
"svi",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"toml 1.0.4+spec-1.1.0",
|
||||
"toml_pretty",
|
||||
"tracing",
|
||||
"transport",
|
||||
@@ -2975,7 +2976,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3224,9 +3225,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mogh_auth_server"
|
||||
version = "1.2.6"
|
||||
version = "1.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0053e21d85b214a3288c50433cf56cd07ac1dacda66201f5e93205810423daec"
|
||||
checksum = "e748cc16d2f7d9a86fbab3eff5f2e847df205db8ff5aa27728e45271f96d327b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -3280,7 +3281,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_yaml_ng",
|
||||
"thiserror 2.0.18",
|
||||
"toml",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"wildcard",
|
||||
]
|
||||
|
||||
@@ -3392,9 +3393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mogh_server"
|
||||
version = "1.4.4"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3292c49e7483691c6eef791fa96aa2120df97b69241514313e53207945c3d69"
|
||||
checksum = "66ab833484ee2242003819a2b948421dc3469e7d153db71bd7579d5de2660b81"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4030,7 +4031,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"encoding",
|
||||
@@ -4082,9 +4083,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@@ -5406,9 +5407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.38.2"
|
||||
version = "0.38.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efc19935b4b66baa6f654ac7924c192f55b175c00a7ab72410fc24284dacda8"
|
||||
checksum = "d03c61d2a49c649a15c407338afe7accafde9dac869995dccb73e5f7ef7d9034"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
@@ -5573,9 +5574,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
version = "1.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -5670,7 +5671,22 @@ dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.0.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c94c3321114413476740df133f0d8862c61d87c8d26f04c6841e033c8c80db47"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime 1.0.0+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
@@ -5686,10 +5702,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
name = "toml_datetime"
|
||||
version = "1.0.0+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -5983,7 +6008,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "transport"
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
||||
+11
-11
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.0.0-dev-118"
|
||||
version = "2.0.0-dev-124"
|
||||
edition = "2024"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -37,7 +37,7 @@ mogh_error = { version = "1.0.3", default-features = false }
|
||||
derive_default_builder = "0.1.8"
|
||||
async_timing_util = "1.1.0"
|
||||
mogh_auth_client = "1.2.2"
|
||||
mogh_auth_server = "1.2.6"
|
||||
mogh_auth_server = "1.2.12"
|
||||
mogh_secret_file = "1.0.1"
|
||||
mogh_validations = "1.0.1"
|
||||
mogh_rate_limit = "1.0.1"
|
||||
@@ -46,7 +46,7 @@ mongo_indexed = "2.0.2"
|
||||
mogh_resolver = "1.0.0"
|
||||
mogh_config = "1.0.2"
|
||||
mogh_logger = "1.3.1"
|
||||
mogh_server = "1.4.4"
|
||||
mogh_server = "1.4.5"
|
||||
toml_pretty = "2.0.0"
|
||||
mogh_cache = "1.1.1"
|
||||
mogh_pki = "1.1.0"
|
||||
@@ -55,10 +55,10 @@ svi = "1.2.0"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.13.2", default-features = false, features = ["json", "stream", "form", "query", "rustls"] }
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.18", features = ["io", "codec"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["sync"] }
|
||||
pin-project-lite = "0.2.16"
|
||||
pin-project-lite = "0.2.17"
|
||||
futures-util = "0.3.32"
|
||||
arc-swap = "1.8.2"
|
||||
|
||||
@@ -77,7 +77,7 @@ indexmap = { version = "2.13.0", features = ["serde"] }
|
||||
serde = { version = "1.0.227", features = ["derive"] }
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
bson = { version = "2.15.0" } # must keep in sync with mongodb version
|
||||
toml = "0.9.11+spec-1.1.0"
|
||||
toml = "1.0.4"
|
||||
serde_yaml_ng = "0.10.0"
|
||||
serde_json = "1.0.149"
|
||||
serde_qs = "1.0.0"
|
||||
@@ -100,7 +100,7 @@ uuid = { version = "1.21.0", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.10.0"
|
||||
urlencoding = "2.1.3"
|
||||
bcrypt = "0.18.0"
|
||||
bcrypt = "0.19.0"
|
||||
hmac = "0.12.1"
|
||||
sha1 = "0.10.6"
|
||||
sha2 = "0.10.9"
|
||||
@@ -113,13 +113,13 @@ portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.20.1"
|
||||
sysinfo = "0.38.2"
|
||||
sysinfo = "0.38.3"
|
||||
shlex = "1.3.0"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.8.14"
|
||||
aws-sdk-ec2 = "1.214.0"
|
||||
aws-credential-types = "1.2.13"
|
||||
aws-config = "1.8.15"
|
||||
aws-sdk-ec2 = "1.216.0"
|
||||
aws-credential-types = "1.2.14"
|
||||
|
||||
## CRON
|
||||
english-to-cron = "0.1.7"
|
||||
|
||||
@@ -33,6 +33,7 @@ colored.workspace = true
|
||||
dotenvy.workspace = true
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
clap.workspace = true
|
||||
|
||||
@@ -12,6 +12,9 @@ mod config;
|
||||
|
||||
async fn app() -> anyhow::Result<()> {
|
||||
dotenvy::dotenv().ok();
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.install_default()
|
||||
.expect("Failed to install default crypto provider");
|
||||
mogh_logger::init(&config::cli_config().cli_logging)?;
|
||||
let args = config::cli_args();
|
||||
let env = config::cli_env();
|
||||
|
||||
@@ -293,7 +293,7 @@ async fn handler(
|
||||
let req_id = Uuid::new_v4();
|
||||
let variant: ReadRequestVariant = (&request).into();
|
||||
|
||||
debug!(
|
||||
trace!(
|
||||
"READ REQUEST {req_id} | METHOD: {variant} | USER: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
@@ -301,7 +301,7 @@ async fn handler(
|
||||
let res = request.resolve(&ReadArgs { user }).await;
|
||||
|
||||
if let Err(e) = &res {
|
||||
debug!(
|
||||
trace!(
|
||||
"READ REQUEST {req_id} | METHOD: {variant} | ERROR: {:#}",
|
||||
e.error
|
||||
);
|
||||
|
||||
@@ -148,7 +148,7 @@ impl AuthImpl for KomodoAuthImpl {
|
||||
|
||||
fn post_link_redirect(&self) -> &str {
|
||||
static POST_LINK_REDIRECT: LazyLock<String> =
|
||||
LazyLock::new(|| format!("{}/settings", core_config().host));
|
||||
LazyLock::new(|| format!("{}/profile", core_config().host));
|
||||
&POST_LINK_REDIRECT
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,14 @@ impl PeripheryConnectionArgs<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
debug!(
|
||||
host = identifiers.host(),
|
||||
query = core_connection_query(),
|
||||
sec_websocket_accept = accept.to_str().unwrap_or_default(),
|
||||
resource_id = connection.args.id,
|
||||
"[PERIPHERY AUTH] Zero trust identifiers"
|
||||
);
|
||||
|
||||
let identifiers = identifiers.build(
|
||||
accept.as_bytes(),
|
||||
core_connection_query().as_bytes(),
|
||||
|
||||
@@ -129,6 +129,14 @@ async fn existing_server_handler(
|
||||
return;
|
||||
};
|
||||
|
||||
debug!(
|
||||
host = identifiers.host.to_str().unwrap_or_default(),
|
||||
query,
|
||||
sec_websocket_accept = identifiers.accept,
|
||||
resource_id = &server.id,
|
||||
"[PERIPHERY AUTH] Zero trust identifiers"
|
||||
);
|
||||
|
||||
let span = info_span!(
|
||||
"PeripheryLogin",
|
||||
server_id = server.id,
|
||||
|
||||
+17
-16
@@ -33,6 +33,7 @@ use komodo_client::{
|
||||
user::{action_user, system_user},
|
||||
},
|
||||
};
|
||||
use mogh_auth_server::api::login::local::sign_up_local_user;
|
||||
use mogh_resolver::Resolve;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -41,6 +42,7 @@ use crate::{
|
||||
execute::{ExecuteArgs, ExecuteRequest},
|
||||
write::WriteArgs,
|
||||
},
|
||||
auth::KomodoAuthImpl,
|
||||
config::core_config,
|
||||
helpers::update::init_execution_update,
|
||||
network, resource,
|
||||
@@ -301,7 +303,7 @@ async fn ensure_init_user_and_resources() {
|
||||
|
||||
// Assumes if there are any existing users, procedures, or tags,
|
||||
// the default procedures do not need to be set up.
|
||||
let Ok((None, None, None)) = tokio::try_join!(
|
||||
let Ok((None, procedures, tags)) = tokio::try_join!(
|
||||
db.users.find_one(Document::new()),
|
||||
db.procedures.find_one(Document::new()),
|
||||
db.tags.find_one(Document::new()),
|
||||
@@ -314,20 +316,16 @@ async fn ensure_init_user_and_resources() {
|
||||
// Init admin user if set in config.
|
||||
if let Some(username) = &config.init_admin_username {
|
||||
info!("Creating init admin user...");
|
||||
// if let Err(e) = (SignUpLocalUser {
|
||||
// username: username.clone(),
|
||||
// password: config.init_admin_password.clone(),
|
||||
// })
|
||||
// .resolve(&AuthArgs {
|
||||
// headers: Default::default(),
|
||||
// ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
|
||||
// session: None,
|
||||
// })
|
||||
// .await
|
||||
// {
|
||||
// error!("Failed to create init admin user | {:#}", e.error);
|
||||
// return;
|
||||
// }
|
||||
if let Err(e) = sign_up_local_user(
|
||||
&KomodoAuthImpl,
|
||||
username.to_string(),
|
||||
&config.init_admin_password,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to create init admin user | {:#}", e.error);
|
||||
return;
|
||||
}
|
||||
match db
|
||||
.users
|
||||
.find_one(doc! { "username": username })
|
||||
@@ -347,7 +345,10 @@ async fn ensure_init_user_and_resources() {
|
||||
}
|
||||
}
|
||||
|
||||
if config.disable_init_resources {
|
||||
if config.disable_init_resources
|
||||
|| procedures.is_some()
|
||||
|| tags.is_some()
|
||||
{
|
||||
info!("System resources init {}", "DISABLED".red());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,13 @@ pub async fn handler(
|
||||
|
||||
already_logged_connection_error = false;
|
||||
|
||||
debug!(
|
||||
host = identifiers.host(),
|
||||
query,
|
||||
sec_websocket_accept = accept.to_str().unwrap_or_default(),
|
||||
"[CORE AUTH] Zero trust identifiers"
|
||||
);
|
||||
|
||||
let identifiers =
|
||||
identifiers.build(accept.as_bytes(), query.as_bytes());
|
||||
|
||||
|
||||
@@ -109,6 +109,13 @@ async fn handler(
|
||||
|
||||
let query = format!("core={}", urlencoding::encode(&core));
|
||||
|
||||
debug!(
|
||||
host = identifiers.host.to_str().unwrap_or_default(),
|
||||
query,
|
||||
sec_websocket_accept = identifiers.accept,
|
||||
"[CORE AUTH] Zero trust identifiers"
|
||||
);
|
||||
|
||||
if let Err(e) =
|
||||
handle_login(&mut socket, identifiers.build(query.as_bytes()))
|
||||
.await
|
||||
|
||||
@@ -1049,7 +1049,7 @@ impl mogh_server::session::SessionConfig for &CoreConfig {
|
||||
"KOMODO_HOST"
|
||||
}
|
||||
fn expiry_seconds(&self) -> i64 {
|
||||
60
|
||||
60 * 3
|
||||
}
|
||||
fn allow_cross_site(&self) -> bool {
|
||||
self.session_allow_cross_site
|
||||
|
||||
@@ -203,7 +203,9 @@ impl Default for UserConfig {
|
||||
impl UserConfig {
|
||||
pub fn sanitize(&mut self) {
|
||||
if let UserConfig::Local { password } = self {
|
||||
password.clear();
|
||||
if !password.is_empty() {
|
||||
*password = "#".repeat(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"mogh_auth_client": "^1.2.0"
|
||||
"mogh_auth_client": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
|
||||
@@ -7,10 +7,10 @@ jwt-decode@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
||||
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||
|
||||
mogh_auth_client@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mogh_auth_client/-/mogh_auth_client-1.2.0.tgz#d840abb85c967133570f247e0d671ac4054db3ff"
|
||||
integrity sha512-d60lSNzbOJcZwwSeyn7jLBmNI0FQTpXtOjR/OJj3M5KnUpmofpjlgGqLYGbVsZkoVipqohhi7aM2uVHEiicCVQ==
|
||||
mogh_auth_client@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/mogh_auth_client/-/mogh_auth_client-1.2.1.tgz#c8b5e9da101dc8da7b30586e5c5463f5f7a95edb"
|
||||
integrity sha512-8uUjgqagwbMW8BKtTRzfQ4txpw+54hqLszbIvcYf99murVIQu5+NsRZ8/vjP/fOMawgXJXCVsR6O2lamm1BCnQ==
|
||||
dependencies:
|
||||
jwt-decode "^4.0.0"
|
||||
|
||||
|
||||
@@ -295,8 +295,8 @@ impl AddressConnectionIdentifiers {
|
||||
/// Used to extract owned connection identifier
|
||||
/// in server side connection handler.
|
||||
pub struct HeaderConnectionIdentifiers {
|
||||
host: HeaderValue,
|
||||
accept: String,
|
||||
pub host: HeaderValue,
|
||||
pub accept: String,
|
||||
}
|
||||
|
||||
impl HeaderConnectionIdentifiers {
|
||||
@@ -310,7 +310,7 @@ impl HeaderConnectionIdentifiers {
|
||||
let key = headers
|
||||
.remove("sec-websocket-key")
|
||||
.context("Headers do not contain Sec-Websocket-Key")?;
|
||||
let accept = compute_accept(key.as_bytes());
|
||||
let accept = compute_sec_websocket_accept(key.as_bytes());
|
||||
Ok(HeaderConnectionIdentifiers { host, accept })
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ impl HeaderConnectionIdentifiers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_accept(sec_websocket_key: &[u8]) -> String {
|
||||
pub fn compute_sec_websocket_accept(sec_websocket_key: &[u8]) -> String {
|
||||
// This is standard GUID to compute Sec-Websocket-Accept
|
||||
const GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
let mut sha1 = sha1::Sha1::new();
|
||||
|
||||
@@ -3,14 +3,14 @@ module.exports = {
|
||||
"postcss-preset-mantine": {},
|
||||
"postcss-simple-vars": {
|
||||
variables: {
|
||||
"mantine-breakpoint-xs": "36em",
|
||||
"mantine-breakpoint-sm": "48em",
|
||||
"mantine-breakpoint-md": "62em",
|
||||
"mantine-breakpoint-lg": "75em",
|
||||
"mantine-breakpoint-xl": "88em",
|
||||
"mantine-breakpoint-xl2": "104em",
|
||||
"mantine-breakpoint-xl3": "120em",
|
||||
"mantine-breakpoint-xl4": "136em",
|
||||
"mantine-breakpoint-xs": "40em",
|
||||
"mantine-breakpoint-sm": "54em",
|
||||
"mantine-breakpoint-md": "68em",
|
||||
"mantine-breakpoint-lg": "82em",
|
||||
"mantine-breakpoint-xl": "96em",
|
||||
"mantine-breakpoint-xl2": "110em",
|
||||
"mantine-breakpoint-xl3": "126em",
|
||||
"mantine-breakpoint-xl4": "142em",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width="438.549px" height="438.549px" viewBox="0 0 438.549 438.549"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#fceade" d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365
|
||||
<path fill="#000" d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365
|
||||
c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63
|
||||
c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996
|
||||
c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -3,7 +3,7 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 210 210" xml:space="preserve">
|
||||
<path fill="#fceade" d="M0,105C0,47.103,47.103,0,105,0c23.383,0,45.515,7.523,64.004,21.756l-24.4,31.696C133.172,44.652,119.477,40,105,40
|
||||
<path fill="#000" d="M0,105C0,47.103,47.103,0,105,0c23.383,0,45.515,7.523,64.004,21.756l-24.4,31.696C133.172,44.652,119.477,40,105,40
|
||||
c-35.841,0-65,29.159-65,65s29.159,65,65,65c28.867,0,53.398-18.913,61.852-45H105V85h105v20c0,57.897-47.103,105-105,105
|
||||
S0,162.897,0,105z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 703 B |
@@ -8,7 +8,7 @@ import LoadingScreen from "@/ui/loading-screen";
|
||||
import UpdateDetails from "@/components/updates/details";
|
||||
import AlertDetails from "@/components/alerts/details";
|
||||
|
||||
export const TOPBAR_HEIGHT = 70;
|
||||
export const TOPBAR_HEIGHT = 62;
|
||||
|
||||
const App = () => {
|
||||
const [opened, { toggle, close }] = useDisclosure();
|
||||
|
||||
@@ -14,10 +14,10 @@ const Sidebar = ({ close }: { close: () => void }) => {
|
||||
const location = useLocation().pathname;
|
||||
const linkProps = { nav, location };
|
||||
return (
|
||||
<Stack justify="space-between" gap="md" h="100%" m="xl" mt="24">
|
||||
<Stack justify="space-between" gap="md" h="96%" m="xl" mt="24" mr="md">
|
||||
{/* TOP AREA (scrolling) */}
|
||||
<ScrollArea>
|
||||
<Stack gap="0.25rem">
|
||||
<Stack gap="0.15rem" mr="md">
|
||||
<SidebarLink
|
||||
label="Dashboard"
|
||||
icon={<ICONS.Dashboard size="1rem" />}
|
||||
@@ -43,7 +43,7 @@ const Sidebar = ({ close }: { close: () => void }) => {
|
||||
Resources
|
||||
</Text>
|
||||
}
|
||||
my="xs"
|
||||
my="0.1rem"
|
||||
/>
|
||||
|
||||
{SIDEBAR_RESOURCES.map((type) => {
|
||||
@@ -65,7 +65,7 @@ const Sidebar = ({ close }: { close: () => void }) => {
|
||||
Notifications
|
||||
</Text>
|
||||
}
|
||||
my="xs"
|
||||
my="0.1rem"
|
||||
/>
|
||||
|
||||
<SidebarLink
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function TopbarAlerts() {
|
||||
<Menu
|
||||
opened={opened}
|
||||
position="bottom"
|
||||
offset={20}
|
||||
offset={16}
|
||||
onOpen={open}
|
||||
onClose={close}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.spotlight-action[data-selected] {
|
||||
// hexColorByIntention("Neutral")
|
||||
background-color: #3B82F6;
|
||||
|
||||
// ALTERNATE
|
||||
// background-color: var(--mantine-color-accent-9);
|
||||
// color: inherit;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Spotlight, spotlight } from "@mantine/spotlight";
|
||||
import { useOmniSearch } from "./hooks";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { useShiftKeyListener } from "@/lib/hooks";
|
||||
import classes from "./index.module.scss";
|
||||
|
||||
export default function OmniSearch({}: {}) {
|
||||
const { search, setSearch, actions } = useOmniSearch();
|
||||
@@ -48,7 +49,11 @@ export default function OmniSearch({}: {}) {
|
||||
{actions.map((group) => (
|
||||
<Spotlight.ActionsGroup key={group.group} label={group.group}>
|
||||
{group.actions.map((action) => (
|
||||
<Spotlight.Action key={action.id} {...action} />
|
||||
<Spotlight.Action
|
||||
key={action.id}
|
||||
className={classes["spotlight-action"]}
|
||||
{...action}
|
||||
/>
|
||||
))}
|
||||
</Spotlight.ActionsGroup>
|
||||
))}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function TopbarUpdates() {
|
||||
<Menu
|
||||
opened={opened}
|
||||
position="bottom"
|
||||
offset={20}
|
||||
offset={16}
|
||||
onOpen={() => {
|
||||
open();
|
||||
setLastSeenUpdate({});
|
||||
|
||||
@@ -35,18 +35,32 @@ export default function UserDropdown() {
|
||||
}
|
||||
};
|
||||
const user = useUser().data;
|
||||
const avatar = (user?.config.data as { avatar?: string }).avatar;
|
||||
const userInvalidate = useUserInvalidate();
|
||||
const accounts = MoghAuth.LOGIN_TOKENS.accounts();
|
||||
const nav = useNavigate();
|
||||
return (
|
||||
<Menu offset={20} opened={open} onChange={setOpen}>
|
||||
<Menu offset={13} opened={open} onChange={setOpen}>
|
||||
<Menu.Target>
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="lg"
|
||||
leftSection={<User size="1.3rem" />}
|
||||
pl="0.5rem"
|
||||
pr={{ base: "-20", lg: "0.5rem" }}
|
||||
leftSection={
|
||||
avatar ? (
|
||||
<img
|
||||
src={avatar}
|
||||
alt="avatar"
|
||||
style={{
|
||||
width: "1.1rem",
|
||||
height: "1.1rem",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<User size="1.3rem" />
|
||||
)
|
||||
}
|
||||
pl="0.7rem"
|
||||
pr={{ base: "-20", lg: "0.7rem" }}
|
||||
>
|
||||
<Username username={user?.username} />
|
||||
</Button>
|
||||
@@ -135,7 +149,7 @@ export default function UserDropdown() {
|
||||
|
||||
function Account({
|
||||
login,
|
||||
current_id,
|
||||
current_id: currentId,
|
||||
setOpen,
|
||||
rerender,
|
||||
viewLogout,
|
||||
@@ -146,17 +160,17 @@ function Account({
|
||||
rerender: () => void;
|
||||
viewLogout: boolean;
|
||||
}) {
|
||||
const user_id = useMemo(
|
||||
const userId = useMemo(
|
||||
() => MoghAuth.extractUserIdFromJwt(login.jwt),
|
||||
[login.jwt],
|
||||
);
|
||||
const { data: user } = useRead(
|
||||
"GetUsername",
|
||||
{ user_id: user_id! },
|
||||
{ enabled: !!user_id },
|
||||
{ user_id: userId! },
|
||||
{ enabled: !!userId },
|
||||
);
|
||||
if (!user_id || !user) return;
|
||||
const selected = user_id === current_id;
|
||||
if (!userId || !user) return;
|
||||
const selected = userId === currentId;
|
||||
return (
|
||||
<Flex align="center" gap="md" w="100%">
|
||||
<Button
|
||||
@@ -177,7 +191,7 @@ function Account({
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
MoghAuth.LOGIN_TOKENS.change(user_id);
|
||||
MoghAuth.LOGIN_TOKENS.change(userId);
|
||||
location.reload();
|
||||
}}
|
||||
>
|
||||
@@ -185,7 +199,7 @@ function Account({
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt="avatar"
|
||||
style={{ width: "1.3rem", height: "1.3rem", marginRight: "0.5rem" }}
|
||||
style={{ width: "1.1rem", height: "1.1rem", marginRight: "0.5rem" }}
|
||||
/>
|
||||
)}
|
||||
{!user.avatar && (
|
||||
@@ -198,7 +212,7 @@ function Account({
|
||||
<ActionIcon
|
||||
color="red"
|
||||
onClick={() => {
|
||||
MoghAuth.LOGIN_TOKENS.remove(user_id);
|
||||
MoghAuth.LOGIN_TOKENS.remove(userId);
|
||||
if (selected) {
|
||||
location.reload();
|
||||
} else {
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function WebsocketStatus() {
|
||||
{/* The hovercard can open unexpectedly on mobile so is hidden */}
|
||||
<Box hiddenFrom="sm">{Target}</Box>
|
||||
<Box visibleFrom="sm">
|
||||
<HoverCard offset={20}>
|
||||
<HoverCard offset={16}>
|
||||
<HoverCard.Target>{Target}</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Text>Websocket Status</Text>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "@/lib/formatting";
|
||||
import { useInvalidate, useRead, useUser, useWrite } from "@/lib/hooks";
|
||||
import { ResourceComponents, UsableResource } from "@/resources";
|
||||
import { Drawer, Group, Stack, Text } from "@mantine/core";
|
||||
import { ActionIcon, Drawer, Group, Stack, Text } from "@mantine/core";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { Clock, Link2 } from "lucide-react";
|
||||
import CopyButton from "@/ui/copy-button";
|
||||
@@ -92,7 +92,12 @@ export function AlertDetailsContent({
|
||||
return (
|
||||
<Stack gap="xl" m="md">
|
||||
{/** HEADER */}
|
||||
<Text fz="h1">{fmtUpperCamelcase(alert.data.type)}</Text>
|
||||
<Group justify="space-between">
|
||||
<Text fz="h1">{fmtUpperCamelcase(alert.data.type)}</Text>
|
||||
<ActionIcon size="lg" variant="filled" color="red" onClick={close}>
|
||||
<ICONS.Clear size="1.3rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/** DETAILS */}
|
||||
<Stack gap="sm">
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
|
||||
const ONE_DAY_MS = 1000 * 60 * 60 * 24;
|
||||
type ExpiresOptions = "90 days" | "180 days" | "1 year" | "never";
|
||||
type ExpiresOptions = "90 days" | "180 days" | "1 year" | "Never";
|
||||
|
||||
export interface NewApiKeyProps {
|
||||
/** For service user api keys */
|
||||
@@ -51,7 +51,7 @@ export default function NewApiKey({ userId }: { userId?: string }) {
|
||||
"90 days": now + ONE_DAY_MS * 90,
|
||||
"180 days": now + ONE_DAY_MS * 180,
|
||||
"1 year": now + ONE_DAY_MS * 365,
|
||||
never: 0,
|
||||
Never: 0,
|
||||
};
|
||||
const create = () => {
|
||||
const data = {
|
||||
@@ -86,7 +86,6 @@ export default function NewApiKey({ userId }: { userId?: string }) {
|
||||
{created && "Api Key Created"}
|
||||
</Text>
|
||||
}
|
||||
size="lg"
|
||||
>
|
||||
<Stack>
|
||||
{!created && (
|
||||
@@ -134,12 +133,12 @@ export default function NewApiKey({ userId }: { userId?: string }) {
|
||||
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text>Key</Text>
|
||||
<CopyText content={created.key} label="api key" />
|
||||
<CopyText content={created.key} label="api key" w={{ base: 200, lg: 250 }} />
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text>Secret</Text>
|
||||
<CopyText content={created.secret} label="api secret" />
|
||||
<CopyText content={created.secret} label="api secret" w={{ base: 200, lg: 250 }} />
|
||||
</Group>
|
||||
|
||||
<Group justify="end" onClick={close}>
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function ApiKeysSection({
|
||||
isPending={isPending}
|
||||
title="Api Keys"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.Key size="1.2rem" />}
|
||||
titleRight={
|
||||
<Box ml="md">
|
||||
|
||||
@@ -64,8 +64,6 @@ export default function ApiKeysTable({
|
||||
header: "Delete",
|
||||
cell: ({ row }) => (
|
||||
<ConfirmButton
|
||||
variant="filled"
|
||||
color="red"
|
||||
icon={<ICONS.Delete size="1rem" />}
|
||||
onClick={() => onDelete(row.original.key)}
|
||||
loading={deletePending}
|
||||
|
||||
@@ -3,28 +3,29 @@ import { useExecute, useSelectedResources, useWrite } from "@/lib/hooks";
|
||||
import { sendCopyNotification, usableResourceExecuteKey } from "@/lib/utils";
|
||||
import { UsableResource } from "@/resources";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
List,
|
||||
Loader,
|
||||
Menu,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
useMatches,
|
||||
} from "@mantine/core";
|
||||
import { Types } from "komodo_client";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { FC, useState } from "react";
|
||||
|
||||
type Request = Types.ExecuteRequest["type"] | Types.WriteRequest["type"];
|
||||
|
||||
export interface BatchExecutionsProps<T extends Request> {
|
||||
type: UsableResource;
|
||||
executions: T[];
|
||||
executions: [T, FC<{ size?: string | number }>][];
|
||||
}
|
||||
|
||||
export default function BatchExecutions<T extends Request>({
|
||||
@@ -45,6 +46,7 @@ export default function BatchExecutions<T extends Request>({
|
||||
<BatchExecutionsModal
|
||||
type={type}
|
||||
execution={execution}
|
||||
icon={executions.find((e) => e[0] === execution)?.[1] ?? ICONS.Check}
|
||||
onClose={() => setExecution(undefined)}
|
||||
/>
|
||||
</>
|
||||
@@ -58,23 +60,22 @@ function BatchExecutionsDropdownMenu<T extends Request>({
|
||||
disabled,
|
||||
}: {
|
||||
type: UsableResource;
|
||||
executions: T[];
|
||||
executions: [T, FC<{ size?: string | number }>][];
|
||||
onSelect: (item: T) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const width = useMatches({
|
||||
base: "target",
|
||||
xs: 250,
|
||||
});
|
||||
return (
|
||||
<Menu
|
||||
position="bottom-start"
|
||||
offset={16}
|
||||
disabled={disabled}
|
||||
width="target"
|
||||
>
|
||||
<Menu position="bottom-start" offset={16} disabled={disabled} width={width}>
|
||||
<Menu.Target>
|
||||
<Button
|
||||
leftSection={<ICONS.Execution size="1rem" />}
|
||||
rightSection={<ChevronDown size="1rem" />}
|
||||
disabled={disabled}
|
||||
w={{ base: "100%", xs: "fit-content" }}
|
||||
w={{ base: "100%", sm: "fit-content" }}
|
||||
>
|
||||
Execute
|
||||
</Button>
|
||||
@@ -90,9 +91,10 @@ function BatchExecutionsDropdownMenu<T extends Request>({
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{executions.map((execution) => (
|
||||
{executions.map(([execution, Icon]) => (
|
||||
<Menu.Item
|
||||
key={execution}
|
||||
leftSection={<Icon size="1rem" />}
|
||||
onClick={() => onSelect(execution)}
|
||||
renderRoot={(props) => <Button fullWidth {...props} />}
|
||||
>
|
||||
@@ -127,23 +129,29 @@ function BatchExecutionsDropdownMenu<T extends Request>({
|
||||
function BatchExecutionsModal({
|
||||
type,
|
||||
execution,
|
||||
onClose,
|
||||
icon: Icon,
|
||||
onClose: _onClose,
|
||||
}: {
|
||||
type: UsableResource;
|
||||
execution: Request | undefined;
|
||||
icon: FC<{ size?: string | number }>;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useSelectedResources(type);
|
||||
const [input, setInput] = useState("");
|
||||
const onClose = () => {
|
||||
setInput("");
|
||||
_onClose();
|
||||
};
|
||||
|
||||
const { mutate: execute, isPending: executePending } = useExecute(
|
||||
execution! as Types.ExecuteRequest["type"],
|
||||
execution as Types.ExecuteRequest["type"],
|
||||
{
|
||||
onSuccess: onClose,
|
||||
},
|
||||
);
|
||||
const { mutate: write, isPending: writePending } = useWrite(
|
||||
execution! as Types.WriteRequest["type"],
|
||||
execution as Types.WriteRequest["type"],
|
||||
{
|
||||
onSuccess: onClose,
|
||||
},
|
||||
@@ -200,8 +208,10 @@ function BatchExecutionsModal({
|
||||
)}
|
||||
|
||||
<Group justify="end">
|
||||
<ConfirmButton
|
||||
icon={<ICONS.Check size="1rem" />}
|
||||
<Button
|
||||
leftSection={
|
||||
isPending ? <Loader size="1rem" /> : <Icon size="1rem" />
|
||||
}
|
||||
onClick={() => {
|
||||
for (const resource of selected) {
|
||||
if (execution.startsWith("Delete")) {
|
||||
@@ -226,10 +236,9 @@ function BatchExecutionsModal({
|
||||
? false
|
||||
: input !== formatted
|
||||
}
|
||||
loading={isPending}
|
||||
>
|
||||
Confirm
|
||||
</ConfirmButton>
|
||||
{formatted}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContainerPortsMap } from "@/lib/hooks";
|
||||
import { Group, HoverCard, Stack, Text } from "@mantine/core";
|
||||
import { Group, GroupProps, HoverCard, Stack, Text } from "@mantine/core";
|
||||
import { Types } from "komodo_client";
|
||||
import { EthernetPort } from "lucide-react";
|
||||
import { colorByIntention } from "@/lib/color";
|
||||
@@ -8,7 +8,7 @@ import { fmtPortMount } from "@/lib/formatting";
|
||||
import { useServerAddress } from "@/resources/server/hooks";
|
||||
import DividedChildren from "@/ui/divided-children";
|
||||
|
||||
export interface ContainerPortsProps {
|
||||
export interface ContainerPortsProps extends GroupProps {
|
||||
ports: Types.Port[];
|
||||
serverId: string | undefined;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export interface ContainerPortsProps {
|
||||
export default function ContainerPorts({
|
||||
ports,
|
||||
serverId,
|
||||
...groupProps
|
||||
}: ContainerPortsProps) {
|
||||
const portsMap = useContainerPortsMap(ports);
|
||||
const sortedNumericPorts = Object.keys(portsMap)
|
||||
@@ -42,7 +43,7 @@ export default function ContainerPorts({
|
||||
}
|
||||
|
||||
return (
|
||||
<DividedChildren>
|
||||
<DividedChildren {...groupProps}>
|
||||
{groupedPorts.map((group) => (
|
||||
<ContainerPort
|
||||
key={group.start}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function ContainerSelector({
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
{...targetProps}
|
||||
>
|
||||
<Group gap="xs">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<DOCKER_LINK_ICONS.Container serverId={serverId} name={selected} />
|
||||
<Text>{name || (placeholder ?? "Select container")}</Text>
|
||||
</Group>
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function ContainersSection({
|
||||
),
|
||||
cell: ({ row }) =>
|
||||
(row.original.networks?.length ?? 0) > 0 ? (
|
||||
<DividedChildren>
|
||||
<DividedChildren wrap="nowrap">
|
||||
{row.original.networks?.map((network) => (
|
||||
<DockerResourceLink
|
||||
key={network}
|
||||
@@ -170,6 +170,7 @@ export default function ContainersSection({
|
||||
<ContainerPorts
|
||||
ports={row.original.ports ?? []}
|
||||
serverId={row.original.server_id}
|
||||
wrap="nowrap"
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -180,7 +181,7 @@ export default function ContainersSection({
|
||||
<SortableHeader column={column} title="Volumes" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<DividedChildren>
|
||||
<DividedChildren wrap="nowrap">
|
||||
{row.original.volumes?.map((volume) => (
|
||||
<DockerResourceLink
|
||||
key={volume}
|
||||
|
||||
@@ -37,6 +37,7 @@ export default function DockerLabelsSection({
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Box>
|
||||
}
|
||||
|
||||
{...props}
|
||||
>
|
||||
<DockerOptions options={filtered} {...groupProps} />
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function BasePermissionsSection({
|
||||
}
|
||||
title="Base Permissions on Resource Types"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
actions={
|
||||
<Group>
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function SpecificPermissionsSection({
|
||||
<Section
|
||||
title="Per Resource Permissions"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
actions={
|
||||
<Group>
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
|
||||
@@ -8,11 +8,13 @@ export interface RepoLinkProps {
|
||||
|
||||
export default function RepoLink({ repo, link }: RepoLinkProps) {
|
||||
return (
|
||||
<a href={link} target="_blank">
|
||||
<Group gap="sm">
|
||||
<FolderGit size="1rem" />
|
||||
<Text>{repo}</Text>
|
||||
</Group>
|
||||
</a>
|
||||
<Group
|
||||
renderRoot={(props) => <a href={link} target="_blank" {...props} />}
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<FolderGit size="1rem" />
|
||||
<Text>{repo}</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRead, useSearchCombobox } from "@/lib/hooks";
|
||||
import { filterBySplit } from "@/lib/utils";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
ButtonProps,
|
||||
Combobox,
|
||||
@@ -13,6 +14,11 @@ import { ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { DOCKER_LINK_ICONS } from "./docker/link";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import {
|
||||
colorByIntention,
|
||||
containerStateIntention,
|
||||
swarmStateIntention,
|
||||
} from "@/lib/color";
|
||||
|
||||
export interface StackServiceSelectorProps extends ComboboxProps {
|
||||
stackId: string;
|
||||
@@ -22,6 +28,7 @@ export interface StackServiceSelectorProps extends ComboboxProps {
|
||||
placeholder?: string;
|
||||
state?: Types.ContainerStateStatusEnum;
|
||||
targetProps?: ButtonProps;
|
||||
clearable?: boolean;
|
||||
}
|
||||
|
||||
export default function StackServiceSelector({
|
||||
@@ -34,22 +41,33 @@ export default function StackServiceSelector({
|
||||
position = "bottom-start",
|
||||
onOptionSubmit,
|
||||
targetProps,
|
||||
clearable = true,
|
||||
...comboboxProps
|
||||
}: StackServiceSelectorProps) {
|
||||
const services = useRead("ListStackServices", {
|
||||
stack: stackId,
|
||||
}).data?.filter((service) => !state || service?.container?.state === state);
|
||||
|
||||
const firstService = services?.[0].service;
|
||||
useEffect(() => {
|
||||
firstService && onSelect?.(firstService);
|
||||
}, [firstService]);
|
||||
const name = services?.find((s) => s.service === selected)?.service;
|
||||
const container = services?.find((s) => s.service === selected)?.container;
|
||||
|
||||
const selectedService = services?.find((s) => s.service === selected);
|
||||
const name = selectedService?.service;
|
||||
const container = selectedService?.container;
|
||||
const swarmService = selectedService?.swarm_service;
|
||||
|
||||
const intention = !selectedService
|
||||
? "None"
|
||||
: swarmService?.State
|
||||
? swarmStateIntention(swarmService.State)
|
||||
: containerStateIntention(
|
||||
container?.state ?? Types.ContainerStateStatusEnum.Empty,
|
||||
);
|
||||
|
||||
const { search, setSearch, combobox } = useSearchCombobox();
|
||||
|
||||
if (!services) return null;
|
||||
|
||||
const filtered = filterBySplit(services, search, (item) => item.service).sort(
|
||||
(a, b) => {
|
||||
if (a.service > b.service) {
|
||||
@@ -76,21 +94,38 @@ export default function StackServiceSelector({
|
||||
>
|
||||
<Combobox.Target>
|
||||
<Button
|
||||
maw={350}
|
||||
justify="space-between"
|
||||
disabled={disabled}
|
||||
rightSection={<ChevronsUpDown size="0.9rem" />}
|
||||
w="fit-content"
|
||||
maw="100%"
|
||||
rightSection={
|
||||
<Group gap="xs" ml="sm" wrap="nowrap">
|
||||
{clearable && (
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="filled"
|
||||
color="red"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect?.("");
|
||||
}}
|
||||
disabled={disabled || !selected}
|
||||
>
|
||||
<ICONS.Clear size="0.8rem" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ChevronsUpDown size="1rem" />
|
||||
</Group>
|
||||
}
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
disabled={!stackId || disabled}
|
||||
loading={!!stackId && !services}
|
||||
{...targetProps}
|
||||
>
|
||||
<Group gap="xs">
|
||||
{container && (
|
||||
<DOCKER_LINK_ICONS.Container
|
||||
serverId={container.server_id!}
|
||||
name={container.name}
|
||||
/>
|
||||
)}
|
||||
<Text>{name || (placeholder ?? "Select container")}</Text>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<ICONS.Service size="1rem" color={colorByIntention(intention)} />
|
||||
<Text className="text-ellipsis">
|
||||
{name || (placeholder ?? "Select service")}
|
||||
</Text>
|
||||
</Group>
|
||||
</Button>
|
||||
</Combobox.Target>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function TagSelector({
|
||||
pl="0.4rem"
|
||||
className="bordered-heavy"
|
||||
justify="start"
|
||||
w={{ base: "100%", xs: "fit-content" }}
|
||||
w={{ base: "100%", xs: "138" }}
|
||||
fw="normal"
|
||||
leftSection={
|
||||
<Badge
|
||||
|
||||
@@ -22,12 +22,16 @@ export default function Tag({
|
||||
onClick={onClick}
|
||||
style={{ cursor: onClick ? "pointer" : undefined }}
|
||||
rightSection={icon}
|
||||
className="text-ellipsis"
|
||||
w="fit-content"
|
||||
h="fit-content"
|
||||
bdrs="sm"
|
||||
py={py}
|
||||
tt="none"
|
||||
fz={{ base: "xs", lg: "sm" }}
|
||||
styles={{
|
||||
label: { width: "fit-content", height: "fit-content" },
|
||||
}}
|
||||
{...badgeProps}
|
||||
>
|
||||
{tag?.name ?? <Loader size="0.6rem" />}
|
||||
|
||||
@@ -163,7 +163,7 @@ export default function Terminal({
|
||||
display: selected ? undefined : "none",
|
||||
}}
|
||||
>
|
||||
<Box ref={termRef} h="calc(100vh - 30rem)" />
|
||||
<Box ref={termRef} h="max(200px, calc(100vh - 320px))" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useRead } from "@/lib/hooks";
|
||||
import { useWebsocketMessages } from "@/lib/socket";
|
||||
import { updateLogToHtml, versionIsNone } from "@/lib/utils";
|
||||
import { ResourceComponents, UsableResource } from "@/resources";
|
||||
import { Code, Drawer, Group, Stack, Text } from "@mantine/core";
|
||||
import { ActionIcon, Code, Drawer, Group, Stack, Text } from "@mantine/core";
|
||||
import UserAvatar from "@/components/user-avatar";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { Clock, Link2, SquarePen } from "lucide-react";
|
||||
@@ -62,6 +62,7 @@ export default function UpdateDetails({
|
||||
}
|
||||
|
||||
export function UpdateDetailsContent({ id }: { id: string }) {
|
||||
const { close } = useUpdateDetails();
|
||||
const { data: update, refetch } = useRead("GetUpdate", { id });
|
||||
// Listen for updates on the update id and refetch
|
||||
useWebsocketMessages("update-details", (update) => {
|
||||
@@ -80,10 +81,15 @@ export function UpdateDetailsContent({ id }: { id: string }) {
|
||||
return (
|
||||
<Stack gap="xl" m="md">
|
||||
{/** HEADER */}
|
||||
<Text fz="h2">
|
||||
{fmtOperation(update.operation)}{" "}
|
||||
{!versionIsNone(update.version) && fmtVersion(update.version)}
|
||||
</Text>
|
||||
<Group justify="space-between">
|
||||
<Text fz="h2">
|
||||
{fmtOperation(update.operation)}{" "}
|
||||
{!versionIsNone(update.version) && fmtVersion(update.version)}
|
||||
</Text>
|
||||
<ActionIcon size="lg" variant="filled" color="red" onClick={close}>
|
||||
<ICONS.Clear size="1.3rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/** DETAILS */}
|
||||
<Stack gap="sm">
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function UpdatesSection({
|
||||
return (
|
||||
<Section
|
||||
title="Updates"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.Update size="1.3rem" />}
|
||||
actions={
|
||||
link && (
|
||||
|
||||
+11
-13
@@ -141,13 +141,13 @@ export const swarmTaskStateIntention: (
|
||||
};
|
||||
|
||||
export const serverStateIntention: (
|
||||
state?: Types.ServerState,
|
||||
hasVersionMismatch?: boolean,
|
||||
) => ColorIntention = (state, hasVersionMismatch) => {
|
||||
state: Types.ServerState | undefined,
|
||||
versionMismatch: boolean,
|
||||
) => ColorIntention = (state, versionMismatch) => {
|
||||
switch (state) {
|
||||
case Types.ServerState.Ok:
|
||||
// If there's a version mismatch and the server is "Ok", show warning instead
|
||||
return hasVersionMismatch ? "Warning" : "Good";
|
||||
return versionMismatch ? "Warning" : "Good";
|
||||
case Types.ServerState.NotOk:
|
||||
return "Critical";
|
||||
case Types.ServerState.Disabled:
|
||||
@@ -157,18 +157,17 @@ export const serverStateIntention: (
|
||||
}
|
||||
};
|
||||
|
||||
export const stackStateIntention = (state?: Types.StackState) => {
|
||||
export const stackStateIntention = (
|
||||
state: Types.StackState | undefined,
|
||||
updateAvailable: boolean | undefined,
|
||||
) => {
|
||||
switch (state) {
|
||||
case undefined:
|
||||
return "None";
|
||||
case Types.StackState.Deploying:
|
||||
return "Warning";
|
||||
case Types.StackState.Running:
|
||||
return "Good";
|
||||
case Types.StackState.Paused:
|
||||
return "Warning";
|
||||
case Types.StackState.Stopped:
|
||||
return "Critical";
|
||||
return updateAvailable ? "Warning" : "Good";
|
||||
case Types.StackState.Restarting:
|
||||
return "Critical";
|
||||
case Types.StackState.Down:
|
||||
@@ -181,7 +180,8 @@ export const stackStateIntention = (state?: Types.StackState) => {
|
||||
};
|
||||
|
||||
export const deploymentStateIntention: (
|
||||
state?: Types.DeploymentState,
|
||||
state: Types.DeploymentState | undefined,
|
||||
updateAvailable: boolean | undefined,
|
||||
) => ColorIntention = (state) => {
|
||||
switch (state) {
|
||||
case undefined:
|
||||
@@ -192,8 +192,6 @@ export const deploymentStateIntention: (
|
||||
return "Good";
|
||||
case Types.DeploymentState.NotDeployed:
|
||||
return "Neutral";
|
||||
case Types.DeploymentState.Paused:
|
||||
return "Warning";
|
||||
case Types.DeploymentState.Unknown:
|
||||
return "Unknown";
|
||||
default:
|
||||
|
||||
+3
-1
@@ -127,7 +127,9 @@ export function useAuthState() {
|
||||
const search = new URLSearchParams(location.search);
|
||||
|
||||
const _passkey = search.get("passkey");
|
||||
const passkey = _passkey ? JSON.parse(_passkey) : null;
|
||||
const passkey = _passkey
|
||||
? JSON.parse(MoghAuth.Passkey.base64UrlDecode(_passkey))
|
||||
: null;
|
||||
|
||||
// guard against multiple reqs sent
|
||||
// maybe isPending would do this but not sure about with render loop, this for sure will.
|
||||
|
||||
@@ -187,6 +187,10 @@ function onUpdate(
|
||||
invalidate(["ListProcedures"]);
|
||||
} else if (update.operation === Types.Operation.RunAction) {
|
||||
invalidate(["ListActions"]);
|
||||
} else if (update.operation === Types.Operation.Deploy) {
|
||||
invalidate(["ListDeployments"]);
|
||||
} else if (update.operation === Types.Operation.DeployStack) {
|
||||
invalidate(["ListStacks"]);
|
||||
}
|
||||
|
||||
// Do invalidations of these only if update is completed
|
||||
|
||||
@@ -343,3 +343,44 @@ export function listsEqual(a: string[], b: string[]) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does deep compare of 2 items, returning `true` if equal.
|
||||
*
|
||||
* - Functions: Always `true`
|
||||
* - Primitives: Returns direct `a === b`
|
||||
* - Arrays: Returns same items and ordering (recursive)
|
||||
* - Objects: Returns same keys / values (recursive)
|
||||
*
|
||||
* @param a Item a
|
||||
* @param b Item b
|
||||
* @returns a === b
|
||||
*/
|
||||
export function deepCompare(a: any, b: any) {
|
||||
const ta = typeof a;
|
||||
const tb = typeof b;
|
||||
|
||||
if (ta !== tb) return false;
|
||||
|
||||
if (ta === "function") return true;
|
||||
|
||||
if (ta === "object") {
|
||||
const ea = Object.entries(a);
|
||||
const kb = Object.keys(b);
|
||||
|
||||
// Length not equal -> false
|
||||
if (ea.length !== kb.length) return false;
|
||||
|
||||
for (const [key, va] of ea) {
|
||||
const vb = b[key];
|
||||
|
||||
// Early return when any not equal
|
||||
if (!deepCompare(va, vb)) return false;
|
||||
}
|
||||
|
||||
// If it gets through all, it's equal
|
||||
return true;
|
||||
}
|
||||
|
||||
return a === b;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useAlertDetails } from "@/components/alerts/details";
|
||||
import AlertLevel from "@/components/alerts/level";
|
||||
import ResourceTypeSelector from "@/components/resource-type-selector";
|
||||
import { alertLevelIntention } from "@/lib/color";
|
||||
import { fmtUpperCamelcase } from "@/lib/formatting";
|
||||
import { useRead } from "@/lib/hooks";
|
||||
import { UsableResource } from "@/resources";
|
||||
import ResourceLink from "@/resources/link";
|
||||
@@ -69,7 +70,7 @@ export default function Alerts() {
|
||||
icon={ICONS.Alert}
|
||||
description="View historical alerts"
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Stack>
|
||||
{/* QUERY */}
|
||||
<Group>
|
||||
{/* RESOURCE TYPE */}
|
||||
@@ -114,7 +115,10 @@ export default function Alerts() {
|
||||
}
|
||||
setParams(p);
|
||||
}}
|
||||
data={alertTypes}
|
||||
data={alertTypes.map((value) => ({
|
||||
value,
|
||||
label: fmtUpperCamelcase(value),
|
||||
}))}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function Containers() {
|
||||
icon={ICONS.Container}
|
||||
description="See all containers across all servers."
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<MultiSelect
|
||||
placeholder="Filter by Servers"
|
||||
|
||||
@@ -75,7 +75,13 @@ export default function DashboardActiveResources() {
|
||||
state: (
|
||||
<StatusBadge
|
||||
text={stack.info.state}
|
||||
intent={stackStateIntention(stack.info.state)}
|
||||
intent={stackStateIntention(
|
||||
stack.info.state,
|
||||
stack.info.services &&
|
||||
!stack.info.services.every(
|
||||
(service) => !service.update_available,
|
||||
),
|
||||
)}
|
||||
/>
|
||||
),
|
||||
})),
|
||||
@@ -85,7 +91,10 @@ export default function DashboardActiveResources() {
|
||||
state: (
|
||||
<StatusBadge
|
||||
text={deployment.info.state}
|
||||
intent={deploymentStateIntention(deployment.info.state)}
|
||||
intent={deploymentStateIntention(
|
||||
deployment.info.state,
|
||||
deployment.info.update_available,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
})),
|
||||
@@ -147,6 +156,7 @@ export default function DashboardActiveResources() {
|
||||
return (
|
||||
<Section
|
||||
title="Active"
|
||||
mb="xl"
|
||||
icon={
|
||||
<Circle
|
||||
size="1rem"
|
||||
|
||||
@@ -27,7 +27,7 @@ import ServerStatsCard from "@/resources/server/stats-card";
|
||||
|
||||
export default function DashboardRecents() {
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
<Stack>
|
||||
<DashboardNoResources />
|
||||
{SIDEBAR_RESOURCES.map((type) => (
|
||||
<RecentRow key={type} type={type} />
|
||||
@@ -53,9 +53,9 @@ function RecentRow({ type }: { type: UsableResource }) {
|
||||
...(resources?.slice(0, 8 - (recents?.length || 0)) ?? []),
|
||||
];
|
||||
|
||||
const Components = ResourceComponents[type];
|
||||
const RC = ResourceComponents[type];
|
||||
|
||||
const data = Components.useDashboardSummaryData?.();
|
||||
const data = RC.useDashboardSummaryData?.();
|
||||
|
||||
if (ids.length === 0) {
|
||||
return;
|
||||
@@ -67,11 +67,11 @@ function RecentRow({ type }: { type: UsableResource }) {
|
||||
<>
|
||||
<DashboardSummary
|
||||
name={name}
|
||||
icon={<Components.Icon />}
|
||||
icon={<RC.Icon />}
|
||||
data={data}
|
||||
onClick={() => nav(`/${usableResourcePath(type)}`)}
|
||||
>
|
||||
{Components.DashboardSummary && <Components.DashboardSummary />}
|
||||
{RC.DashboardSummary && <RC.DashboardSummary />}
|
||||
</DashboardSummary>
|
||||
<Stack w="100%" px="lg" py="md">
|
||||
<Flex align="center" gap="xs" opacity={0.6}>
|
||||
@@ -115,8 +115,8 @@ function RecentCard({
|
||||
id: string;
|
||||
visibleFrom?: MantineBreakpoint;
|
||||
}) {
|
||||
const Components = ResourceComponents[type];
|
||||
const resource = Components.useListItem(id);
|
||||
const RC = ResourceComponents[type];
|
||||
const resource = RC.useListItem(id);
|
||||
const { preferences } = useDashboardPreferences();
|
||||
|
||||
if (!resource) {
|
||||
@@ -145,7 +145,7 @@ function RecentCard({
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Group style={{ textWrap: "nowrap" }} gap="sm">
|
||||
<Components.Icon id={id} />
|
||||
<RC.Icon id={id} />
|
||||
<ResourceName type={type} id={id} />
|
||||
{resource.template && <TemplateMarker type={type} />}
|
||||
</Group>
|
||||
|
||||
@@ -89,7 +89,6 @@ export default function ContainerTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
+104
-108
@@ -8,9 +8,10 @@ import ResourceSubPage from "@/resources/sub-page";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
import { DataTable } from "@/ui/data-table";
|
||||
import PageGuard from "@/ui/page-guard";
|
||||
import Section from "@/ui/section";
|
||||
import ShowHideButton from "@/ui/show-hide-button";
|
||||
import { Box, Center, Group, Loader, Text } from "@mantine/core";
|
||||
import { Box, Center, Group, Text } from "@mantine/core";
|
||||
import { Types } from "komodo_client";
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
@@ -79,132 +80,127 @@ function ImageInner({
|
||||
},
|
||||
);
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<Center h="30vh">
|
||||
<Loader size="xl" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Center h="30vh">
|
||||
<Text>Failed to inspect image.</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
return (
|
||||
<Center h="30vh">
|
||||
<Text>No image found with given name: {imageName}</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const unused = containers && containers.length === 0 ? true : false;
|
||||
const intention = unused ? "Critical" : "Good";
|
||||
|
||||
return (
|
||||
<ResourceSubPage
|
||||
entityTypeName="Image"
|
||||
parentType="Server"
|
||||
parentId={serverId}
|
||||
name={imageName}
|
||||
icon={ICONS.Image}
|
||||
intent={intention}
|
||||
state={unused ? "Unused" : "In Use"}
|
||||
info={
|
||||
image.Id && (
|
||||
<Group gap="xs">
|
||||
<Text>Id:</Text>
|
||||
<Text title={image.Id} maw={150} className="text-ellipsis">
|
||||
{image.Id}
|
||||
</Text>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
executions={
|
||||
unused && (
|
||||
<ConfirmButton
|
||||
variant="filled"
|
||||
color="red"
|
||||
icon={<ICONS.Delete size="1rem" />}
|
||||
loading={deletePending}
|
||||
onClick={() => deleteImage({ server: serverId, name: imageName })}
|
||||
>
|
||||
Delete Image
|
||||
</ConfirmButton>
|
||||
)
|
||||
<PageGuard
|
||||
isPending={isPending}
|
||||
error={
|
||||
isError
|
||||
? "Failed to inspect image."
|
||||
: !image
|
||||
? "No image found with name: " + imageName
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{containers && containers.length > 0 && (
|
||||
<ContainersSection serverId={serverId} containers={containers} />
|
||||
)}
|
||||
|
||||
{/* TOP LEVEL IMAGE INFO */}
|
||||
<Section title="Details" icon={<ICONS.Info size="1.3rem" />}>
|
||||
<DataTable
|
||||
tableKey="image-info"
|
||||
data={[image]}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "Architecture",
|
||||
header: "Architecture",
|
||||
},
|
||||
{
|
||||
accessorKey: "Os",
|
||||
header: "Os",
|
||||
},
|
||||
{
|
||||
accessorKey: "Size",
|
||||
header: "Size",
|
||||
cell: ({ row }) =>
|
||||
row.original.Size ? fmtSizeBytes(row.original.Size) : "Unknown",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{history && history.length > 0 && (
|
||||
<Section
|
||||
title="History"
|
||||
icon={<ICONS.History size="1.3rem" />}
|
||||
titleRight={
|
||||
<Box pl="md">
|
||||
<ShowHideButton show={showHistory} setShow={setShowHistory} />
|
||||
</Box>
|
||||
{image && (
|
||||
<ResourceSubPage
|
||||
entityTypeName="Image"
|
||||
parentType="Server"
|
||||
parentId={serverId}
|
||||
name={imageName}
|
||||
icon={ICONS.Image}
|
||||
intent={intention}
|
||||
state={unused ? "Unused" : "In Use"}
|
||||
info={
|
||||
image.Id && (
|
||||
<Group gap="xs">
|
||||
<Text>Id:</Text>
|
||||
<Text title={image.Id} maw={150} className="text-ellipsis">
|
||||
{image.Id}
|
||||
</Text>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
executions={
|
||||
unused && (
|
||||
<ConfirmButton
|
||||
variant="filled"
|
||||
color="red"
|
||||
icon={<ICONS.Delete size="1rem" />}
|
||||
loading={deletePending}
|
||||
onClick={() =>
|
||||
deleteImage({ server: serverId, name: imageName })
|
||||
}
|
||||
>
|
||||
Delete Image
|
||||
</ConfirmButton>
|
||||
)
|
||||
}
|
||||
>
|
||||
{showHistory && (
|
||||
{containers && containers.length > 0 && (
|
||||
<ContainersSection serverId={serverId} containers={containers} />
|
||||
)}
|
||||
|
||||
{/* TOP LEVEL IMAGE INFO */}
|
||||
<Section title="Details" icon={<ICONS.Info size="1.3rem" />}>
|
||||
<DataTable
|
||||
tableKey="image-history"
|
||||
data={history.toReversed()}
|
||||
tableKey="image-info"
|
||||
data={[image]}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "CreatedBy",
|
||||
header: "Created By",
|
||||
size: 400,
|
||||
accessorKey: "Architecture",
|
||||
header: "Architecture",
|
||||
},
|
||||
{
|
||||
accessorKey: "Created",
|
||||
header: "Timestamp",
|
||||
accessorKey: "Os",
|
||||
header: "Os",
|
||||
},
|
||||
{
|
||||
accessorKey: "Size",
|
||||
header: "Size",
|
||||
cell: ({ row }) =>
|
||||
fmtDateWithMinutes(new Date(row.original.Created * 1000)),
|
||||
size: 200,
|
||||
row.original.Size
|
||||
? fmtSizeBytes(row.original.Size)
|
||||
: "Unknown",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{history && history.length > 0 && (
|
||||
<Section
|
||||
title="History"
|
||||
icon={<ICONS.History size="1.3rem" />}
|
||||
titleRight={
|
||||
<Box pl="md">
|
||||
<ShowHideButton show={showHistory} setShow={setShowHistory} />
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{showHistory && (
|
||||
<DataTable
|
||||
tableKey="image-history"
|
||||
data={history.toReversed()}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "CreatedBy",
|
||||
header: "Created By",
|
||||
size: 400,
|
||||
},
|
||||
{
|
||||
accessorKey: "Created",
|
||||
header: "Timestamp",
|
||||
cell: ({ row }) =>
|
||||
fmtDateWithMinutes(
|
||||
new Date(row.original.Created * 1000),
|
||||
),
|
||||
size: 200,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{specific.includes(Types.SpecificPermission.Inspect) && (
|
||||
<InspectSection json={image} showToggle />
|
||||
)}
|
||||
{specific.includes(Types.SpecificPermission.Inspect) && (
|
||||
<InspectSection json={image} showToggle />
|
||||
)}
|
||||
|
||||
<DockerLabelsSection labels={image?.Config?.Labels} />
|
||||
</ResourceSubPage>
|
||||
<DockerLabelsSection labels={image?.Config?.Labels} />
|
||||
</ResourceSubPage>
|
||||
)}
|
||||
</PageGuard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { komodo_client, useLoginOptions } from "@/lib/hooks";
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
useComputedColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { MoghAuth } from "komodo_client";
|
||||
import { KeyRound } from "lucide-react";
|
||||
|
||||
export default function LoginHeader({
|
||||
secondFactorPending,
|
||||
}: {
|
||||
secondFactorPending: boolean;
|
||||
}) {
|
||||
const options = useLoginOptions().data;
|
||||
const theme = useComputedColorScheme();
|
||||
return (
|
||||
<Group justify="space-between">
|
||||
<Group gap="sm">
|
||||
<img src="/mogh-512x512.png" width={42} height={42} alt="moghtech" />
|
||||
<Stack gap="0">
|
||||
<Text fz="h2" fw="450" lts="0.1rem">
|
||||
KOMODO
|
||||
</Text>
|
||||
<Text size="md" opacity={0.6} mt={-8}>
|
||||
Log In
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group gap="sm">
|
||||
{(
|
||||
[
|
||||
[options?.oidc, "Oidc"],
|
||||
[options?.github, "Github"],
|
||||
[options?.google, "Google"],
|
||||
] as Array<
|
||||
[boolean | undefined, MoghAuth.Types.ExternalLoginProvider]
|
||||
>
|
||||
).map(
|
||||
([enabled, provider]) =>
|
||||
enabled && (
|
||||
<Button
|
||||
key={provider}
|
||||
onClick={() => komodo_client().auth.externalLogin(provider)}
|
||||
leftSection={
|
||||
provider === "Oidc" ? (
|
||||
<KeyRound size="1rem" />
|
||||
) : (
|
||||
<img
|
||||
src={`/icons/${provider.toLowerCase()}.svg`}
|
||||
alt={provider}
|
||||
style={{
|
||||
width: "1rem",
|
||||
height: "1rem",
|
||||
filter: theme === "dark" ? "invert(1)" : undefined,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
w={110}
|
||||
disabled={secondFactorPending}
|
||||
>
|
||||
{provider}
|
||||
</Button>
|
||||
),
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
komodo_client,
|
||||
useLogin,
|
||||
useLoginOptions,
|
||||
useUserInvalidate,
|
||||
} from "@/lib/hooks";
|
||||
import { useLogin, useLoginOptions, useUserInvalidate } from "@/lib/hooks";
|
||||
import { sanitizeQuery } from "@/lib/utils";
|
||||
import {
|
||||
Button,
|
||||
@@ -12,15 +7,15 @@ import {
|
||||
Group,
|
||||
Loader,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { MoghAuth } from "komodo_client";
|
||||
import { KeyRound } from "lucide-react";
|
||||
import { AlertTriangle, KeyRound } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import LoginHeader from "./header";
|
||||
|
||||
export default function Login({
|
||||
passkeyIsPending: _passkeyIsPending,
|
||||
@@ -101,11 +96,11 @@ export default function Login({
|
||||
},
|
||||
);
|
||||
|
||||
// const noAuthConfigured =
|
||||
// options !== undefined &&
|
||||
// Object.values(options).every((value) => value === false);
|
||||
const noAuthConfigured =
|
||||
options !== undefined &&
|
||||
Object.values(options).every((value) => value === false);
|
||||
|
||||
// const showSignUp = options !== undefined && !options.registration_disabled;
|
||||
const showSignUp = options !== undefined && !options.registration_disabled;
|
||||
|
||||
const localForm = useForm({
|
||||
mode: "uncontrolled",
|
||||
@@ -131,67 +126,10 @@ export default function Login({
|
||||
},
|
||||
});
|
||||
|
||||
const registration_disabled = options?.registration_disabled ?? true;
|
||||
|
||||
return (
|
||||
<Center h="80vh">
|
||||
<Fieldset
|
||||
legend={
|
||||
<Group gap="4rem" justify="space-between">
|
||||
<Group gap="sm">
|
||||
<img
|
||||
src="/mogh-512x512.png"
|
||||
width={42}
|
||||
height={42}
|
||||
alt="moghtech"
|
||||
/>
|
||||
<Stack gap="0">
|
||||
<Text fz="h2" fw="450" lts="0.1rem">
|
||||
KOMODO
|
||||
</Text>
|
||||
<Text size="md" opacity={0.6} mt={-8}>
|
||||
Log In
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Group gap="sm">
|
||||
{(
|
||||
[
|
||||
[options?.oidc, "Oidc"],
|
||||
[options?.github, "Github"],
|
||||
[options?.google, "Google"],
|
||||
] as Array<
|
||||
[boolean | undefined, MoghAuth.Types.ExternalLoginProvider]
|
||||
>
|
||||
).map(
|
||||
([enabled, provider]) =>
|
||||
enabled && (
|
||||
<Button
|
||||
key={provider}
|
||||
onClick={() =>
|
||||
komodo_client().auth.externalLogin(provider)
|
||||
}
|
||||
leftSection={
|
||||
provider === "Oidc" ? (
|
||||
<KeyRound size="1rem" />
|
||||
) : (
|
||||
<img
|
||||
src={`/icons/${provider.toLowerCase()}.svg`}
|
||||
alt={provider}
|
||||
style={{ width: "1rem", height: "1rem" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
w={110}
|
||||
disabled={secondFactorPending}
|
||||
>
|
||||
{provider}
|
||||
</Button>
|
||||
),
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
}
|
||||
legend={<LoginHeader secondFactorPending={secondFactorPending} />}
|
||||
component="form"
|
||||
onSubmit={
|
||||
totpIsPending
|
||||
@@ -199,7 +137,7 @@ export default function Login({
|
||||
: (localForm.onSubmit((form) => login(form)) as any)
|
||||
}
|
||||
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
|
||||
miw={{ lg: "500px" }}
|
||||
miw={{ base: "95vw", xs: "530px" }}
|
||||
maw="95vw"
|
||||
>
|
||||
{options?.local && !secondFactorPending && (
|
||||
@@ -209,16 +147,22 @@ export default function Login({
|
||||
autoFocus
|
||||
label="Username"
|
||||
placeholder="Enter username"
|
||||
autoComplete="username"
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
key={localForm.key("username")}
|
||||
/>
|
||||
<PasswordInput
|
||||
{...localForm.getInputProps("password")}
|
||||
label="Password"
|
||||
placeholder="Enter password"
|
||||
autoComplete="password"
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
key={localForm.key("password")}
|
||||
/>
|
||||
<Group mt="sm" justify="end">
|
||||
{!registration_disabled && (
|
||||
{showSignUp && (
|
||||
<Button
|
||||
variant="outline"
|
||||
w={110}
|
||||
@@ -228,11 +172,7 @@ export default function Login({
|
||||
Sign Up
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
w={110}
|
||||
type="submit"
|
||||
loading={loginPending}
|
||||
>
|
||||
<Button w={110} type="submit" loading={loginPending}>
|
||||
Log In
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -240,13 +180,11 @@ export default function Login({
|
||||
)}
|
||||
|
||||
{passkeyIsPending && (
|
||||
<>
|
||||
<Group justify="center" my="lg">
|
||||
<KeyRound size="1.5rem" />
|
||||
<Group>
|
||||
<Loader />
|
||||
<Text size="lg">Provide your passkey to finish login</Text>
|
||||
</Group>
|
||||
</>
|
||||
<Text size="lg">Provide your passkey to finish login...</Text>
|
||||
<Loader />
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{totpIsPending && (
|
||||
@@ -276,6 +214,25 @@ export default function Login({
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
||||
{noAuthConfigured && (
|
||||
<Group my="lg">
|
||||
<AlertTriangle size="2rem" />
|
||||
<Text>
|
||||
No login methods have been configured. <br />
|
||||
See the{" "}
|
||||
<a
|
||||
href="https://github.com/moghtech/komodo/blob/main/config/core.config.toml"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover-underline"
|
||||
>
|
||||
<b>example config</b>
|
||||
</a>{" "}
|
||||
for information on configuring auth.
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Fieldset>
|
||||
</Center>
|
||||
);
|
||||
@@ -31,13 +31,13 @@ export default function Profile() {
|
||||
const [password, setPassword] = useState("");
|
||||
const { mutate: updateUsername } = useManageAuth("UpdateUsername", {
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: "Username updated." });
|
||||
notifications.show({ message: "Username updated.", color: "green" });
|
||||
refetchUser();
|
||||
},
|
||||
});
|
||||
const { mutate: updatePassword } = useManageAuth("UpdatePassword", {
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: "Password updated." });
|
||||
notifications.show({ message: "Password updated.", color: "green" });
|
||||
setPassword("");
|
||||
refetchUser();
|
||||
},
|
||||
@@ -48,6 +48,7 @@ export default function Profile() {
|
||||
onSuccess: () => {
|
||||
notifications.show({
|
||||
message: "External login skip 2fa mode updated.",
|
||||
color: "green",
|
||||
});
|
||||
refetchUser();
|
||||
},
|
||||
@@ -65,7 +66,6 @@ export default function Profile() {
|
||||
<Section
|
||||
title="Login"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
icon={<ICONS.Key size="1.2rem" />}
|
||||
withBorder
|
||||
>
|
||||
@@ -115,7 +115,6 @@ export default function Profile() {
|
||||
<Section
|
||||
title="2FA"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
icon={<ICONS.Key size="1.2rem" />}
|
||||
withBorder
|
||||
>
|
||||
|
||||
@@ -95,7 +95,6 @@ export const LinkedLogins = ({
|
||||
<Section
|
||||
title="Providers"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
icon={<ICONS.Provider size="1.2rem" />}
|
||||
withBorder
|
||||
>
|
||||
@@ -151,7 +150,6 @@ export const LinkedLogins = ({
|
||||
}) =>
|
||||
data ? (
|
||||
<ConfirmModal
|
||||
targetProps={{ color: "red" }}
|
||||
icon={<ICONS.Unlink size="1rem" />}
|
||||
onConfirm={() => unlink({ provider })}
|
||||
confirmText="Unlink"
|
||||
|
||||
@@ -13,7 +13,10 @@ export const EnrollPasskey = ({ user }: { user: Types.User }) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
userInvalidate();
|
||||
notifications.show({ message: "Unenrolled in passkey 2FA" });
|
||||
notifications.show({
|
||||
message: "Unenrolled in passkey 2FA",
|
||||
color: "green",
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -23,7 +26,10 @@ export const EnrollPasskey = ({ user }: { user: Types.User }) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
userInvalidate();
|
||||
notifications.show({ message: "Enrolled in passkey authentication" });
|
||||
notifications.show({
|
||||
message: "Enrolled in passkey authentication",
|
||||
color: "green",
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -62,9 +68,7 @@ export const EnrollPasskey = ({ user }: { user: Types.User }) => {
|
||||
icon={<Trash size="1rem" />}
|
||||
loading={unenrollPending}
|
||||
onConfirm={() => unenroll({})}
|
||||
variant="filled"
|
||||
color="red"
|
||||
w={220}
|
||||
targetProps={{ variant: "filled", color: "red", c: "bw", w: 220 }}
|
||||
>
|
||||
Unenroll Passkey 2FA
|
||||
</ConfirmModal>
|
||||
|
||||
@@ -39,7 +39,10 @@ export const EnrollTotp = ({ user }: { user: Types.User }) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
userInvalidate();
|
||||
notifications.show({ message: "Unenrolled in TOTP 2FA." });
|
||||
notifications.show({
|
||||
message: "Unenrolled in TOTP 2FA.",
|
||||
color: "green",
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -132,8 +135,7 @@ export const EnrollTotp = ({ user }: { user: Types.User }) => {
|
||||
icon={<Trash size="1rem" />}
|
||||
loading={unenrollPending}
|
||||
onConfirm={() => unenroll({})}
|
||||
color="red"
|
||||
w={220}
|
||||
targetProps={{ variant: "filled", color: "red", c: "bw", w: 220 }}
|
||||
>
|
||||
Unenroll TOTP 2FA
|
||||
</ConfirmModal>
|
||||
|
||||
@@ -92,7 +92,7 @@ function ResourceInner({ type, id }: { type: UsableResource; id: string }) {
|
||||
{canExecute && Object.keys(RC.Executions).length > 0 && (
|
||||
<Section
|
||||
title="Execute"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.Execution size="1rem" />}
|
||||
my="md"
|
||||
>
|
||||
|
||||
+24
-22
@@ -11,7 +11,7 @@ import {
|
||||
import { ResourceComponents, UsableResource } from "@/resources";
|
||||
import { Types } from "komodo_client";
|
||||
import Page from "@/ui/page";
|
||||
import { Group } from "@mantine/core";
|
||||
import { Group, Stack } from "@mantine/core";
|
||||
import TableSkeleton from "@/ui/table-skeleton";
|
||||
import TemplateQuerySelector from "@/components/template-query-selector";
|
||||
import TagsFilter from "@/components/tags/filter";
|
||||
@@ -72,29 +72,31 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
|
||||
</Group>
|
||||
}
|
||||
>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Group w={{ base: "100%", xs: "fit-content" }}>
|
||||
{(is_admin || !disable_non_admin_create) && <RC.New />}
|
||||
<RC.BatchExecutions />
|
||||
<Stack>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Group w={{ base: "100%", xs: "fit-content" }}>
|
||||
{(is_admin || !disable_non_admin_create) && <RC.New />}
|
||||
<RC.BatchExecutions />
|
||||
</Group>
|
||||
|
||||
<Group w={{ base: "100%", xs: "fit-content" }}>
|
||||
{(type === "Stack" || type === "Deployment") && (
|
||||
<LabelledSwitch
|
||||
label="Pending Update"
|
||||
checked={filterUpdateAvailable}
|
||||
onCheckedChange={toggleFilterUpdateAvailable}
|
||||
opacity={0.7}
|
||||
fz="sm"
|
||||
/>
|
||||
)}
|
||||
<TemplateQuerySelector />
|
||||
<TagsFilter />
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Group w={{ base: "100%", xs: "fit-content" }}>
|
||||
{(type === "Stack" || type === "Deployment") && (
|
||||
<LabelledSwitch
|
||||
label="Pending Update"
|
||||
checked={filterUpdateAvailable}
|
||||
onCheckedChange={toggleFilterUpdateAvailable}
|
||||
opacity={0.7}
|
||||
fz="sm"
|
||||
/>
|
||||
)}
|
||||
<TemplateQuerySelector />
|
||||
<TagsFilter />
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{filtered ? <RC.Table resources={filtered ?? []} /> : <TableSkeleton />}
|
||||
{filtered ? <RC.Table resources={filtered ?? []} /> : <TableSkeleton />}
|
||||
</Stack>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
+73
-71
@@ -14,7 +14,7 @@ import { ICONS } from "@/theme/icons";
|
||||
import { DataTable, SortableHeader } from "@/ui/data-table";
|
||||
import Page from "@/ui/page";
|
||||
import SearchInput from "@/ui/search-input";
|
||||
import { Group, Switch } from "@mantine/core";
|
||||
import { Group, Stack, Switch } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -31,78 +31,80 @@ export default function Schedules() {
|
||||
title="Schedules"
|
||||
description="See an overview of your scheduled Actions and Procedures."
|
||||
>
|
||||
<Group justify="end">
|
||||
<TagsFilter />
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
<Stack>
|
||||
<Group justify="end">
|
||||
<TagsFilter />
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
|
||||
<DataTable
|
||||
tableKey="schedules"
|
||||
data={filtered}
|
||||
columns={[
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Target" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<ResourceLink
|
||||
type={row.original.target.type as UsableResource}
|
||||
id={row.original.target.id}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "schedule",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Schedule" />
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "next_scheduled_run",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Next Run" />
|
||||
),
|
||||
sortingFn: (a, b) => {
|
||||
const sa = a.original.next_scheduled_run;
|
||||
const sb = b.original.next_scheduled_run;
|
||||
|
||||
if (!sa && !sb) return 0;
|
||||
if (!sa) return 1;
|
||||
if (!sb) return -1;
|
||||
|
||||
if (sa > sb) return 1;
|
||||
else if (sa < sb) return -1;
|
||||
else return 0;
|
||||
<DataTable
|
||||
tableKey="schedules"
|
||||
data={filtered}
|
||||
columns={[
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Target" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<ResourceLink
|
||||
type={row.original.target.type as UsableResource}
|
||||
id={row.original.target.id}
|
||||
/>
|
||||
),
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.original.next_scheduled_run
|
||||
? new Date(row.original.next_scheduled_run).toLocaleString()
|
||||
: "Not Scheduled",
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
accessorKey: "enabled",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Enabled" />
|
||||
),
|
||||
cell: ({ row: { original: schedule } }) => (
|
||||
<ScheduleEnableSwitch
|
||||
type={schedule.target.type as UsableResource}
|
||||
id={schedule.target.id}
|
||||
enabled={schedule.enabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
cell: ({ row }) => <TableTags tagIds={row.original.tags} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "schedule",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Schedule" />
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "next_scheduled_run",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Next Run" />
|
||||
),
|
||||
sortingFn: (a, b) => {
|
||||
const sa = a.original.next_scheduled_run;
|
||||
const sb = b.original.next_scheduled_run;
|
||||
|
||||
if (!sa && !sb) return 0;
|
||||
if (!sa) return 1;
|
||||
if (!sb) return -1;
|
||||
|
||||
if (sa > sb) return 1;
|
||||
else if (sa < sb) return -1;
|
||||
else return 0;
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.original.next_scheduled_run
|
||||
? new Date(row.original.next_scheduled_run).toLocaleString()
|
||||
: "Not Scheduled",
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
accessorKey: "enabled",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Enabled" />
|
||||
),
|
||||
cell: ({ row: { original: schedule } }) => (
|
||||
<ScheduleEnableSwitch
|
||||
type={schedule.target.type as UsableResource}
|
||||
id={schedule.target.id}
|
||||
enabled={schedule.enabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
cell: ({ row }) => <TableTags tagIds={row.original.tags} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export default function Settings() {
|
||||
<MobileFriendlyTabs
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
actions={currentView === "Variables" && <ExportToml includeVariables />}
|
||||
tabsProps={{ color: "green" }}
|
||||
tabs={[
|
||||
{
|
||||
value: "Variables",
|
||||
@@ -64,9 +66,6 @@ export default function Settings() {
|
||||
icon: ICONS.OnboardingKey,
|
||||
},
|
||||
]}
|
||||
actions={currentView === "Variables" && <ExportToml includeVariables />}
|
||||
tabProps={{ w: 150 }}
|
||||
tabsProps={{ color: "green" }}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useInvalidate, useRead, useSetTitle, useWrite } from "@/lib/hooks";
|
||||
import ResourceSelector from "@/resources/selector";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { DataTable, SortableHeader } from "@/ui/data-table";
|
||||
import { Badge, Group, Switch, TextInput } from "@mantine/core";
|
||||
import { Badge, Group, Switch, TextInput, useMatches } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Types } from "komodo_client";
|
||||
@@ -25,6 +25,10 @@ export default function SettingsOnboardingKeys() {
|
||||
notifications.show({ message: "Updated onboarding key", color: "green" });
|
||||
},
|
||||
});
|
||||
const expiresSize = useMatches({
|
||||
base: "sm",
|
||||
xl: "md",
|
||||
});
|
||||
const columns: (
|
||||
| ColumnDef<Types.OnboardingKey, unknown>
|
||||
| false
|
||||
@@ -46,6 +50,7 @@ export default function SettingsOnboardingKeys() {
|
||||
})
|
||||
}
|
||||
onKeyDown={(e) => e.key === "Enter" && e.currentTarget.blur()}
|
||||
miw={200}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -74,7 +79,7 @@ export default function SettingsOnboardingKeys() {
|
||||
(tag) => !row.original.tags?.includes(tag._id?.$oid!),
|
||||
);
|
||||
return (
|
||||
<Group>
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<TagSelector
|
||||
title="Select Tags"
|
||||
tags={otherTags}
|
||||
@@ -180,9 +185,10 @@ export default function SettingsOnboardingKeys() {
|
||||
}) => (
|
||||
<Badge
|
||||
color={expires && expires <= Date.now() ? "red" : "accent"}
|
||||
fz={{ base: "sm", lg: "md" }}
|
||||
p={{ base: "sm", lg: "md" }}
|
||||
size="md"
|
||||
fz={{ base: "sm", xl: "md" }}
|
||||
p={{ base: "sm", xl: "md" }}
|
||||
styles={{ label: { width: "fit-content", height: "fit-content" } }}
|
||||
size={expiresSize}
|
||||
>
|
||||
{expires ? fmtDateWithMinutes(new Date(expires)) : "Never"}
|
||||
</Badge>
|
||||
|
||||
@@ -10,12 +10,13 @@ import {
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
useMatches,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useState } from "react";
|
||||
|
||||
type ExpiresOptions = "1 day" | "7 days" | "30 days" | "never";
|
||||
type ExpiresOptions = "1 day" | "7 days" | "30 days" | "Never";
|
||||
const ONE_DAY_MS = 1000 * 60 * 60 * 24;
|
||||
|
||||
export default function NewOnboardingKey() {
|
||||
@@ -38,7 +39,7 @@ export default function NewOnboardingKey() {
|
||||
"1 day": now + ONE_DAY_MS,
|
||||
"7 days": now + ONE_DAY_MS * 7,
|
||||
"30 days": now + ONE_DAY_MS * 90,
|
||||
never: 0,
|
||||
Never: 0,
|
||||
};
|
||||
const create = () =>
|
||||
mutate({
|
||||
@@ -55,6 +56,8 @@ export default function NewOnboardingKey() {
|
||||
_close();
|
||||
};
|
||||
|
||||
const size = useMatches({ base: "90%", md: 500 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -66,7 +69,7 @@ export default function NewOnboardingKey() {
|
||||
{created && "Onboarding Key Created"}
|
||||
</Text>
|
||||
}
|
||||
size="lg"
|
||||
size={size}
|
||||
>
|
||||
<Stack>
|
||||
{!created && (
|
||||
@@ -117,11 +120,15 @@ export default function NewOnboardingKey() {
|
||||
|
||||
{created && (
|
||||
<>
|
||||
<Text>
|
||||
Use as the <b>PERIPHERY_ONBOARDING_KEY</b>
|
||||
<Text size="md" my="sm">
|
||||
Copy the onboarding key below. <b>It won't be shown again</b>.
|
||||
</Text>
|
||||
|
||||
<CopyText content={created.private_key} label="private key" />
|
||||
<CopyText
|
||||
content={created.private_key}
|
||||
label="private key"
|
||||
w="90%"
|
||||
/>
|
||||
|
||||
<Group justify="end" onClick={close}>
|
||||
<Button leftSection={<ICONS.Clear />}>Close</Button>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function SettingsUserGroups({
|
||||
return (
|
||||
<Section
|
||||
title="User Groups"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.Users size="1.3rem" />}
|
||||
titleRight={
|
||||
<Box ml="md">
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function SettingsUsers() {
|
||||
|
||||
<Section
|
||||
title="Users"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.User size="1.3rem" />}
|
||||
titleRight={
|
||||
<Group ml="md">
|
||||
|
||||
@@ -3,7 +3,13 @@ import { ICONS } from "@/theme/icons";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
|
||||
export default function DeleteVariable({ name, disabled }: { name: string; disabled: boolean }) {
|
||||
export default function DeleteVariable({
|
||||
name,
|
||||
disabled,
|
||||
}: {
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const invalidate = useInvalidate();
|
||||
const { mutate, isPending } = useWrite("DeleteVariable", {
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -93,7 +93,6 @@ export default function StackServiceTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ export default function SwarmConfigEditSection({
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={{ contents: data }}
|
||||
content={{ contents: edit }}
|
||||
original={{ contents: data }}
|
||||
update={{ contents: edit }}
|
||||
onConfirm={async () =>
|
||||
name &&
|
||||
edit !== undefined &&
|
||||
|
||||
@@ -67,7 +67,6 @@ export default function SwarmConfigTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ export default function SwarmNodeTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ export default function SwarmSecretEditSection({
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={{ contents: "" }}
|
||||
content={{ contents: edit }}
|
||||
original={{ contents: "" }}
|
||||
update={{ contents: edit }}
|
||||
onConfirm={async () =>
|
||||
name &&
|
||||
edit !== undefined &&
|
||||
|
||||
@@ -67,7 +67,6 @@ export default function SwarmSecretTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ export default function SwarmServiceTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ export default function SwarmStackTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ export default function SwarmTaskTabs({
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -83,7 +82,7 @@ export default function SwarmTaskTabs({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs color={colorByIntention(intent)} value={view}>
|
||||
<Tabs color={colorByIntention(intent)} value={view} mt="lg">
|
||||
{View}
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import DockerResourceLink from "@/components/docker/link";
|
||||
import TargetTerminal from "@/components/terminal/target";
|
||||
import { useSetTitle, useWrite } from "@/lib/hooks";
|
||||
import { useSetTitle } from "@/lib/hooks";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { useDeployment } from "@/resources/deployment";
|
||||
import { useServer } from "@/resources/server";
|
||||
import { useStack } from "@/resources/stack";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
import Page from "@/ui/page";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { Types } from "komodo_client";
|
||||
import { ReactNode, useMemo } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router-dom";
|
||||
import ResourceLink from "@/resources/link";
|
||||
import DeleteTerminal from "./terminals/delete";
|
||||
|
||||
type WithTerminal = "servers" | "deployments" | "stacks" | string;
|
||||
|
||||
@@ -211,7 +210,12 @@ function TerminalLayout({
|
||||
customDescription={
|
||||
<>
|
||||
<Text>Terminal</Text>|{Link}|
|
||||
<DeleteTerminal terminal={terminal} target={target} />
|
||||
<DeleteTerminal
|
||||
terminal={terminal}
|
||||
target={target}
|
||||
size="xs"
|
||||
navTo="/terminals"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
@@ -219,37 +223,3 @@ function TerminalLayout({
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteTerminal({
|
||||
terminal,
|
||||
target,
|
||||
}: {
|
||||
terminal: string;
|
||||
target: Types.TerminalTarget;
|
||||
}) {
|
||||
const nav = useNavigate();
|
||||
const { mutate, isPending } = useWrite("DeleteTerminal", {
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: `Deleted Terminal '${terminal}'` });
|
||||
nav("/terminals");
|
||||
},
|
||||
});
|
||||
return (
|
||||
<ConfirmButton
|
||||
variant="filled"
|
||||
color="red"
|
||||
icon={<ICONS.Delete size="1rem" />}
|
||||
onClick={() =>
|
||||
mutate({
|
||||
target,
|
||||
terminal,
|
||||
})
|
||||
}
|
||||
loading={isPending}
|
||||
w={120}
|
||||
size="xs"
|
||||
>
|
||||
Delete
|
||||
</ConfirmButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
import { useTerminalTargetPermissions, useWrite } from "@/lib/hooks";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
import { ButtonProps } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { Types } from "komodo_client";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function DeleteTerminal({
|
||||
target,
|
||||
terminal,
|
||||
refetch,
|
||||
navTo,
|
||||
...buttonProps
|
||||
}: {
|
||||
target: Types.TerminalTarget;
|
||||
terminal: string;
|
||||
refetch: () => void;
|
||||
}) {
|
||||
refetch?: () => void;
|
||||
navTo?: string;
|
||||
} & ButtonProps) {
|
||||
const nav = useNavigate();
|
||||
const { canWrite } = useTerminalTargetPermissions(target);
|
||||
const { mutate, isPending } = useWrite("DeleteTerminal", {
|
||||
onSuccess: () => {
|
||||
refetch();
|
||||
notifications.show({ message: `Deleted Terminal '${terminal}'` });
|
||||
refetch?.();
|
||||
notifications.show({
|
||||
message: `Deleted Terminal '${terminal}'`,
|
||||
color: "green",
|
||||
});
|
||||
navTo && nav(navTo);
|
||||
},
|
||||
});
|
||||
return (
|
||||
@@ -29,6 +39,7 @@ export default function DeleteTerminal({
|
||||
w={120}
|
||||
disabled={!canWrite}
|
||||
loading={isPending}
|
||||
{...buttonProps}
|
||||
>
|
||||
Delete
|
||||
</ConfirmButton>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ICONS } from "@/theme/icons";
|
||||
import { filterBySplit, terminalLink } from "@/lib/utils";
|
||||
import { DataTable, SortableHeader } from "@/ui/data-table";
|
||||
import Page from "@/ui/page";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import DeleteTerminal from "./delete";
|
||||
@@ -24,101 +24,103 @@ export default function Terminals() {
|
||||
icon={ICONS.Terminal}
|
||||
description="Manage Terminals across all your Servers."
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<CreateTerminal />
|
||||
<BatchDeleteAllTerminals
|
||||
refetch={refetch}
|
||||
noTerminals={!terminals?.length}
|
||||
/>
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<CreateTerminal />
|
||||
<BatchDeleteAllTerminals
|
||||
refetch={refetch}
|
||||
noTerminals={!terminals?.length}
|
||||
/>
|
||||
</Group>
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
<SearchInput value={search} onSearch={setSearch} />
|
||||
</Group>
|
||||
|
||||
<DataTable
|
||||
tableKey="terminals"
|
||||
data={filtered}
|
||||
loading={isPending}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Link
|
||||
to={terminalLink(row.original)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Group className="hover-underline" fz="md" wrap="nowrap">
|
||||
<ICONS.Terminal size="1rem" />
|
||||
{row.original.name}
|
||||
</Group>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "target",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Target" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<TerminalTargetLink target={row.original.target} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "command",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Command" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Text ff="monospace" fz="sm">
|
||||
{row.original.command}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
accessorKey: "size",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Size" />
|
||||
),
|
||||
cell: ({
|
||||
row: {
|
||||
original: { stored_size_kb },
|
||||
},
|
||||
}) => (
|
||||
<span className="font-mono px-2 py-1 bg-secondary rounded-md">
|
||||
{stored_size_kb.toFixed()} KiB
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({
|
||||
row: {
|
||||
original: { created_at },
|
||||
},
|
||||
}) => fmtDateWithMinutes(new Date(created_at)),
|
||||
},
|
||||
{
|
||||
header: "Delete",
|
||||
cell: ({ row }) => (
|
||||
<DeleteTerminal
|
||||
target={row.original.target}
|
||||
terminal={row.original.name}
|
||||
refetch={refetch}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<DataTable
|
||||
tableKey="terminals"
|
||||
data={filtered}
|
||||
loading={isPending}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Link
|
||||
to={terminalLink(row.original)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Group className="hover-underline" fz="md" wrap="nowrap">
|
||||
<ICONS.Terminal size="1rem" />
|
||||
{row.original.name}
|
||||
</Group>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
accessorKey: "target",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Target" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<TerminalTargetLink target={row.original.target} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "command",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Command" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Text ff="monospace" fz="sm">
|
||||
{row.original.command}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
accessorKey: "size",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Size" />
|
||||
),
|
||||
cell: ({
|
||||
row: {
|
||||
original: { stored_size_kb },
|
||||
},
|
||||
}) => (
|
||||
<span className="font-mono px-2 py-1 bg-secondary rounded-md">
|
||||
{stored_size_kb.toFixed()} KiB
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({
|
||||
row: {
|
||||
original: { created_at },
|
||||
},
|
||||
}) => fmtDateWithMinutes(new Date(created_at)),
|
||||
},
|
||||
{
|
||||
header: "Delete",
|
||||
cell: ({ row }) => (
|
||||
<DeleteTerminal
|
||||
target={row.original.target}
|
||||
terminal={row.original.name}
|
||||
refetch={refetch}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
+15
-10
@@ -1,7 +1,11 @@
|
||||
import ResourceTypeSelector from "@/components/resource-type-selector";
|
||||
import { useUpdateDetails } from "@/components/updates/details";
|
||||
import UserAvatar from "@/components/user-avatar";
|
||||
import { fmtDateWithMinutes, fmtOperation } from "@/lib/formatting";
|
||||
import {
|
||||
fmtDateWithMinutes,
|
||||
fmtOperation,
|
||||
fmtUpperCamelcase,
|
||||
} from "@/lib/formatting";
|
||||
import { useRead, useSetTitle } from "@/lib/hooks";
|
||||
import { UsableResource } from "@/resources";
|
||||
import ResourceLink from "@/resources/link";
|
||||
@@ -49,7 +53,7 @@ export default function Updates() {
|
||||
icon={ICONS.Update}
|
||||
description="View historical updates"
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Stack>
|
||||
{/* QUERY */}
|
||||
<Group>
|
||||
{/* RESOURCE TYPE */}
|
||||
@@ -94,13 +98,12 @@ export default function Updates() {
|
||||
}
|
||||
setParams(p);
|
||||
}}
|
||||
data={
|
||||
type
|
||||
? OPERATIONS_BY_RESOURCE[type]
|
||||
: Object.values(Types.Operation).filter(
|
||||
(o) => o !== Types.Operation.None,
|
||||
)
|
||||
}
|
||||
data={(type
|
||||
? OPERATIONS_BY_RESOURCE[type]
|
||||
: Object.values(Types.Operation).filter(
|
||||
(o) => o !== Types.Operation.None,
|
||||
)
|
||||
).map((value) => ({ value, label: fmtUpperCamelcase(value) }))}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
@@ -209,7 +212,9 @@ export default function Updates() {
|
||||
{
|
||||
header: "Operator",
|
||||
accessorKey: "operator",
|
||||
cell: ({ row }) => <UserAvatar userId={row.original.operator} fz="md" />,
|
||||
cell: ({ row }) => (
|
||||
<UserAvatar userId={row.original.operator} fz="md" />
|
||||
),
|
||||
},
|
||||
]}
|
||||
onRowClick={(row) => openDetails(row.id)}
|
||||
|
||||
@@ -97,7 +97,7 @@ export default function UserGroup() {
|
||||
<Section
|
||||
title="Users"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
icon={<ICONS.User size="1.2rem" />}
|
||||
titleRight={
|
||||
<Group ml="md">
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function User() {
|
||||
<Section
|
||||
title="User Permissions"
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
withBorder
|
||||
>
|
||||
<Group>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function UserMemberGroups({ userId }: { userId: string }) {
|
||||
title="Groups"
|
||||
icon={<ICONS.UserGroup size="1.2rem" />}
|
||||
titleFz="h3"
|
||||
titleMb="0"
|
||||
|
||||
titleRight={
|
||||
<Box ml="md">
|
||||
<UserAddUserGroup userId={userId} />
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function ActionConfig({ id }: { id: string }) {
|
||||
: false
|
||||
}
|
||||
disabled={disabled || !(update.schedule ?? config.schedule)}
|
||||
onChange={(schedule_enabled) => set({ schedule_enabled })}
|
||||
onCheckedChange={(schedule_enabled) => set({ schedule_enabled })}
|
||||
/>
|
||||
),
|
||||
schedule_format: (schedule_format, set) => (
|
||||
@@ -262,7 +262,7 @@ export default function ActionConfig({ id }: { id: string }) {
|
||||
placeholder="Branch"
|
||||
value={branch}
|
||||
onChange={(e) => setBranch(e.target.value)}
|
||||
w={200}
|
||||
w={{ base: "100%", sm: 200 }}
|
||||
disabled={branch === "__ANY__"}
|
||||
/>
|
||||
<LabelledSwitch
|
||||
|
||||
@@ -61,7 +61,7 @@ export const ActionComponents: RequiredResourceComponents<
|
||||
New: () => <NewResource type="Action" />,
|
||||
|
||||
BatchExecutions: () => (
|
||||
<BatchExecutions type="Action" executions={["RunAction"]} />
|
||||
<BatchExecutions type="Action" executions={[["RunAction", ICONS.Run]]} />
|
||||
),
|
||||
|
||||
Table: ActionTable,
|
||||
@@ -158,7 +158,7 @@ export const ActionComponents: RequiredResourceComponents<
|
||||
|
||||
return (
|
||||
<ConfirmModalWithDisable
|
||||
icon={<ICONS.Action size="1rem" />}
|
||||
icon={<ICONS.Run size="1rem" />}
|
||||
confirmText={action.name}
|
||||
onConfirm={async () => {
|
||||
await mutateAsync({ action: id, args: {} });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRead } from "@/lib/hooks";
|
||||
import { useExecute, useRead } from "@/lib/hooks";
|
||||
import { ICONS } from "@/theme/icons";
|
||||
import { RequiredResourceComponents } from "..";
|
||||
import { Types } from "komodo_client";
|
||||
@@ -8,6 +8,7 @@ import ResourceHeader from "../header";
|
||||
import AlerterConfig from "./config";
|
||||
import { hexColorByIntention } from "@/lib/color";
|
||||
import BatchExecutions from "@/components/batch-executions";
|
||||
import ConfirmButton from "@/ui/confirm-button";
|
||||
|
||||
export function useAlerter(id: string | undefined) {
|
||||
return useRead("ListAlerters", {}).data?.find((r) => r.id === id);
|
||||
@@ -38,7 +39,10 @@ export const AlerterComponents: RequiredResourceComponents<
|
||||
New: () => <NewResource type="Alerter" />,
|
||||
|
||||
BatchExecutions: () => (
|
||||
<BatchExecutions type="Alerter" executions={["TestAlerter"]} />
|
||||
<BatchExecutions
|
||||
type="Alerter"
|
||||
executions={[["TestAlerter", ICONS.Test]]}
|
||||
/>
|
||||
),
|
||||
|
||||
Table: AlerterTable,
|
||||
@@ -72,7 +76,23 @@ export const AlerterComponents: RequiredResourceComponents<
|
||||
State: () => null,
|
||||
Info: {},
|
||||
|
||||
Executions: {},
|
||||
Executions: {
|
||||
TestAlerter: ({ id }) => {
|
||||
const { mutate, isPending } = useExecute("TestAlerter");
|
||||
const alerter = useAlerter(id);
|
||||
if (!alerter) return null;
|
||||
return (
|
||||
<ConfirmButton
|
||||
icon={<ICONS.Test size="1rem" />}
|
||||
loading={isPending}
|
||||
onClick={() => mutate({ alerter: id })}
|
||||
disabled={isPending}
|
||||
>
|
||||
Test Alerter
|
||||
</ConfirmButton>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
Config: AlerterConfig,
|
||||
|
||||
|
||||
@@ -87,7 +87,13 @@ export const BuildComponents: RequiredResourceComponents<
|
||||
},
|
||||
|
||||
BatchExecutions: () => (
|
||||
<BatchExecutions type="Build" executions={["RunBuild"]} />
|
||||
<BatchExecutions
|
||||
type="Build"
|
||||
executions={[
|
||||
["RunBuild", ICONS.Build],
|
||||
["CancelBuild", ICONS.Cancel],
|
||||
]}
|
||||
/>
|
||||
),
|
||||
|
||||
Table: BuildTable,
|
||||
|
||||
@@ -67,11 +67,7 @@ export default function BuildInfo({
|
||||
<Section gap="xl" titleOther={titleOther}>
|
||||
{/* Errors */}
|
||||
{remoteError && remoteError.length > 0 && (
|
||||
<Stack
|
||||
className="bordered-light"
|
||||
bdrs="md"
|
||||
p="xl"
|
||||
>
|
||||
<Stack className="bordered-light" bdrs="md" p="xl">
|
||||
{/* HEADER */}
|
||||
<Group justify="between">
|
||||
<Group ff="monospace">
|
||||
@@ -112,11 +108,7 @@ export default function BuildInfo({
|
||||
|
||||
{/* Update latest contents */}
|
||||
{remoteContents && remoteContents.length > 0 && (
|
||||
<Stack
|
||||
className="bordered-light"
|
||||
bdrs="md"
|
||||
p="xl"
|
||||
>
|
||||
<Stack className="bordered-light" bdrs="md" p="xl">
|
||||
{/* HEADER */}
|
||||
<Group
|
||||
justify="space-between"
|
||||
@@ -160,8 +152,8 @@ export default function BuildInfo({
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={{ contents: remoteContents }}
|
||||
content={{ contents: edits.contents }}
|
||||
original={{ contents: remoteContents }}
|
||||
update={{ contents: edits.contents }}
|
||||
onConfirm={async () => {
|
||||
if (build) {
|
||||
return await mutateAsync({
|
||||
@@ -194,7 +186,9 @@ export default function BuildInfo({
|
||||
)}
|
||||
|
||||
{lastBuild && lastBuild.logs.length > 0 && (
|
||||
<Code fz="lg" fw="bold" w="fit-content">Last Build Logs</Code>
|
||||
<Code fz="lg" fw="bold" w="fit-content">
|
||||
Last Build Logs
|
||||
</Code>
|
||||
)}
|
||||
{lastBuild &&
|
||||
lastBuild.logs?.map((log, i) => (
|
||||
@@ -216,11 +210,7 @@ export default function BuildInfo({
|
||||
withBorder
|
||||
>
|
||||
{log.command && (
|
||||
<Stack
|
||||
className="bordered-light"
|
||||
bdrs="md"
|
||||
p="md"
|
||||
>
|
||||
<Stack className="bordered-light" bdrs="md" p="md">
|
||||
<Text fw="bold">command</Text>
|
||||
<Code
|
||||
fz="sm"
|
||||
@@ -234,11 +224,7 @@ export default function BuildInfo({
|
||||
</Stack>
|
||||
)}
|
||||
{log.stdout && (
|
||||
<Stack
|
||||
className="bordered-light"
|
||||
bdrs="md"
|
||||
p="md"
|
||||
>
|
||||
<Stack className="bordered-light" bdrs="md" p="md">
|
||||
<Text fw="bold">stdout</Text>
|
||||
<Code
|
||||
component="pre"
|
||||
@@ -252,11 +238,7 @@ export default function BuildInfo({
|
||||
</Stack>
|
||||
)}
|
||||
{log.stderr && (
|
||||
<Stack
|
||||
className="bordered-light"
|
||||
bdrs="md"
|
||||
p="md"
|
||||
>
|
||||
<Stack className="bordered-light" bdrs="md" p="md">
|
||||
<Text fw="bold">stderr</Text>
|
||||
<Code
|
||||
component="pre"
|
||||
|
||||
@@ -62,7 +62,6 @@ export default function BuildTabs({ id }: { id: string }) {
|
||||
tabs={tabsNoContent}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -75,8 +75,14 @@ export const BuilderComponents: RequiredResourceComponents<
|
||||
? builder.info.instance_type
|
||||
: undefined,
|
||||
);
|
||||
const coreVersion = useRead("GetVersion", {}).data?.version;
|
||||
const intent = server?.info.state
|
||||
? serverStateIntention(server.info.state)
|
||||
? serverStateIntention(
|
||||
server.info.state,
|
||||
!!coreVersion &&
|
||||
!!server.info.version &&
|
||||
coreVersion !== server.info.version,
|
||||
)
|
||||
: "Neutral";
|
||||
return (
|
||||
<ResourceHeader
|
||||
|
||||
@@ -284,7 +284,7 @@ export default function DeploymentConfig({
|
||||
label="Poll for Updates"
|
||||
description="Check for updates to the image during Global Auto Update."
|
||||
value={autoUpdate || poll}
|
||||
onChange={(poll_for_updates) => set({ poll_for_updates })}
|
||||
onCheckedChange={(poll_for_updates) => set({ poll_for_updates })}
|
||||
disabled={disabled || autoUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function DeploymentNetworkSelector({
|
||||
}}
|
||||
disabled={disabled}
|
||||
data={networks}
|
||||
w="fit-content"
|
||||
w={{ base: "100%", xs: "fit-content" }}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { fmtUpperCamelcase } from "@/lib/formatting";
|
||||
import { ConfigItem } from "@/ui/config/item";
|
||||
import { Select } from "@mantine/core";
|
||||
import { Types } from "komodo_client";
|
||||
@@ -27,13 +28,11 @@ export default function DeploymentRestartSelector({
|
||||
disabled={disabled}
|
||||
placeholder="Select Mode"
|
||||
data={Object.entries(Types.RestartMode).map(([label, value]) => ({
|
||||
label:
|
||||
label === "NoRestart"
|
||||
? "don't restart"
|
||||
: value.split("-").join(" "),
|
||||
label: fmtUpperCamelcase(label),
|
||||
value,
|
||||
}))}
|
||||
w="fit-content"
|
||||
tt="capitalize"
|
||||
w={{ base: "100%", xs: "fit-content" }}
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ export function TerminationSignal({
|
||||
disabled={disabled}
|
||||
placeholder="Select signal"
|
||||
data={Object.values(Types.TerminationSignal).reverse()}
|
||||
w={{ base: "100%", xs: "fit-content" }}
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
|
||||
@@ -90,12 +90,12 @@ export const DeploymentComponents: RequiredResourceComponents<
|
||||
<BatchExecutions
|
||||
type="Deployment"
|
||||
executions={[
|
||||
"CheckDeploymentForUpdate",
|
||||
"PullDeployment",
|
||||
"Deploy",
|
||||
"RestartDeployment",
|
||||
"StopDeployment",
|
||||
"DestroyDeployment",
|
||||
["CheckDeploymentForUpdate", ICONS.UpdateAvailable],
|
||||
["PullDeployment", ICONS.Pull],
|
||||
["Deploy", ICONS.Deploy],
|
||||
["RestartDeployment", ICONS.Restart],
|
||||
["StopDeployment", ICONS.Stop],
|
||||
["DestroyDeployment", ICONS.Destroy],
|
||||
]}
|
||||
/>
|
||||
),
|
||||
@@ -103,11 +103,15 @@ export const DeploymentComponents: RequiredResourceComponents<
|
||||
Table: DeploymentTable,
|
||||
|
||||
Icon: ({ id, size = "1rem", noColor }) => {
|
||||
const state = useRead("ListDeployments", {}).data?.find((r) => r.id === id)
|
||||
?.info.state;
|
||||
const info = useRead("ListDeployments", {}).data?.find(
|
||||
(r) => r.id === id,
|
||||
)?.info;
|
||||
const color = noColor
|
||||
? undefined
|
||||
: state && hexColorByIntention(deploymentStateIntention(state));
|
||||
: info &&
|
||||
hexColorByIntention(
|
||||
deploymentStateIntention(info.state, info.update_available),
|
||||
);
|
||||
return <ICONS.Deployment size={size} color={color} />;
|
||||
},
|
||||
|
||||
@@ -118,7 +122,10 @@ export const DeploymentComponents: RequiredResourceComponents<
|
||||
type="Deployment"
|
||||
id={id}
|
||||
resource={deployment}
|
||||
intent={deploymentStateIntention(deployment?.info.state)}
|
||||
intent={deploymentStateIntention(
|
||||
deployment?.info.state,
|
||||
deployment?.info.update_available,
|
||||
)}
|
||||
icon={ICONS.Deployment}
|
||||
name={deployment?.name}
|
||||
state={deployment?.info.state}
|
||||
@@ -128,9 +135,12 @@ export const DeploymentComponents: RequiredResourceComponents<
|
||||
},
|
||||
|
||||
State: ({ id }) => {
|
||||
let state = useDeployment(id)?.info.state;
|
||||
let info = useDeployment(id)?.info;
|
||||
return (
|
||||
<StatusBadge text={state} intent={deploymentStateIntention(state)} />
|
||||
<StatusBadge
|
||||
text={info?.state}
|
||||
intent={deploymentStateIntention(info?.state, info?.update_available)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Info: {
|
||||
|
||||
@@ -122,7 +122,7 @@ const Image = ({
|
||||
} else {
|
||||
const [img] = image.split(":");
|
||||
return (
|
||||
<Group>
|
||||
<Group wrap="nowrap">
|
||||
<ICONS.Image size="1rem" />
|
||||
{img}
|
||||
</Group>
|
||||
|
||||
@@ -91,7 +91,6 @@ export default function DeploymentTabs({ id }: { id: string }) {
|
||||
tabs={tabs}
|
||||
value={view}
|
||||
onValueChange={setView as any}
|
||||
tabProps={{ w: 140 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -130,7 +129,9 @@ export default function DeploymentTabs({ id }: { id: string }) {
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
color={colorByIntention(deploymentStateIntention(state))}
|
||||
color={colorByIntention(
|
||||
deploymentStateIntention(state, info?.update_available),
|
||||
)}
|
||||
value={view}
|
||||
>
|
||||
{View}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user