diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0e5f5fb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "cSpell.words": [
+ "Uninstantiated"
+ ]
+}
\ No newline at end of file
diff --git a/web/src/assets/svg/server-outlined.svg b/web/src/assets/svg/server-outlined.svg
new file mode 100644
index 0000000..ce67aad
--- /dev/null
+++ b/web/src/assets/svg/server-outlined.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svg/server.svg b/web/src/assets/svg/server.svg
new file mode 100644
index 0000000..1b2e8c5
--- /dev/null
+++ b/web/src/assets/svg/server.svg
@@ -0,0 +1,53 @@
+
+
+
diff --git a/web/src/common/ErrorContent.tsx b/web/src/common/ErrorContent.tsx
new file mode 100644
index 0000000..e04a996
--- /dev/null
+++ b/web/src/common/ErrorContent.tsx
@@ -0,0 +1,53 @@
+import React from "react";
+import { Typography } from "@mui/material";
+import clsx from "clsx";
+import SentimentVeryDissatisfiedIcon from "@mui/icons-material/SentimentVeryDissatisfied";
+
+interface ErrorContentType {
+ title: string;
+ description: string;
+ className?: string;
+ onTryAgain?: () => void;
+}
+
+/**
+ *
+ * @param {ErrorContentProps} props
+ */
+
+function ErrorContent(props: ErrorContentType): JSX.Element {
+ const { title, description, className, onTryAgain, ...rest } = props;
+
+ return (
+
+
+ {title}
+
+
+
+
+
+ {description}
+
+
+ );
+}
+
+ErrorContent.defaultProps = {
+ title: "Something went wrong",
+ description: "We're quite sorry about this!",
+};
+
+export default ErrorContent;
+
+/**
+ * @typedef {{
+ * onTryAgain: Function
+ * } & import("@mui/material").PaperProps} ErrorContentProps
+ */
diff --git a/web/src/common/ErrorDialog.tsx b/web/src/common/ErrorDialog.tsx
new file mode 100644
index 0000000..7861588
--- /dev/null
+++ b/web/src/common/ErrorDialog.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ DialogActions,
+ Button,
+} from "@mui/material";
+
+interface ErrorDialogProps {
+ title?: string;
+ description?: string;
+ onRetry?: () => void;
+ retryText?: string;
+}
+/**
+ *
+ * @param {ErrorDialogProps} props
+ */
+export function ErrorDialog(props: ErrorDialogProps) {
+ const { title, description, onRetry, retryText, ...rest } = props;
+ function handleRetry(e: any) {
+ e.stopPropagation();
+ if (onRetry) {
+ onRetry();
+ }
+ }
+
+ return (
+
+ );
+}
+
+ErrorDialog.defaultProps = {
+ title: "Something went wrong",
+ description:
+ "Sorry, something went wrong, Please try again later or contact our support.",
+ retryText: "Try Again",
+};
+
+export default ErrorDialog;
+
+/**
+ * @typedef {import("./ErrorDialogContext").ErrorOptions & import("@material-ui/core").DialogProps} ErrorDialogProps
+ */
diff --git a/web/src/common/LoadingContent.tsx b/web/src/common/LoadingContent.tsx
new file mode 100644
index 0000000..bb1940f
--- /dev/null
+++ b/web/src/common/LoadingContent.tsx
@@ -0,0 +1,98 @@
+import React, { FC, ReactNode, useEffect } from "react";
+import useDataRef from "hooks/useDataRef";
+import LoadingIndicator from "./LoadingIndicator";
+import ErrorContent from "common/ErrorContent";
+import { Box } from "@mui/material";
+
+interface LoadingContentType {
+ size: number;
+ error: boolean;
+ loading: boolean;
+ children: JSX.Element | (() => JSX.Element);
+ onReload: () => void;
+ onMount: () => void;
+ loadingContent: JSX.Element | ((x: JSX.Element) => ReactNode);
+ errorContent: JSX.Element | ((x: JSX.Element) => ReactNode);
+ className: string;
+}
+/**
+ *
+ * @param {LoadingContentProps} props
+ */
+function LoadingContent(props: Partial): JSX.Element {
+ const {
+ size,
+ error,
+ loading,
+ children,
+ onReload,
+ onMount,
+ loadingContent,
+ errorContent,
+ className,
+ ...rest
+ } = props;
+
+ const dataRef = useDataRef({ onReload, onMount });
+
+ useEffect(() => {
+ dataRef.current.onMount?.();
+ }, [dataRef]);
+
+ if (!loading && !error) {
+ if (children !== undefined) {
+ return typeof children === "function" ? children() : children;
+ }
+ }
+
+ const defaultLoadingContent = ;
+
+ const defaultErrorContent = onReload?.()} />;
+
+ return (
+
+ {error ? (
+ <>
+ {errorContent
+ ? typeof errorContent === "function"
+ ? errorContent(defaultErrorContent)
+ : errorContent
+ : defaultErrorContent}
+ >
+ ) : loadingContent ? (
+ typeof loadingContent === "function" ? (
+ loadingContent(defaultLoadingContent)
+ ) : (
+ loadingContent
+ )
+ ) : (
+ defaultLoadingContent
+ )}
+
+ );
+}
+
+LoadingContent.defaultProps = {
+ size: 40,
+ children: null,
+};
+
+export default LoadingContent;
+
+/**
+ * @typedef {{
+ * size: string | number,
+ * onMount: Function,
+ * onReload: Function,
+ * error: boolean,
+ * loading: boolean,
+ * errorContent: React.ReactNode,
+ * loadingContent: React.ReactNode,
+ * } & React.ComponentPropsWithoutRef<'div'>} LoadingContentProps
+ */
diff --git a/web/src/common/LoadingIndicator.tsx b/web/src/common/LoadingIndicator.tsx
new file mode 100644
index 0000000..458c974
--- /dev/null
+++ b/web/src/common/LoadingIndicator.tsx
@@ -0,0 +1,12 @@
+import { CircularProgress } from "@mui/material";
+import React from "react";
+
+/**
+ *
+ * @param {import("@mui/material").CircularProgressProps} props
+ */
+function LoadingIndicator(props: { [rest: string]: any }) {
+ return ;
+}
+
+export default LoadingIndicator;
diff --git a/web/src/hooks/useDataRef.tsx b/web/src/hooks/useDataRef.tsx
new file mode 100644
index 0000000..092cd0e
--- /dev/null
+++ b/web/src/hooks/useDataRef.tsx
@@ -0,0 +1,13 @@
+import { useRef } from "react";
+
+/**
+ * @template T
+ * @param {T} data
+ */
+function useDataRef(data: any) {
+ const ref = useRef(data);
+ ref.current = data;
+ return ref;
+}
+
+export default useDataRef;
diff --git a/web/src/server/ServerList.tsx b/web/src/server/ServerList.tsx
index 01e900f..8a85bcf 100644
--- a/web/src/server/ServerList.tsx
+++ b/web/src/server/ServerList.tsx
@@ -1,20 +1,33 @@
-import React from "react";
+import { Card, CardActionArea, Grid, Typography } from "@mui/material";
+import { Box, Container } from "@mui/system";
+import React, { useState } from "react";
import useWebSocket, { ReadyState } from "react-use-websocket";
+import { ReactComponent as ServerIcon } from "../assets/svg/server.svg";
+import LoadingContent from "../common/LoadingContent";
+import ThemeConfig from "../ThemeConfig";
+import { ServerResponse } from "./ServerType";
export default function ServerList() {
+ const [servers, setServers] = useState([]);
const { sendJsonMessage, getWebSocket, readyState } = useWebSocket(
`ws://localhost:3000/metrics`,
{
onOpen: () => console.log("WebSocket connection opened."),
onClose: () => console.log("WebSocket connection closed."),
shouldReconnect: (closeEvent) => true,
- onMessage: (event: WebSocketEventMap["message"]) =>
- console.log("new Data", event.data),
+ onMessage: (event: WebSocketEventMap["message"]) => {
+ const newMessage: ServerResponse = JSON.parse(event.data);
+ setServers((prev: ServerResponse[]) => {
+ if (!newMessage.Error) {
+ return prev.concat(newMessage);
+ }
+ });
+ },
}
);
getWebSocket();
- const connectionStatus = {
+ const connectionStatus: string = {
[ReadyState.CONNECTING]: "Connecting",
[ReadyState.OPEN]: "Open",
[ReadyState.CLOSING]: "Closing",
@@ -22,6 +35,44 @@ export default function ServerList() {
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
}[readyState];
+ console.log(servers);
// console.log("getWebsocket", getWebSocket()?.OPEN);
- return ServerList
;
+ return (
+
+
+
+ {servers.map((server: any, index: number) => (
+
+
+
+
+
+
+ localhost -{" "}
+
+ linux
+
+
+
+
+
+
+ ))}
+
+
+
+ );
}
diff --git a/web/src/server/ServerType.tsx b/web/src/server/ServerType.tsx
new file mode 100644
index 0000000..d3fb706
--- /dev/null
+++ b/web/src/server/ServerType.tsx
@@ -0,0 +1,16 @@
+export interface ServerResponse {
+ Error: boolean;
+ Message: {
+ Host: string;
+ Name:
+ | "disk"
+ | "docker"
+ | "uptime"
+ | "memory"
+ | "process"
+ | "loadavg"
+ | "tcp";
+ Platform: string;
+ Data: Object;
+ };
+}
diff --git a/web/tsconfig.json b/web/tsconfig.json
index a273b0c..9aa2001 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -6,6 +6,7 @@
"dom.iterable",
"esnext"
],
+ "baseUrl": "src",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,