feat: add server stats

This commit is contained in:
2024-11-13 22:45:03 +00:00
parent 2d4c81e15d
commit 7a00992ff9
12 changed files with 452 additions and 32 deletions
@@ -3,6 +3,8 @@ import Terminal from "./terminal";
import VNCViewer from "./vncviewer";
import { useAuthStore } from "@/stores/auth";
import { AppServer, useServer } from "@/stores/app";
import { useWebsocketUrl } from "@/hooks/useWebsocket";
import ServerStatsBar from "./server-stats-bar";
type SSHSessionProps = {
type: "ssh";
@@ -30,20 +32,25 @@ export type InteractiveSessionProps = {
const InteractiveSession = ({ type, params }: InteractiveSessionProps) => {
const { token } = useAuthStore();
const server = useServer();
const query = new URLSearchParams({ ...params, sid: token || "" });
const url = `${getBaseUrl(server)}/ws/term?${query}`;
const ws = useWebsocketUrl({ ...params, sid: token || "" });
const termUrl = ws("term");
const statsUrl = ws("stats");
switch (type) {
case "ssh":
return <Terminal url={url} />;
return (
<>
<Terminal url={termUrl} />
<ServerStatsBar url={statsUrl} />
</>
);
case "pve":
case "incus":
return params.client === "vnc" ? (
<VNCViewer url={url} />
<VNCViewer url={termUrl} />
) : (
<Terminal url={url} />
<Terminal url={termUrl} />
);
default:
@@ -51,8 +58,4 @@ const InteractiveSession = ({ type, params }: InteractiveSessionProps) => {
}
};
function getBaseUrl(server?: AppServer | null) {
return server?.url.replace("http://", "ws://") || "";
}
export default InteractiveSession;
@@ -0,0 +1,84 @@
import { View, Text, XStack, Separator } from "tamagui";
import React, { useState } from "react";
import { useWebSocket } from "@/hooks/useWebsocket";
import Icons from "../ui/icons";
type Props = {
url: string;
};
const ServerStatsBar = ({ url }: Props) => {
const [cpu, setCPU] = useState(0);
const [memory, setMemory] = useState({ total: 0, used: 0, available: 0 });
const [disk, setDisk] = useState({ total: "0", used: "0", percent: "0%" });
const [network, setNetwork] = useState({ tx: 0, rx: 0 });
const { isConnected } = useWebSocket(url, {
onMessage: (msg) => {
const type = msg.substring(0, 1);
const value = msg.substring(1);
let values: string[];
switch (type) {
case "\x01":
setCPU(parseFloat(value));
break;
case "\x02":
values = value.split(",");
const total = parseInt(values[0]) || 0;
const available = parseInt(values[1]) || 0;
const used = total - available;
setMemory({ total, used, available });
break;
case "\x03":
values = value.split(",");
setDisk({ total: values[0], used: values[1], percent: values[2] });
break;
case "\x04":
values = value.split(",");
setNetwork({
tx: parseInt(values[0]) || 0,
rx: parseInt(values[1]) || 0,
});
break;
}
},
});
if (!isConnected || !memory.total) {
return null;
}
return (
<XStack gap="$1" p="$2" alignItems="center">
<XStack gap="$1" alignItems="center" minWidth={48}>
<Icons name="desktop-tower" size={16} />
<Text fontSize="$2">{Math.round(cpu)}%</Text>
</XStack>
<Separator vertical h="100%" mx="$2" borderColor="$color" />
<Icons name="memory" size={16} />
<Text fontSize="$2">
{memory.used} MB / {memory.total} MB (
{Math.round((memory.used / memory.total) * 100) || 0}%)
</Text>
<Separator vertical h="100%" mx="$2" borderColor="$color" />
<Icons name="harddisk" size={16} />
<Text fontSize="$2">
{disk.used} / {disk.total} ({disk.percent})
</Text>
<Separator vertical h="100%" mx="$2" borderColor="$color" />
<Icons name="download" size={16} />
<Text fontSize="$2">{network.rx} MB</Text>
<Icons name="upload" size={16} />
<Text fontSize="$2">{network.tx} MB</Text>
</XStack>
);
};
export default ServerStatsBar;
+59
View File
@@ -0,0 +1,59 @@
import { useServer } from "@/stores/app";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
type UseWebsocketOptions = {
onMessage?: (message: string) => void;
};
export const useWebSocket = (url: string, opt?: UseWebsocketOptions) => {
const [isConnected, setIsConnected] = useState(false);
const websocketRef = useRef<WebSocket | null>(null);
useEffect(() => {
// Create WebSocket connection
const ws = new WebSocket(url);
websocketRef.current = ws;
// Connection opened
ws.onopen = () => {
setIsConnected(true);
console.log("WebSocket connected");
};
// Listen for messages
ws.onmessage = (event) => {
opt?.onMessage?.(event.data);
};
// Connection closed
ws.onclose = () => {
setIsConnected(false);
console.log("WebSocket disconnected");
};
// Cleanup on unmount
return () => {
ws.close();
};
}, [url]);
// Send message function
const send = (msg: string) => {
if (isConnected && websocketRef.current) {
websocketRef.current.send(msg);
}
};
return { isConnected, send };
};
export const useWebsocketUrl = (initParams: any = {}) => {
const server = useServer();
const baseUrl = server?.url.replace("http://", "ws://") || "";
return (url: string, params: any = {}) => {
const query = new URLSearchParams({ ...initParams, ...params });
return `${baseUrl}/ws/${url}?${query}`;
};
};