Skip to content

Commit 3d5724e

Browse files
committed
update
1 parent 93d1194 commit 3d5724e

11 files changed

+362
-5
lines changed

Diff for: .vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.words": [
3+
"Uninstantiated"
4+
]
5+
}

Diff for: web/src/assets/svg/server-outlined.svg

+1
Loading

Diff for: web/src/assets/svg/server.svg

+53
Loading

Diff for: web/src/common/ErrorContent.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from "react";
2+
import { Typography } from "@mui/material";
3+
import clsx from "clsx";
4+
import SentimentVeryDissatisfiedIcon from "@mui/icons-material/SentimentVeryDissatisfied";
5+
6+
interface ErrorContentType {
7+
title: string;
8+
description: string;
9+
className?: string;
10+
onTryAgain?: () => void;
11+
}
12+
13+
/**
14+
*
15+
* @param {ErrorContentProps} props
16+
*/
17+
18+
function ErrorContent(props: ErrorContentType): JSX.Element {
19+
const { title, description, className, onTryAgain, ...rest } = props;
20+
21+
return (
22+
<div
23+
className={clsx(
24+
"p-1 d-flex text-danger justify-content-center align-items-center flex-column w-100",
25+
className
26+
)}
27+
{...rest}
28+
>
29+
<Typography variant="h5" className="font-bold text-center">
30+
{title}
31+
</Typography>
32+
<div>
33+
<SentimentVeryDissatisfiedIcon fontSize="large" />
34+
</div>
35+
<Typography variant="body2" className="text-center mb-4 font-bold w-100">
36+
{description}
37+
</Typography>
38+
</div>
39+
);
40+
}
41+
42+
ErrorContent.defaultProps = {
43+
title: "Something went wrong",
44+
description: "We're quite sorry about this!",
45+
};
46+
47+
export default ErrorContent;
48+
49+
/**
50+
* @typedef {{
51+
* onTryAgain: Function
52+
* } & import("@mui/material").PaperProps} ErrorContentProps
53+
*/

Diff for: web/src/common/ErrorDialog.tsx

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from "react";
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogContentText,
6+
DialogTitle,
7+
DialogActions,
8+
Button,
9+
} from "@mui/material";
10+
11+
interface ErrorDialogProps {
12+
title?: string;
13+
description?: string;
14+
onRetry?: () => void;
15+
retryText?: string;
16+
}
17+
/**
18+
*
19+
* @param {ErrorDialogProps} props
20+
*/
21+
export function ErrorDialog(props: ErrorDialogProps) {
22+
const { title, description, onRetry, retryText, ...rest } = props;
23+
function handleRetry(e: any) {
24+
e.stopPropagation();
25+
if (onRetry) {
26+
onRetry();
27+
}
28+
}
29+
30+
return (
31+
<Dialog open={true} fullWidth {...rest}>
32+
<DialogTitle>{title}</DialogTitle>
33+
<DialogContent>
34+
<DialogContentText>{description}</DialogContentText>
35+
</DialogContent>
36+
<DialogActions>
37+
<Button onClick={handleRetry}>{retryText}</Button>
38+
</DialogActions>
39+
</Dialog>
40+
);
41+
}
42+
43+
ErrorDialog.defaultProps = {
44+
title: "Something went wrong",
45+
description:
46+
"Sorry, something went wrong, Please try again later or contact our support.",
47+
retryText: "Try Again",
48+
};
49+
50+
export default ErrorDialog;
51+
52+
/**
53+
* @typedef {import("./ErrorDialogContext").ErrorOptions & import("@material-ui/core").DialogProps} ErrorDialogProps
54+
*/

Diff for: web/src/common/LoadingContent.tsx

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React, { FC, ReactNode, useEffect } from "react";
2+
import useDataRef from "hooks/useDataRef";
3+
import LoadingIndicator from "./LoadingIndicator";
4+
import ErrorContent from "common/ErrorContent";
5+
import { Box } from "@mui/material";
6+
7+
interface LoadingContentType {
8+
size: number;
9+
error: boolean;
10+
loading: boolean;
11+
children: JSX.Element | (() => JSX.Element);
12+
onReload: () => void;
13+
onMount: () => void;
14+
loadingContent: JSX.Element | ((x: JSX.Element) => ReactNode);
15+
errorContent: JSX.Element | ((x: JSX.Element) => ReactNode);
16+
className: string;
17+
}
18+
/**
19+
*
20+
* @param {LoadingContentProps} props
21+
*/
22+
function LoadingContent(props: Partial<LoadingContentType>): JSX.Element {
23+
const {
24+
size,
25+
error,
26+
loading,
27+
children,
28+
onReload,
29+
onMount,
30+
loadingContent,
31+
errorContent,
32+
className,
33+
...rest
34+
} = props;
35+
36+
const dataRef = useDataRef({ onReload, onMount });
37+
38+
useEffect(() => {
39+
dataRef.current.onMount?.();
40+
}, [dataRef]);
41+
42+
if (!loading && !error) {
43+
if (children !== undefined) {
44+
return typeof children === "function" ? children() : children;
45+
}
46+
}
47+
48+
const defaultLoadingContent = <LoadingIndicator size={size} />;
49+
50+
const defaultErrorContent = <ErrorContent onTryAgain={() => onReload?.()} />;
51+
52+
return (
53+
<Box
54+
display={"flex"}
55+
justifyContent={"center"}
56+
alignItems={"center"}
57+
p={2}
58+
{...rest}
59+
>
60+
{error ? (
61+
<>
62+
{errorContent
63+
? typeof errorContent === "function"
64+
? errorContent(defaultErrorContent)
65+
: errorContent
66+
: defaultErrorContent}
67+
</>
68+
) : loadingContent ? (
69+
typeof loadingContent === "function" ? (
70+
loadingContent(defaultLoadingContent)
71+
) : (
72+
loadingContent
73+
)
74+
) : (
75+
defaultLoadingContent
76+
)}
77+
</Box>
78+
);
79+
}
80+
81+
LoadingContent.defaultProps = {
82+
size: 40,
83+
children: null,
84+
};
85+
86+
export default LoadingContent;
87+
88+
/**
89+
* @typedef {{
90+
* size: string | number,
91+
* onMount: Function,
92+
* onReload: Function,
93+
* error: boolean,
94+
* loading: boolean,
95+
* errorContent: React.ReactNode,
96+
* loadingContent: React.ReactNode,
97+
* } & React.ComponentPropsWithoutRef<'div'>} LoadingContentProps
98+
*/

Diff for: web/src/common/LoadingIndicator.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CircularProgress } from "@mui/material";
2+
import React from "react";
3+
4+
/**
5+
*
6+
* @param {import("@mui/material").CircularProgressProps} props
7+
*/
8+
function LoadingIndicator(props: { [rest: string]: any }) {
9+
return <CircularProgress {...props}></CircularProgress>;
10+
}
11+
12+
export default LoadingIndicator;

Diff for: web/src/hooks/useDataRef.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useRef } from "react";
2+
3+
/**
4+
* @template T
5+
* @param {T} data
6+
*/
7+
function useDataRef(data: any) {
8+
const ref = useRef(data);
9+
ref.current = data;
10+
return ref;
11+
}
12+
13+
export default useDataRef;

Diff for: web/src/server/ServerList.tsx

+56-5
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,78 @@
1-
import React from "react";
1+
import { Card, CardActionArea, Grid, Typography } from "@mui/material";
2+
import { Box, Container } from "@mui/system";
3+
import React, { useState } from "react";
24
import useWebSocket, { ReadyState } from "react-use-websocket";
5+
import { ReactComponent as ServerIcon } from "../assets/svg/server.svg";
6+
import LoadingContent from "../common/LoadingContent";
7+
import ThemeConfig from "../ThemeConfig";
8+
import { ServerResponse } from "./ServerType";
39

410
export default function ServerList() {
11+
const [servers, setServers] = useState<ServerResponse[]>([]);
512
const { sendJsonMessage, getWebSocket, readyState } = useWebSocket(
613
`ws://localhost:3000/metrics`,
714
{
815
onOpen: () => console.log("WebSocket connection opened."),
916
onClose: () => console.log("WebSocket connection closed."),
1017
shouldReconnect: (closeEvent) => true,
11-
onMessage: (event: WebSocketEventMap["message"]) =>
12-
console.log("new Data", event.data),
18+
onMessage: (event: WebSocketEventMap["message"]) => {
19+
const newMessage: ServerResponse = JSON.parse(event.data);
20+
setServers((prev: ServerResponse[]) => {
21+
if (!newMessage.Error) {
22+
return prev.concat(newMessage);
23+
}
24+
});
25+
},
1326
}
1427
);
1528
getWebSocket();
1629

17-
const connectionStatus = {
30+
const connectionStatus: string = {
1831
[ReadyState.CONNECTING]: "Connecting",
1932
[ReadyState.OPEN]: "Open",
2033
[ReadyState.CLOSING]: "Closing",
2134
[ReadyState.CLOSED]: "Closed",
2235
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
2336
}[readyState];
2437

38+
console.log(servers);
2539
// console.log("getWebsocket", getWebSocket()?.OPEN);
26-
return <div>ServerList</div>;
40+
return (
41+
<Container>
42+
<LoadingContent
43+
loading={connectionStatus === "Connecting"}
44+
error={connectionStatus === "Closed"}
45+
>
46+
<Grid container spacing={2} my={10}>
47+
{servers.map((server: any, index: number) => (
48+
<Grid item xs={6} md={4}>
49+
<Card key={index}>
50+
<CardActionArea>
51+
<Box
52+
display={"flex"}
53+
justifyContent="center"
54+
alignItems="center"
55+
flexDirection="column"
56+
>
57+
<ServerIcon width={"100px"} />
58+
<Typography
59+
textTransform={"capitalize"}
60+
mb={2}
61+
noWrap
62+
fontWeight={600}
63+
>
64+
localhost -{" "}
65+
<span style={{ color: ThemeConfig.palette.success.dark }}>
66+
linux
67+
</span>
68+
</Typography>
69+
</Box>
70+
</CardActionArea>
71+
</Card>
72+
</Grid>
73+
))}
74+
</Grid>
75+
</LoadingContent>
76+
</Container>
77+
);
2778
}

Diff for: web/src/server/ServerType.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface ServerResponse {
2+
Error: boolean;
3+
Message: {
4+
Host: string;
5+
Name:
6+
| "disk"
7+
| "docker"
8+
| "uptime"
9+
| "memory"
10+
| "process"
11+
| "loadavg"
12+
| "tcp";
13+
Platform: string;
14+
Data: Object;
15+
};
16+
}

0 commit comments

Comments
 (0)