mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-18 14:40:42 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6559814b1 | ||
|
|
c8c080183f | ||
|
|
597b67f799 | ||
|
|
ec52d5f422 | ||
|
|
34806304d6 | ||
|
|
87953d5495 | ||
|
|
b6c7c80c95 | ||
|
|
77e568d5c3 | ||
|
|
699fc51cf7 | ||
|
|
21029c90b7 | ||
|
|
6b0530eb7f | ||
|
|
f7061c7225 | ||
|
|
750f698369 | ||
|
|
ec5ef42298 | ||
|
|
46820b0044 | ||
|
|
425a6648f7 |
8
.vscode/tasks.json
vendored
8
.vscode/tasks.json
vendored
@@ -100,14 +100,6 @@
|
||||
"cwd": "${workspaceFolder}/lib/monitor_client"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "publish",
|
||||
"label": "publish monitor cli",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "docker compose up -d",
|
||||
|
||||
34
Cargo.lock
generated
34
Cargo.lock
generated
@@ -740,7 +740,7 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "core"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -759,7 +759,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"jwt",
|
||||
"monitor_helpers",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"mungos",
|
||||
"periphery_client",
|
||||
"serde",
|
||||
@@ -993,10 +993,10 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
|
||||
|
||||
[[package]]
|
||||
name = "db_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"mungos",
|
||||
]
|
||||
|
||||
@@ -1857,12 +1857,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"async_timing_util",
|
||||
"clap",
|
||||
"colored",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"rand",
|
||||
"run_command",
|
||||
"serde",
|
||||
@@ -1874,12 +1874,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"envy",
|
||||
"futures-util",
|
||||
"monitor_types 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"monitor_types 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -1891,11 +1891,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_helpers"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1904,7 +1904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -1916,7 +1916,7 @@ dependencies = [
|
||||
"envy",
|
||||
"futures",
|
||||
"monitor_helpers",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"run_command",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -1930,7 +1930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_types"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bollard",
|
||||
@@ -1947,9 +1947,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_types"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bf35db6341431dea9f062f5d676305a213834638410fb9cdc49ca2521635c43"
|
||||
checksum = "b2b2809cdf9e2c1f1faa0093e6da57e6e4d5833f7dd492df490cc4c66f73a383"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bollard",
|
||||
@@ -2196,11 +2196,11 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
"monitor_types 0.2.10",
|
||||
"monitor_types 0.2.11",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_cli"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "monitor cli | tools to setup monitor system"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "core"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
state::{State, StateExtension},
|
||||
};
|
||||
|
||||
const NUM_UPDATES_PER_PAGE: usize = 10;
|
||||
const NUM_UPDATES_PER_PAGE: usize = 20;
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new().route(
|
||||
|
||||
@@ -11,6 +11,7 @@ const Users = lazy(() => import("./components/users/Users"));
|
||||
const User = lazy(() => import("./components/users/User"));
|
||||
const Stats = lazy(() => import("./components/stats/Stats"));
|
||||
const Account = lazy(() => import("./components/account/Account"));
|
||||
const Updates = lazy(() => import("./components/Updates"));
|
||||
|
||||
const App: Component = () => {
|
||||
const { user } = useUser();
|
||||
@@ -19,6 +20,7 @@ const App: Component = () => {
|
||||
<Topbar />
|
||||
<Routes>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/updates" component={Updates} />
|
||||
<Route path="/build/:id" component={Build} />
|
||||
<Route path="/deployment/:id" component={Deployment} />
|
||||
<Route path="/server/:id" component={Server} />
|
||||
|
||||
@@ -69,10 +69,10 @@ const CopyMenu: Component<{
|
||||
targetClass="blue"
|
||||
content={() => (
|
||||
<Grid placeItems="center">
|
||||
<Flex alignItems="center">
|
||||
<Flex class="full-width" alignItems="center">
|
||||
<Input
|
||||
placeholder="copy name"
|
||||
class="card dark"
|
||||
class="card dark full-width"
|
||||
style={{ padding: "0.5rem" }}
|
||||
value={newName()}
|
||||
onEdit={setNewName}
|
||||
@@ -87,6 +87,8 @@ const CopyMenu: Component<{
|
||||
targetClass="blue"
|
||||
targetStyle={{ display: "flex", gap: "0.5rem" }}
|
||||
searchStyle={{ width: "100%" }}
|
||||
menuClass="scroller"
|
||||
menuStyle={{ "max-height": "40vh" }}
|
||||
position="bottom right"
|
||||
useSearch
|
||||
/>
|
||||
|
||||
157
frontend/src/components/Updates.tsx
Normal file
157
frontend/src/components/Updates.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
For,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { OPERATIONS } from "..";
|
||||
import { useAppDimensions } from "../state/DimensionProvider";
|
||||
import { useAppState } from "../state/StateProvider";
|
||||
import { Operation, Update as UpdateType, UpdateStatus } from "../types";
|
||||
import { readableMonitorTimestamp, readableVersion } from "../util/helpers";
|
||||
import Icon from "./shared/Icon";
|
||||
import Input from "./shared/Input";
|
||||
import Flex from "./shared/layout/Flex";
|
||||
import Grid from "./shared/layout/Grid";
|
||||
import Loading from "./shared/loading/Loading";
|
||||
import Selector from "./shared/menu/Selector";
|
||||
import UpdateMenu from "./update/UpdateMenu";
|
||||
|
||||
const Updates: Component<{}> = (p) => {
|
||||
const { isMobile } = useAppDimensions();
|
||||
const { updates, usernames, name_from_update_target } = useAppState();
|
||||
const [operation, setOperation] = createSignal<Operation>();
|
||||
createEffect(() => {
|
||||
if (operation()) {
|
||||
updates.load([operation()!]);
|
||||
} else {
|
||||
updates.load();
|
||||
}
|
||||
});
|
||||
const [search, setSearch] = createSignal("");
|
||||
const filtered_updates = createMemo(() => {
|
||||
return updates.collection()?.filter((u) => {
|
||||
const name = name_from_update_target(u.target);
|
||||
if (name.includes(search())) return true;
|
||||
const username = usernames.get(u.operator);
|
||||
if (username?.includes(search())) return true;
|
||||
});
|
||||
});
|
||||
return (
|
||||
<Grid class="full-width card shadow">
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>updates</h1>
|
||||
<Flex alignItems="center">
|
||||
<Input class="lightgrey" placeholder="search" onEdit={setSearch} />
|
||||
<Selector
|
||||
label={isMobile() ? undefined : "operation: "}
|
||||
selected={operation() ? operation()! : "all"}
|
||||
items={["all", ...OPERATIONS]}
|
||||
onSelect={(o) =>
|
||||
o === "all"
|
||||
? setOperation(undefined)
|
||||
: setOperation(o.replaceAll(" ", "_") as Operation)
|
||||
}
|
||||
targetClass="blue"
|
||||
position="bottom right"
|
||||
searchStyle={{ width: "15rem" }}
|
||||
menuClass="scroller"
|
||||
menuStyle={{ "max-height": "50vh" }}
|
||||
useSearch
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Show
|
||||
when={updates.loaded()}
|
||||
fallback={
|
||||
<Flex justifyContent="center">
|
||||
<Loading type="three-dot" />
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<For each={filtered_updates()}>
|
||||
{(update) => <Update update={update} />}
|
||||
</For>
|
||||
<Show when={!updates.noMore()}>
|
||||
<button
|
||||
class="grey full-width"
|
||||
onClick={() =>
|
||||
operation()
|
||||
? updates.loadMore([operation()!])
|
||||
: updates.loadMore()
|
||||
}
|
||||
>
|
||||
load more
|
||||
</button>
|
||||
</Show>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Updates;
|
||||
|
||||
const Update: Component<{ update: UpdateType }> = (p) => {
|
||||
const { isMobile } = useAppDimensions();
|
||||
const { usernames, name_from_update_target } = useAppState();
|
||||
const name = () => name_from_update_target(p.update.target);
|
||||
const operation = () => {
|
||||
if (p.update.operation === Operation.BuildBuild) {
|
||||
return `build ${readableVersion(p.update.version!)}`;
|
||||
}
|
||||
return `${p.update.operation.replaceAll("_", " ")}${
|
||||
p.update.version ? " " + readableVersion(p.update.version) : ""
|
||||
}`;
|
||||
};
|
||||
const link_to = () => {
|
||||
return p.update.target.type === "System"
|
||||
? "/"
|
||||
: `/${p.update.target.type.toLowerCase()}/${p.update.target.id}`;
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
class="card light shadow wrap"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
style={{ width: isMobile() ? "100%" : undefined }}
|
||||
>
|
||||
<A style={{ padding: 0 }} href={link_to()}>
|
||||
<h2 class="text-hover">{name()}</h2>
|
||||
</A>
|
||||
<div
|
||||
style={{
|
||||
color: !p.update.success ? "rgb(182, 47, 52)" : "inherit",
|
||||
}}
|
||||
>
|
||||
{operation()}
|
||||
</div>
|
||||
<Show when={p.update.status === UpdateStatus.InProgress}>
|
||||
<div style={{ opacity: 0.7 }}>(in progress)</div>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
style={{ width: isMobile() ? "100%" : undefined }}
|
||||
>
|
||||
<Flex gap="0.5rem">
|
||||
<Icon type="user" />
|
||||
<div>{usernames.get(p.update.operator)}</div>
|
||||
</Flex>
|
||||
<Flex alignItems="center">
|
||||
<div style={{ "place-self": "center end" }}>
|
||||
{readableMonitorTimestamp(p.update.start_ts)}
|
||||
</div>
|
||||
<UpdateMenu update={p.update} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,8 @@ import BuildArgs from "./BuildArgs";
|
||||
import Version from "./Version";
|
||||
import Repo from "./Repo";
|
||||
import WebhookUrl from "./WebhookUrl";
|
||||
import ExtraArgs from "./ExtraArgs";
|
||||
import UseBuildx from "./UseBuildx";
|
||||
|
||||
const BuildConfig: Component<{}> = (p) => {
|
||||
const { build, reset, save, userCanUpdate } = useConfig();
|
||||
@@ -23,6 +25,8 @@ const BuildConfig: Component<{}> = (p) => {
|
||||
<Docker />
|
||||
<CliBuild />
|
||||
<BuildArgs />
|
||||
<ExtraArgs />
|
||||
<UseBuildx />
|
||||
<Show when={userCanUpdate()}>
|
||||
<WebhookUrl />
|
||||
</Show>
|
||||
|
||||
59
frontend/src/components/build/tabs/config/ExtraArgs.tsx
Normal file
59
frontend/src/components/build/tabs/config/ExtraArgs.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import Icon from "../../../shared/Icon";
|
||||
import Input from "../../../shared/Input";
|
||||
import Flex from "../../../shared/layout/Flex";
|
||||
import Grid from "../../../shared/layout/Grid";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const ExtraArgs: Component<{}> = (p) => {
|
||||
const { build, setBuild, userCanUpdate } = useConfig();
|
||||
const onAdd = () => {
|
||||
setBuild("docker_build_args", "extra_args", (extra_args: any) => [
|
||||
...extra_args,
|
||||
"",
|
||||
]);
|
||||
};
|
||||
const onRemove = (index: number) => {
|
||||
setBuild("docker_build_args", "extra_args", (extra_args) =>
|
||||
extra_args!.filter((_, i) => i !== index)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Grid class="config-item shadow">
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<h1>extra args</h1>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="green" onClick={onAdd}>
|
||||
<Icon type="plus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<For each={[...build.docker_build_args!.extra_args!.keys()]}>
|
||||
{(_, index) => (
|
||||
<Flex
|
||||
justifyContent={userCanUpdate() ? "space-between" : undefined}
|
||||
alignItems="center"
|
||||
style={{ "flex-wrap": "wrap" }}
|
||||
>
|
||||
<Input
|
||||
placeholder="--extra-arg=value"
|
||||
value={build.docker_build_args!.extra_args![index()]}
|
||||
style={{ width: "80%" }}
|
||||
onEdit={(value) =>
|
||||
setBuild("docker_build_args", "extra_args", index(), value)
|
||||
}
|
||||
disabled={!userCanUpdate()}
|
||||
/>
|
||||
<Show when={userCanUpdate()}>
|
||||
<button class="red" onClick={() => onRemove(index())}>
|
||||
<Icon type="minus" />
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
)}
|
||||
</For>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtraArgs;
|
||||
30
frontend/src/components/build/tabs/config/UseBuildx.tsx
Normal file
30
frontend/src/components/build/tabs/config/UseBuildx.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import Flex from "../../../shared/layout/Flex";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const UseBuildx: Component<{}> = (p) => {
|
||||
const { build, setBuild, userCanUpdate } = useConfig();
|
||||
const use_buildx = () => build.docker_build_args?.use_buildx || false;
|
||||
return (
|
||||
<Flex
|
||||
class="config-item shadow"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<h1>use buildx</h1>
|
||||
<Show
|
||||
when={userCanUpdate()}
|
||||
fallback={<div>{use_buildx() ? "enabled" : "disabled"}</div>}
|
||||
>
|
||||
<button
|
||||
class={use_buildx() ? "green" : "red"}
|
||||
onClick={() => setBuild("docker_build_args", "use_buildx", (c) => !c)}
|
||||
>
|
||||
{use_buildx() ? "enabled" : "disabled"}
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UseBuildx;
|
||||
@@ -119,7 +119,18 @@ const Header: Component<{}> = (p) => {
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
<div style={{ opacity: 0.7 }}>{image()}</div>
|
||||
<Show
|
||||
when={deployment().deployment.build_id}
|
||||
fallback={<div style={{ opacity: 0.7 }}>{image()}</div>}
|
||||
>
|
||||
<A
|
||||
href={`/build/${deployment().deployment.build_id}`}
|
||||
class="text-hover"
|
||||
style={{ opacity: 0.7, padding: 0 }}
|
||||
>
|
||||
{image()}
|
||||
</A>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Show when={userCanUpdate()}>
|
||||
<Flex alignItems="center">
|
||||
|
||||
@@ -14,26 +14,29 @@ const Summary: Component<{}> = (p) => {
|
||||
const serverCount = useServerCount();
|
||||
return (
|
||||
<Grid
|
||||
class="card shadow"
|
||||
class="full-size"
|
||||
gridTemplateColumns={isMobile() ? "1fr" : "1fr 1fr"}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
"box-sizing": "border-box",
|
||||
}}
|
||||
placeItems="center"
|
||||
gap="0"
|
||||
>
|
||||
<div
|
||||
style={{ width: `${PIE_CHART_SIZE}px`, height: `${PIE_CHART_SIZE}px` }}
|
||||
>
|
||||
<PieChart title="deployments" sections={deployentCount()} />
|
||||
</div>
|
||||
<div
|
||||
style={{ width: `${PIE_CHART_SIZE}px`, height: `${PIE_CHART_SIZE}px` }}
|
||||
>
|
||||
<PieChart title="servers" sections={serverCount()} />
|
||||
</div>
|
||||
<Grid class="card shadow full-size" placeItems="center">
|
||||
<div
|
||||
style={{
|
||||
width: `${PIE_CHART_SIZE}px`,
|
||||
height: `${PIE_CHART_SIZE}px`,
|
||||
}}
|
||||
>
|
||||
<PieChart title="deployments" sections={deployentCount()} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid class="card shadow full-size" placeItems="center">
|
||||
<div
|
||||
style={{
|
||||
width: `${PIE_CHART_SIZE}px`,
|
||||
height: `${PIE_CHART_SIZE}px`,
|
||||
}}
|
||||
>
|
||||
<PieChart title="servers" sections={serverCount()} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
import { Component, createMemo, For, Show } from "solid-js";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { DockerContainerState, ServerStatus } from "../../types";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
|
||||
const Summary: Component<{}> = (p) => {
|
||||
return (
|
||||
<Grid class="card shadow" gridTemplateRows="auto 1fr 1fr 1fr">
|
||||
<h1>summary</h1>
|
||||
<DeploymentsSummary />
|
||||
<ServersSummary />
|
||||
<BuildsSummary />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Summary;
|
||||
|
||||
const SummaryItem: Component<{
|
||||
title: string;
|
||||
metrics: Array<{ title: string; class: string; count?: number }>;
|
||||
}> = (p) => {
|
||||
return (
|
||||
<Flex
|
||||
class="card light shadow wrap"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<h2>{p.title}</h2>
|
||||
<Flex class="wrap">
|
||||
<For each={p.metrics}>
|
||||
{(metric) => (
|
||||
<Show when={metric?.count && metric.count > 0}>
|
||||
<Flex gap="0.4rem" alignItems="center">
|
||||
<div>{metric.title}</div>
|
||||
<h2 class={metric.class}>{metric.count}</h2>
|
||||
</Flex>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const BuildsSummary = () => {
|
||||
const { builds } = useAppState();
|
||||
return (
|
||||
<SummaryItem
|
||||
title="builds"
|
||||
metrics={[
|
||||
{ title: "total", class: "text-green", count: builds.ids()?.length },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DeploymentsSummary = () => {
|
||||
const deployentCount = useDeploymentCount();
|
||||
return (
|
||||
<SummaryItem
|
||||
title="deployments"
|
||||
metrics={[
|
||||
{
|
||||
title: "total",
|
||||
class: "text-green",
|
||||
count: deployentCount().total,
|
||||
},
|
||||
{
|
||||
title: "running",
|
||||
class: "text-green",
|
||||
count: deployentCount().running,
|
||||
},
|
||||
{
|
||||
title: "stopped",
|
||||
class: "text-red",
|
||||
count: deployentCount().stopped,
|
||||
},
|
||||
{
|
||||
title: "not deployed",
|
||||
class: "text-blue",
|
||||
count: deployentCount().notDeployed,
|
||||
},
|
||||
{
|
||||
title: "unknown",
|
||||
class: "text-blue",
|
||||
count: deployentCount().unknown,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ServersSummary = () => {
|
||||
const serverCount = useServerCount();
|
||||
return (
|
||||
<SummaryItem
|
||||
title="servers"
|
||||
metrics={[
|
||||
{ title: "total", class: "text-green", count: serverCount().total },
|
||||
{ title: "healthy", class: "text-green", count: serverCount().healthy },
|
||||
{
|
||||
title: "unhealthy",
|
||||
class: "text-red",
|
||||
count: serverCount().unhealthy,
|
||||
},
|
||||
{
|
||||
title: "disabled",
|
||||
class: "text-blue",
|
||||
count: serverCount().disabled,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function useDeploymentCount() {
|
||||
const { deployments } = useAppState();
|
||||
const count = createMemo(() => {
|
||||
const ids = deployments.ids();
|
||||
if (!ids)
|
||||
return { total: 0, running: 0, stopped: 0, notDeployed: 0, unknown: 0 };
|
||||
let running = 0;
|
||||
let stopped = 0;
|
||||
let notDeployed = 0;
|
||||
let unknown = 0;
|
||||
for (const id of ids) {
|
||||
const state = deployments.get(id)!.state;
|
||||
if (state === DockerContainerState.NotDeployed) {
|
||||
notDeployed++;
|
||||
} else if (state === DockerContainerState.Running) {
|
||||
running++;
|
||||
} else if (state === DockerContainerState.Exited) {
|
||||
stopped++;
|
||||
} else if (state === DockerContainerState.Unknown) {
|
||||
unknown++;
|
||||
}
|
||||
}
|
||||
return { total: ids.length, running, stopped, notDeployed, unknown };
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
function useServerCount() {
|
||||
const { servers } = useAppState();
|
||||
const count = createMemo(() => {
|
||||
const ids = servers.ids();
|
||||
if (!ids) return { total: 0, healthy: 0, unhealthy: 0, disabled: 0 };
|
||||
let healthy = 0;
|
||||
let unhealthy = 0;
|
||||
let disabled = 0;
|
||||
for (const id of ids) {
|
||||
const server = servers.get(id)!;
|
||||
if (server.status === ServerStatus.Disabled) {
|
||||
disabled++;
|
||||
} else if (server.status === ServerStatus.Ok) {
|
||||
healthy++;
|
||||
} else if (server.status === ServerStatus.NotOk) {
|
||||
unhealthy++;
|
||||
}
|
||||
}
|
||||
return { total: ids.length, healthy, unhealthy, disabled };
|
||||
});
|
||||
return count;
|
||||
}
|
||||
@@ -14,18 +14,9 @@ import UpdateMenu from "../../update/UpdateMenu";
|
||||
import s from "./update.module.scss";
|
||||
|
||||
const Update: Component<{ update: UpdateType }> = (p) => {
|
||||
const { deployments, servers, builds, usernames } = useAppState();
|
||||
const name = () => {
|
||||
if (p.update.target.type === "Deployment" && deployments.loaded()) {
|
||||
return deployments.get(p.update.target.id!)?.deployment.name || "deleted";
|
||||
} else if (p.update.target.type === "Server" && servers.loaded()) {
|
||||
return servers.get(p.update.target.id)?.server.name || "deleted";
|
||||
} else if (p.update.target.type === "Build" && builds.loaded()) {
|
||||
return builds.get(p.update.target.id)?.name || "deleted";
|
||||
} else {
|
||||
return "monitor";
|
||||
}
|
||||
};
|
||||
const { usernames, name_from_update_target } =
|
||||
useAppState();
|
||||
const name = () => name_from_update_target(p.update.target);
|
||||
const operation = () => {
|
||||
if (p.update.operation === Operation.BuildBuild) {
|
||||
return `build ${readableVersion(p.update.version!)}`;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import { Component, createEffect, createSignal, For, Show } from "solid-js";
|
||||
import { OPERATIONS } from "../../..";
|
||||
import { useAppState } from "../../../state/StateProvider";
|
||||
import { Operation } from "../../../types";
|
||||
import Flex from "../../shared/layout/Flex";
|
||||
@@ -7,10 +9,6 @@ import Loading from "../../shared/loading/Loading";
|
||||
import Selector from "../../shared/menu/Selector";
|
||||
import Update from "./Update";
|
||||
|
||||
const OPERATIONS = Object.values(Operation)
|
||||
.filter((e) => e !== "none" && !e.includes("user"))
|
||||
.map((e) => e.replaceAll("_", " "));
|
||||
|
||||
const Updates: Component<{}> = () => {
|
||||
const { updates } = useAppState();
|
||||
const [operation, setOperation] = createSignal<Operation>();
|
||||
@@ -24,8 +22,11 @@ const Updates: Component<{}> = () => {
|
||||
return (
|
||||
<Grid class="card shadow" style={{ "flex-grow": 1 }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>updates</h1>
|
||||
<A href="/updates" style={{ padding: 0 }}>
|
||||
<h1>updates</h1>
|
||||
</A>
|
||||
<Selector
|
||||
label="operation: "
|
||||
selected={operation() ? operation()! : "all"}
|
||||
items={["all", ...OPERATIONS]}
|
||||
onSelect={(o) =>
|
||||
@@ -50,7 +51,7 @@ const Updates: Component<{}> = () => {
|
||||
}
|
||||
>
|
||||
<Grid class="updates-container-small scroller">
|
||||
<For each={updates.collection()!}>
|
||||
<For each={updates.collection()}>
|
||||
{(update) => <Update update={update} />}
|
||||
</For>
|
||||
<Show when={!updates.noMore()}>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { readableStorageAmount } from "../../../util/helpers";
|
||||
import Flex from "../../shared/layout/Flex";
|
||||
import Grid from "../../shared/layout/Grid";
|
||||
import Loading from "../../shared/loading/Loading";
|
||||
import HoverMenu from "../../shared/menu/HoverMenu";
|
||||
|
||||
const Info: Component<{}> = (p) => {
|
||||
const { isMobile } = useAppDimensions();
|
||||
|
||||
@@ -76,7 +76,7 @@ const Child: Component<{
|
||||
>
|
||||
<Grid
|
||||
class={combineClasses(s.Menu, "shadow")}
|
||||
style={{ padding: (p.padding as any) || "1rem", ...p.style }}
|
||||
style={{ padding: (p.padding as any) || "2rem", ...p.style }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
|
||||
@@ -37,10 +37,10 @@ const Selector: Component<{
|
||||
}> = (p) => {
|
||||
const [show, toggle] = useToggle();
|
||||
const [search, setSearch] = createSignal("");
|
||||
let ref: HTMLInputElement | undefined;
|
||||
let search_ref: HTMLInputElement | undefined;
|
||||
const current = () => (p.itemMap ? p.itemMap(p.selected) : p.selected);
|
||||
createEffect(() => {
|
||||
if (show()) setTimeout(() => ref?.focus(), 200);
|
||||
if (show()) setTimeout(() => search_ref?.focus(), 200);
|
||||
});
|
||||
return (
|
||||
<Show
|
||||
@@ -70,7 +70,7 @@ const Selector: Component<{
|
||||
<>
|
||||
<Show when={p.useSearch}>
|
||||
<Input
|
||||
ref={ref}
|
||||
ref={search_ref}
|
||||
placeholder="search"
|
||||
value={search()}
|
||||
onEdit={setSearch}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
width: fit-content;
|
||||
/* border: solid 1px rgba(2, 107, 121, 0.25); */
|
||||
background-color: c.$grey;
|
||||
border: solid c.$darkgrey 2px;
|
||||
z-index: 21;
|
||||
border-radius: 0.25rem;
|
||||
box-sizing: border-box;
|
||||
@@ -142,6 +143,11 @@ $anim-time: 350ms;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.CenterMenuHeader {
|
||||
border-bottom: solid rgba(c.$lightgrey, 0.9) 2px;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.SelectorItem:hover {
|
||||
background-color: c.$lightgrey;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
.TabTitle {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { Accessor, Component, For, ParentComponent, Show } from "solid-js";
|
||||
import {
|
||||
Accessor,
|
||||
Component,
|
||||
For,
|
||||
JSXElement,
|
||||
ParentComponent,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { COLORS } from "../../style/colors";
|
||||
import { SystemStats, SystemStatsRecord } from "../../types";
|
||||
import {
|
||||
convertTsMsToLocalUnixTsInSecs,
|
||||
get_to_one_sec_divisor,
|
||||
} from "../../util/helpers";
|
||||
import { useLocalStorage, useLocalStorageToggle } from "../../util/hooks";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import LightweightChart, { LightweightValue } from "../shared/LightweightChart";
|
||||
import s from "./stats.module.scss";
|
||||
@@ -15,13 +24,18 @@ const SMALL_CHART_HEIGHT = "150px";
|
||||
const SingleStatChart: Component<{
|
||||
line?: LightweightValue[];
|
||||
header: string;
|
||||
headerRight?: JSXElement;
|
||||
label: string;
|
||||
color: string;
|
||||
small?: boolean;
|
||||
disableScroll?: boolean;
|
||||
}> = (p) => {
|
||||
return (
|
||||
<StatChartContainer header={p.header} small={p.small}>
|
||||
<StatChartContainer
|
||||
header={p.header}
|
||||
headerRight={p.headerRight}
|
||||
small={p.small}
|
||||
>
|
||||
<Show when={p.line}>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
@@ -44,23 +58,25 @@ const SingleStatChart: Component<{
|
||||
|
||||
const StatChartContainer: ParentComponent<{
|
||||
header: string;
|
||||
headerRight?: JSXElement;
|
||||
small?: boolean;
|
||||
}> = (p) => {
|
||||
return (
|
||||
<Grid
|
||||
gap="0.5rem"
|
||||
class="card shadow"
|
||||
class="card shadow full-width"
|
||||
style={{
|
||||
height: "fit-content",
|
||||
width: "100%",
|
||||
"box-sizing": "border-box",
|
||||
"padding-top": "0.5rem",
|
||||
"padding-bottom": "0.2rem",
|
||||
}}
|
||||
>
|
||||
<Show when={!p.small} fallback={<div>{p.header}</div>}>
|
||||
<h2>{p.header}</h2>
|
||||
</Show>
|
||||
<Flex justifyContent="space-between">
|
||||
<Show when={!p.small} fallback={<div>{p.header}</div>}>
|
||||
<h2>{p.header}</h2>
|
||||
</Show>
|
||||
{p.headerRight}
|
||||
</Flex>
|
||||
{p.children}
|
||||
</Grid>
|
||||
);
|
||||
@@ -152,20 +168,42 @@ export const MemChart: Component<{
|
||||
small?: boolean;
|
||||
disableScroll?: boolean;
|
||||
}> = (p) => {
|
||||
const [absolute, toggleAbsolute] = useLocalStorageToggle("stats-mem-mode-v2");
|
||||
const symbol = () => (absolute() ? "GiB" : "%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: (100 * s.mem_used_gb) / s.mem_total_gb,
|
||||
};
|
||||
});
|
||||
if (absolute()) {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: s.mem_used_gb,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: (100 * s.mem_used_gb) / s.mem_total_gb,
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SingleStatChart
|
||||
header="memory"
|
||||
label="mem %"
|
||||
headerRight={
|
||||
<button
|
||||
class="green"
|
||||
style={{ padding: "0.2rem" }}
|
||||
onClick={toggleAbsolute}
|
||||
>
|
||||
{symbol()}
|
||||
</button>
|
||||
}
|
||||
label={`mem ${symbol()}`}
|
||||
color={COLORS.green}
|
||||
line={line()}
|
||||
small={p.small}
|
||||
@@ -179,20 +217,43 @@ export const DiskChart: Component<{
|
||||
small?: boolean;
|
||||
disableScroll?: boolean;
|
||||
}> = (p) => {
|
||||
const [absolute, toggleAbsolute] =
|
||||
useLocalStorageToggle("stats-disk-mode-v2");
|
||||
const symbol = () => (absolute() ? "GiB" : "%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: (100 * s.disk.used_gb) / s.disk.total_gb,
|
||||
};
|
||||
});
|
||||
if (absolute()) {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: s.disk.used_gb,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(
|
||||
(s as SystemStatsRecord).ts || (s as SystemStats).refresh_ts
|
||||
),
|
||||
value: (100 * s.disk.used_gb) / s.disk.total_gb,
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SingleStatChart
|
||||
header="disk"
|
||||
label="disk %"
|
||||
headerRight={
|
||||
<button
|
||||
class="orange"
|
||||
style={{ padding: "0.2rem" }}
|
||||
onClick={toggleAbsolute}
|
||||
>
|
||||
{symbol()}
|
||||
</button>
|
||||
}
|
||||
label={`disk ${symbol()}`}
|
||||
color={COLORS.orange}
|
||||
line={line()}
|
||||
small={p.small}
|
||||
|
||||
@@ -29,20 +29,23 @@ const HistoricalStats: Component<{
|
||||
const params = useParams();
|
||||
const { timelength, page } = useStatsState();
|
||||
const [stats, setStats] = createSignal<SystemStatsRecord[]>();
|
||||
createEffect(() => {
|
||||
client
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
createEffect(async () => {
|
||||
setLoading(true);
|
||||
const stats = await client
|
||||
.get_server_stats_history(params.id, {
|
||||
interval: timelength(),
|
||||
page: page(),
|
||||
limit: 500,
|
||||
networks: true,
|
||||
components: true,
|
||||
})
|
||||
.then(setStats);
|
||||
});
|
||||
setStats(stats);
|
||||
setLoading(false);
|
||||
});
|
||||
return (
|
||||
<Grid class={s.Content} placeItems="start center">
|
||||
<Show when={stats()} fallback={<Loading type="three-dot" />}>
|
||||
<Show when={stats() && !loading()} fallback={<Loading type="three-dot" />}>
|
||||
<SimpleTabs
|
||||
localStorageKey="historical-stats-view-v3"
|
||||
defaultSelected="basic"
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { ParentComponent, createContext, useContext, createSignal, createResource } from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { SystemInformation, Timelength } from "../../types";
|
||||
import { useLocalStorage } from "../../util/hooks";
|
||||
|
||||
export enum StatsView {
|
||||
Current = "current",
|
||||
Historical = "historical",
|
||||
Info = "info"
|
||||
}
|
||||
|
||||
const value = () => {
|
||||
const params = useParams();
|
||||
const [view, setView] = useLocalStorage("current", "stats-view-v1");
|
||||
const [view, setView] = useLocalStorage(StatsView.Current, "stats-view-v2");
|
||||
const [timelength, setTimelength] = useLocalStorage(
|
||||
Timelength.OneMinute,
|
||||
"stats-timelength-v3"
|
||||
@@ -16,12 +23,7 @@ const value = () => {
|
||||
`${params.id}-stats-poll-v3`
|
||||
);
|
||||
const [page, setPage] = createSignal(0);
|
||||
// const [wsOpen, setWsOpen] = createSignal(false);
|
||||
const [sysInfo] = createResource<SystemInformation>(() =>
|
||||
client.get_server_system_info(params.id)
|
||||
);
|
||||
return {
|
||||
sysInfo,
|
||||
view,
|
||||
setView,
|
||||
timelength,
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { A, useParams } from "@solidjs/router";
|
||||
import {
|
||||
Component,
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
} from "solid-js";
|
||||
import { MAX_PAGE_WIDTH } from "../..";
|
||||
import { Component, createResource, For, Match, Show, Switch } from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { ServerStatus, Timelength } from "../../types";
|
||||
import { readableStorageAmount } from "../../util/helpers";
|
||||
import Icon from "../shared/Icon";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import Selector from "../shared/menu/Selector";
|
||||
import CurrentStats from "./CurrentStats";
|
||||
import HistoricalStats from "./HistoricalStats";
|
||||
import { StatsProvider, useStatsState } from "./Provider";
|
||||
import { StatsProvider, useStatsState, StatsView } from "./Provider";
|
||||
|
||||
const TIMELENGTHS = [
|
||||
Timelength.FifteenSeconds,
|
||||
@@ -38,115 +34,182 @@ const Stats = () => {
|
||||
const StatsComp: Component<{}> = () => {
|
||||
const { view } = useStatsState();
|
||||
return (
|
||||
<Grid
|
||||
style={{
|
||||
width: "100%",
|
||||
"box-sizing": "border-box",
|
||||
}}
|
||||
>
|
||||
<Flex justifyContent="space-between" style={{ width: "100%" }}>
|
||||
<Header />
|
||||
<SysInfo />
|
||||
</Flex>
|
||||
<Show when={view() === "historical"}>
|
||||
<Grid class="full-width">
|
||||
<Header />
|
||||
<Show when={view() === StatsView.Historical}>
|
||||
<Flex alignItems="center" style={{ "place-self": "center" }}>
|
||||
<PageManager />
|
||||
</Flex>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={view() === "current"}>
|
||||
<Match when={view() === StatsView.Current}>
|
||||
<CurrentStats />
|
||||
</Match>
|
||||
<Match when={view() === "historical"}>
|
||||
<Match when={view() === StatsView.Historical}>
|
||||
<HistoricalStats />
|
||||
</Match>
|
||||
<Match when={view() === StatsView.Info}>
|
||||
<SysInfo />
|
||||
</Match>
|
||||
</Switch>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header: Component<{}> = (p) => {
|
||||
const { servers } = useAppState();
|
||||
const { servers, serverInfo } = useAppState();
|
||||
const params = useParams();
|
||||
const server = () => servers.get(params.id);
|
||||
const { view, setView, timelength, setTimelength, setPage, pollRate, setPollRate } = useStatsState();
|
||||
const {
|
||||
view,
|
||||
setView,
|
||||
timelength,
|
||||
setTimelength,
|
||||
setPage,
|
||||
pollRate,
|
||||
setPollRate,
|
||||
} = useStatsState();
|
||||
const sysInfo = () => serverInfo.get(params.id);
|
||||
return (
|
||||
<Flex alignItems="center" style={{ height: "fit-content" }}>
|
||||
<h1>{server()?.server.name}</h1>
|
||||
<A
|
||||
href={`/server/${params.id}`}
|
||||
class={
|
||||
server()?.server.enabled
|
||||
? server()?.status === ServerStatus.Ok
|
||||
? "green"
|
||||
: "red"
|
||||
: "blue"
|
||||
}
|
||||
style={{
|
||||
"border-radius": ".35rem",
|
||||
transition: "background-color 125ms ease-in-out",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{server()?.status.replaceAll("_", " ").toUpperCase()}
|
||||
</A>
|
||||
<Grid gap="0" gridTemplateColumns="repeat(2, 1fr)">
|
||||
<button
|
||||
class={view() === "current" ? "selected" : "grey"}
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => setView("current")}
|
||||
>
|
||||
current
|
||||
</button>
|
||||
<button
|
||||
class={view() === "historical" ? "selected" : "grey"}
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => setView("historical")}
|
||||
>
|
||||
historical
|
||||
</button>
|
||||
</Grid>
|
||||
<Show when={view() === "historical"}>
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
selected={timelength()}
|
||||
items={TIMELENGTHS}
|
||||
onSelect={(selected) => {
|
||||
setPage(0);
|
||||
setTimelength(selected as Timelength);
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<Flex alignItems="center" style={{ height: "fit-content" }}>
|
||||
<h1>{server()?.server.name}</h1>
|
||||
<A
|
||||
href={`/server/${params.id}`}
|
||||
class={
|
||||
server()?.server.enabled
|
||||
? server()?.status === ServerStatus.Ok
|
||||
? "green"
|
||||
: "red"
|
||||
: "blue"
|
||||
}
|
||||
style={{
|
||||
"border-radius": ".35rem",
|
||||
transition: "background-color 125ms ease-in-out",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{server()?.status.replaceAll("_", " ").toUpperCase()}
|
||||
</A>
|
||||
<Selector
|
||||
targetClass="blue"
|
||||
selected={view()}
|
||||
items={Object.values(StatsView)}
|
||||
onSelect={(v) => setView(v as StatsView)}
|
||||
position="bottom right"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={view() === "current"}>
|
||||
<Flex gap="0.5rem" alignItems="center">
|
||||
<div>poll:</div>
|
||||
<Show when={view() === "historical"}>
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
selected={timelength()}
|
||||
items={TIMELENGTHS}
|
||||
itemMap={(t) => t.replaceAll("-", " ")}
|
||||
itemClass="full-width"
|
||||
onSelect={(selected) => {
|
||||
setPage(0);
|
||||
setTimelength(selected as Timelength);
|
||||
}}
|
||||
position="bottom right"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={view() === "current"}>
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
label="poll: "
|
||||
selected={pollRate()}
|
||||
items={[Timelength.OneSecond, Timelength.FiveSeconds]}
|
||||
onSelect={(selected) => {
|
||||
setPollRate(selected as Timelength);
|
||||
}}
|
||||
position="bottom right"
|
||||
/>
|
||||
</Flex>
|
||||
</Show>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<div>{sysInfo()?.cpu_brand}</div>
|
||||
<div>
|
||||
{sysInfo()?.core_count} core
|
||||
{sysInfo()?.core_count && sysInfo()?.core_count! > 1 ? "s" : ""}
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const SysInfo = () => {
|
||||
const { sysInfo } = useStatsState();
|
||||
const { serverInfo } = useAppState();
|
||||
const params = useParams();
|
||||
const sysInfo = () => serverInfo.get(params.id);
|
||||
const [stats] = createResource(() =>
|
||||
client.get_server_stats(params.id, { disks: true })
|
||||
);
|
||||
const os_cards = () => {
|
||||
return [
|
||||
{
|
||||
label: "os",
|
||||
info: sysInfo()?.os,
|
||||
},
|
||||
{
|
||||
label: "kernel",
|
||||
info: sysInfo()?.kernel,
|
||||
},
|
||||
].filter((i) => i.info) as Array<{ label: string; info: string }>;
|
||||
};
|
||||
const cpu_cards = () => {
|
||||
return [
|
||||
{
|
||||
label: "cpu",
|
||||
info: sysInfo()?.cpu_brand,
|
||||
},
|
||||
{
|
||||
label: "core count",
|
||||
info: `${sysInfo()?.core_count} cores`,
|
||||
},
|
||||
].filter((i) => i.info) as Array<{ label: string; info: string }>;
|
||||
};
|
||||
const stats_cards = () => {
|
||||
return [
|
||||
{
|
||||
label: "mem",
|
||||
info:
|
||||
stats()?.mem_total_gb &&
|
||||
readableStorageAmount(stats()?.mem_total_gb!),
|
||||
},
|
||||
{
|
||||
label: "disk",
|
||||
info:
|
||||
stats()?.disk.total_gb &&
|
||||
readableStorageAmount(stats()?.disk.total_gb!),
|
||||
},
|
||||
].filter((i) => i.info) as Array<{ label: string; info: string }>;
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
style={{ "place-self": "center end", width: "fit-content" }}
|
||||
>
|
||||
<div>{sysInfo()?.os}</div>
|
||||
{/* <div>{sysInfo()?.kernel}</div> */}
|
||||
<div>{sysInfo()?.cpu_brand}</div>
|
||||
<div>{sysInfo()?.core_count} cores</div>
|
||||
<Grid class="full-width" placeItems="center">
|
||||
<Show when={sysInfo()?.host_name}>
|
||||
<Grid class="card full-width" style={{ "max-width": "700px" }}>
|
||||
<InfoCard info={{ label: "hostname", info: sysInfo()?.host_name! }} />
|
||||
</Grid>
|
||||
</Show>
|
||||
<Grid class="card full-width" style={{ "max-width": "700px" }}>
|
||||
<For each={os_cards()}>{(i) => <InfoCard info={i} />}</For>
|
||||
</Grid>
|
||||
<Grid class="card full-width" style={{ "max-width": "700px" }}>
|
||||
<For each={cpu_cards()}>{(i) => <InfoCard info={i} />}</For>
|
||||
</Grid>
|
||||
<Grid class="card full-width" style={{ "max-width": "700px" }}>
|
||||
<For each={stats_cards()}>{(i) => <InfoCard info={i} />}</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const InfoCard: Component<{ info: { label: string; info: string } }> = (p) => {
|
||||
return (
|
||||
<Flex class="full-width" justifyContent="space-between">
|
||||
<h2>{p.info.label}</h2>
|
||||
<div>{p.info.info}</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -58,7 +58,8 @@ export const Search: Component<{}> = (p) => {
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
class={s.SearchInput}
|
||||
class="lightgrey"
|
||||
style={{ width: "30rem" }}
|
||||
placeholder="search"
|
||||
value={search.value()}
|
||||
onEdit={input.onEdit}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UserProvider } from "./state/UserProvider";
|
||||
import { Client } from "./util/client";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { AppStateProvider } from "./state/StateProvider";
|
||||
import { Operation } from "./types";
|
||||
|
||||
export const TOPBAR_HEIGHT = 50;
|
||||
export const MAX_PAGE_WIDTH = 1200;
|
||||
@@ -29,6 +30,10 @@ const token =
|
||||
|
||||
export const client = new Client(MONITOR_BASE_URL, token);
|
||||
|
||||
export const OPERATIONS = Object.values(Operation)
|
||||
.filter((e) => e !== "none" && !e.includes("user"))
|
||||
.map((e) => e.replaceAll("_", " "));
|
||||
|
||||
export const { Notifications, pushNotification } = makeNotifications();
|
||||
|
||||
client.initialize().then(() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "./hooks";
|
||||
import connectToWs from "./ws";
|
||||
import { useUser } from "./UserProvider";
|
||||
import { AwsBuilderConfig, PermissionLevel } from "../types";
|
||||
import { AwsBuilderConfig, PermissionLevel, UpdateTarget } from "../types";
|
||||
import { client } from "..";
|
||||
|
||||
export type State = {
|
||||
@@ -42,6 +42,7 @@ export type State = {
|
||||
aws_builder_config: Resource<AwsBuilderConfig>;
|
||||
docker_organizations: Resource<string[]>;
|
||||
github_webhook_base_url: Resource<string>;
|
||||
name_from_update_target: (target: UpdateTarget) => string;
|
||||
};
|
||||
|
||||
const context = createContext<
|
||||
@@ -148,6 +149,17 @@ export const AppStateProvider: ParentComponent = (p) => {
|
||||
aws_builder_config,
|
||||
docker_organizations,
|
||||
github_webhook_base_url,
|
||||
name_from_update_target: (target) => {
|
||||
if (target.type === "Deployment" && deployments) {
|
||||
return deployments.get(target.id!)?.deployment.name || "deleted";
|
||||
} else if (target.type === "Server" && servers) {
|
||||
return servers.get(target.id)?.server.name || "deleted";
|
||||
} else if (target.type === "Build" && builds) {
|
||||
return builds.get(target.id)?.name || "deleted";
|
||||
} else {
|
||||
return "admin";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// createEffect(() => {
|
||||
|
||||
@@ -288,7 +288,7 @@ export function useUpdates(target?: UpdateTarget, show_builds?: boolean) {
|
||||
operations
|
||||
);
|
||||
updates.addManyToEnd(newUpdates);
|
||||
if (newUpdates.length !== 10) {
|
||||
if (newUpdates.length !== 20) {
|
||||
setNoMore(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,22 @@ svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.full-size {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// .hoverable {
|
||||
// transition: all 250ms ease-in-out;
|
||||
// }
|
||||
|
||||
@@ -56,6 +56,8 @@ export interface DockerBuildArgs {
|
||||
build_path: string;
|
||||
dockerfile_path?: string;
|
||||
build_args?: EnvironmentVar[];
|
||||
extra_args?: string[];
|
||||
use_buildx?: boolean;
|
||||
}
|
||||
|
||||
export interface BuildVersionsReponse {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import {
|
||||
Build,
|
||||
Deployment,
|
||||
DeploymentWithContainerState,
|
||||
DockerContainerState,
|
||||
EnvironmentVar,
|
||||
Server,
|
||||
ServerStatus,
|
||||
ServerWithStatus,
|
||||
Timelength,
|
||||
UpdateTarget,
|
||||
User,
|
||||
Version,
|
||||
} from "../types";
|
||||
@@ -238,10 +244,10 @@ export function readableVersion(version: Version) {
|
||||
|
||||
export function readableUserType(user: User) {
|
||||
if (user.github_id) {
|
||||
return "github"
|
||||
return "github";
|
||||
} else if (user.google_id) {
|
||||
return "google"
|
||||
return "google";
|
||||
} else {
|
||||
return "local"
|
||||
return "local";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "db_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
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.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "helpers used as dependency for mogh tech monitor"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "a client to interact with the monitor system"
|
||||
@@ -9,8 +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.2.10"
|
||||
# monitor_types = { path = "../types" }
|
||||
monitor_types = "0.2.11"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio-tungstenite = { version = "0.18", features=["native-tls"] }
|
||||
tokio = { version = "1.25", features = ["full"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "periphery_client"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
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.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "types for the mogh tech monitor"
|
||||
|
||||
@@ -155,6 +155,12 @@ pub struct DockerBuildArgs {
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub build_args: Vec<EnvironmentVar>,
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub use_buildx: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "monitor_periphery"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
edition = "2021"
|
||||
authors = ["MoghTech"]
|
||||
description = "monitor periphery binary"
|
||||
|
||||
@@ -6,7 +6,7 @@ use types::{Build, DockerBuildArgs, EnvironmentVar, Log, Version};
|
||||
|
||||
use crate::helpers::run_monitor_command;
|
||||
|
||||
use super::docker_login;
|
||||
use super::{docker_login, parse_extra_args};
|
||||
|
||||
pub async fn prune_images() -> Log {
|
||||
let command = format!("docker image prune -a -f");
|
||||
@@ -32,6 +32,8 @@ pub async fn build(
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
build_args,
|
||||
extra_args,
|
||||
use_buildx,
|
||||
} = docker_build_args
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("build missing docker build args"))?;
|
||||
@@ -46,6 +48,12 @@ pub async fn build(
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
let build_args = parse_build_args(build_args);
|
||||
let extra_args = parse_extra_args(extra_args);
|
||||
let buildx = if *use_buildx {
|
||||
" buildx"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let image_name = get_image_name(&name, docker_account, docker_organization);
|
||||
let image_tags = image_tags(&image_name, &version);
|
||||
let docker_push = if using_account {
|
||||
@@ -54,7 +62,7 @@ pub async fn build(
|
||||
String::new()
|
||||
};
|
||||
let command = format!(
|
||||
"cd {} && docker build {build_args}{image_tags} -f {dockerfile_path} .{docker_push}",
|
||||
"cd {} && docker{buildx} build {build_args}{extra_args}{image_tags} -f {dockerfile_path} .{docker_push}",
|
||||
build_dir.display()
|
||||
);
|
||||
if *skip_secret_interp {
|
||||
|
||||
@@ -7,7 +7,7 @@ use types::{
|
||||
Conversion, Deployment, DockerContainerStats, DockerRunArgs, EnvironmentVar, Log, RestartMode,
|
||||
};
|
||||
|
||||
use crate::helpers::run_monitor_command;
|
||||
use crate::helpers::{run_monitor_command, docker::parse_extra_args};
|
||||
|
||||
use super::docker_login;
|
||||
|
||||
@@ -197,12 +197,3 @@ fn parse_post_image(post_image: &Option<String>) -> String {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_extra_args(extra_args: &Vec<String>) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if args.len() > 0 {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,3 +38,12 @@ pub async fn docker_login(
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_extra_args(extra_args: &Vec<String>) -> String {
|
||||
let args = extra_args.join(" ");
|
||||
if args.len() > 0 {
|
||||
format!(" {args}")
|
||||
} else {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,7 @@ pub async fn test_build(monitor: &MonitorClient) -> anyhow::Result<Update> {
|
||||
});
|
||||
build.docker_build_args = Some(DockerBuildArgs {
|
||||
build_path: "periphery".to_string(),
|
||||
dockerfile_path: None,
|
||||
build_args: Vec::new(),
|
||||
..Default::default()
|
||||
});
|
||||
let build = monitor.update_build(build).await?;
|
||||
println!("updated build.");
|
||||
|
||||
Reference in New Issue
Block a user