Skip to content

Commit 7f9bb35

Browse files
committed
incident now brings you to a copy of monitor
1 parent f119cdb commit 7f9bb35

File tree

12 files changed

+885
-8
lines changed

12 files changed

+885
-8
lines changed

apps/dashboard/src/pages/Dashboard/Incidents/components/IncidentsList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const IncidentsListItem: FunctionComponent<IncidentsListItemProps> = (
6161
columns={{ xs: 1, md: 2 }}
6262
justifyContent="space-between"
6363
gap={{ xs: 2, md: 4 }}
64-
onClick={() => navigate(`/incidents/${incident.id}`)} // Navigate to the incident page
64+
onClick={() => navigate(`/incidents/${incident.monitorId}/details`)} // Navigate to the incident page
6565
>
6666
<Grid item>
6767
<Stack spacing={1}>

apps/dashboard/src/pages/Dashboard/Incidents/components/OverviewList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const OverviewListItem: FunctionComponent<OverviewListItemProps> = (
6161
columns={{ xs: 1, md: 2 }}
6262
justifyContent="space-between"
6363
gap={{ xs: 2, md: 4 }}
64-
onClick={() => navigate(`/incidents/${monitor.id}`)} // Navigate to the incident page
64+
onClick={() => navigate(`/incidents/${monitor.id}/monitor`)} // Navigate to the incident page
6565
>
6666
<Grid item>
6767
<Stack spacing={1}>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Card, CardContent, Skeleton, Typography } from "@mui/material";
2+
import { FunctionComponent, useEffect, useState } from "react";
3+
import { useMonitor } from "../../../../../hooks/monitors.query";
4+
import Conditional from "../../../../../components/Conditional";
5+
6+
interface AverageResponseTimeCardProps {
7+
monitorId: number;
8+
}
9+
10+
const AverageResponseTimeCard: FunctionComponent<
11+
AverageResponseTimeCardProps
12+
> = ({ monitorId }) => {
13+
const { data, isLoading } = useMonitor(monitorId);
14+
const [Average, setAverage] = useState<string>("None");
15+
16+
useEffect(() => {
17+
if (!data?.stats.averageResponseTime) {
18+
return;
19+
}
20+
21+
setAverage(`${Math.floor(data.stats.averageResponseTime)} ms`);
22+
}, [monitorId, data]);
23+
24+
return (
25+
<Card sx={{ flex: 1 }}>
26+
<CardContent>
27+
<Typography sx={{ opacity: 0.5 }}>Average response time (24-hour)</Typography>
28+
29+
<Conditional value={isLoading}>
30+
<Skeleton variant="text" width={100} height={28} />
31+
</Conditional>
32+
33+
<Conditional value={!isLoading}>
34+
<Typography fontSize="1.25rem">{Average}</Typography>
35+
</Conditional>
36+
</CardContent>
37+
</Card>
38+
);
39+
};
40+
41+
export default AverageResponseTimeCard;
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { Box, Button, Chip, Stack, useTheme } from "@mui/material";
2+
import { GridColDef } from "@mui/x-data-grid";
3+
import { FunctionComponent, useState } from "react";
4+
import { useNavigate } from "react-router-dom";
5+
import DataGrid from "../../../../../components/DataGrid";
6+
import { Check } from "../../../../../api/models/checks";
7+
import { IoLockClosed, IoWarning } from "react-icons/io5";
8+
import { AiOutlineLeft, AiOutlineRight } from "react-icons/ai";
9+
import { useMonitorChecks } from "../../../../../hooks/monitors.query";
10+
import { isCertValid } from "../../../../../utilities/tls";
11+
12+
const CHECKS_PER_PAGE = 5;
13+
interface MonitorChecksDataGridProps {
14+
teamId?: number;
15+
monitorId: number;
16+
}
17+
18+
const ChecksDataGrid: FunctionComponent<MonitorChecksDataGridProps> = (
19+
props,
20+
) => {
21+
const navigate = useNavigate();
22+
23+
const [offset, setOffset] = useState(0);
24+
25+
const { data, isLoading } = useMonitorChecks(
26+
props.monitorId,
27+
offset,
28+
CHECKS_PER_PAGE,
29+
);
30+
31+
const formatTime = (time: number) => {
32+
return Math.floor(time / 1000000) + " ms";
33+
};
34+
35+
const columns: GridColDef<Check>[] = [
36+
{
37+
field: "statusCode",
38+
headerName: "Status",
39+
width: 72,
40+
align: "left",
41+
headerAlign: "left",
42+
sortable: false,
43+
renderCell: (row) => <Chip label={row.value} color="success" />,
44+
},
45+
{
46+
field: "tls",
47+
headerName: "TLS",
48+
width: 64,
49+
align: "center",
50+
headerAlign: "center",
51+
sortable: false,
52+
renderCell: (col) => {
53+
if (isCertValid(col.row.tls.notAfter)) {
54+
return <Chip label={<IoLockClosed />} color="success" />;
55+
} else {
56+
return <Chip label={<IoWarning size={18} />} color="error" />;
57+
}
58+
},
59+
},
60+
{
61+
field: "timing",
62+
headerName: "Time",
63+
width: 150,
64+
align: "left",
65+
headerAlign: "left",
66+
sortable: false,
67+
renderCell: (col) => <>{formatTime(col.row.timing.total)}</>,
68+
},
69+
{
70+
field: "phases",
71+
headerName: "Phases",
72+
align: "left",
73+
headerAlign: "left",
74+
flex: 1,
75+
sortable: false,
76+
renderCell: (col) => <PhasesThumb check={col.row} />,
77+
},
78+
{
79+
field: "createdAt",
80+
headerName: "Timestamp",
81+
width: 300,
82+
align: "right",
83+
headerAlign: "right",
84+
sortable: false,
85+
renderCell: (col) => <>{new Date(col.value).toLocaleString()}</>,
86+
},
87+
];
88+
89+
return (
90+
<>
91+
<DataGrid
92+
columns={columns}
93+
rows={data?.checks || []}
94+
rowHeight={42}
95+
autoHeight
96+
loading={isLoading}
97+
onPaginationModelChange={(model) => {
98+
setOffset(model.page * CHECKS_PER_PAGE);
99+
}}
100+
onRowClick={(row) => {
101+
navigate(`/monitors/${props.monitorId}/checks/${row.id}`);
102+
}}
103+
/>
104+
<Stack direction="row" justifyContent="center" spacing={2}>
105+
<Button
106+
onClick={() => setOffset(offset - CHECKS_PER_PAGE)}
107+
disabled={offset === 0}
108+
>
109+
<AiOutlineLeft size={18} />
110+
</Button>
111+
<Button
112+
onClick={() => setOffset(offset + CHECKS_PER_PAGE)}
113+
disabled={(data?.checks.length || 0) < CHECKS_PER_PAGE}
114+
>
115+
<AiOutlineRight size={18} />
116+
</Button>
117+
</Stack>
118+
</>
119+
);
120+
};
121+
122+
function getRowWidth(time: number, total: number): string {
123+
return (time / total) * 100 + "%";
124+
}
125+
126+
interface PhasesThumbProps {
127+
check: Check;
128+
}
129+
130+
const PhasesThumb: FunctionComponent<PhasesThumbProps> = (props) => {
131+
const theme = useTheme();
132+
133+
const { check } = props;
134+
135+
const barHeight = 12;
136+
137+
const total =
138+
check.timing.dnsLookup +
139+
check.timing.tcpConnection +
140+
check.timing.tlsHandshake +
141+
check.timing.serverProcessing +
142+
check.timing.contentTransfer;
143+
144+
const dnsLookupWidth = getRowWidth(check.timing.dnsLookup, total);
145+
const connectWidth = getRowWidth(check.timing.tcpConnection, total);
146+
const preTransferWidth = getRowWidth(check.timing.tlsHandshake, total);
147+
const startTransferWidth = getRowWidth(check.timing.serverProcessing, total);
148+
const contentTransferWidth = getRowWidth(check.timing.contentTransfer, total);
149+
150+
return (
151+
<Stack
152+
direction="row"
153+
width="100%"
154+
height={barHeight}
155+
sx={{
156+
opacity: 0.5,
157+
borderRadius: 1,
158+
overflow: "hidden",
159+
}}
160+
>
161+
<Box
162+
sx={{
163+
width: dnsLookupWidth,
164+
height: barHeight,
165+
bgcolor: "info.main",
166+
}}
167+
/>
168+
<Box
169+
sx={{
170+
width: connectWidth,
171+
height: barHeight,
172+
bgcolor: "success.main",
173+
}}
174+
/>
175+
<Box
176+
sx={{
177+
width: preTransferWidth,
178+
height: barHeight,
179+
bgcolor: "warning.main",
180+
}}
181+
/>
182+
<Box
183+
sx={{
184+
width: startTransferWidth,
185+
height: barHeight,
186+
bgcolor: "#9b59b6", // TODO: use theme color
187+
}}
188+
/>
189+
<Box
190+
sx={{
191+
width: contentTransferWidth,
192+
height: barHeight,
193+
bgcolor: "error.main",
194+
}}
195+
/>
196+
</Stack>
197+
);
198+
};
199+
200+
export { PhasesThumb, ChecksDataGrid };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { FunctionComponent } from "react";
2+
import * as React from "react";
3+
import Flatpickr from "react-flatpickr";
4+
5+
interface DateRangePickerProps {}
6+
7+
const DatePicker: FunctionComponent<DateRangePickerProps> = (props) => {
8+
const [dateStart, handleDateStart] = React.useState(new Date());
9+
const [dateEnd, handleDateEnd] = React.useState(new Date());
10+
11+
return (
12+
<Flatpickr
13+
data-enable-time
14+
value={dateStart}
15+
onChange={(date: any) => {
16+
handleDateStart(date);
17+
}}
18+
/>
19+
);
20+
};
21+
22+
export default DatePicker;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Card, CardContent, Skeleton, Typography } from "@mui/material";
2+
import { FunctionComponent, useEffect, useMemo, useState } from "react";
3+
import { useLatestMonitorCheck } from "../../../../../hooks/monitors.query";
4+
import Conditional from "../../../../../components/Conditional";
5+
import { timeAgoHumanize } from "../../../../../utilities/time";
6+
7+
interface LastCheckCardProps {
8+
monitorId?: number;
9+
}
10+
11+
const LastCheckCard: FunctionComponent<LastCheckCardProps> = ({
12+
monitorId,
13+
}) => {
14+
const { data, isLoading } = useLatestMonitorCheck(monitorId);
15+
const [lastCheck, setLastCheck] = useState<string>("Never");
16+
17+
useEffect(() => {
18+
updateLastCheck();
19+
20+
const interval = setInterval(updateLastCheck, 1000);
21+
22+
return () => {
23+
clearInterval(interval);
24+
};
25+
}, [monitorId, data]);
26+
27+
const updateLastCheck = () => {
28+
if (!data?.createdAt) {
29+
return;
30+
}
31+
32+
setLastCheck(timeAgoHumanize(data.createdAt));
33+
};
34+
35+
return (
36+
<Card sx={{ flex: 1 }}>
37+
<CardContent>
38+
<Typography sx={{ opacity: 0.5 }}>Last checked</Typography>
39+
40+
<Conditional value={isLoading}>
41+
<Skeleton variant="text" width={100} height={28} />
42+
</Conditional>
43+
44+
<Conditional value={!isLoading}>
45+
<Typography fontSize="1.25rem">{lastCheck}</Typography>
46+
</Conditional>
47+
</CardContent>
48+
</Card>
49+
);
50+
};
51+
52+
export default LastCheckCard;

0 commit comments

Comments
 (0)