Compare commits

..
34 Commits
Author SHA1 Message Date
mbecker20 ae5f36fe51 0.1.15 use passkey for addition core - periphery auth layer 2023-02-17 18:29:13 +00:00
beckerinj 69ce1e4f36 start adding passkey auth to core periphery communication 2023-02-17 12:53:09 -05:00
mbecker20 6e444b9032 temp remove deployment state polling 2023-02-14 18:33:32 +00:00
beckerinj 73eff72da4 work on docs 2023-02-13 11:16:27 -05:00
beckerinj 698e3c214b opinionated 2023-02-12 14:48:28 -05:00
beckerinj 9da77667dc add next page 2023-02-12 04:14:06 -05:00
beckerinj c30793fb8f to 2023-02-12 04:12:49 -05:00
beckerinj 84fdaab24d close intro better 2023-02-12 04:10:10 -05:00
beckerinj cbd67bb609 back to table 2023-02-12 04:05:35 -05:00
beckerinj 00f58e9008 architecture 2023-02-12 03:59:26 -05:00
beckerinj 7738fab351 intro 2023-02-12 03:57:55 -05:00
beckerinj 06e8f6589b links 2023-02-12 00:55:14 -05:00
beckerinj 57d9287724 start docs 2023-02-12 00:51:05 -05:00
mbecker20 2cc65595ee log only polls when container is running 2023-02-11 20:29:25 +00:00
mbecker20 3dd2b97873 add polling for deployment state and logs 2023-02-11 20:17:13 +00:00
mbecker20 3c805ebbf7 fix deployment action build A tag styling 2023-02-11 19:36:36 +00:00
beckerinjandGitHub a854160018 Update periphery.config.example.toml 2023-02-08 13:40:00 -05:00
mbecker20 a99d9e5969 make update menu wider 2023-02-08 05:56:09 +00:00
mbecker20 813b6c1182 confirm menu 2023-02-08 05:37:27 +00:00
mbecker20 2958f9589b deployment / build delete wont fail if server disabled / unreachable. 2023-02-08 05:37:12 +00:00
beckerinj 69b4e26176 ConfirmMenuButton 2023-02-07 23:49:27 -05:00
beckerinj 78b00f139d delete server side effects 2023-02-07 23:23:46 -05:00
beckerinj dc1e8de851 widen env 2023-02-07 23:23:18 -05:00
mbecker20 3187b335a3 cli 0.1.20 2023-02-07 20:12:20 +00:00
beckerinj 54b5a2b420 add logrotate to mongo startup 2023-02-07 15:10:36 -05:00
beckerinj 14c6bd00a8 use the scroller class for LogContainer 2023-02-02 16:14:11 -05:00
mbecker20 e9c3646450 0.1.14 fix deployment builder defaults 2023-01-30 17:45:34 +00:00
mbecker20 4f20257479 0.1.13 improve Builder structs with defaults 2023-01-30 17:24:03 +00:00
beckerinj 65749991de 0.1.12 MonitorClient::new_from_env 2023-01-30 11:50:48 -05:00
mbecker20 237a1d802d handle stuff when server disabled / unreachable 2023-01-28 09:03:00 +00:00
mbecker20 e4336f19f3 default copy server to curr server 2023-01-25 23:08:31 +00:00
mbecker20 c895e5e67f fix selector search behavior when using itemMap 2023-01-25 23:01:11 +00:00
mbecker20 4e4e210736 implement build / deployment copy 2023-01-25 22:53:27 +00:00
beckerinj 09dfc8faa3 cli 0.1.19 add Restart=on-failure to service unit file 2023-01-25 14:58:54 -05:00
63 changed files with 996 additions and 339 deletions
Generated
+69 -67
View File
@@ -130,9 +130,9 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d"
checksum = "51227033e4d3acad15c879092ac8a228532707b5db5ff2628f638334f63e1b7a"
dependencies = [
"axum",
"bytes",
@@ -182,13 +182,14 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bcrypt"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641"
checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a"
dependencies = [
"base64 0.13.1",
"base64 0.21.0",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
@@ -300,9 +301,9 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cc"
version = "1.0.78"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
@@ -398,7 +399,7 @@ dependencies = [
[[package]]
name = "core"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"async_timing_util",
@@ -414,8 +415,8 @@ dependencies = [
"hex",
"hmac",
"jwt",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_helpers 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"mungos",
"periphery_client",
"serde",
@@ -520,9 +521,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e"
checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -532,9 +533,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200"
checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8"
dependencies = [
"cc",
"codespan-reporting",
@@ -547,15 +548,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea"
checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971"
[[package]]
name = "cxxbridge-macro"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e"
checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e"
dependencies = [
"proc-macro2",
"quote",
@@ -650,10 +651,10 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "db_client"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"mungos",
]
@@ -740,9 +741,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "encoding_rs"
@@ -846,9 +847,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -861,9 +862,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -871,15 +872,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -888,15 +889,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-macro"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -905,21 +906,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.25"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1478,7 +1479,7 @@ dependencies = [
[[package]]
name = "monitor_cli"
version = "0.1.18"
version = "0.1.21"
dependencies = [
"async_timing_util",
"clap",
@@ -1494,11 +1495,12 @@ dependencies = [
[[package]]
name = "monitor_client"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"envy",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest",
"serde",
"serde_derive",
@@ -1510,7 +1512,7 @@ dependencies = [
[[package]]
name = "monitor_helpers"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"async_timing_util",
@@ -1518,7 +1520,7 @@ dependencies = [
"bollard",
"futures",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rand",
"run_command",
"serde",
@@ -1529,9 +1531,9 @@ dependencies = [
[[package]]
name = "monitor_helpers"
version = "0.1.11"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b1ff7bac3697e1fc06044b669b029964b11182483ce3066e3996c5c0f66b57"
checksum = "f2e58a1c8a1e92c967b5c4ba10511bc4bbf65c824230d234b78ad9d3ed992b75"
dependencies = [
"anyhow",
"async_timing_util",
@@ -1539,7 +1541,7 @@ dependencies = [
"bollard",
"futures",
"futures-util",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rand",
"run_command",
"serde",
@@ -1550,7 +1552,7 @@ dependencies = [
[[package]]
name = "monitor_periphery"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"async_timing_util",
@@ -1561,8 +1563,8 @@ dependencies = [
"dotenv",
"envy",
"futures-util",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_helpers 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"run_command",
"serde",
"serde_derive",
@@ -1576,7 +1578,7 @@ dependencies = [
[[package]]
name = "monitor_types"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"bollard",
@@ -1593,9 +1595,9 @@ dependencies = [
[[package]]
name = "monitor_types"
version = "0.1.11"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31a6a8d9bf88d5f6f0ede8d35efa7b7018ebf4b62cf7b9d052c18eed185ca805"
checksum = "bc2fc31230469c5a56e57eacf0200422897d4e0808ba176a767ceb59a872d543"
dependencies = [
"anyhow",
"bollard",
@@ -1854,12 +1856,12 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "periphery_client"
version = "0.1.11"
version = "0.1.15"
dependencies = [
"anyhow",
"futures-util",
"monitor_helpers 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_helpers 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"monitor_types 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest",
"serde",
"serde_json",
@@ -2199,9 +2201,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.8.1"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721"
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [
"bitflags",
"core-foundation",
@@ -2614,9 +2616,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.24.2"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -2694,9 +2696,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85"
dependencies = [
"serde",
"serde_spanned",
@@ -2706,18 +2708,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.18.0"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa"
dependencies = [
"indexmap",
"nom8",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "monitor_cli"
version = "0.1.18"
version = "0.1.21"
edition = "2021"
authors = ["MoghTech"]
description = "monitor cli | tools to setup monitor system"
@@ -18,7 +18,7 @@ async_timing_util = "0.1.14"
rand = "0.8"
serde = "1.0"
serde_derive = "1.0"
toml = "0.6"
toml = "0.7"
run_command = "0.0.5"
colored = "2"
strum = "0.24"
+5 -2
View File
@@ -81,6 +81,7 @@ pub fn gen_core_config(sub_matches: &ArgMatches) {
},
jwt_secret: generate_secret(40),
github_webhook_secret: generate_secret(30),
passkey: generate_secret(30),
};
write_to_toml(&path, &config);
@@ -176,7 +177,7 @@ pub fn start_mongo(sub_matches: &ArgMatches) {
}
}
let command = format!("docker stop {name} && docker container rm {name} && docker run -d --name {name} -p {port}:27017 --network {network} -v {mount}:/data/db{env} --restart {restart} mongo --quiet");
let command = format!("docker stop {name} && docker container rm {name} && docker run -d --name {name} -p {port}:27017 --network {network} -v {mount}:/data/db{env} --restart {restart} --log-opt max-size=15m --log-opt max-file=3 mongo --quiet");
let output = run_command_pipe_to_terminal(&command);
@@ -318,9 +319,10 @@ pub fn gen_periphery_config(sub_matches: &ArgMatches) {
let config = PeripheryConfig {
port,
repo_dir,
stats_polling_rate,
allowed_ips,
repo_dir,
passkeys: vec![],
secrets: Default::default(),
github_accounts: Default::default(),
docker_accounts: Default::default(),
@@ -588,6 +590,7 @@ Description=agent to connect with monitor core
[Service]
ExecStart={home}/.monitor/bin/periphery --config-path {config_path} --home-dir {home}
Restart=on-failure
TimeoutStartSec=0
[Install]
+5
View File
@@ -32,6 +32,9 @@ pub struct CoreConfig {
// used to verify validity from github webhooks
pub github_webhook_secret: String,
// sent in auth header with req to periphery
pub passkey: String,
// integration with slack app
pub slack_url: Option<String>,
@@ -104,6 +107,8 @@ pub struct PeripheryConfig {
#[serde(default)]
pub allowed_ips: Vec<IpAddr>,
#[serde(default)]
pub passkeys: Vec<String>,
#[serde(default)]
pub secrets: SecretsMap,
#[serde(default)]
pub github_accounts: GithubAccounts,
+5
View File
@@ -22,6 +22,9 @@ slack_url = "your_slack_app_webhook_url"
# token that has to be given to github during webhook config as the Secret
github_webhook_secret = "your_random_webhook_secret"
# token used to authenticate core requests to periphery
passkey = "your_random_passkey"
# can be 30-sec, 1-min, 2-min, 5-min
monitoring_interval = "1-min"
@@ -29,10 +32,12 @@ monitoring_interval = "1-min"
local_auth = true
[github_oauth]
enabled = true
id = "your_github_client_id"
secret = "your_github_client_secret"
[google_oauth]
enabled = true
id = "your_google_client_id"
secret = "your_google_client_secret"
+3 -2
View File
@@ -1,7 +1,8 @@
port = 9001 # optional. 9001 is default
port = 8000 # optional. 8000 is default
repo_dir = "/repos" # optional. /repos is default. no reason to change if running the docker container, just mount your desired repo dir to /repos in the container
stats_polling_rate = "5-sec" # optional. 5-sec is default. can use 1-sec, 5-sec, 10-sec, 30-sec, 1-min. controls granularity of system stats recorded
allowed_ips = ["127.0.0.1"] # optional. default is empty, which will not block any request by ip.
passkeys = ["abcdefghijk"] # optional. default is empty, which will not require any passkey to be passed by core.
[secrets] # optional. can inject these values into your deployments configuration.
secret_variable = "secret_value"
@@ -12,4 +13,4 @@ github_username2 = "github_token2"
[docker_accounts] # optional
docker_username1 = "docker_token1"
docker_username2 = "docker_token2"
docker_username2 = "docker_token2"
+6 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "core"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -8,16 +8,16 @@ edition = "2021"
[dependencies]
# helpers = { package = "monitor_helpers", path = "../lib/helpers" }
# types = { package = "monitor_types", path = "../lib/types" }
helpers = { package = "monitor_helpers", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.11" }
helpers = { package = "monitor_helpers", version = "0.1.15" }
types = { package = "monitor_types", version = "0.1.15" }
db = { package = "db_client", path = "../lib/db_client" }
periphery = { package = "periphery_client", path = "../lib/periphery_client" }
axum_oauth2 = { path = "../lib/axum_oauth2" }
tokio = { version = "1.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio-util = "0.7"
axum = { version = "0.6", features = ["ws", "json"] }
axum-extra = { version = "0.4", features = ["spa"] }
axum-extra = { version = "0.5", features = ["spa"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.3", features = ["cors"] }
slack = { package = "slack_client_rs", version = "0.0.8" }
@@ -28,7 +28,7 @@ serde_json = "1.0"
dotenv = "0.15"
envy = "0.4"
anyhow = "1.0"
bcrypt = "0.13"
bcrypt = "0.14"
jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"
+6 -6
View File
@@ -5,7 +5,7 @@ use mungos::{doc, to_bson};
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget,
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget, Version,
};
use crate::{
@@ -104,6 +104,7 @@ impl State {
.await?;
build.name = new_name;
build.server_id = new_server_id;
build.version = Version::default();
let build = self.create_full_build(build, user).await?;
Ok(build)
}
@@ -117,11 +118,10 @@ impl State {
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&build.server_id).await?;
let delete_repo_log = self
.periphery
.delete_repo(&server, &build.name)
.await
.context("failed at deleting repo")?;
let delete_repo_log = match self.periphery.delete_repo(&server, &build.name).await {
Ok(log) => log,
Err(e) => Log::error("delete repo", format!("{e:#?}")),
};
self.db.builds.delete_one(build_id).await?;
let update = Update {
target: UpdateTarget::Build(build_id.to_string()),
+6 -2
View File
@@ -119,10 +119,14 @@ impl State {
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&deployment.server_id).await?;
let log = self
let log = match self
.periphery
.container_remove(&server, &deployment.name)
.await?;
.await
{
Ok(log) => log,
Err(e) => Log::error("destroy container", format!("{e:#?}")),
};
self.db
.deployments
.delete_one(deployment_id)
+48 -8
View File
@@ -1,6 +1,8 @@
use anyhow::{anyhow, Context};
use diff::Diff;
use futures_util::future::join_all;
use helpers::to_monitor_name;
use mungos::doc;
use types::{
monitor_timestamp,
traits::{Busy, Permissioned},
@@ -102,21 +104,59 @@ impl State {
.get_server_check_permissions(server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
self.db.servers.delete_one(&server_id).await?;
let update = Update {
let mut update = Update {
target: UpdateTarget::Server(server_id.to_string()),
operation: Operation::DeleteServer,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
logs: vec![Log::simple(
"delete server",
format!("deleted server {}", server.name),
)],
success: true,
status: UpdateStatus::InProgress,
..Default::default()
};
self.add_update(update).await?;
update.id = self.add_update(update.clone()).await?;
let res = {
let delete_deployments = self
.db
.deployments
.get_some(doc! { "server_id": server_id }, None)
.await?
.into_iter()
.map(|d| async move { self.delete_deployment(&d.id, user).await });
let delete_builds = self
.db
.builds
.get_some(doc! { "server_id": server_id }, None)
.await?
.into_iter()
.map(|d| async move { self.delete_deployment(&d.id, user).await });
let update_groups = self
.db
.groups
.update_many(doc! {}, doc! { "$pull": { "servers": server_id } });
let (dep_res, build_res, group_res) = tokio::join!(
join_all(delete_deployments),
join_all(delete_builds),
update_groups
);
dep_res.into_iter().collect::<anyhow::Result<Vec<_>>>()?;
build_res.into_iter().collect::<anyhow::Result<Vec<_>>>()?;
group_res?;
self.db.servers.delete_one(&server_id).await?;
anyhow::Ok(())
};
let log = match res {
Ok(_) => Log::simple("delete server", format!("deleted server {}", server.name)),
Err(e) => Log::error("delete server", format!("failed to delete server\n{e:#?}")),
};
update.end_ts = Some(monitor_timestamp());
update.status = UpdateStatus::Complete;
update.success = log.success;
update.logs.push(log);
self.update_update(update).await?;
Ok(server)
}
+1 -1
View File
@@ -32,9 +32,9 @@ impl State {
let state = State {
db: DbClient::new(config.mongo.clone()).await,
slack: config.slack_url.clone().map(|url| slack::Client::new(&url)),
periphery: PeripheryClient::new(config.passkey.clone()),
config,
update: UpdateWsChannel::new(),
periphery: PeripheryClient::default(),
build_action_states: Default::default(),
deployment_action_states: Default::default(),
server_action_states: Default::default(),
+28
View File
@@ -0,0 +1,28 @@
# building images
Monitor builds docker images by cloning the source repository from Github and running ```docker build``` on the configured Dockerfile, which should be present in the source repository.
## repo configuration
Setting related to the github repo are under the *repo* tab on respective build's page.
To specify the github repo to build, just give it the name of the repo and the branch under *github config*. The name is given like ```mbecker20/monitor```, it includes the username / organization that owns the repo.
Many repos are private, in this case a Github access token is required in the periphery.config.toml of the building server. these are specified in the config like ```username = "access_token"```. An account which has access to the repo and is available on the periphery server can be selected to use via the *github account* dropdown menu.
Sometimes a command needs to be run when the repo is cloned, you can configure this in the *on clone* section.
There are two fields to pass for *on clone*. the first is *path*, which changes to working directory. To run the command in the root of the repo, just pass ".". The second field is *command*, this is the shell command to be executed after the repo is cloned.
For example, say your repo had a folder in it called "scripts" with a shell script "on-clone.sh". You would give *path* as "scripts" and command as "sh on-clone.sh". Or you could make *path* just "." and then command would be "sh scripts/on-clone.sh". Either way works fine.
## build configuration
## versioning
Monitor uses a major.minor.patch versioning scheme. Every build will auto increment the patch number, and push the image to docker hub with the version tag as well as the "latest" tag.
[next: deploying](https://github.com/mbecker20/monitor/blob/main/docs/deployments.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+3
View File
@@ -0,0 +1,3 @@
## deploying applications
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+46
View File
@@ -0,0 +1,46 @@
# introduction
If you have many servers running many applications, it can be a challenge to keep things organized and easily accessible. Without structure, things can become messy quickly, which means operational issues are more likely to arise and they can take longer to resolve. Ultimately these issues hinder productivity and waste valuable time. Monitor is a web app to provide this structure for how applications are built, deployed, and managed across many servers.
## docker
Monitor is opinionated by design, and [docker](https://docs.docker.com/) is the tool of choice. Docker provides the ability to package applications and their runtime dependencies into a standalone bundle, called an *image*. This makes them easy to "ship" to any server and run without the hassle of setting up the runtime environment. Docker uses the image as a sort of template to create *containers*. Containers are kind of like virtual machines but with different performance characteristics, namely that processes contained still run natively on the system kernel. The file system is seperate though, and like virtual machines, they can be created, started, stopped, and destroyed.
## monitor
Monitor is a solution for handling for the following:
1. Build application source into auto-versioned images.
2. Create, start, stop, and restart Docker containers, and view their status and logs.
3. Keep a record of all the actions that are performed and by whom.
4. View realtime and historical system resource usage.
5. Alerting for server health, like high cpu, memory, disk, etc.
## architecture and components
Monitor is composed of a single core and any amount of connected servers running the periphery application.
### monitor core
The core is a web server that hosts the core API and serves the frontend to be accessed in a web browser. All user interaction with the connected servers flow through the core. It is the stateful part of the system, with the application state stored on an instance of MongoDB.
### monitor periphery
The periphery is a stateless web server that exposes API called by the core. The core calls this API to get system usage and container status / logs, clone git repos, and perform docker actions. It is only intended to be reached from the core, and has an address whitelist to limit the IPs allowed to call this API.
### monitor cli
This is a simple standalone cli that helps perform some actions required to setup monitor core and periphery, like generating config files.
## core API
Monitor exposes powerful functionality over the core's REST API, enabling infrastructure engineers to manage deployments programmatically in addition to with the GUI. There is a [rust crate](https://crates.io/crates/monitor_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests.
## permissioning
Monitor is a system designed to be used by many users, whether they are developers, operations personnel, or administrators. The ability to affect an applications state is very powerful, so monitor has a granular permissioning system to only provide this functionality to the intended users. The permissioning system is explained in detail in the [permissioning](https://github.com/mbecker20/monitor/blob/main/docs/permissions.md) section.
User sign-on is possible using username / password, or with Oauth (Github and Google). Allowed login methods can be configured from the [core config](https://github.com/mbecker20/monitor/blob/main/config_example/core.config.example.toml).
[next: connecting servers](https://github.com/mbecker20/monitor/blob/main/docs/servers.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+1
View File
@@ -0,0 +1 @@
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+30
View File
@@ -0,0 +1,30 @@
# connecting servers
Integrating a device into the monitor system has 2 steps:
1. Setup and start the periphery agent on the server
2. Adding the server to monitor via the core API
## setup monitor periphery
The easiest way to do this is to follow the [monitor guide](https://github.com/mbecker20/monitor-guide). This is a repo containing directions and scripts enabling command line installation via ssh or remotely.
### manual install steps
1. Download the periphery binary from the latest [release](https://github.com/mbecker20/monitor/releases) or install it using [cargo](https://crates.io/crates/monitor_periphery). If the monitor cli.
2. Create and edit ~/.monitor/periphery.config.toml, following the [config example](https://github.com/mbecker20/monitor/blob/main/config_example/periphery.config.example.toml). The file can be anywhere, it can be passed to periphery via the --config-path flag or with the CONFIG_PATH environment variable. The monitor cli can also be used: ```monitor periphery gen-config```
3. Ensure that inbound connectivity is allowed on the port specified in periphery.config.toml (default 8000).
4. Install docker. Make sure whatever user periphery is run as has access to the docker group without sudo.
5. Start the periphery binary with your preferred process manager, like systemd. The config read from the file is printed on startup, ensure that it is as expected.
## adding the server to monitor
The easiest way to add the server is with the GUI. On the home page, click the + button to the right of the server search bar, configure the name and address of the server. The address is the full http/s url to the periphery server, eg http://12.34.56.78:8000.
Once it is added, you can use access the GUI to modify some config, like the alerting thresholds for cpu, memory and disk usage. A server can also be temporarily disabled, this will prevent alerting if it goes offline.
Since no state is stored on the periphery servers, you can easily redirect all builds / deployments to be hosted on a different server. Just update the address to point to the new server.
[next: building](https://github.com/mbecker20/monitor/blob/main/docs/builds.md)
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+5
View File
@@ -0,0 +1,5 @@
# setting up monitor core
[back to table of contents](https://github.com/mbecker20/monitor/blob/main/readme.md)
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Rounded_Rectangle_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
<g id="Rounded_Rectangle_2">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#fceade" d="M15,4H1C0.45,4,0,4.45,0,5v14c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1V5
C16,4.45,15.55,4,15,4z M14,18H2V6h12V18z M19,0H5C4.45,0,4,0.45,4,1v2h2V2h12v12h-1v2h2c0.55,0,1-0.45,1-1V1
C20,0.45,19.55,0,19,0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 679 B

+105
View File
@@ -0,0 +1,105 @@
import { useNavigate } from "@solidjs/router";
import { Component, createSignal } from "solid-js";
import { client, pushNotification } from "..";
import { useAppState } from "../state/StateProvider";
import { Build, Deployment } from "../types";
import { getId } from "../util/helpers";
import { useToggle } from "../util/hooks";
import ConfirmButton from "./shared/ConfirmButton";
import Icon from "./shared/Icon";
import Input from "./shared/Input";
import Flex from "./shared/layout/Flex";
import Grid from "./shared/layout/Grid";
import CenterMenu from "./shared/menu/CenterMenu";
import Selector from "./shared/menu/Selector";
const CopyMenu: Component<{
type: "deployment" | "build";
id: string;
}> = (p) => {
const navigate = useNavigate();
const [show, toggleShow] = useToggle();
const [newName, setNewName] = createSignal("");
const { builds, deployments, servers } = useAppState();
const curr_server = () => {
if (p.type === "build") {
return builds.get(p.id)!.server_id;
} else {
return deployments.get(p.id)!.deployment.server_id;
}
}
const [selectedId, setSelected] = createSignal(curr_server());
const name = () => {
if (p.type === "build") {
return builds.get(p.id)?.name;
} else if (p.type === "deployment") {
return deployments.get(p.id)?.deployment.name;
}
};
const copy = () => {
if (newName().length !== 0) {
let promise: Promise<Build | Deployment>;
if (p.type === "build") {
promise = client.copy_build(p.id, {
name: newName(),
server_id: selectedId(),
});
} else {
promise = client.copy_deployment(p.id, {
name: newName(),
server_id: selectedId(),
});
}
toggleShow();
promise.then((val) => {
navigate(`/${p.type}/${getId(val)}`);
});
} else {
pushNotification("bad", "copy name cannot be empty");
}
};
return (
<CenterMenu
show={show}
toggleShow={toggleShow}
title={`copy ${p.type} | ${name()}`}
target={<Icon type="duplicate" />}
targetClass="blue"
content={() => (
<Grid placeItems="center">
<Flex alignItems="center">
<Input
placeholder="copy name"
class="card dark"
style={{ padding: "0.5rem" }}
value={newName()}
onEdit={setNewName}
/>
<Selector
label="target: "
selected={selectedId()}
items={servers.ids()!}
onSelect={setSelected}
itemMap={(id) => servers.get(id)!.server.name}
targetClass="blue"
targetStyle={{ display: "flex", gap: "0.5rem" }}
searchStyle={{ width: "100%" }}
position="bottom right"
useSearch
/>
</Flex>
<ConfirmButton
class="green"
style={{ width: "100%" }}
onConfirm={copy}
>
copy {p.type}
</ConfirmButton>
</Grid>
)}
position="center"
/>
);
};
export default CopyMenu;
+4 -3
View File
@@ -10,20 +10,21 @@ import { useActionStates } from "./ActionStateProvider";
import { client } from "../..";
import { combineClasses, getId } from "../../util/helpers";
import { useParams } from "@solidjs/router";
import { PermissionLevel } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
const Actions: Component<{}> = (p) => {
const { user } = useUser();
const params = useParams() as { id: string };
const { builds } = useAppState();
const { builds, servers } = useAppState();
const build = () => builds.get(params.id)!;
const server = () => build() && servers.get(build()!.server_id);
const actions = useActionStates();
const userCanExecute = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Execute ||
build().permissions![getId(user())] === PermissionLevel.Update;
return (
<Show when={userCanExecute()}>
<Show when={userCanExecute() && server()?.status === ServerStatus.Ok}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Grid style={{ height: "fit-content" }}>
+22 -15
View File
@@ -13,6 +13,8 @@ import { A, useParams } from "@solidjs/router";
import { PermissionLevel } from "../../types";
import { client } from "../..";
import HoverMenu from "../shared/menu/HoverMenu";
import CopyMenu from "../CopyMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { builds, servers } = useAppState();
@@ -43,21 +45,26 @@ const Header: Component<{}> = (p) => {
<Flex alignItems="center" justifyContent="space-between">
<h1>{build().name}</h1>
<Show when={userCanUpdate()}>
<HoverMenu
target={
<ConfirmButton
onConfirm={() => {
client.delete_build(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
}
content="delete build"
position="bottom center"
padding="0.5rem"
/>
<Flex alignItems="center">
<CopyMenu type="build" id={params.id} />
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_build(params.id);
}}
class="red"
title={`delete build | ${build().name}`}
match={build().name}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete build"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">
@@ -10,7 +10,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { Build, Operation, PermissionLevel } from "../../../types";
import { Build, Operation, PermissionLevel, ServerWithStatus } from "../../../types";
import { getId } from "../../../util/helpers";
type ConfigBuild = Build & {
@@ -22,6 +22,7 @@ type ConfigBuild = Build & {
type State = {
build: ConfigBuild;
setBuild: SetStoreFunction<ConfigBuild>;
server: () => ServerWithStatus | undefined
reset: () => void;
save: () => void;
userCanUpdate: () => boolean;
@@ -30,7 +31,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, builds } = useAppState();
const { ws, builds, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [build, set] = createStore({
@@ -44,6 +45,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
set(...args);
set("updated", true);
};
const server = () => servers.get(builds.get(params.id)!.server_id);
const load = () => {
// console.log("load build");
@@ -105,6 +107,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const state = {
build,
setBuild,
server,
reset: load,
save,
userCanUpdate,
@@ -74,7 +74,7 @@ const EditBuildArgs: Component<{}> = (p) => {
value={buildArgs()}
onEdit={setBuildArgs}
style={{
width: "700px",
width: "1000px",
"max-width": "90vw",
height: "80vh",
padding: "1rem",
@@ -1,6 +1,7 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { ServerStatus } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import Input from "../../../shared/Input";
import Flex from "../../../shared/layout/Flex";
@@ -9,10 +10,14 @@ import Selector from "../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Docker: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_docker_accounts(build.server_id).then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(build.server_id)
.then(setDockerAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>
@@ -1,4 +1,4 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { Component, createEffect, createSignal } from "solid-js";
import Grid from "../../../shared/layout/Grid";
import { useConfig } from "../Provider";
import Flex from "../../../shared/layout/Flex";
@@ -6,12 +6,15 @@ import Input from "../../../shared/Input";
import Selector from "../../../shared/menu/Selector";
import { combineClasses } from "../../../../util/helpers";
import { client } from "../../../..";
import { ServerStatus } from "../../../../types";
const Git: Component<{}> = (p) => {
const { build, setBuild, userCanUpdate } = useConfig();
const { build, setBuild, server, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts)
if (server()?.status === ServerStatus.Ok) {
client.get_server_github_accounts(build.server_id).then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>
+57 -18
View File
@@ -1,5 +1,5 @@
import { Component, Match, Show, Switch } from "solid-js";
import { client, pushNotification } from "../..";
import { client } from "../..";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import ConfirmButton from "../shared/ConfirmButton";
@@ -9,24 +9,33 @@ import Grid from "../shared/layout/Grid";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
import { useActionStates } from "./ActionStateProvider";
import { combineClasses, getId } from "../../util/helpers";
import { combineClasses } from "../../util/helpers";
import { A, useParams } from "@solidjs/router";
import { DockerContainerState, PermissionLevel } from "../../types";
import {
DockerContainerState,
PermissionLevel,
ServerStatus,
} from "../../types";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Actions: Component<{}> = (p) => {
const { deployments, builds, getPermissionOnDeployment } = useAppState();
const { deployments, builds, servers, getPermissionOnDeployment } =
useAppState();
const params = useParams();
const { user, user_id } = useUser();
const deployment = () => deployments.get(params.id)!;
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
const show = () => {
const permissions = getPermissionOnDeployment(params.id);
return (
server()?.status === ServerStatus.Ok &&
deployment() &&
(user().admin ||
permissions === PermissionLevel.Execute ||
permissions === PermissionLevel.Update)
);
};
const deployment = () => deployments.get(params.id)!;
const showBuild = () => {
const build = deployment().deployment.build_id
? builds.get(deployment().deployment.build_id!)
@@ -117,13 +126,17 @@ const Actions: Component<{}> = (p) => {
};
const Build: Component = () => {
const { ws, deployments } = useAppState();
const { deployments } = useAppState();
const params = useParams();
const actions = useActionStates();
const buildID = () => deployments.get(params.id)!.deployment.build_id!;
return (
<Flex class={combineClasses("action shadow")}>
<A href={`/build/${buildID()}`} class="pointer">
<A
href={`/build/${buildID()}`}
class="pointer"
style={{ padding: 0, "font-size": "16px" }}
>
build
</A>
<Show
@@ -152,6 +165,8 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
const params = useParams();
// const deployment = () => deployments.get(params.id)!;
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.deploying}
@@ -163,14 +178,30 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
>
<HoverMenu
target={
<ConfirmButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
<Show
when={p.redeploy}
fallback={
<ConfirmButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
>
<Icon type={"play"} />
</ConfirmButton>
}
>
<Icon type={p.redeploy ? "reset" : "play"} />
</ConfirmButton>
<ConfirmMenuButton
class="green"
onConfirm={() => {
client.deploy_container(params.id);
}}
title={`redeploy container | ${name()}`}
match={name()!}
>
<Icon type={"reset"} />
</ConfirmMenuButton>
</Show>
}
content={p.redeploy ? "redeploy container" : "deploy container"}
position="bottom center"
@@ -183,6 +214,8 @@ const Deploy: Component<{ redeploy?: boolean }> = (p) => {
const RemoveContainer = () => {
const params = useParams();
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.removing}
@@ -194,14 +227,16 @@ const RemoveContainer = () => {
>
<HoverMenu
target={
<ConfirmButton
<ConfirmMenuButton
class="red"
onConfirm={() => {
client.remove_container(params.id);
}}
title={`destroy container | ${name()}`}
match={name()!}
>
<Icon type="trash" />
</ConfirmButton>
</ConfirmMenuButton>
}
content="delete container"
position="bottom center"
@@ -245,6 +280,8 @@ const Start = () => {
const Stop = () => {
const params = useParams();
const actions = useActionStates();
const { deployments } = useAppState();
const name = () => deployments.get(params.id)?.deployment.name;
return (
<Show
when={!actions.stopping}
@@ -256,14 +293,16 @@ const Stop = () => {
>
<HoverMenu
target={
<ConfirmButton
<ConfirmMenuButton
class="orange"
onConfirm={() => {
client.stop_container(params.id);
}}
title={`stop container | ${name()}`}
match={name()!}
>
<Icon type="pause" />
</ConfirmButton>
</ConfirmMenuButton>
}
content="stop container"
position="bottom center"
@@ -1,27 +1,35 @@
import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { MAX_PAGE_WIDTH } from "../..";
import { Component, onCleanup, Show } from "solid-js";
import { client } from "../..";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useUser } from "../../state/UserProvider";
import { PermissionLevel } from "../../types";
import { combineClasses, getId } from "../../util/helpers";
import { ServerStatus } from "../../types";
import NotFound from "../NotFound";
import Grid from "../shared/layout/Grid";
import Actions from "./Actions";
import { ActionStateProvider } from "./ActionStateProvider";
import Header from "./Header";
import { ConfigProvider } from "./tabs/config/Provider";
import DeploymentTabs from "./tabs/Tabs";
import Updates from "./Updates";
const Deployment2: Component<{}> = (p) => {
const POLLING_RATE = 10000;
// let interval = -1;
const Deployment: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const { isSemiMobile } = useAppDimensions();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
// clearInterval(interval);
// interval = setInterval(async () => {
// if (server()?.status === ServerStatus.Ok) {
// const deployment = await client.get_deployment(params.id);
// deployments.update(deployment);
// }
// }, POLLING_RATE);
// onCleanup(() => clearInterval(interval));
return (
<Show
when={deployment() && server()}
@@ -53,46 +61,4 @@ const Deployment2: Component<{}> = (p) => {
);
};
const Deployment: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () => deployment() && servers.get(deployment()!.deployment.server_id);
const { isSemiMobile } = useAppDimensions();
const { user } = useUser();
const userCanUpdate = () => user().admin || deployment()?.deployment.permissions![getId(user())] === PermissionLevel.Update;
return (
<Show
when={deployment() && server()}
fallback={<NotFound type="deployment" />}
>
<ActionStateProvider>
<Grid class={combineClasses("content")}>
{/* left / actions */}
<Grid class="left-content">
<Header />
<Actions />
<Show when={!isSemiMobile() && userCanUpdate()}>
<Updates />
</Show>
</Grid>
{/* right / tabs */}
<Show
when={userCanUpdate()}
fallback={
<h2 class={combineClasses("card tabs shadow")}>
you do not have permission to view this deployment
</h2>
}
>
<ConfigProvider>
<DeploymentTabs />
</ConfigProvider>
</Show>
</Grid>
</ActionStateProvider>
</Show>
);
};
export default Deployment2;
export default Deployment;
+31 -16
View File
@@ -6,7 +6,6 @@ import {
deploymentHeaderStateClass,
getId,
} from "../../util/helpers";
import ConfirmButton from "../shared/ConfirmButton";
import Icon from "../shared/Icon";
import Flex from "../shared/layout/Flex";
import Grid from "../shared/layout/Grid";
@@ -17,6 +16,8 @@ import Updates from "./Updates";
import { DockerContainerState, PermissionLevel } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import CopyMenu from "../CopyMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { deployments, servers } = useAppState();
@@ -53,21 +54,35 @@ const Header: Component<{}> = (p) => {
<Flex alignItems="center" justifyContent="space-between">
<h1>{deployment()!.deployment.name}</h1>
<Show when={userCanUpdate()}>
<HoverMenu
target={
<ConfirmButton
onConfirm={() => {
client.delete_deployment(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
}
content="delete deployment"
position="bottom center"
padding="0.5rem"
/>
<Flex alignItems="center">
<CopyMenu type="deployment" id={params.id} />
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_deployment(params.id);
}}
class="red"
title={`delete deployment | ${
deployment().deployment.name
}`}
match={deployment().deployment.name}
info={
<Show when={deployment().container}>
<div style={{ opacity: 0.7 }}>
warning! this will destroy this deployments container
</div>
</Show>
}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete deployment"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">
@@ -17,6 +17,7 @@ import {
DockerContainerState,
Log as LogType,
Operation,
ServerStatus,
} from "../../../types";
import { client } from "../../..";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
@@ -26,18 +27,23 @@ import { useUser } from "../../../state/UserProvider";
const DeploymentTabs: Component<{}> = () => {
const { user } = useUser();
const { deployments, ws } = useAppState();
const { deployments, ws, servers } = useAppState();
const params = useParams();
const deployment = () => deployments.get(params.id);
const server = () =>
deployment() && servers.get(deployment()!.deployment.server_id);
const [logTail, setLogTail] = createSignal(50);
const [log, setLog] = createSignal<LogType>();
const status = () =>
deployment()!.state === DockerContainerState.NotDeployed
? "not deployed"
: deployment()!.container?.state;
// const status = () =>
// deployment()!.state === DockerContainerState.NotDeployed
// ? "not deployed"
// : deployment()!.container?.state;
const log_available = () =>
server()?.status === ServerStatus.Ok &&
deployment()?.state !== DockerContainerState.NotDeployed;
const loadLog = async () => {
console.log("load log");
if (deployment()?.state !== DockerContainerState.NotDeployed) {
if (log_available()) {
console.log("load log");
const log = await client.get_deployment_container_log(
params.id,
logTail()
@@ -78,7 +84,7 @@ const DeploymentTabs: Component<{}> = () => {
title: "config",
element: () => <Config />,
},
status() !== "not deployed" && [
log_available() && [
{
title: "log",
element: () => (
@@ -90,7 +96,7 @@ const DeploymentTabs: Component<{}> = () => {
/>
),
},
status() !== "not deployed" && {
{
title: "error log",
titleElement: () => (
<Flex gap="0.5rem" alignItems="center">
@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Deployment, Operation, PermissionLevel } from "../../../../types";
import { Deployment, Operation, PermissionLevel, ServerStatus, ServerWithStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigDeployment = Deployment & {
@@ -25,6 +25,7 @@ type State = {
editing: Accessor<boolean>;
deployment: ConfigDeployment;
setDeployment: SetStoreFunction<ConfigDeployment>;
server: () => ServerWithStatus | undefined;
reset: () => void;
save: () => void;
networks: Accessor<any[]>;
@@ -34,7 +35,7 @@ type State = {
const context = createContext<State>();
export const ConfigProvider: ParentComponent<{}> = (p) => {
const { ws, deployments } = useAppState();
const { ws, deployments, servers } = useAppState();
const params = useParams();
const { user } = useUser();
const [editing] = createSignal(false);
@@ -87,11 +88,13 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
createEffect(load);
const [networks, setNetworks] = createSignal<any[]>([]);
const server = () => servers.get(deployments.get(params.id)!.deployment.server_id);
createEffect(() => {
console.log("load networks");
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
if (server()?.status === ServerStatus.Ok) {
client
.get_docker_networks(deployments.get(params.id)!.deployment.server_id)
.then(setNetworks);
}
});
const save = () => {
@@ -141,6 +144,7 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
editing,
deployment,
setDeployment,
server,
reset: load,
save,
networks,
@@ -1,18 +1,20 @@
import { Component, createEffect, createSignal, Show } from "solid-js";
import { client } from "../../../../..";
import { useAppState } from "../../../../../state/StateProvider";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Flex from "../../../../shared/layout/Flex";
import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const DockerAccount: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, setDeployment, server, userCanUpdate } = useConfig();
const [dockerAccounts, setDockerAccounts] = createSignal<string[]>();
createEffect(() => {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_docker_accounts(deployment.server_id)
.then(setDockerAccounts);
}
});
return (
<Flex
@@ -74,7 +74,7 @@ const EditDotEnv: Component<{}> = (p) => {
value={dotenv()}
onEdit={setDotEnv}
style={{
width: "700px",
width: "1000px",
"max-width": "90vw",
height: "80vh",
padding: "1rem",
@@ -1,5 +1,6 @@
import { Component, createEffect, createSignal } from "solid-js";
import { client } from "../../../../..";
import { ServerStatus } from "../../../../../types";
import { combineClasses } from "../../../../../util/helpers";
import Input from "../../../../shared/Input";
import Flex from "../../../../shared/layout/Flex";
@@ -8,10 +9,14 @@ import Selector from "../../../../shared/menu/Selector";
import { useConfig } from "../Provider";
const Git: Component<{}> = (p) => {
const { deployment, setDeployment, userCanUpdate } = useConfig();
const { deployment, server, setDeployment, userCanUpdate } = useConfig();
const [githubAccounts, setGithubAccounts] = createSignal<string[]>();
createEffect(() => {
client.get_server_github_accounts(deployment.server_id).then(setGithubAccounts);
if (server()?.status === ServerStatus.Ok) {
client
.get_server_github_accounts(deployment.server_id)
.then(setGithubAccounts);
}
});
return (
<Grid class={combineClasses("config-item shadow")}>
@@ -1,10 +1,17 @@
import { useParams } from "@solidjs/router";
import { Component, createEffect, createSignal, Show } from "solid-js";
import {
Component,
createEffect,
createMemo,
createSignal,
onCleanup,
Show,
} from "solid-js";
import { client, pushNotification } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { DockerContainerState, Log as LogType } from "../../../../types";
import { combineClasses } from "../../../../util/helpers";
import { useBuffer } from "../../../../util/hooks";
import { useBuffer, useLocalStorageToggle } from "../../../../util/hooks";
import Icon from "../../../shared/Icon";
import Flex from "../../../shared/layout/Flex";
import Grid from "../../../shared/layout/Grid";
@@ -12,6 +19,10 @@ import Selector from "../../../shared/menu/Selector";
import { useConfig } from "../config/Provider";
import s from "./log.module.scss";
const POLLING_RATE = 5000;
let interval = -1;
const Log: Component<{
log?: LogType;
logTail: number;
@@ -57,9 +68,23 @@ const Log: Component<{
}
};
const buffer = useBuffer(scrolled, 250);
const [poll, togglePoll] = useLocalStorageToggle(
"deployment-log-polling",
true
);
clearInterval(interval);
interval = setInterval(() => {
if (poll() && deployment()?.state === DockerContainerState.Running) {
p.reload();
}
}, POLLING_RATE);
onCleanup(() => clearInterval(interval));
return (
<Show when={p.log}>
<Grid gap="0.5rem" style={{ height: "100%", "grid-template-rows": "auto 1fr" }}>
<Grid
gap="0.5rem"
style={{ height: "100%", "grid-template-rows": "auto 1fr" }}
>
<Flex
alignItems="center"
justifyContent="flex-end"
@@ -100,6 +125,9 @@ const Log: Component<{
>
<Icon type="refresh" />
</button>
<button class={poll() ? "green" : "red"} onClick={togglePoll}>
{poll() ? "" : "don't "}poll
</button>
</Flex>
<div style={{ position: "relative", height: "100%" }}>
<div
+20 -18
View File
@@ -25,25 +25,27 @@ const Actions: Component<{}> = (p) => {
<Show
when={server() && server().status === ServerStatus.Ok && userCanExecute()}
>
<Grid class={combineClasses("card shadow")}>
<Grid class={combineClasses("card shadow")} gridTemplateRows="auto 1fr">
<h1>actions</h1>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
<Grid style={{ height: "fit-content" }}>
<Flex class={combineClasses("action shadow")}>
prune images <PruneImages />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune containers <PruneContainers />
</Flex>
<Flex class={combineClasses("action shadow")}>
prune networks{" "}
<ConfirmButton
class="green"
onConfirm={() => {
client.prune_docker_networks(params.id);
}}
>
<Icon type="cut" />
</ConfirmButton>
</Flex>
</Grid>
</Grid>
</Show>
);
+60 -39
View File
@@ -9,10 +9,12 @@ import Grid from "../shared/layout/Grid";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useLocalStorageToggle } from "../../util/hooks";
import Updates from "./Updates";
import { PermissionLevel, Server } from "../../types";
import { PermissionLevel, ServerStatus } from "../../types";
import { A, useParams } from "@solidjs/router";
import { client } from "../..";
import Loading from "../shared/loading/Loading";
import HoverMenu from "../shared/menu/HoverMenu";
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
const Header: Component<{}> = (p) => {
const { servers } = useAppState();
@@ -26,25 +28,73 @@ const Header: Component<{}> = (p) => {
const userCanUpdate = () =>
user().admin ||
server().server.permissions![getId(user())] === PermissionLevel.Update;
const [version] = createResource(async () => {
return await client.get_server_version(params.id).catch();
});
const [version] = createResource(
() => server() && server().status === ServerStatus.Ok,
async (do_it?: boolean) => {
if (!do_it) return;
return await client.get_server_version(params.id).catch();
}
);
return (
<>
<Flex
<Grid
gap="0.5rem"
class={combineClasses("card shadow")}
justifyContent="space-between"
alignItems="center"
style={{
position: "relative",
cursor: isSemiMobile() ? "pointer" : undefined,
height: "fit-content",
}}
onClick={() => {
if (isSemiMobile()) toggleShowUpdates();
}}
>
<Grid gap="0.1rem">
<Flex alignItems="center" justifyContent="space-between">
<h1>{server().server.name}</h1>
<Show when={userCanUpdate()}>
<Flex alignItems="center">
<div class={serverStatusClass(server().status)}>{status()}</div>
<HoverMenu
target={
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
}
content="server stats"
position="bottom center"
padding="0.5rem"
/>
<HoverMenu
target={
<ConfirmMenuButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
title={`delete server | ${server().server.name}`}
match={server().server.name}
info={
<div style={{ opacity: 0.7 }}>
warning! this will also delete all builds and
deployments on this server
</div>
}
>
<Icon type="trash" />
</ConfirmMenuButton>
}
content="delete server"
position="bottom center"
padding="0.5rem"
/>
</Flex>
</Show>
</Flex>
<Flex alignItems="center" justifyContent="space-between">
<Flex gap="0.2rem" alignItems="center" style={{ opacity: 0.8 }}>
<div>server</div>
<Show when={server().server.region}>
@@ -52,42 +102,13 @@ const Header: Component<{}> = (p) => {
{server().server.region}
</Show>
</Flex>
</Grid>
<Flex alignItems="center">
<Show when={!isMobile()}>
<Show when={version()} fallback={<Loading type="three-dot" />}>
<Show when={version()}>
<div style={{ opacity: 0.7 }}>periphery v{version()}</div>
</Show>
</Show>
<div class={serverStatusClass(server().status)}>{status()}</div>
<A
href={`/server/${params.id}/stats`}
class="blue"
onClick={(e) => e.stopPropagation()}
>
<Icon type="timeline-line-chart" />
</A>
<Show when={userCanUpdate()}>
<ConfirmButton
onConfirm={() => {
client.delete_server(params.id);
}}
class="red"
>
<Icon type="trash" />
</ConfirmButton>
</Show>
</Flex>
<Show when={isSemiMobile()}>
<Flex gap="0.5rem" alignItems="center" class="show-updates-indicator">
updates{" "}
<Icon
type={showUpdates() ? "chevron-up" : "chevron-down"}
width="0.9rem"
/>
</Flex>
</Show>
</Flex>
</Grid>
<Show when={isSemiMobile() && showUpdates()}>
<Updates />
</Show>
+1 -1
View File
@@ -32,7 +32,7 @@ const Server: Component<{}> = (p) => {
style={{ width: "100%" }}
gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}
>
<Grid>
<Grid style={{ "flex-grow": 1, "grid-auto-rows": "auto 1fr" }}>
<Header />
<Actions />
</Grid>
+2 -1
View File
@@ -2,6 +2,7 @@ import { useParams } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { ServerStatus } from "../../../types";
import SimpleTabs from "../../shared/tabs/SimpleTabs";
import { Tab } from "../../shared/tabs/Tabs";
import Config from "./config/Config";
@@ -26,7 +27,7 @@ const ServerTabs: Component<{}> = (p) => {
title: "config",
element: () => <Config />,
},
{
server()?.status === ServerStatus.Ok && {
title: "info",
element: () => <Info />
},
@@ -12,7 +12,7 @@ import { createStore, SetStoreFunction } from "solid-js/store";
import { client } from "../../../..";
import { useAppState } from "../../../../state/StateProvider";
import { useUser } from "../../../../state/UserProvider";
import { Server, Operation, PermissionLevel } from "../../../../types";
import { Server, Operation, PermissionLevel, ServerStatus } from "../../../../types";
import { getId } from "../../../../util/helpers";
type ConfigServer = Server & { loaded: boolean; updated: boolean };
@@ -59,7 +59,9 @@ export const ConfigProvider: ParentComponent<{}> = (p) => {
const [networks, setNetworks] = createSignal<any[]>([]);
const loadNetworks = () => {
// console.log("load networks");
client.get_docker_networks(params.id).then(setNetworks);
if (servers.get(params.id)?.status === ServerStatus.Ok) {
client.get_docker_networks(params.id).then(setNetworks);
}
};
createEffect(loadNetworks);
@@ -0,0 +1,83 @@
import { Component, createSignal, JSX } from "solid-js";
import { pushNotification } from "../..";
import { useToggle } from "../../util/hooks";
import ConfirmButton from "./ConfirmButton";
import Input from "./Input";
import Grid from "./layout/Grid";
import CenterMenu from "./menu/CenterMenu";
const ConfirmMenuButton: Component<{
onConfirm?: () => void;
class?: string;
style?: JSX.CSSProperties;
title: string;
match: string;
info?: JSX.Element;
children: JSX.Element;
}> = (p) => {
const [show, toggleShow] = useToggle();
return (
<CenterMenu
show={show}
toggleShow={toggleShow}
title={p.title}
targetClass={p.class}
target={p.children}
content={() => (
<ConfirmMenuContent
class={p.class}
title={p.title}
match={p.match}
info={p.info}
onConfirm={p.onConfirm}
/>
)}
position="center"
/>
);
};
const ConfirmMenuContent: Component<{
class?: string;
title: string;
match: string;
onConfirm?: () => void;
info?: JSX.Element;
}> = (p) => {
const [input, setInput] = createSignal("");
return (
<Grid placeItems="center">
{p.info}
<Input
class="darkgrey"
style={{
padding: "0.5rem",
width: "100%",
"border-style": "solid",
"border-width": "1px",
"border-color": input() === p.match ? "#41764c" : "#952E23",
}}
placeholder={`enter '${p.match}'`}
onEdit={setInput}
value={input()}
autofocus
/>
<ConfirmButton
class={p.class}
style={{ width: "100%" }}
onConfirm={() => {
if (input() === p.match) {
p.onConfirm && p.onConfirm();
} else {
pushNotification("bad", "must enter value to confirm");
}
}}
>
{p.title}
</ConfirmButton>
</Grid>
);
};
export default ConfirmMenuButton;
+2 -1
View File
@@ -45,7 +45,8 @@ export type IconType =
| "cog"
| "home"
| "timeline-line-chart"
| "arrow-right";
| "arrow-right"
| "duplicate";
const ICON_DIR = import.meta.env.VITE_ICON_DIR || "/assets/icons"
@@ -33,10 +33,12 @@ const Selector: Component<{
itemClass?: string;
itemStyle?: JSX.CSSProperties;
label?: JSXElement;
itemMap?: (item: string) => string;
}> = (p) => {
const [show, toggle] = useToggle();
const [search, setSearch] = createSignal("");
let ref: HTMLInputElement | undefined;
const current = () => (p.itemMap ? p.itemMap(p.selected) : p.selected);
createEffect(() => {
if (show()) setTimeout(() => ref?.focus(), 200);
});
@@ -46,7 +48,7 @@ const Selector: Component<{
fallback={
<div class={p.disabledClass} style={p.disabledStyle}>
<Show when={p.label}>{p.label}</Show>
{p.selected}
{current()}
</div>
}
>
@@ -60,7 +62,7 @@ const Selector: Component<{
target={
<button class={p.targetClass} onClick={toggle} style={p.targetStyle}>
<Show when={p.label}>{p.label}</Show>
{p.selected}
{current()}
<Icon type="chevron-down" />
</button>
}
@@ -84,7 +86,11 @@ const Selector: Component<{
<For
each={
p.useSearch
? p.items.filter((item) => item.includes(search()))
? p.items.filter((item) =>
p.itemMap
? p.itemMap(item).includes(search())
: item.includes(search())
)
: p.items
}
>
@@ -101,7 +107,7 @@ const Selector: Component<{
}}
class={combineClasses(p.itemClass, s.SelectorItem)}
>
{item}
{p.itemMap ? p.itemMap(item) : item}
</button>
)}
</For>
@@ -52,7 +52,7 @@ export default UpdateMenu;
const UpdateMenuContent: Component<{ update: UpdateType }> = (p) => {
return (
<Grid class={s.LogContainer}>
<Grid class={combineClasses(s.LogContainer, "scroller")}>
<UpdateSummary update={p.update} />
<UpdateLogs update={p.update} />
</Grid>
@@ -66,7 +66,7 @@ const UpdateSummary: Component<{ update: UpdateType }> = (p) => {
gap="0.5rem"
class="card light shadow"
gridTemplateColumns="1fr 1fr"
style={{ width: "40rem", "max-width": "90vw" }}
style={{ width: "100%", "box-sizing": "border-box" }}
>
<Flex gap="0.5rem" alignItems="center">
status: <h2>{p.update.status.replaceAll("_", " ")}</h2>
@@ -41,7 +41,6 @@
.LogContainer {
max-height: 80vh;
overflow: scroll;
}
.Log {
@@ -49,8 +48,8 @@
overflow-wrap: anywhere;
/* word-wrap: break-word; */
tab-size: 2;
width: 40rem;
max-width: 90vw;
width: 1000px;
max-width: 80vw;
box-sizing: border-box;
background-color: rgba(c.$darkgrey, 0.6);
padding: 1rem;
+2 -2
View File
@@ -102,8 +102,8 @@ export const AppStateProvider: ParentComponent = (p) => {
return PermissionLevel.None;
}
},
serverStats: useServerStats(),
serverInfo: useServerInfo(),
serverStats: useServerStats(servers),
serverInfo: useServerInfo(servers),
groups,
getPermissionOnGroup: (id: string) => {
const group = groups.get(id)!;
+18 -6
View File
@@ -47,13 +47,19 @@ export function useServers() {
);
}
export function useServerStats() {
export function useServerStats(servers: ReturnType<typeof useServers>) {
const [stats, set] = createSignal<Record<string, SystemStats | undefined>>(
{}
);
const load = async (serverID: string) => {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const stats = await client.get_server_stats(serverID);
set((s) => ({ ...s, [serverID]: stats }));
} catch (error) {
console.log("error getting server stats");
}
}
};
const loading: Record<string, boolean> = {};
setTimeout(() => Object.keys(stats()).forEach(load), 30000);
@@ -74,13 +80,19 @@ export function useServerStats() {
};
}
export function useServerInfo() {
export function useServerInfo(servers: ReturnType<typeof useServers>) {
const [info, set] = createSignal<
Record<string, SystemInformation | undefined>
>({});
const load = async (serverID: string) => {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
if (servers.get(serverID)?.status === ServerStatus.Ok) {
try {
const info = await client.get_server_system_info(serverID);
set((s) => ({ ...s, [serverID]: info }));
} catch (error) {
console.log("error getting server info", error);
}
}
};
const loading: Record<string, boolean> = {};
return {
+4 -1
View File
@@ -11,9 +11,12 @@ body {
background-color: c.$darkgrey;
color: c.$app-color;
scrollbar-color: rgba(252, 234, 222, 0.3) transparent;
scrollbar-width: thin;
display: grid;
grid-template-columns: 1fr;
place-items: center;
place-items: center;
}
code {
+2 -2
View File
@@ -1,12 +1,12 @@
[package]
name = "db_client"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { package = "monitor_types", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.15" }
# types = { package = "monitor_types", path = "../types" }
mungos = "0.3.0"
anyhow = "1.0"
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "monitor_helpers"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
authors = ["MoghTech"]
description = "helpers used as dependency for mogh tech monitor"
@@ -10,7 +10,7 @@ license = "GPL-3.0-or-later"
[dependencies]
# types = { package = "monitor_types", path = "../types" }
types = { package = "monitor_types", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.15" }
async_timing_util = "0.1.14"
bollard = "0.13"
anyhow = "1.0"
@@ -18,7 +18,7 @@ axum = { version = "0.6", features = ["ws", "json"] }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
toml = "0.6"
toml = "0.7"
run_command = { version = "0.0.5", features = ["async_tokio"] }
rand = "0.8"
futures = "0.3"
+5 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "monitor_client"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
authors = ["MoghTech"]
description = "a client to interact with the monitor system"
@@ -9,14 +9,15 @@ license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
monitor_types = "0.1.11"
monitor_types = "0.1.15"
# monitor_types = { path = "../types" }
reqwest = { version = "0.11", features = ["json"] }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio = { version = "1.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
tokio-util = "0.7"
anyhow = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
futures-util = "0.3"
futures-util = "0.3"
envy = "0.4"
+30 -1
View File
@@ -3,7 +3,7 @@
use anyhow::{anyhow, Context};
use monitor_types::User;
use reqwest::StatusCode;
use serde::{de::DeserializeOwned, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::json;
pub use futures_util;
@@ -20,6 +20,15 @@ mod secret;
mod server;
mod update;
#[derive(Deserialize)]
struct MonitorEnv {
monitor_url: String,
monitor_token: Option<String>,
monitor_username: Option<String>,
monitor_password: Option<String>,
monitor_secret: Option<String>,
}
#[derive(Clone)]
pub struct MonitorClient {
http_client: reqwest::Client,
@@ -80,6 +89,26 @@ impl MonitorClient {
Ok(client)
}
pub async fn new_from_env() -> anyhow::Result<MonitorClient> {
let env = envy::from_env::<MonitorEnv>()
.context("failed to parse environment for monitor client")?;
if let Some(token) = env.monitor_token {
Ok(MonitorClient::new_with_token(&env.monitor_url, token))
} else if let Some(password) = env.monitor_password {
let username = env.monitor_username.ok_or(anyhow!(
"must provide MONITOR_USERNAME to authenticate with MONITOR_PASSWORD"
))?;
MonitorClient::new_with_password(&env.monitor_url, username, password).await
} else if let Some(secret) = env.monitor_secret {
let username = env.monitor_username.ok_or(anyhow!(
"must provide MONITOR_USERNAME to authenticate with MONITOR_SECRET"
))?;
MonitorClient::new_with_secret(&env.monitor_url, username, secret).await
} else {
Err(anyhow!("failed to initialize monitor client from env | must provide one of: (MONITOR_TOKEN), (MONITOR_USERNAME and MONITOR_PASSWORD), (MONITOR_USERNAME and MONITOR_SECRET)"))
}
}
pub async fn create_user(
&self,
username: impl Into<String>,
+4 -4
View File
@@ -1,17 +1,17 @@
[package]
name = "periphery_client"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
helpers = { package = "monitor_helpers", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.11" }
helpers = { package = "monitor_helpers", version = "0.1.15" }
types = { package = "monitor_types", version = "0.1.15" }
# types = { package = "monitor_types", path = "../types" }
# helpers = { package = "monitor_helpers", path = "../helpers" }
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
tokio = "1.24"
tokio = "1.25"
reqwest = { version = "0.11", features = ["json"] }
serde = "1.0"
serde_json = "1.0"
+12 -2
View File
@@ -14,12 +14,19 @@ mod git;
mod image;
mod network;
#[derive(Default)]
pub struct PeripheryClient {
http_client: reqwest::Client,
passkey: String,
}
impl PeripheryClient {
pub fn new(passkey: String) -> PeripheryClient {
PeripheryClient {
http_client: Default::default(),
passkey
}
}
pub async fn health_check(&self, server: &Server) -> anyhow::Result<String> {
self.get_text(server, "/health", 1000)
.await
@@ -95,7 +102,8 @@ impl PeripheryClient {
) -> anyhow::Result<String> {
let mut req = self
.http_client
.get(format!("{}{endpoint}", server.address));
.get(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey);
if let Some(timeout) = timeout_ms.into() {
req = req.timeout(Duration::from_millis(timeout))
@@ -130,6 +138,7 @@ impl PeripheryClient {
let res = self
.http_client
.get(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey)
.send()
.await
.context(format!(
@@ -165,6 +174,7 @@ impl PeripheryClient {
let res = self
.http_client
.post(format!("{}{endpoint}", server.address))
.header("authorization", &self.passkey)
.json(body)
.send()
.await
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "monitor_types"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
authors = ["MoghTech"]
description = "types for the mogh tech monitor"
+10
View File
@@ -35,28 +35,36 @@ pub struct Build {
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub server_id: String, // server which this image should be built on
#[builder(default)]
pub version: Version,
// git related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub branch: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub github_account: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_clone: Option<Command>,
// build related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub pre_build: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_build_args: Option<DockerBuildArgs>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_account: Option<String>,
@@ -128,8 +136,10 @@ impl Version {
#[diff(attr(#[derive(Debug, Serialize, PartialEq)]))]
pub struct DockerBuildArgs {
pub build_path: String,
#[builder(default)]
pub dockerfile_path: Option<String>,
#[serde(default)]
#[builder(default)]
pub build_args: Vec<EnvironmentVar>,
}
+5
View File
@@ -42,6 +42,9 @@ pub struct CoreConfig {
// used to verify validity from github webhooks
pub github_webhook_secret: String,
// sent in auth header with req to periphery
pub passkey: String,
// integration with slack app
pub slack_url: Option<String>,
@@ -104,6 +107,8 @@ pub struct PeripheryConfig {
#[serde(default)]
pub allowed_ips: Vec<IpAddr>,
#[serde(default)]
pub passkeys: Vec<String>,
#[serde(default)]
pub secrets: SecretsMap,
#[serde(default)]
pub github_accounts: GithubAccounts,
+19
View File
@@ -32,31 +32,40 @@ pub struct Deployment {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "docker_run_args_diff_no_change")]))]
pub docker_run_args: DockerRunArgs,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub build_id: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub build_version: Option<Version>,
// deployment repo related
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub branch: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub github_account: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_clone: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub on_pull: Option<Command>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub repo_mount: Option<Conversion>,
@@ -94,39 +103,49 @@ pub struct DeploymentActionState {
#[derive(Serialize, Deserialize, Debug, Clone, Diff, Builder)]
#[diff(attr(#[derive(Debug, PartialEq, Serialize)]))]
pub struct DockerRunArgs {
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub image: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub ports: Vec<Conversion>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub volumes: Vec<Conversion>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub environment: Vec<EnvironmentVar>,
#[serde(default = "default_network")]
#[builder(default = "default_network()")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub network: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "restart_mode_diff_no_change")]))]
pub restart: RestartMode,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub post_image: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub container_user: Option<String>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub extra_args: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub docker_account: Option<String>, // the username of the dockerhub account
}
+5
View File
@@ -28,18 +28,23 @@ pub struct Group {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub builds: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub deployments: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub servers: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub procedures: Vec<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub groups: Vec<String>,
+2
View File
@@ -24,10 +24,12 @@ pub struct Procedure {
pub name: String,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub stages: Vec<ProcedureStage>,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub webhook_branches: Vec<String>,
+9
View File
@@ -34,36 +34,45 @@ pub struct Server {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[builder(default = "true")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub enabled: bool,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub to_notify: Vec<String>, // slack users to notify
#[serde(default)]
#[builder(default = "true")]
#[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))]
pub auto_prune: bool,
#[serde(default = "default_cpu_alert")]
#[builder(default = "default_cpu_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f32_diff_no_change")]))]
pub cpu_alert: f32,
#[serde(default = "default_mem_alert")]
#[builder(default = "default_mem_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f64_diff_no_change")]))]
pub mem_alert: f64,
#[serde(default = "default_disk_alert")]
#[builder(default = "default_disk_alert()")]
#[diff(attr(#[serde(skip_serializing_if = "f64_diff_no_change")]))]
pub disk_alert: f64,
#[serde(default)]
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "timelength_diff_no_change")]))]
pub stats_interval: Timelength,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub region: Option<String>,
#[builder(default)]
#[diff(attr(#[serde(skip_serializing_if = "option_diff_no_change")]))]
pub instance_id: Option<String>,
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "monitor_periphery"
version = "0.1.11"
version = "0.1.15"
edition = "2021"
authors = ["MoghTech"]
description = "monitor periphery binary | run monitor periphery as system daemon"
@@ -15,11 +15,11 @@ path = "src/main.rs"
[dependencies]
# helpers = { package = "monitor_helpers", path = "../lib/helpers" }
# types = { package = "monitor_types", path = "../lib/types" }
helpers = { package = "monitor_helpers", version = "0.1.11" }
types = { package = "monitor_types", version = "0.1.11" }
helpers = { package = "monitor_helpers", version = "0.1.15" }
types = { package = "monitor_types", version = "0.1.15" }
run_command = { version = "0.0.5", features = ["async_tokio"] }
async_timing_util = "0.1.14"
tokio = { version = "1.24", features = ["full"] }
tokio = { version = "1.25", features = ["full"] }
axum = { version = "0.6", features = ["ws"] }
tower = { version = "0.4", features = ["full"] }
dotenv = "0.15"
@@ -30,7 +30,7 @@ bollard = "0.13"
anyhow = "1.0"
envy = "0.4"
sysinfo = "0.27.7"
toml = "0.6"
toml = "0.7"
daemonize = "0.4"
clap = { version = "4.0", features = ["derive"] }
futures-util = "0.3"
+57 -5
View File
@@ -43,7 +43,8 @@ pub fn router(config: PeripheryConfigExtension, home_dir: HomeDirExtension) -> R
.nest("/build", build::router())
.nest("/image", image::router())
.layer(DockerClient::extension())
.layer(middleware::from_fn(guard_request))
.layer(middleware::from_fn(guard_request_by_ip))
.layer(middleware::from_fn(guard_request_by_passkey))
.layer(StatsClient::extension(
config.stats_polling_rate.to_string().parse().unwrap(),
))
@@ -51,7 +52,59 @@ pub fn router(config: PeripheryConfigExtension, home_dir: HomeDirExtension) -> R
.layer(home_dir)
}
async fn guard_request(
async fn guard_request_by_passkey(
req: Request<Body>,
next: Next<Body>,
) -> Result<Response, (StatusCode, String)> {
let config = req.extensions().get::<Arc<PeripheryConfig>>().ok_or((
StatusCode::INTERNAL_SERVER_ERROR,
"could not get periphery config".to_string(),
))?;
if config.passkeys.is_empty() {
return Ok(next.run(req).await);
}
let req_passkey = req.headers().get("authorization");
if req_passkey.is_none() {
return Err((
StatusCode::UNAUTHORIZED,
format!("request was not sent with passkey"),
));
}
let req_passkey = req_passkey
.unwrap()
.to_str()
.map_err(|e| {
(
StatusCode::UNAUTHORIZED,
format!("failed to get passkey from authorization header as str: {e:?}"),
)
})?
.to_string();
if config.passkeys.contains(&req_passkey) {
Ok(next.run(req).await)
} else {
let ConnectInfo(socket_addr) =
req.extensions().get::<ConnectInfo<SocketAddr>>().ok_or((
StatusCode::UNAUTHORIZED,
"could not get socket addr of request".to_string(),
))?;
let ip = socket_addr.ip();
let method = req.method().to_owned();
let uri = req.uri().to_owned();
let body = req
.extract::<Json<Value>, _>()
.await
.ok()
.map(|Json(body)| body);
eprintln!(
"{} | unauthorized request from {ip} (bad passkey) | method: {method} | uri: {uri} | body: {body:?}",
monitor_timestamp(),
);
Err((StatusCode::UNAUTHORIZED, format!("request passkey invalid")))
}
}
async fn guard_request_by_ip(
req: Request<Body>,
next: Next<Body>,
) -> Result<Response, (StatusCode, String)> {
@@ -78,9 +131,8 @@ async fn guard_request(
.ok()
.map(|Json(body)| body);
eprintln!(
"{} | unauthorized request from {ip} | method: {method} | uri: {uri} | body: {:?}",
monitor_timestamp(),
body
"{} | unauthorized request from {ip} | method: {method} | uri: {uri} | body: {body:?}",
monitor_timestamp()
);
Err((
StatusCode::UNAUTHORIZED,
+11
View File
@@ -0,0 +1,11 @@
# monitor 🦎
a tool to build and deploy software across many servers
## table of contents
1. [introduction](https://github.com/mbecker20/monitor/blob/main/docs/introduction.md)
2. [connecting servers](https://github.com/mbecker20/monitor/blob/main/docs/servers.md)
3. [building](https://github.com/mbecker20/monitor/blob/main/docs/builds.md)
4. [deploying](https://github.com/mbecker20/monitor/blob/main/docs/deployments.md)
5. [permissioning](https://github.com/mbecker20/monitor/blob/main/docs/permissions.md)