Skip to content

Commit 2a79953

Browse files
committed
FEAT: page header
1 parent 9110b13 commit 2a79953

File tree

4 files changed

+163
-35
lines changed

4 files changed

+163
-35
lines changed

web/src/common/PageHeader.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.PageHeader {
2+
display: flex;
3+
justify-content: space-between;
4+
align-items: center;
5+
padding: 10px 0 10px 0;
6+
flex-wrap: wrap;
7+
}
8+
9+
.PageHeader__title {
10+
font-weight: 800;
11+
}

web/src/common/PageHeader.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
Breadcrumbs,
3+
Typography,
4+
Link as MuiLink,
5+
Toolbar,
6+
} from "@mui/material";
7+
import { Link } from "react-router-dom";
8+
import clsx from "clsx";
9+
import "./PageHeader.css";
10+
11+
interface BreadCrumbType {
12+
name: string;
13+
to?: string;
14+
}
15+
16+
interface PageHeaderType {
17+
title: string;
18+
className?: string;
19+
breadcrumbs: BreadCrumbType[];
20+
children?: React.ReactElement;
21+
classes?: {
22+
root: string;
23+
title: string;
24+
content: string;
25+
rootContent: string;
26+
};
27+
beforeTitle?: React.ReactElement;
28+
}
29+
/**
30+
*
31+
* @param {PageHeaderProps} props
32+
*/
33+
function PageHeader(props: PageHeaderType) {
34+
const {
35+
title,
36+
className,
37+
breadcrumbs,
38+
children,
39+
classes,
40+
beforeTitle,
41+
...rest
42+
} = props;
43+
return (
44+
<>
45+
<Toolbar
46+
disableGutters
47+
className={clsx("PageHeader", className, classes?.root)}
48+
{...rest}
49+
>
50+
{beforeTitle}
51+
<Typography
52+
variant="h5"
53+
className={clsx("PageHeader__title", classes?.title)}
54+
>
55+
{title}
56+
</Typography>
57+
<div className={clsx("PageHeader__content", classes?.rootContent)}>
58+
{children}
59+
</div>
60+
<div className="flex-1" />
61+
{!!breadcrumbs.length && (
62+
<Breadcrumbs>
63+
{breadcrumbs.map((breadcrumb: BreadCrumbType, key: number) => {
64+
const isPage = key === breadcrumbs.length - 1;
65+
66+
if (isPage) {
67+
return <Typography key={key}>{breadcrumb.name}</Typography>;
68+
}
69+
70+
return (
71+
<MuiLink key={key} component={Link} to={breadcrumb.to || "#"}>
72+
{breadcrumb.name}
73+
</MuiLink>
74+
);
75+
})}
76+
</Breadcrumbs>
77+
)}
78+
</Toolbar>
79+
{!!children && (
80+
<Toolbar
81+
disableGutters
82+
className={clsx("PageHeader-content", classes?.content)}
83+
>
84+
{children}
85+
</Toolbar>
86+
)}
87+
</>
88+
);
89+
}
90+
91+
PageHeader.defaultProps = {
92+
breadcrumbs: [],
93+
classes: {},
94+
};
95+
96+
export default PageHeader;
97+
98+
/**
99+
* @typedef {{
100+
* breadcrumbs: {name: string, to: string}[];
101+
* classes: {root: string; title: string; content: string; rootContent: string}
102+
* beforeTitle: import("react").ReactNode
103+
* } & import("react").ComponentPropsWithoutRef<'div'>} PageHeaderProps
104+
*/

web/src/server/ServerDetail.tsx

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,32 @@ import {
99
} from "./ServerType";
1010
import { Pie } from "react-chartjs-2";
1111
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
12+
import Tabs from "@mui/material/Tabs";
13+
import Tab from "@mui/material/Tab";
14+
import Typography from "@mui/material/Typography";
15+
import Box from "@mui/material/Box";
16+
import PageHeader from "common/PageHeader";
17+
18+
interface TabPanelProps {
19+
children?: React.ReactNode;
20+
index: number;
21+
value: number;
22+
}
1223

1324
ChartJS.register(ArcElement, Tooltip, Legend);
1425

1526
const wssMetricsBaseURL = `${process.env.REACT_APP_WS_BASE_URL}/metrics`;
1627

28+
function a11yProps(index: number) {
29+
return {
30+
id: `simple-tab-${index}`,
31+
"aria-controls": `simple-tabpanel-${index}`,
32+
};
33+
}
34+
1735
export default function ServerDetail() {
1836
const { host } = useParams<{ host: string }>();
37+
const [tabIndex, setTabIndex] = React.useState<number>(0);
1938

2039
const [servers, setServers] = useState<ServerResponseType[]>([]);
2140

@@ -53,48 +72,42 @@ export default function ServerDetail() {
5372

5473
console.log(serversGroupedByName);
5574

56-
const memoryToPieChartData = (serverResponse: ServerResponseType) => {
57-
const { Message } = serverResponse;
58-
const { Data, Name } = Message;
59-
60-
let labels = [];
61-
let data = [];
62-
63-
for (const [key, value] of Object.entries(Data)) {
64-
labels.push(key);
65-
data.push(value);
66-
}
67-
return {
68-
labels,
69-
datasets: [
70-
{
71-
label: Name,
72-
data,
73-
backgroundColor: "#494788",
74-
borderWidth: 1,
75-
},
76-
],
77-
};
75+
const handleChangeTabIndex = (
76+
event: React.SyntheticEvent,
77+
newValue: number
78+
) => {
79+
setTabIndex(newValue);
7880
};
7981

8082
return (
8183
<Container>
84+
<PageHeader
85+
title={`${host}`}
86+
breadcrumbs={[{ name: "Servers", to: "/" }, { name: `${host}` }]}
87+
></PageHeader>
88+
8289
<LoadingContent
8390
loading={connectionStatus === "Connecting"}
8491
error={connectionStatus === "Closed"}
8592
>
8693
<>
94+
<Tabs
95+
value={tabIndex}
96+
onChange={handleChangeTabIndex}
97+
aria-label={`${host} Tab`}
98+
variant="scrollable"
99+
scrollButtons="auto"
100+
>
101+
{Object.keys(serversGroupedByName)?.map(
102+
(serverName: string, index: number) => (
103+
<Tab label={serverName} {...a11yProps(index)} key={index} />
104+
)
105+
)}
106+
</Tabs>
107+
87108
{Object.keys(serversGroupedByName)?.map(
88109
(serverName: string, index: number) => (
89-
<div key={index}>
90-
{serverName === "memory" && (
91-
<Pie
92-
data={memoryToPieChartData(
93-
serversGroupedByName[serverName]?.slice(-1)[0]
94-
)}
95-
/>
96-
)}
97-
</div>
110+
<div key={index}>{index === tabIndex && <>{serverName}</>}</div>
98111
)
99112
)}
100113
</>

web/src/server/ServerList.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Card, CardActionArea, Grid, Typography } from "@mui/material";
22
import { Box, Container } from "@mui/system";
3-
import React, { useState } from "react";
3+
import React, { useEffect, useState } from "react";
44
import { useNavigate } from "react-router-dom";
55
import useWebSocket, { ReadyState } from "react-use-websocket";
66
import { ReactComponent as ServerIcon } from "assets/svg/server.svg";
@@ -39,8 +39,6 @@ export default function ServerList() {
3939
},
4040
});
4141

42-
sendJsonMessage({ FilterBy: "" });
43-
4442
const connectionStatus: string = {
4543
[ReadyState.CONNECTING]: "Connecting",
4644
[ReadyState.OPEN]: "Open",
@@ -49,7 +47,9 @@ export default function ServerList() {
4947
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
5048
}[readyState];
5149

52-
// console.log("serversGroupedByHost", serversGroupedByHost);
50+
useEffect(() => {
51+
sendJsonMessage({ FilterBy: "" });
52+
}, []);
5353

5454
return (
5555
<Container>

0 commit comments

Comments
 (0)