mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-12 10:33:16 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a666df099f | ||
|
|
21dd0ee072 | ||
|
|
bd2a1d4236 | ||
|
|
7acdbcfd8f | ||
|
|
58514c5c93 | ||
|
|
580e800923 | ||
|
|
29f6b19f33 | ||
|
|
e090247723 | ||
|
|
1374c26cd8 | ||
|
|
5467b40b2e | ||
|
|
165b9012da | ||
|
|
22630f665e | ||
|
|
3d867084ba | ||
|
|
171dd2d9e0 | ||
|
|
9709239f88 | ||
|
|
60d457b285 |
476
Cargo.lock
generated
476
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_cli"
|
||||
version = "0.1.23"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "monitor cli | tools to setup monitor system"
|
||||
|
||||
@@ -178,7 +178,9 @@ 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} --log-opt max-size=15m --log-opt max-file=3 mongo --quiet");
|
||||
let stop = run_command_pipe_to_terminal(&format!("docker stop {name} && docker container rm {name}"));
|
||||
|
||||
let command = format!("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);
|
||||
|
||||
|
||||
@@ -36,19 +36,19 @@ fn cli() -> Command {
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--mongo-uri <URI> "sets the mongo uri to use. default is 'mongodb://monitor-mongo'")
|
||||
arg!(--"mongo-uri" <URI> "sets the mongo uri to use. default is 'mongodb://monitor-mongo'")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--mongo-db-name <NAME> "sets the db name to use. default is 'monitor'")
|
||||
arg!(--"mongo-db-name" <NAME> "sets the db name to use. default is 'monitor'")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--jwt-valid-for <TIMELENGTH> "sets the length of time jwt stays valid for. default is 1-wk (one week)")
|
||||
arg!(--"jwt-valid-for" <TIMELENGTH> "sets the length of time jwt stays valid for. default is 1-wk (one week)")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--slack-url <URL> "sets the slack url to use for slack notifications")
|
||||
arg!(--"slack-url" <URL> "sets the slack url to use for slack notifications")
|
||||
.required(false)
|
||||
),
|
||||
)
|
||||
@@ -96,7 +96,7 @@ fn cli() -> Command {
|
||||
arg!(--name <NAME> "specify the name of the monitor core container. default is monitor-core")
|
||||
)
|
||||
.arg(
|
||||
arg!(--config-path <PATH> "specify the file path to use for config. default is ~/.monitor/core.config.toml")
|
||||
arg!(--"config-path" <PATH> "specify the file path to use for config. default is ~/.monitor/core.config.toml")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
@@ -111,7 +111,7 @@ fn cli() -> Command {
|
||||
arg!(--restart <RESTART> "sets docker restart mode of monitor core container. default is unless-stopped")
|
||||
)
|
||||
.arg(
|
||||
arg!(--add-internal-host "adds the docker flag '--add-host=host.docker.internal:host-gateway'. default is true")
|
||||
arg!(--"add-internal-host" "adds the docker flag '--add-host=host.docker.internal:host-gateway'. default is true")
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -133,15 +133,15 @@ fn cli() -> Command {
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--stats-polling-rate <INTERVAL> "sets stats polling rate to control granularity of system stats returned. default is 5-sec. options: 1-sec, 5-sec, 10-sec, 30-sec, 1-min")
|
||||
arg!(--"stats-polling-rate" <INTERVAL> "sets stats polling rate to control granularity of system stats returned. default is 5-sec. options: 1-sec, 5-sec, 10-sec, 30-sec, 1-min")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--allowed-ips <IPS> "used to only accept requests from known ips. give ips as comma seperated list, like '--allowed_ips 127.0.0.1,10.20.30.43'. default is empty, which will not block any ip.")
|
||||
arg!(--"allowed-ips" <IPS> "used to only accept requests from known ips. give ips as comma seperated list, like '--allowed_ips 127.0.0.1,10.20.30.43'. default is empty, which will not block any ip.")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--repo-dir <PATH> "if running in container, this should be '/repos'. default is ~/.monitor/repos").required(false)
|
||||
arg!(--"repo-dir" <PATH> "if running in container, this should be '/repos'. default is ~/.monitor/repos").required(false)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
@@ -157,7 +157,7 @@ fn cli() -> Command {
|
||||
arg!(--install "specify this to install periphery from crates.io")
|
||||
)
|
||||
.arg(
|
||||
arg!(--config-path <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
arg!(--"config-path" <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
.required(false)
|
||||
)
|
||||
)
|
||||
@@ -171,7 +171,7 @@ fn cli() -> Command {
|
||||
arg!(--install "specify this to install periphery from crates.io")
|
||||
)
|
||||
.arg(
|
||||
arg!(--config-path <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
arg!(--"config-path" <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
@@ -183,32 +183,32 @@ fn cli() -> Command {
|
||||
.required(false)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("container")
|
||||
.about("start up monitor periphery in docker container")
|
||||
.arg(
|
||||
arg!(--yes "used in scripts to skip 'enter to continue' step")
|
||||
)
|
||||
.arg(
|
||||
arg!(--name <NAME> "specify the name of the monitor periphery container. default is monitor-periphery")
|
||||
)
|
||||
.arg(
|
||||
arg!(--config-path <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
.required(false)
|
||||
)
|
||||
.arg(arg!(--repo-dir <PATH> "specify the folder on host to clone repos into. default is ~/.monitor/repos").required(false))
|
||||
.arg(
|
||||
arg!(--port <PORT> "sets port monitor periphery will run on. default is 8000")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--network <NETWORK> "sets docker network of monitor periphery container. default is bridge")
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
arg!(--restart <RESTART> "sets docker restart mode of monitor periphery container. default is unless-stopped")
|
||||
)
|
||||
)
|
||||
// .subcommand(
|
||||
// Command::new("container")
|
||||
// .about("start up monitor periphery in docker container")
|
||||
// .arg(
|
||||
// arg!(--yes "used in scripts to skip 'enter to continue' step")
|
||||
// )
|
||||
// .arg(
|
||||
// arg!(--name <NAME> "specify the name of the monitor periphery container. default is monitor-periphery")
|
||||
// )
|
||||
// .arg(
|
||||
// arg!(--"config-path" <PATH> "specify the file path to use for config. default is ~/.monitor/periphery.config.toml")
|
||||
// .required(false)
|
||||
// )
|
||||
// .arg(arg!(--"repo-dir" <PATH> "specify the folder on host to clone repos into. default is ~/.monitor/repos").required(false))
|
||||
// .arg(
|
||||
// arg!(--port <PORT> "sets port monitor periphery will run on. default is 8000")
|
||||
// .required(false)
|
||||
// )
|
||||
// .arg(
|
||||
// arg!(--network <NETWORK> "sets docker network of monitor periphery container. default is bridge")
|
||||
// .required(false)
|
||||
// )
|
||||
// .arg(
|
||||
// arg!(--restart <RESTART> "sets docker restart mode of monitor periphery container. default is unless-stopped")
|
||||
// )
|
||||
// )
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -239,7 +239,7 @@ fn main() {
|
||||
match periphery_start_command {
|
||||
("systemd", sub_matches) => start_periphery_systemd(sub_matches),
|
||||
("daemon", sub_matches) => start_periphery_daemon(sub_matches),
|
||||
("container", sub_matches) => start_periphery_container(sub_matches),
|
||||
// ("container", sub_matches) => start_periphery_container(sub_matches),
|
||||
_ => println!("\n❌ invalid call, should be 'monitor periphery start <daemon, container> <flags>' ❌\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "core"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -33,6 +33,13 @@ struct ModifyUserCreateServerBody {
|
||||
create_server_permissions: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ModifyUserCreateBuildBody {
|
||||
user_id: String,
|
||||
create_build_permissions: bool,
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
@@ -62,6 +69,15 @@ pub fn router() -> Router {
|
||||
response!(Json(update))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/modify_create_build",
|
||||
post(|state, user, body| async {
|
||||
let update = modify_user_create_build_permissions(state, user, body)
|
||||
.await
|
||||
.map_err(handle_anyhow_error)?;
|
||||
response!(Json(update))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_permissions(
|
||||
@@ -309,3 +325,58 @@ async fn modify_user_create_server_permissions(
|
||||
update.id = state.add_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
async fn modify_user_create_build_permissions(
|
||||
Extension(state): StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Json(ModifyUserCreateBuildBody {
|
||||
user_id,
|
||||
create_build_permissions,
|
||||
}): Json<ModifyUserCreateBuildBody>,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
return Err(anyhow!(
|
||||
"user does not have permissions for this action (not admin)"
|
||||
));
|
||||
}
|
||||
let user = state
|
||||
.db
|
||||
.users
|
||||
.find_one_by_id(&user_id)
|
||||
.await
|
||||
.context("failed at mongo query to find target user")?
|
||||
.ok_or(anyhow!("did not find any user with user_id {user_id}"))?;
|
||||
state
|
||||
.db
|
||||
.users
|
||||
.update_one::<Document>(
|
||||
&user_id,
|
||||
mungos::Update::Set(doc! { "create_build_permissions": create_build_permissions }),
|
||||
)
|
||||
.await?;
|
||||
let update_type = if create_build_permissions {
|
||||
"enabled"
|
||||
} else {
|
||||
"disabled"
|
||||
};
|
||||
let ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: UpdateTarget::System,
|
||||
operation: Operation::ModifyUserCreateBuildPermissions,
|
||||
logs: vec![Log::simple(
|
||||
"modify user create build permissions",
|
||||
format!(
|
||||
"{update_type} create build permissions for {} (id: {})",
|
||||
user.username, user.id
|
||||
),
|
||||
)],
|
||||
start_ts: ts.clone(),
|
||||
end_ts: Some(ts),
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = state.add_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Input from "./shared/Input";
|
||||
import Flex from "./shared/layout/Flex";
|
||||
import Grid from "./shared/layout/Grid";
|
||||
import CenterMenu from "./shared/menu/CenterMenu";
|
||||
import HoverMenu from "./shared/menu/HoverMenu";
|
||||
import Selector from "./shared/menu/Selector";
|
||||
|
||||
const CopyMenu: Component<{
|
||||
@@ -58,47 +59,53 @@ const CopyMenu: Component<{
|
||||
}
|
||||
};
|
||||
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}
|
||||
/>
|
||||
<Show when={p.type === "deployment"}>
|
||||
<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
|
||||
/>
|
||||
</Show>
|
||||
</Flex>
|
||||
<ConfirmButton
|
||||
class="green"
|
||||
style={{ width: "100%" }}
|
||||
onConfirm={copy}
|
||||
>
|
||||
copy {p.type}
|
||||
</ConfirmButton>
|
||||
</Grid>
|
||||
)}
|
||||
position="center"
|
||||
<HoverMenu
|
||||
target={
|
||||
<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}
|
||||
/>
|
||||
<Show when={p.type === "deployment"}>
|
||||
<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
|
||||
/>
|
||||
</Show>
|
||||
</Flex>
|
||||
<ConfirmButton
|
||||
class="green"
|
||||
style={{ width: "100%" }}
|
||||
onConfirm={copy}
|
||||
>
|
||||
copy {p.type}
|
||||
</ConfirmButton>
|
||||
</Grid>
|
||||
)}
|
||||
position="center"
|
||||
/>
|
||||
}
|
||||
content={`copy ${p.type}`}
|
||||
position="bottom center"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,9 @@ const BuilderConfig: Component<{}> = (p) => {
|
||||
<Show when={build.aws_config}>
|
||||
<AwsBuilderConfig />
|
||||
</Show>
|
||||
<Show when={!build.server_id && !build.aws_config}>
|
||||
<div style={{ height: "12rem" }} />
|
||||
</Show>
|
||||
</Grid>
|
||||
<Show when={userCanUpdate() && build.updated}>
|
||||
<Show
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, Show } from "solid-js";
|
||||
import { useAppState } from "../../../../state/StateProvider";
|
||||
import Flex from "../../../shared/layout/Flex";
|
||||
import Grid from "../../../shared/layout/Grid";
|
||||
import Selector from "../../../shared/menu/Selector";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const BuilderType: Component<{}> = (p) => {
|
||||
@@ -24,34 +25,25 @@ const BuilderType: Component<{}> = (p) => {
|
||||
>
|
||||
<h1>builder type</h1>
|
||||
<Show when={userCanUpdate()} fallback={<h2>{builderType()}</h2>}>
|
||||
<Grid gap="0" gridTemplateColumns="1fr 1fr">
|
||||
<button
|
||||
class={builderType() === "server" ? "blue" : "grey"}
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => {
|
||||
if (builderType() !== "server") {
|
||||
<Selector
|
||||
targetClass="blue"
|
||||
selected={builderType() || "select type"}
|
||||
items={["aws", "server"]}
|
||||
position="bottom right"
|
||||
onSelect={(type) => {
|
||||
if (type !== builderType()) {
|
||||
if (type === "server") {
|
||||
const server_id =
|
||||
servers.ids()?.length || 0 > 0
|
||||
? servers.ids()![0]
|
||||
: undefined;
|
||||
setBuild({ server_id, aws_config: undefined });
|
||||
}
|
||||
}}
|
||||
>
|
||||
server
|
||||
</button>
|
||||
<button
|
||||
class={builderType() === "aws" ? "blue" : "grey"}
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => {
|
||||
if (builderType() !== "aws") {
|
||||
} else if (type === "aws") {
|
||||
setBuild({ server_id: undefined, aws_config: {} });
|
||||
}
|
||||
}}
|
||||
>
|
||||
aws
|
||||
</button>
|
||||
</Grid>
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import { Component, createResource, Show } from "solid-js";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { useUser } from "../../state/UserProvider";
|
||||
import {
|
||||
combineClasses,
|
||||
deploymentHeaderStateClass,
|
||||
getId,
|
||||
readableVersion,
|
||||
} from "../../util/helpers";
|
||||
import Icon from "../shared/Icon";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
@@ -20,7 +21,7 @@ import CopyMenu from "../CopyMenu";
|
||||
import ConfirmMenuButton from "../shared/ConfirmMenuButton";
|
||||
|
||||
const Header: Component<{}> = (p) => {
|
||||
const { deployments, servers } = useAppState();
|
||||
const { deployments, servers, builds } = useAppState();
|
||||
const params = useParams();
|
||||
const deployment = () => deployments.get(params.id)!;
|
||||
const { user } = useUser();
|
||||
@@ -37,6 +38,27 @@ const Header: Component<{}> = (p) => {
|
||||
deployment().deployment.permissions![getId(user())] ===
|
||||
PermissionLevel.Update;
|
||||
const server = () => servers.get(deployment().deployment.server_id);
|
||||
const [deployed_version] = createResource(() =>
|
||||
client.get_deployment_deployed_version(params.id)
|
||||
);
|
||||
const image = () => {
|
||||
if (deployment().deployment.build_id) {
|
||||
const build = builds.get(deployment().deployment.build_id!)!;
|
||||
if (deployment().state === DockerContainerState.NotDeployed) {
|
||||
const version = deployment().deployment.build_version
|
||||
? readableVersion(deployment().deployment.build_version!).replaceAll(
|
||||
"v",
|
||||
""
|
||||
)
|
||||
: "latest";
|
||||
return `${build.name}:${version}`;
|
||||
} else {
|
||||
return deployed_version() && `${build.name}:${deployed_version()}`;
|
||||
}
|
||||
} else {
|
||||
return deployment().deployment.docker_run_args.image || "unknown";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
@@ -52,7 +74,10 @@ const Header: Component<{}> = (p) => {
|
||||
}}
|
||||
>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>{deployment()!.deployment.name}</h1>
|
||||
<Flex alignItems="center">
|
||||
<h1>{deployment()!.deployment.name}</h1>
|
||||
<div style={{ opacity: 0.7 }}>{image()}</div>
|
||||
</Flex>
|
||||
<Show when={userCanUpdate()}>
|
||||
<Flex alignItems="center">
|
||||
<CopyMenu type="deployment" id={params.id} />
|
||||
|
||||
@@ -49,9 +49,9 @@ const Config: Component<{}> = () => {
|
||||
</Show>
|
||||
<Network />
|
||||
<Restart />
|
||||
<Env />
|
||||
<Ports />
|
||||
<Mounts />
|
||||
<Env />
|
||||
<ExtraArgs />
|
||||
<PostImage />
|
||||
<Show when={isMobile()}>
|
||||
|
||||
@@ -17,7 +17,7 @@ const Env: Component<{}> = (p) => {
|
||||
<Grid class={combineClasses("config-item shadow")}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>environment</h1>
|
||||
<Flex alignItems="center" gap="0.2rem">
|
||||
<Flex alignItems="center">
|
||||
<Show
|
||||
when={
|
||||
!deployment.docker_run_args.environment ||
|
||||
|
||||
@@ -22,21 +22,11 @@ const ExtraArgs: Component<{}> = (p) => {
|
||||
<Grid class="config-item shadow">
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<h1>extra args</h1>
|
||||
<Flex alignItems="center">
|
||||
<Show
|
||||
when={
|
||||
!deployment.docker_run_args.extra_args ||
|
||||
deployment.docker_run_args.extra_args.length === 0
|
||||
}
|
||||
>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="green" onClick={onAdd}>
|
||||
<Icon type="plus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="green" onClick={onAdd}>
|
||||
<Icon type="plus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<For each={[...deployment.docker_run_args.extra_args!.keys()]}>
|
||||
{(_, index) => (
|
||||
|
||||
@@ -20,6 +20,7 @@ const Network: Component<{}> = (p) => {
|
||||
onSelect={(network) => setDeployment("docker_run_args", { network })}
|
||||
position="bottom right"
|
||||
disabled={!userCanUpdate()}
|
||||
searchStyle={{ width: "100%", "min-width": "12rem" }}
|
||||
useSearch
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -23,21 +23,11 @@ const Volumes: Component<{}> = (p) => {
|
||||
<Grid class={combineClasses("config-item shadow")}>
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<h1>volumes</h1>
|
||||
<Flex alignItems="center">
|
||||
<Show
|
||||
when={
|
||||
!deployment.docker_run_args.volumes ||
|
||||
deployment.docker_run_args.volumes.length === 0
|
||||
}
|
||||
>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="green" onClick={onAdd}>
|
||||
<Icon type="plus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="green" onClick={onAdd}>
|
||||
<Icon type="plus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<For each={deployment.docker_run_args.volumes}>
|
||||
{({ local, container }, index) => (
|
||||
|
||||
@@ -70,7 +70,7 @@ const Log: Component<{
|
||||
const buffer = useBuffer(scrolled, 250);
|
||||
const [poll, togglePoll] = useLocalStorageToggle(
|
||||
"deployment-log-polling",
|
||||
true
|
||||
false
|
||||
);
|
||||
clearInterval(interval);
|
||||
interval = setInterval(() => {
|
||||
|
||||
@@ -134,7 +134,7 @@ const Build: Component<{ id: string }> = (p) => {
|
||||
<Show when={isAwsBuild()}>
|
||||
<div style={{ opacity: 0.7 }}>aws build</div>
|
||||
</Show>
|
||||
<div>{version()}</div>
|
||||
<h2>{version()}</h2>
|
||||
<Show when={!isMobile()}>
|
||||
<div style={{ opacity: 0.7 }}>{lastBuiltAt()}</div>
|
||||
</Show>
|
||||
|
||||
@@ -33,6 +33,7 @@ const Updates: Component<{}> = () => {
|
||||
? setOperation(undefined)
|
||||
: setOperation(o.replaceAll(" ", "_") as Operation)
|
||||
}
|
||||
targetClass="blue"
|
||||
targetStyle={{ padding: "0" }}
|
||||
position="bottom right"
|
||||
searchStyle={{ width: "15rem" }}
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import { Component, createResource, Show } from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { Component, Show } from "solid-js";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { DockerContainerState } from "../../types";
|
||||
import {
|
||||
combineClasses,
|
||||
deploymentStateClass,
|
||||
getId,
|
||||
readableVersion,
|
||||
} from "../../util/helpers";
|
||||
import Circle from "../shared/Circle";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import s from "./serverchildren.module.scss";
|
||||
|
||||
const Deployment: Component<{ id: string }> = (p) => {
|
||||
const { deployments, builds } = useAppState();
|
||||
const deployment = () => deployments.get(p.id)!;
|
||||
const [deployed_version] = createResource(() =>
|
||||
client.get_deployment_deployed_version(p.id)
|
||||
);
|
||||
const image = () => {
|
||||
if (deployment().deployment.build_id) {
|
||||
const build = builds.get(deployment().deployment.build_id!)!;
|
||||
if (deployment().state === DockerContainerState.NotDeployed) {
|
||||
if (deployment().state === DockerContainerState.NotDeployed) {
|
||||
if (deployment().deployment.build_id) {
|
||||
const build = builds.get(deployment().deployment.build_id!);
|
||||
if (build === undefined) return "unknown"
|
||||
const version = deployment().deployment.build_version
|
||||
? readableVersion(deployment().deployment.build_version!).replaceAll(
|
||||
"v",
|
||||
@@ -31,18 +28,20 @@ const Deployment: Component<{ id: string }> = (p) => {
|
||||
: "latest";
|
||||
return `${build.name}:${version}`;
|
||||
} else {
|
||||
return deployed_version() && `${build.name}:${deployed_version()}`;
|
||||
return deployment().deployment.docker_run_args.image || "unknown";
|
||||
}
|
||||
} else {
|
||||
return deployment().deployment.docker_run_args.image || "unknown";
|
||||
return deployment().container?.image || "unknown"
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Show when={deployment()}>
|
||||
<A href={`/deployment/${p.id}`} class={combineClasses(s.DropdownItem)}>
|
||||
<h2>{deployment().deployment.name}</h2>
|
||||
<Flex alignItems="center">
|
||||
<Grid gap="0">
|
||||
<h2>{deployment().deployment.name}</h2>
|
||||
<div style={{ opacity: 0.7 }}>{image()}</div>
|
||||
</Grid>
|
||||
<Flex alignItems="center">
|
||||
<div style={{ opacity: 0.7 }}>{deployments.status(p.id)}</div>
|
||||
<Circle
|
||||
size={1}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
Accessor,
|
||||
Component,
|
||||
createEffect,
|
||||
createSignal,
|
||||
JSX,
|
||||
JSXElement,
|
||||
Show,
|
||||
@@ -27,16 +25,16 @@ const CenterMenu: Component<{
|
||||
style?: JSX.CSSProperties;
|
||||
position?: "top" | "center";
|
||||
}> = (p) => {
|
||||
const [buffer, set] = createSignal(p.show());
|
||||
createEffect(() => {
|
||||
if (p.show()) {
|
||||
set(true);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
set(false);
|
||||
}, 350);
|
||||
}
|
||||
});
|
||||
// const [buffer, set] = createSignal(p.show());
|
||||
// createEffect(() => {
|
||||
// if (p.show()) {
|
||||
// set(true);
|
||||
// } else {
|
||||
// setTimeout(() => {
|
||||
// set(false);
|
||||
// }, 350);
|
||||
// }
|
||||
// });
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -49,7 +47,7 @@ const CenterMenu: Component<{
|
||||
>
|
||||
{p.target}
|
||||
</button>
|
||||
<Show when={buffer()}>
|
||||
<Show when={p.show()}>
|
||||
<Child {...p} show={p.show} toggleShow={p.toggleShow} />
|
||||
</Show>
|
||||
</>
|
||||
@@ -69,7 +67,7 @@ const Child: Component<{
|
||||
useKeyDown("Escape", p.toggleShow);
|
||||
return (
|
||||
<Grid
|
||||
class={combineClasses(s.CenterMenuContainer, p.show() ? s.Enter : s.Exit)}
|
||||
class={combineClasses(s.CenterMenuContainer)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
p.toggleShow();
|
||||
|
||||
@@ -22,18 +22,18 @@ const HoverMenu: Component<{
|
||||
containerStyle?: JSX.CSSProperties;
|
||||
}> = (p) => {
|
||||
const [show, set] = createSignal(false);
|
||||
const [buffer, setBuffer] = createSignal(false);
|
||||
let timeout: number;
|
||||
createEffect(() => {
|
||||
clearTimeout(timeout);
|
||||
if (show()) {
|
||||
setBuffer(true);
|
||||
} else {
|
||||
timeout = setTimeout(() => {
|
||||
setBuffer(false);
|
||||
}, 350);
|
||||
}
|
||||
});
|
||||
// const [buffer, setBuffer] = createSignal(false);
|
||||
// let timeout: number;
|
||||
// createEffect(() => {
|
||||
// clearTimeout(timeout);
|
||||
// if (show()) {
|
||||
// setBuffer(true);
|
||||
// } else {
|
||||
// timeout = setTimeout(() => {
|
||||
// setBuffer(false);
|
||||
// }, 350);
|
||||
// }
|
||||
// });
|
||||
return (
|
||||
<Flex
|
||||
class={s.HoverMenuTarget}
|
||||
@@ -44,13 +44,13 @@ const HoverMenu: Component<{
|
||||
alignItems="center"
|
||||
>
|
||||
{p.target}
|
||||
<Show when={buffer()}>
|
||||
<Show when={show()}>
|
||||
<div
|
||||
class={combineClasses(
|
||||
p.contentClass,
|
||||
getPositionClass(p.position),
|
||||
s.HoverMenu,
|
||||
show() ? s.Enter : s.Exit,
|
||||
// show() ? s.Enter : s.Exit,
|
||||
)}
|
||||
onMouseOut={() => {
|
||||
set(false);
|
||||
@@ -59,7 +59,7 @@ const HoverMenu: Component<{
|
||||
set(false)
|
||||
e.stopPropagation();
|
||||
}}
|
||||
style={{ ...p.contentStyle, padding: p.padding }}
|
||||
style={{ ...p.contentStyle, padding: p.padding || "0.5rem" }}
|
||||
>
|
||||
{p.content}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createSignal,
|
||||
JSX,
|
||||
JSXElement,
|
||||
Show,
|
||||
@@ -22,20 +20,20 @@ const Menu: Component<{
|
||||
containerStyle?: JSX.CSSProperties;
|
||||
backgroundColor?: string;
|
||||
}> = (p) => {
|
||||
const [buffer, set] = createSignal(p.show);
|
||||
createEffect(() => {
|
||||
if (p.show) {
|
||||
set(true);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
set(false);
|
||||
}, 350);
|
||||
}
|
||||
});
|
||||
// const [buffer, set] = createSignal(p.show);
|
||||
// createEffect(() => {
|
||||
// if (p.show) {
|
||||
// set(true);
|
||||
// } else {
|
||||
// setTimeout(() => {
|
||||
// set(false);
|
||||
// }, 350);
|
||||
// }
|
||||
// });
|
||||
return (
|
||||
<div class={s.MenuContainer} style={p.containerStyle}>
|
||||
{p.target}
|
||||
<Show when={buffer()}>
|
||||
<Show when={p.show}>
|
||||
<div
|
||||
class={s.MenuBackground}
|
||||
style={{ "background-color": p.backgroundColor }}
|
||||
@@ -47,7 +45,7 @@ const Menu: Component<{
|
||||
s.Menu,
|
||||
"shadow",
|
||||
getPositionClass(p.position),
|
||||
p.show ? s.Enter : s.Exit
|
||||
// p.show ? s.Enter : s.Exit
|
||||
)}
|
||||
style={{ padding: p.padding as any, ...p.menuStyle }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
|
||||
@@ -16,7 +16,6 @@ const Account: Component<{ close: () => void }> = (p) => {
|
||||
<Show when={isMobile()}>
|
||||
<Flex justifyContent="center">{user().username}</Flex>
|
||||
</Show>
|
||||
<Flex justifyContent="center">admin: {user().admin.toString()}</Flex>
|
||||
<Show when={user().admin}>
|
||||
<A
|
||||
href="/users"
|
||||
@@ -27,12 +26,12 @@ const Account: Component<{ close: () => void }> = (p) => {
|
||||
manage users
|
||||
</A>
|
||||
</Show>
|
||||
<Show when={!user().admin}>
|
||||
{/* <Show when={!user().admin}>
|
||||
<Flex justifyContent="center">
|
||||
create server permissions:{" "}
|
||||
{user().create_server_permissions.toString()}
|
||||
{user().create_server_permissions?.toString()}
|
||||
</Flex>
|
||||
</Show>
|
||||
</Show> */}
|
||||
<A
|
||||
href="/account"
|
||||
class="grey"
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ControlledTabs } from "../../shared/tabs/Tabs";
|
||||
import { useAppDimensions } from "../../../state/DimensionProvider";
|
||||
import Grid from "../../shared/layout/Grid";
|
||||
import { A, useNavigate } from "@solidjs/router";
|
||||
import { ServerStatus } from "../../../types";
|
||||
import { Build, ServerStatus } from "../../../types";
|
||||
|
||||
const mobileStyle: JSX.CSSProperties = {
|
||||
// position: "fixed",
|
||||
@@ -191,8 +191,10 @@ const Builds: Component<{ close: () => void }> = (p) => {
|
||||
gap="0.2rem"
|
||||
style={{ opacity: 0.6, "font-size": "0.9rem" }}
|
||||
>
|
||||
{servers.get(build.server_id)?.server.name}
|
||||
<Icon type="caret-right" width="0.7rem" />
|
||||
<Show when={build.server_id}>
|
||||
{build.server_id && servers.get(build.server_id)?.server.name}
|
||||
<Icon type="caret-right" width="0.7rem" />
|
||||
</Show>
|
||||
build
|
||||
</Flex>
|
||||
</Grid>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { useAppDimensions } from "../../state/DimensionProvider";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { Operation } from "../../types";
|
||||
import { combineClasses, getId } from "../../util/helpers";
|
||||
@@ -18,9 +19,19 @@ import Loading from "../shared/loading/Loading";
|
||||
import s from "./users.module.scss";
|
||||
|
||||
const Users: Component<{}> = (p) => {
|
||||
const { isMobile } = useAppDimensions();
|
||||
const { ws } = useAppState();
|
||||
const [users, { refetch }] = createResource(() => client.list_users());
|
||||
onCleanup(ws.subscribe([Operation.ModifyUserEnabled], refetch));
|
||||
onCleanup(
|
||||
ws.subscribe(
|
||||
[
|
||||
Operation.ModifyUserEnabled,
|
||||
Operation.ModifyUserCreateServerPermissions,
|
||||
Operation.ModifyUserCreateBuildPermissions,
|
||||
],
|
||||
refetch
|
||||
)
|
||||
);
|
||||
const [search, setSearch] = createSignal("");
|
||||
const filteredUsers = createMemo(() =>
|
||||
users()?.filter((user) => user.username.includes(search()))
|
||||
@@ -34,55 +45,78 @@ const Users: Component<{}> = (p) => {
|
||||
</Grid>
|
||||
}
|
||||
>
|
||||
<Grid class={s.UsersContent}>
|
||||
<Grid class={combineClasses(s.Users, "card shadow")}>
|
||||
<Flex justifyContent="space-between">
|
||||
<h1>users</h1>
|
||||
<Input
|
||||
class="lightgrey"
|
||||
placeholder="search"
|
||||
value={search()}
|
||||
onEdit={setSearch}
|
||||
/>
|
||||
</Flex>
|
||||
<For each={filteredUsers()}>
|
||||
{(user) => (
|
||||
<Flex class={combineClasses(s.User, "shadow")}>
|
||||
<div class={s.Username}>{user.username}</div>
|
||||
<Flex alignItems="center">
|
||||
<button
|
||||
class={user.enabled ? "green" : "red"}
|
||||
onClick={() => {
|
||||
client.modify_user_enabled({
|
||||
user_id: getId(user),
|
||||
enabled: !user.enabled,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.enabled ? "enabled" : "disabled"}
|
||||
</button>
|
||||
<button
|
||||
class={user.create_server_permissions ? "green" : "red"}
|
||||
onClick={() => {
|
||||
client.modify_user_create_server_permissions({
|
||||
user_id: getId(user),
|
||||
create_server_permissions: !user.create_server_permissions,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.create_server_permissions ? "can create servers" : "cannot create servers"}
|
||||
</button>
|
||||
{/* <ConfirmButton
|
||||
<Grid
|
||||
class="card shadow"
|
||||
style={{ width: "100%", "box-sizing": "border-box" }}
|
||||
>
|
||||
<Flex justifyContent="space-between">
|
||||
<h1>users</h1>
|
||||
<Input
|
||||
class="lightgrey"
|
||||
placeholder="search"
|
||||
value={search()}
|
||||
onEdit={setSearch}
|
||||
/>
|
||||
</Flex>
|
||||
<For each={filteredUsers()}>
|
||||
{(user) => (
|
||||
<Flex class={combineClasses(s.User, "shadow")}>
|
||||
<div class={s.Username}>{user.username}</div>
|
||||
<Grid
|
||||
placeItems="center end"
|
||||
gridTemplateColumns={!isMobile() ? "1fr 1fr 1fr" : undefined}
|
||||
>
|
||||
<button
|
||||
class={user.enabled ? "green" : "red"}
|
||||
style={{ width: isMobile() ? "11rem" : "6rem" }}
|
||||
onClick={() => {
|
||||
client.modify_user_enabled({
|
||||
user_id: getId(user),
|
||||
enabled: !user.enabled,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.enabled ? "enabled" : "disabled"}
|
||||
</button>
|
||||
<button
|
||||
class={user.create_server_permissions ? "green" : "red"}
|
||||
style={{ width: "11rem" }}
|
||||
onClick={() => {
|
||||
client.modify_user_create_server_permissions({
|
||||
user_id: getId(user),
|
||||
create_server_permissions:
|
||||
!user.create_server_permissions,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.create_server_permissions
|
||||
? "can create servers"
|
||||
: "cannot create servers"}
|
||||
</button>
|
||||
<button
|
||||
class={user.create_build_permissions ? "green" : "red"}
|
||||
style={{ width: "11rem" }}
|
||||
onClick={() => {
|
||||
client.modify_user_create_build_permissions({
|
||||
user_id: getId(user),
|
||||
create_build_permissions: !user.create_build_permissions,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{user.create_build_permissions
|
||||
? "can create builds"
|
||||
: "cannot create builds"}
|
||||
</button>
|
||||
{/* <ConfirmButton
|
||||
class="red"
|
||||
onConfirm={() => deleteUser(user._id!)}
|
||||
>
|
||||
<Icon type="trash" />
|
||||
</ConfirmButton> */}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Flex>
|
||||
)}
|
||||
</For>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
|
||||
@@ -140,6 +140,7 @@ export interface DockerRunArgs {
|
||||
export interface BasicContainerInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
image: string;
|
||||
state: DockerContainerState;
|
||||
status?: string;
|
||||
}
|
||||
@@ -432,6 +433,7 @@ export enum Operation {
|
||||
DeleteGroup = "delete_group",
|
||||
ModifyUserEnabled = "modify_user_enabled",
|
||||
ModifyUserCreateServerPermissions = "modify_user_create_server_permissions",
|
||||
ModifyUserCreateBuildPermissions = "modify_user_create_build_permissions",
|
||||
ModifyUserPermissions = "modify_user_permissions",
|
||||
AutoBuild = "auto_build",
|
||||
AutoPull = "auto_pull",
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
CreateSecretBody,
|
||||
CreateServerBody,
|
||||
LoginOptions,
|
||||
ModifyUserCreateBuildBody,
|
||||
ModifyUserCreateServerBody,
|
||||
ModifyUserEnabledBody,
|
||||
PermissionsUpdateBody,
|
||||
@@ -463,6 +464,12 @@ export class Client {
|
||||
return this.post("/api/permissions/modify_create_server", body);
|
||||
}
|
||||
|
||||
modify_user_create_build_permissions(
|
||||
body: ModifyUserCreateBuildBody
|
||||
): Promise<Update> {
|
||||
return this.post("/api/permissions/modify_create_build", body);
|
||||
}
|
||||
|
||||
async get<R = any>(url: string): Promise<R> {
|
||||
return await axios({
|
||||
method: "get",
|
||||
|
||||
@@ -54,6 +54,11 @@ export interface ModifyUserCreateServerBody {
|
||||
create_server_permissions: boolean;
|
||||
}
|
||||
|
||||
export interface ModifyUserCreateBuildBody {
|
||||
user_id: string;
|
||||
create_build_permissions: boolean;
|
||||
}
|
||||
|
||||
export interface CreateProcedureBody {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "db_client"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_helpers"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "helpers used as dependency for mogh tech monitor"
|
||||
@@ -13,7 +13,7 @@ tokio = "1.25"
|
||||
types = { package = "monitor_types", path = "../types" }
|
||||
periphery_client = { path = "../periphery_client" }
|
||||
async_timing_util = "0.1.14"
|
||||
bollard = "0.13"
|
||||
bollard = "0.14.0"
|
||||
anyhow = "1.0"
|
||||
axum = { version = "0.6", features = ["ws", "json"] }
|
||||
serde = "1.0"
|
||||
|
||||
@@ -45,6 +45,7 @@ impl DockerClient {
|
||||
.pop()
|
||||
.ok_or(anyhow!("no names on container (empty vec)"))?
|
||||
.replace("/", ""),
|
||||
image: s.image.unwrap_or(String::from("unknown")),
|
||||
state: s.state.unwrap().parse().unwrap(),
|
||||
status: s.status,
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ async fn clone(
|
||||
access_token: Option<GithubToken>,
|
||||
) -> Log {
|
||||
let _ = std::fs::remove_dir_all(destination);
|
||||
let access_token = match access_token {
|
||||
let access_token_at = match &access_token {
|
||||
Some(token) => format!("{token}@"),
|
||||
None => String::new(),
|
||||
};
|
||||
@@ -91,12 +91,12 @@ async fn clone(
|
||||
Some(branch) => format!(" -b {branch}"),
|
||||
None => String::new(),
|
||||
};
|
||||
let repo_url = format!("https://{access_token}github.com/{repo}.git");
|
||||
let repo_url = format!("https://{access_token_at}github.com/{repo}.git");
|
||||
let command = format!("git clone {repo_url} {destination}{branch}");
|
||||
let start_ts = monitor_timestamp();
|
||||
let output = async_run_command(&command).await;
|
||||
let command = if access_token.len() > 0 {
|
||||
command.replace(&access_token, "<TOKEN>")
|
||||
let command = if access_token_at.len() > 0 {
|
||||
command.replace(&access_token.unwrap(), "<TOKEN>")
|
||||
} else {
|
||||
command
|
||||
};
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
use std::{fs::File, io::Read, net::SocketAddr, str::FromStr};
|
||||
use std::{borrow::Borrow, fs::File, io::Read, net::SocketAddr, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::http::StatusCode;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use run_command::{async_run_command, CommandOutput};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Map, Value};
|
||||
use types::{monitor_timestamp, Log};
|
||||
|
||||
pub mod aws;
|
||||
pub mod docker;
|
||||
pub mod git;
|
||||
|
||||
pub fn parse_config_files<'a, T: DeserializeOwned>(
|
||||
paths: impl IntoIterator<Item = impl Borrow<String>>,
|
||||
merge_nested: bool,
|
||||
extend_array: bool,
|
||||
) -> anyhow::Result<T> {
|
||||
let mut target = Map::new();
|
||||
for path in paths {
|
||||
target = merge_objects(
|
||||
target,
|
||||
parse_config_file(path.borrow())?,
|
||||
merge_nested,
|
||||
extend_array,
|
||||
)?;
|
||||
}
|
||||
serde_json::from_str(&serde_json::to_string(&target)?)
|
||||
.context("failed to parse final config into expected type")
|
||||
}
|
||||
|
||||
pub fn parse_config_file<T: DeserializeOwned>(path: &str) -> anyhow::Result<T> {
|
||||
let mut file = File::open(&path).expect(&format!("failed to find config at {path}"));
|
||||
let config = if path.ends_with("toml") {
|
||||
@@ -26,6 +45,90 @@ pub fn parse_config_file<T: DeserializeOwned>(path: &str) -> anyhow::Result<T> {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// object is serde_json::Map<String, serde_json::Value>
|
||||
/// source will overide target
|
||||
/// will recurse when field is object if merge_object = true, otherwise object will be replaced
|
||||
/// will extend when field is array if extend_array = true, otherwise array will be replaced
|
||||
/// will return error when types on source and target fields do not match
|
||||
fn merge_objects(
|
||||
mut target: Map<String, Value>,
|
||||
source: Map<String, Value>,
|
||||
merge_nested: bool,
|
||||
extend_array: bool,
|
||||
) -> anyhow::Result<Map<String, Value>> {
|
||||
for (key, value) in source {
|
||||
let curr = target.remove(&key);
|
||||
if curr.is_none() {
|
||||
target.insert(key, value);
|
||||
continue;
|
||||
}
|
||||
let curr = curr.unwrap();
|
||||
match curr {
|
||||
Value::Object(target_obj) => {
|
||||
if !merge_nested {
|
||||
target.insert(key, value);
|
||||
continue;
|
||||
}
|
||||
match value {
|
||||
Value::Object(source_obj) => {
|
||||
target.insert(
|
||||
key,
|
||||
Value::Object(merge_objects(
|
||||
target_obj,
|
||||
source_obj,
|
||||
merge_nested,
|
||||
extend_array,
|
||||
)?),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"types on field {key} do not match. got {value:?}, expected object"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(mut target_arr) => {
|
||||
if !extend_array {
|
||||
target.insert(key, value);
|
||||
continue;
|
||||
}
|
||||
match value {
|
||||
Value::Array(source_arr) => {
|
||||
target_arr.extend(source_arr);
|
||||
target.insert(key, Value::Array(target_arr));
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"types on field {key} do not match. got {value:?}, expected array"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
target.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
pub fn parse_comma_seperated_list<T: FromStr>(
|
||||
comma_sep_list: impl Borrow<str>,
|
||||
) -> anyhow::Result<Vec<T>> {
|
||||
comma_sep_list
|
||||
.borrow()
|
||||
.split(",")
|
||||
.filter(|item| item.len() > 0)
|
||||
.map(|item| {
|
||||
let item = item
|
||||
.parse()
|
||||
.map_err(|_| anyhow!("error parsing string {item} into type T"))?;
|
||||
Ok::<T, anyhow::Error>(item)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn output_into_log(
|
||||
stage: &str,
|
||||
command: String,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_client"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "a client to interact with the monitor system"
|
||||
@@ -9,7 +9,7 @@ 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.17"
|
||||
monitor_types = "0.2.1"
|
||||
# monitor_types = { path = "../types" }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "periphery_client"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_types"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "types for the mogh tech monitor"
|
||||
@@ -15,7 +15,7 @@ bson = "2.4"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
diff-struct = "0.5"
|
||||
bollard = "0.13"
|
||||
bollard = "0.14.0"
|
||||
derive_builder = "0.12"
|
||||
typeshare = "1.0.0"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -176,6 +176,7 @@ fn default_network() -> String {
|
||||
pub struct BasicContainerInfo {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
pub image: String,
|
||||
pub state: DockerContainerState,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ pub enum Operation {
|
||||
// user
|
||||
ModifyUserEnabled,
|
||||
ModifyUserCreateServerPermissions,
|
||||
ModifyUserCreateBuildPermissions,
|
||||
ModifyUserPermissions,
|
||||
|
||||
// github webhook automation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_periphery"
|
||||
version = "0.1.17"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "monitor periphery binary | run monitor periphery as system daemon"
|
||||
@@ -24,12 +24,12 @@ dotenv = "0.15"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
bollard = "0.13"
|
||||
bollard = "0.14.0"
|
||||
anyhow = "1.0"
|
||||
envy = "0.4"
|
||||
sysinfo = "0.28"
|
||||
toml = "0.7"
|
||||
daemonize = "0.4"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
daemonize = "0.5.0"
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
futures-util = "0.3"
|
||||
tokio-util = "0.7"
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use axum::Extension;
|
||||
use clap::Parser;
|
||||
use dotenv::dotenv;
|
||||
use helpers::parse_config_file;
|
||||
use helpers::{parse_comma_seperated_list, parse_config_files};
|
||||
use serde::Deserialize;
|
||||
use types::PeripheryConfig;
|
||||
|
||||
@@ -25,9 +25,12 @@ pub struct Args {
|
||||
#[arg(long, default_value = "~/.monitor/periphery.log.err")]
|
||||
pub stderr: String,
|
||||
|
||||
/// Sets the path of config file to use
|
||||
/// Sets the path of a config file to use. can use multiple times
|
||||
#[arg(short, long)]
|
||||
pub config_path: Option<String>,
|
||||
pub config_path: Option<Vec<String>>,
|
||||
|
||||
#[arg(short, long)]
|
||||
pub merge_nested_config: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
pub home_dir: Option<String>,
|
||||
@@ -39,7 +42,7 @@ pub struct Args {
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
#[serde(default = "default_config_path")]
|
||||
config_path: String,
|
||||
config_paths: String,
|
||||
}
|
||||
|
||||
pub fn load() -> (Args, u16, PeripheryConfigExtension, HomeDirExtension) {
|
||||
@@ -51,15 +54,24 @@ pub fn load() -> (Args, u16, PeripheryConfigExtension, HomeDirExtension) {
|
||||
std::process::exit(0)
|
||||
}
|
||||
let home_dir = get_home_dir(&args.home_dir);
|
||||
let config_path = args
|
||||
let config_paths = args
|
||||
.config_path
|
||||
.as_ref()
|
||||
.unwrap_or(&env.config_path)
|
||||
.replace("~", &home_dir);
|
||||
let config =
|
||||
parse_config_file::<PeripheryConfig>(&config_path).expect("failed to parse config file");
|
||||
.unwrap_or(
|
||||
&parse_comma_seperated_list(env.config_paths)
|
||||
.expect("failed to parse config paths on environment into comma seperated list"),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|p| p.replace("~", &home_dir))
|
||||
.collect();
|
||||
let config = parse_config_files::<PeripheryConfig>(
|
||||
&config_paths,
|
||||
args.merge_nested_config,
|
||||
args.merge_nested_config,
|
||||
)
|
||||
.expect("failed at parsing config");
|
||||
let _ = std::fs::create_dir(&config.repo_dir);
|
||||
print_startup_log(&config_path, &args, &config);
|
||||
print_startup_log(config_paths, &args, &config);
|
||||
(
|
||||
args,
|
||||
config.port,
|
||||
@@ -68,8 +80,8 @@ pub fn load() -> (Args, u16, PeripheryConfigExtension, HomeDirExtension) {
|
||||
)
|
||||
}
|
||||
|
||||
fn print_startup_log(config_path: &str, args: &Args, config: &PeripheryConfig) {
|
||||
println!("\nconfig path: {config_path}");
|
||||
fn print_startup_log(config_paths: Vec<String>, args: &Args, config: &PeripheryConfig) {
|
||||
println!("\nconfig paths: {config_paths:?}");
|
||||
let mut config = config.clone();
|
||||
config.github_accounts = config
|
||||
.github_accounts
|
||||
@@ -94,7 +106,7 @@ fn print_startup_log(config_path: &str, args: &Args, config: &PeripheryConfig) {
|
||||
}
|
||||
|
||||
fn default_config_path() -> String {
|
||||
"/config/periphery.config.toml".to_string()
|
||||
"~/.monitor/periphery.config.toml".to_string()
|
||||
}
|
||||
|
||||
fn get_home_dir(home_dir_arg: &Option<String>) -> String {
|
||||
|
||||
Reference in New Issue
Block a user