From af948edbeae5b71900c420c188b033347bb87fa3 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sun, 8 Jan 2023 07:50:47 +0000 Subject: [PATCH] make standalone stats page --- frontend/src/App.tsx | 2 + frontend/src/components/home/Tree/Server.tsx | 6 +- frontend/src/components/server/tabs/Tabs.tsx | 5 - .../components/server/tabs/stats/Stats.tsx | 300 ---------------- .../server/tabs/stats/stats.module.scss | 9 - .../components/shared/LightweightChart.tsx | 6 +- .../src/components/stats/CurrentStats.tsx | 67 ++++ .../src/components/stats/HistoricalStats.tsx | 331 ++++++++++++++++++ frontend/src/components/stats/Stats.tsx | 53 +++ .../src/components/stats/stats.module.scss | 15 + frontend/src/index.tsx | 2 +- frontend/src/state/ws.ts | 4 +- frontend/src/style/app.scss | 2 +- 13 files changed, 480 insertions(+), 322 deletions(-) delete mode 100644 frontend/src/components/server/tabs/stats/Stats.tsx delete mode 100644 frontend/src/components/server/tabs/stats/stats.module.scss create mode 100644 frontend/src/components/stats/CurrentStats.tsx create mode 100644 frontend/src/components/stats/HistoricalStats.tsx create mode 100644 frontend/src/components/stats/Stats.tsx create mode 100644 frontend/src/components/stats/stats.module.scss diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 19e978980..39e9baa5e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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 = () => { + diff --git a/frontend/src/components/home/Tree/Server.tsx b/frontend/src/components/home/Tree/Server.tsx index a8c6fa045..21f1a9665 100644 --- a/frontend/src/components/home/Tree/Server.tsx +++ b/frontend/src/components/home/Tree/Server.tsx @@ -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) => { - {/* */} + + + diff --git a/frontend/src/components/server/tabs/Tabs.tsx b/frontend/src/components/server/tabs/Tabs.tsx index eecb355f9..955772094 100644 --- a/frontend/src/components/server/tabs/Tabs.tsx +++ b/frontend/src/components/server/tabs/Tabs.tsx @@ -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: () => , }, - { - title: "stats", - element: () => , - }, user().admin && { title: "collaborators", element: () => , diff --git a/frontend/src/components/server/tabs/stats/Stats.tsx b/frontend/src/components/server/tabs/stats/Stats.tsx deleted file mode 100644 index dd21748b0..000000000 --- a/frontend/src/components/server/tabs/stats/Stats.tsx +++ /dev/null @@ -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(); - const [loadingCurr, setLoadingCurr] = createSignal(false); - const [stats, setStats] = createSignal(); - 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 ( - - - - }> - - cpu:

{currStats()!.cpu_perc.toFixed(1)}%

-
- - mem: -
{currStats()!.mem_total_gb.toFixed(1)} GB
-

- {( - (100 * currStats()!.mem_used_gb) / - currStats()!.mem_total_gb - ).toFixed(1)} - % full -

-
- - disk: -
{currStats()!.disk.total_gb.toFixed(1)} GB
-

- {( - (100 * currStats()!.disk.used_gb) / - currStats()!.disk.total_gb - ).toFixed(1)} - % full -

-
- -
-
- - - - -
page: {page() + 1}
-
- setTimelength(selected as Timelength)} - /> -
- - - - } - > - - - - - - - -
- ); -}; - -export default Stats; - -const CpuChart: Component<{ - stats: Accessor; -}> = (p) => { - const line = () => { - return p.stats()?.map((s) => { - return { - time: convertTsMsToLocalUnixTsInSecs(s.ts), - value: s.cpu_perc, - }; - }); - }; - return ( - - -

cpu

- [{ title: "%", color: "#184e9f", line: line()! }]} - /> -
-
- ); -}; - -const MemChart: Component<{ - stats: Accessor; -}> = (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 ( - - - -

memory

- -
- [{ title: selected(), color: "#184e9f", line: line()! }]} - /> -
-
- ); -}; - -const DiskChart: Component<{ - stats: Accessor; -}> = (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 ( - - - -

disk

- -
- [{ title: selected(), color: "#184e9f", line: line()! }]} - /> -
-
- ); -}; - -const NetworkChart: Component<{ - stats: Accessor; -}> = (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 ( - - - -

network kb/s

-
- [ - { title: "recv", color: "#41764c", line: recv_line()! }, - { title: "send", color: "#952E23", line: trans_line()! }, - ]} - /> -
-
- ); -}; diff --git a/frontend/src/components/server/tabs/stats/stats.module.scss b/frontend/src/components/server/tabs/stats/stats.module.scss deleted file mode 100644 index f80dc529d..000000000 --- a/frontend/src/components/server/tabs/stats/stats.module.scss +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/frontend/src/components/shared/LightweightChart.tsx b/frontend/src/components/shared/LightweightChart.tsx index aa79d7ea5..cbd6b62cc 100644 --- a/frontend/src/components/shared/LightweightChart.tsx +++ b/frontend/src/components/shared/LightweightChart.tsx @@ -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); diff --git a/frontend/src/components/stats/CurrentStats.tsx b/frontend/src/components/stats/CurrentStats.tsx new file mode 100644 index 000000000..0d8d3f258 --- /dev/null +++ b/frontend/src/components/stats/CurrentStats.tsx @@ -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(); + const open = useStatsWs(params, setStats); + createEffect(() => { + client + .get_server_stats(params.id, { + networks: true, + components: true, + processes: true, + }) + .then(setStats); + }); + return ( + + +
cpu:
+

{}

+
+
+ ); +}; + +export default CurrentStats; + +function useStatsWs(params: Params, setStats: Setter) { + 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, + }; +} diff --git a/frontend/src/components/stats/HistoricalStats.tsx b/frontend/src/components/stats/HistoricalStats.tsx new file mode 100644 index 000000000..ab5fd8dd0 --- /dev/null +++ b/frontend/src/components/stats/HistoricalStats.tsx @@ -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(); + 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 ( + + + + + + +
page: {page() + 1}
+
+ { + setPage(0); + setTimelength(selected as Timelength); + }} + /> + { + setView(selected); + }} + /> +
+ }> + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +const CpuChart: Component<{ + stats: Accessor; +}> = (p) => { + const line = () => { + return p.stats()?.map((s) => { + return { + time: convertTsMsToLocalUnixTsInSecs(s.ts), + value: s.cpu_perc, + }; + }); + }; + return ( + + +

cpu

+ [{ title: "%", color: COLORS.blue, line: line()! }]} + /> +
+
+ ); +}; + +const MemChart: Component<{ + stats: Accessor; +}> = (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 ( + + + +

memory

+ {/* */} +
+ [{ title: selected(), color: COLORS.blue, line: line()! }]} + /> +
+
+ ); +}; + +const DiskChart: Component<{ + stats: Accessor; +}> = (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 ( + + + +

disk

+ {/* */} +
+ [{ title: selected(), color: "#184e9f", line: line()! }]} + /> +
+
+ ); +}; + +const NetworkChart: Component<{ + stats: Accessor; +}> = (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 ( + + + +

network sent kb/s

+
+ [ + { title: "kb/s", color: "#184e9f", line: trans_line()! }, + ]} + /> +
+ + +

network received kb/s

+
+ [ + { title: "kb/s", color: "#184e9f", line: recv_line()! }, + ]} + /> +
+
+ ); +}; + +const TempuratureChart: Component<{ + stats: Accessor; +}> = (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 ( + + {(label) => ( + + +

{label}

+ {/* */} +
+ [ + { title: "temp", color: "#184e9f", line: line(label)! }, + ]} + /> +
+ )} +
+ ); +}; + +export default HistoricalStats; diff --git a/frontend/src/components/stats/Stats.tsx b/frontend/src/components/stats/Stats.tsx new file mode 100644 index 000000000..3c053f7e7 --- /dev/null +++ b/frontend/src/components/stats/Stats.tsx @@ -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 ( + + +
+ + + + + + + + + + + + ); +}; + +export const Header = () => { + const { servers } = useAppState(); + const params = useParams(); + const server = () => servers.get(params.id); + return ( + +

{server()?.server.name} - system stats

+
+ ); +} + +export default Stats; diff --git a/frontend/src/components/stats/stats.module.scss b/frontend/src/components/stats/stats.module.scss new file mode 100644 index 000000000..4dc36c432 --- /dev/null +++ b/frontend/src/components/stats/stats.module.scss @@ -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; +} \ No newline at end of file diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 5b006129f..8853336d4 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -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) || diff --git a/frontend/src/state/ws.ts b/frontend/src/state/ws.ts index 9373039aa..75ecbb7b9 100644 --- a/frontend/src/state/ws.ts +++ b/frontend/src/state/ws.ts @@ -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); diff --git a/frontend/src/style/app.scss b/frontend/src/style/app.scss index 5bd7d81a9..6d129dcd8 100644 --- a/frontend/src/style/app.scss +++ b/frontend/src/style/app.scss @@ -28,7 +28,7 @@ .card { background-color: c.$grey; - padding: 0.5rem; + padding: 1rem; } .card.light {