mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-08 08:01:11 +00:00
make standalone stats page
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Route, Routes } from "@solidjs/router";
|
||||
import { Component, lazy, Show } from "solid-js";
|
||||
import Stats from "./components/stats/Stats";
|
||||
import Topbar from "./components/topbar/Topbar";
|
||||
import { useUser } from "./state/UserProvider";
|
||||
|
||||
@@ -19,6 +20,7 @@ const App: Component = () => {
|
||||
<Route path="/build/:id" component={Build} />
|
||||
<Route path="/deployment/:id" component={Deployment} />
|
||||
<Route path="/server/:id" component={Server} />
|
||||
<Route path="/server/:id/stats" component={Stats} />
|
||||
<Show when={user().admin}>
|
||||
<Route path="/users" component={Users} />
|
||||
</Show>
|
||||
|
||||
@@ -10,7 +10,7 @@ import Deployment from "./Deployment";
|
||||
import s from "../home.module.scss";
|
||||
import { NewBuild, NewDeployment } from "./New";
|
||||
import Loading from "../../shared/loading/Loading";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { A, useNavigate } from "@solidjs/router";
|
||||
import { PermissionLevel, ServerStatus } from "../../../types";
|
||||
import { useAppDimensions } from "../../../state/DimensionProvider";
|
||||
import Build from "./Build";
|
||||
@@ -101,7 +101,9 @@ const Server: Component<{ id: string }> = (p) => {
|
||||
<Icon type="refresh" width="0.85rem" />
|
||||
</button>
|
||||
</Show>
|
||||
{/* <StatGraphs id={p.id} /> */}
|
||||
<A href={`/server/${p.id}/stats`} class="blue">
|
||||
<Icon type="timeline-line-chart" />
|
||||
</A>
|
||||
</Flex>
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Tab } from "../../shared/tabs/Tabs";
|
||||
import Config from "./config/Config";
|
||||
import { ConfigProvider } from "./config/Provider";
|
||||
import Owners from "./Owners";
|
||||
import Stats from "./stats/Stats";
|
||||
|
||||
const ServerTabs: Component<{}> = (p) => {
|
||||
const { servers } = useAppState();
|
||||
@@ -25,10 +24,6 @@ const ServerTabs: Component<{}> = (p) => {
|
||||
title: "config",
|
||||
element: () => <Config />,
|
||||
},
|
||||
{
|
||||
title: "stats",
|
||||
element: () => <Stats />,
|
||||
},
|
||||
user().admin && {
|
||||
title: "collaborators",
|
||||
element: () => <Owners />,
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
import { useParams } from "@solidjs/router";
|
||||
import {
|
||||
Accessor,
|
||||
Component,
|
||||
createEffect,
|
||||
createSignal,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { client } from "../../../..";
|
||||
import { SystemStats, SystemStatsRecord, Timelength } from "../../../../types";
|
||||
import {
|
||||
convertTsMsToLocalUnixTsInSecs,
|
||||
get_to_one_sec_divisor,
|
||||
} from "../../../../util/helpers";
|
||||
import { useLocalStorage } from "../../../../util/hooks";
|
||||
import Icon from "../../../shared/Icon";
|
||||
import Flex from "../../../shared/layout/Flex";
|
||||
import Grid from "../../../shared/layout/Grid";
|
||||
import LightweightChart from "../../../shared/LightweightChart";
|
||||
import Loading from "../../../shared/loading/Loading";
|
||||
import Selector from "../../../shared/menu/Selector";
|
||||
import s from "./stats.module.scss";
|
||||
|
||||
const TIMELENGTHS = [
|
||||
Timelength.OneMinute,
|
||||
Timelength.FiveMinutes,
|
||||
Timelength.FifteenMinutes,
|
||||
Timelength.OneHour,
|
||||
Timelength.SixHours,
|
||||
Timelength.TwelveHours,
|
||||
Timelength.OneDay,
|
||||
];
|
||||
|
||||
const Stats: Component<{}> = (p) => {
|
||||
const params = useParams();
|
||||
const [timelength, setTimelength] = useLocalStorage(
|
||||
Timelength.OneMinute,
|
||||
"server-stats-timelength-v3"
|
||||
);
|
||||
const [currStats, setCurrStats] = createSignal<SystemStats>();
|
||||
const [loadingCurr, setLoadingCurr] = createSignal(false);
|
||||
const [stats, setStats] = createSignal<SystemStatsRecord[]>();
|
||||
const [page, setPage] = createSignal(0);
|
||||
const load_curr_stats = () => {
|
||||
setLoadingCurr(true);
|
||||
client.get_server_stats(params.id).then((stats) => {
|
||||
setCurrStats(stats);
|
||||
setLoadingCurr(false);
|
||||
});
|
||||
};
|
||||
createEffect(() => {
|
||||
client
|
||||
.get_server_stats_history(params.id, {
|
||||
interval: timelength(),
|
||||
page: page(),
|
||||
limit: 1000,
|
||||
networks: true,
|
||||
components: true,
|
||||
})
|
||||
.then(setStats);
|
||||
});
|
||||
createEffect(() => {
|
||||
load_curr_stats();
|
||||
})
|
||||
// createEffect(() => console.log(stats()))
|
||||
return (
|
||||
<Grid
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "fit-content",
|
||||
padding: "1rem 3rem",
|
||||
"box-sizing": "border-box",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
style={{ width: "100%" }}
|
||||
alignItems="center"
|
||||
// justifyContent="space-between"
|
||||
>
|
||||
<Flex class="card light shadow" alignItems="center">
|
||||
<Show when={currStats()} fallback={<Loading type="three-dot" />}>
|
||||
<Grid gap="0" placeItems="start center">
|
||||
cpu: <h2>{currStats()!.cpu_perc.toFixed(1)}%</h2>
|
||||
</Grid>
|
||||
<Grid gap="0" placeItems="start center">
|
||||
mem:
|
||||
<div>{currStats()!.mem_total_gb.toFixed(1)} GB</div>
|
||||
<h2>
|
||||
{(
|
||||
(100 * currStats()!.mem_used_gb) /
|
||||
currStats()!.mem_total_gb
|
||||
).toFixed(1)}
|
||||
% full
|
||||
</h2>
|
||||
</Grid>
|
||||
<Grid gap="0" placeItems="start center">
|
||||
disk:
|
||||
<div>{currStats()!.disk.total_gb.toFixed(1)} GB</div>
|
||||
<h2>
|
||||
{(
|
||||
(100 * currStats()!.disk.used_gb) /
|
||||
currStats()!.disk.total_gb
|
||||
).toFixed(1)}
|
||||
% full
|
||||
</h2>
|
||||
</Grid>
|
||||
<button class="blue" onClick={load_curr_stats}>
|
||||
<Show when={!loadingCurr()} fallback={<Loading />}>
|
||||
<Icon type="refresh" />
|
||||
</Show>
|
||||
</button>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Flex class="card light shadow" alignItems="center">
|
||||
<button class="darkgrey" onClick={() => {
|
||||
setPage(page => page + 1);
|
||||
}}>
|
||||
<Icon type="chevron-left" />
|
||||
</button>
|
||||
<button class="darkgrey" onClick={() => {
|
||||
setPage(page => page > 0 ? page - 1 : 0);
|
||||
}}>
|
||||
<Icon type="chevron-right" />
|
||||
</button>
|
||||
<button class="darkgrey" onClick={() => {
|
||||
setPage(0)
|
||||
}}>
|
||||
<Icon type="double-chevron-right" />
|
||||
</button>
|
||||
<div>page: {page() + 1}</div>
|
||||
</Flex>
|
||||
<Selector
|
||||
selected={timelength()}
|
||||
items={TIMELENGTHS}
|
||||
onSelect={(selected) => setTimelength(selected as Timelength)}
|
||||
/>
|
||||
</Flex>
|
||||
<Show
|
||||
when={stats()}
|
||||
fallback={
|
||||
<div style={{ "place-self": "center" }}>
|
||||
<Loading type="three-dot" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Grid class={s.Charts}>
|
||||
<CpuChart stats={stats} />
|
||||
<MemChart stats={stats} />
|
||||
<DiskChart stats={stats} />
|
||||
<NetworkChart stats={stats} />
|
||||
</Grid>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Stats;
|
||||
|
||||
const CpuChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value: s.cpu_perc,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card dark shadow" style={{ height: "fit-content" }}>
|
||||
<h2>cpu</h2>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: "%", color: "#184e9f", line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const MemChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const [selected, setSelected] = createSignal("%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
selected() === "%"
|
||||
? (100 * s.mem_used_gb) / s.mem_total_gb
|
||||
: s.mem_used_gb,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card dark shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>memory</h2>
|
||||
<Selector
|
||||
selected={selected()}
|
||||
items={["%", "GB"]}
|
||||
onSelect={setSelected}
|
||||
/>
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: selected(), color: "#184e9f", line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const DiskChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const [selected, setSelected] = createSignal("%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
selected() === "%"
|
||||
? (100 * s.disk.used_gb) / s.disk.total_gb
|
||||
: s.disk.used_gb,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card dark shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>disk</h2>
|
||||
<Selector
|
||||
selected={selected()}
|
||||
items={["%", "GB"]}
|
||||
onSelect={setSelected}
|
||||
/>
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: selected(), color: "#184e9f", line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const NetworkChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const recv_line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
s.networks?.length || 0 > 0
|
||||
? s.networks!.map((n) => n.recieved_kb).reduce((p, c) => p + c) /
|
||||
get_to_one_sec_divisor(s.polling_rate)!
|
||||
: 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
const trans_line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
s.networks?.length || 0 > 0
|
||||
? s.networks!.map((n) => n.transmitted_kb).reduce((p, c) => p + c) /
|
||||
get_to_one_sec_divisor(s.polling_rate)!
|
||||
: 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={recv_line()}>
|
||||
<Grid gap="0" class="card dark shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>network kb/s</h2>
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [
|
||||
{ title: "recv", color: "#41764c", line: recv_line()! },
|
||||
{ title: "send", color: "#952E23", line: trans_line()! },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
@use "../../../../style/colors.scss" as c;
|
||||
|
||||
.Charts {
|
||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||
}
|
||||
|
||||
.LightweightChart {
|
||||
background-color: c.$darkgrey;
|
||||
}
|
||||
@@ -45,10 +45,12 @@ const LightweightChart: Component<{
|
||||
textColor: "white",
|
||||
},
|
||||
grid: {
|
||||
horzLines: { color: "transparent" },
|
||||
vertLines: { color: "transparent" },
|
||||
horzLines: { color: "#3f454d" },
|
||||
vertLines: { color: "#3f454d" },
|
||||
},
|
||||
timeScale: { timeVisible: true },
|
||||
// handleScroll: false,
|
||||
// handleScale: false,
|
||||
});
|
||||
chart.timeScale().fitContent();
|
||||
setChart(chart);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Params, useParams } from "@solidjs/router";
|
||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||
import { Component, createEffect, createSignal, Setter } from "solid-js";
|
||||
import { client, URL } from "../..";
|
||||
import { SystemStats } from "../../types";
|
||||
import { generateQuery } from "../../util/helpers";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import s from "./stats.module.scss";
|
||||
|
||||
const CurrentStats: Component<{}> = (p) => {
|
||||
const params = useParams();
|
||||
const [stats, setStats] = createSignal<SystemStats>();
|
||||
const open = useStatsWs(params, setStats);
|
||||
createEffect(() => {
|
||||
client
|
||||
.get_server_stats(params.id, {
|
||||
networks: true,
|
||||
components: true,
|
||||
processes: true,
|
||||
})
|
||||
.then(setStats);
|
||||
});
|
||||
return (
|
||||
<Grid class={s.Content}>
|
||||
<Flex>
|
||||
<div>cpu:</div>
|
||||
<h2>{}</h2>
|
||||
</Flex>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default CurrentStats;
|
||||
|
||||
function useStatsWs(params: Params, setStats: Setter<SystemStats>) {
|
||||
const ws = new ReconnectingWebSocket(
|
||||
`${URL.replace("http", "ws")}/ws/stats/${params.id}${generateQuery({
|
||||
networks: "true",
|
||||
components: "true",
|
||||
processes: "true",
|
||||
})}`
|
||||
);
|
||||
const [open, setOpen] = createSignal(false);
|
||||
ws.addEventListener("open", () => {
|
||||
// console.log("connection opened");
|
||||
ws.send(client.token!);
|
||||
setOpen(true);
|
||||
});
|
||||
ws.addEventListener("message", ({ data }) => {
|
||||
if (data === "LOGGED_IN") {
|
||||
console.log("logged in to ws");
|
||||
return;
|
||||
}
|
||||
const stats = JSON.parse(data) as SystemStats;
|
||||
console.log(stats);
|
||||
setStats(stats);
|
||||
});
|
||||
ws.addEventListener("close", () => {
|
||||
console.log("stats connection closed");
|
||||
// clearInterval(int);
|
||||
setOpen(false);
|
||||
});
|
||||
return {
|
||||
open,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { Accessor, Component, createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||
import { client } from "../..";
|
||||
import { SystemStatsRecord, Timelength } from "../../types";
|
||||
import { convertTsMsToLocalUnixTsInSecs, get_to_one_sec_divisor } from "../../util/helpers";
|
||||
import { useLocalStorage } from "../../util/hooks";
|
||||
import Icon from "../shared/Icon";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import LightweightChart from "../shared/LightweightChart";
|
||||
import Loading from "../shared/loading/Loading";
|
||||
import Selector from "../shared/menu/Selector";
|
||||
import s from "./stats.module.scss";
|
||||
|
||||
const TIMELENGTHS = [
|
||||
Timelength.OneMinute,
|
||||
Timelength.FiveMinutes,
|
||||
Timelength.FifteenMinutes,
|
||||
Timelength.OneHour,
|
||||
Timelength.SixHours,
|
||||
Timelength.TwelveHours,
|
||||
Timelength.OneDay,
|
||||
];
|
||||
|
||||
const COLORS = {
|
||||
blue: "#184e9f",
|
||||
orange: "#ac5c36",
|
||||
purple: "#5A0B4D",
|
||||
green: "#41764c",
|
||||
red: "#952E23",
|
||||
};
|
||||
|
||||
const VIEWS = [
|
||||
"basic",
|
||||
"i/o",
|
||||
"temp"
|
||||
];
|
||||
|
||||
const HistoricalStats: Component<{}> = (p) => {
|
||||
const params = useParams();
|
||||
const [timelength, setTimelength] = useLocalStorage(
|
||||
Timelength.OneMinute,
|
||||
"server-stats-timelength-v3"
|
||||
);
|
||||
const [view, setView] = useLocalStorage("basic", "historical-stats-view-v1")
|
||||
const [stats, setStats] = createSignal<SystemStatsRecord[]>();
|
||||
const [page, setPage] = createSignal(0);
|
||||
createEffect(() => {
|
||||
client
|
||||
.get_server_stats_history(params.id, {
|
||||
interval: timelength(),
|
||||
page: page(),
|
||||
limit: 1000,
|
||||
networks: true,
|
||||
components: true,
|
||||
})
|
||||
.then(setStats);
|
||||
});
|
||||
return (
|
||||
<Grid class={s.Content}>
|
||||
<Flex alignItems="center">
|
||||
<Flex class="card light shadow" alignItems="center">
|
||||
<button
|
||||
class="darkgrey"
|
||||
onClick={() => {
|
||||
setPage((page) => page + 1);
|
||||
}}
|
||||
>
|
||||
<Icon type="chevron-left" />
|
||||
</button>
|
||||
<button
|
||||
class="darkgrey"
|
||||
onClick={() => {
|
||||
setPage((page) => (page > 0 ? page - 1 : 0));
|
||||
}}
|
||||
>
|
||||
<Icon type="chevron-right" />
|
||||
</button>
|
||||
<button
|
||||
class="darkgrey"
|
||||
onClick={() => {
|
||||
setPage(0);
|
||||
}}
|
||||
>
|
||||
<Icon type="double-chevron-right" />
|
||||
</button>
|
||||
<div>page: {page() + 1}</div>
|
||||
</Flex>
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
selected={timelength()}
|
||||
items={TIMELENGTHS}
|
||||
onSelect={(selected) => {
|
||||
setPage(0);
|
||||
setTimelength(selected as Timelength);
|
||||
}}
|
||||
/>
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
selected={view()}
|
||||
items={VIEWS}
|
||||
onSelect={(selected) => {
|
||||
setView(selected);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Show when={stats()} fallback={<Loading type="three-dot" />}>
|
||||
<Switch>
|
||||
<Match when={view() === "basic"}>
|
||||
<Grid class={s.Charts}>
|
||||
<CpuChart stats={stats} />
|
||||
<MemChart stats={stats} />
|
||||
<DiskChart stats={stats} />
|
||||
</Grid>
|
||||
</Match>
|
||||
<Match when={view() === "i/o"}>
|
||||
<Grid class={s.Charts}>
|
||||
<NetworkChart stats={stats} />
|
||||
</Grid>
|
||||
</Match>
|
||||
<Match when={view() === "temp"}>
|
||||
<Grid class={s.Charts}>
|
||||
<TempuratureChart stats={stats} />
|
||||
</Grid>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const CpuChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value: s.cpu_perc,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card shadow" style={{ height: "fit-content" }}>
|
||||
<h2>cpu</h2>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: "%", color: COLORS.blue, line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const MemChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const [selected, setSelected] = createSignal("%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
selected() === "%"
|
||||
? (100 * s.mem_used_gb) / s.mem_total_gb
|
||||
: s.mem_used_gb,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>memory</h2>
|
||||
{/* <Selector
|
||||
selected={selected()}
|
||||
items={["%", "GB"]}
|
||||
onSelect={setSelected}
|
||||
/> */}
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: selected(), color: COLORS.blue, line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const DiskChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const [selected, setSelected] = createSignal("%");
|
||||
const line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
selected() === "%"
|
||||
? (100 * s.disk.used_gb) / s.disk.total_gb
|
||||
: s.disk.used_gb,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={line()}>
|
||||
<Grid gap="0" class="card shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>disk</h2>
|
||||
{/* <Selector
|
||||
selected={selected()}
|
||||
items={["%", "GB"]}
|
||||
onSelect={setSelected}
|
||||
/> */}
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [{ title: selected(), color: "#184e9f", line: line()! }]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const NetworkChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
const recv_line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
s.networks?.length || 0 > 0
|
||||
? s.networks!.map((n) => n.recieved_kb).reduce((p, c) => p + c) /
|
||||
get_to_one_sec_divisor(s.polling_rate)!
|
||||
: 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
const trans_line = () => {
|
||||
return p.stats()?.map((s) => {
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value:
|
||||
s.networks?.length || 0 > 0
|
||||
? s.networks!.map((n) => n.transmitted_kb).reduce((p, c) => p + c) /
|
||||
get_to_one_sec_divisor(s.polling_rate)!
|
||||
: 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Show when={recv_line()}>
|
||||
<Grid gap="0" class="card shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>network sent kb/s</h2>
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [
|
||||
{ title: "kb/s", color: "#184e9f", line: trans_line()! },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid gap="0" class="card shadow" style={{ height: "fit-content" }}>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>network received kb/s</h2>
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [
|
||||
{ title: "kb/s", color: "#184e9f", line: recv_line()! },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const TempuratureChart: Component<{
|
||||
stats: Accessor<SystemStatsRecord[] | undefined>;
|
||||
}> = (p) => {
|
||||
// const [selected, setSelected] = createSignal(p.stats()![p.stats()!.length - 1].components![0].label);
|
||||
const labels = () => {
|
||||
return p.stats()![p.stats()!.length - 1].components!.map((c) => c.label);
|
||||
};
|
||||
const line = (component: string) => {
|
||||
return p.stats()?.map((s) => {
|
||||
const temp = s.components!.find((c) => c.label === component)?.temp;
|
||||
return {
|
||||
time: convertTsMsToLocalUnixTsInSecs(s.ts),
|
||||
value: temp || 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
return (
|
||||
<For each={labels()}>
|
||||
{(label) => (
|
||||
<Grid
|
||||
gap="0"
|
||||
class="card shadow"
|
||||
style={{ height: "fit-content" }}
|
||||
>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h2>{label}</h2>
|
||||
{/* <Selector
|
||||
selected={selected()}
|
||||
items={labels()}
|
||||
onSelect={setSelected}
|
||||
/> */}
|
||||
</Flex>
|
||||
<LightweightChart
|
||||
class={s.LightweightChart}
|
||||
style={{ height: "200px" }}
|
||||
lines={() => [
|
||||
{ title: "temp", color: "#184e9f", line: line(label)! },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</For>
|
||||
);
|
||||
};
|
||||
|
||||
export default HistoricalStats;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { Component, Match, Switch } from "solid-js";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { useLocalStorage } from "../../util/hooks";
|
||||
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 s from "./stats.module.scss";
|
||||
|
||||
const VIEWS = [
|
||||
"current",
|
||||
"historical"
|
||||
]
|
||||
|
||||
const Stats: Component<{}> = (p) => {
|
||||
const [view, setView] = useLocalStorage("current", "stats-view-v1");
|
||||
return (
|
||||
<Grid class={s.Content}>
|
||||
<Flex alignItems="center">
|
||||
<Header />
|
||||
<Selector
|
||||
targetClass="grey"
|
||||
selected={view()}
|
||||
items={VIEWS}
|
||||
onSelect={setView}
|
||||
/>
|
||||
</Flex>
|
||||
<Switch>
|
||||
<Match when={view() === "current"}>
|
||||
<CurrentStats />
|
||||
</Match>
|
||||
<Match when={view() === "historical"}>
|
||||
<HistoricalStats />
|
||||
</Match>
|
||||
</Switch>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = () => {
|
||||
const { servers } = useAppState();
|
||||
const params = useParams();
|
||||
const server = () => servers.get(params.id);
|
||||
return (
|
||||
<Grid gap="0.1rem">
|
||||
<h1>{server()?.server.name} - system stats</h1>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stats;
|
||||
@@ -0,0 +1,15 @@
|
||||
@use "../../style/colors.scss" as c;
|
||||
|
||||
.Charts {
|
||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||
}
|
||||
|
||||
.LightweightChart {
|
||||
background-color: c.$grey;
|
||||
}
|
||||
|
||||
.Content {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export const URL =
|
||||
? location.origin
|
||||
: (import.meta.env.VITE_MONITOR_HOST as string) || "http://localhost:9000";
|
||||
|
||||
export const WS_URL = URL.replace("http", "ws") + "/ws/update";
|
||||
export const UPDATE_WS_URL = URL.replace("http", "ws") + "/ws/update";
|
||||
|
||||
const token =
|
||||
(import.meta.env.VITE_ACCESS_TOKEN as string) ||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { client, pushNotification, WS_URL } from "..";
|
||||
import { client, pushNotification, UPDATE_WS_URL } from "..";
|
||||
import { State } from "./StateProvider";
|
||||
import { createSignal } from "solid-js";
|
||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
||||
import { Operation, Update, UpdateStatus, UpdateTarget } from "../types";
|
||||
|
||||
function connectToWs(state: State) {
|
||||
const ws = new ReconnectingWebSocket(WS_URL);
|
||||
const ws = new ReconnectingWebSocket(UPDATE_WS_URL);
|
||||
|
||||
const [isOpen, setOpen] = createSignal(false);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
.card {
|
||||
background-color: c.$grey;
|
||||
padding: 0.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card.light {
|
||||
|
||||
Reference in New Issue
Block a user