diff --git a/src/components/Canary/CanaryPopup/CheckDetails.js b/src/components/Canary/CanaryPopup/CheckDetails.js
new file mode 100644
index 000000000..eaf59e176
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/CheckDetails.js
@@ -0,0 +1,151 @@
+import React from "react";
+import { usePrevious } from "../../../utils/hooks";
+import { Badge } from "../../Badge";
+import { toFormattedDuration } from "../renderers";
+import { AccordionBox } from "../../AccordionBox";
+import {
+ capitalizeFirstLetter,
+ toFixedIfNecessary
+} from "../../../utils/common";
+import styles from "../index.module.css";
+import { PopupTabs } from "./tabs";
+import { CheckStat } from "./CheckStat";
+import { getUptimePercentage } from "./utils";
+import { StatusHistory } from "./StatusHistory";
+import { DetailField } from "./DetailField";
+
+export function CheckDetails({ check, ...rest }) {
+ const prevCheck = usePrevious(check);
+ const validCheck = check || prevCheck;
+
+ const [val, unit] = toFormattedDuration(validCheck?.latency?.rolling1h);
+ const latencyValue = validCheck?.latency?.rolling1h ? `${val}${unit}` : "-";
+ const uptimeValue = toFixedIfNecessary(getUptimePercentage(validCheck), 2);
+ const validUptime =
+ !Number.isNaN(validCheck?.uptime?.passed) &&
+ !Number.isNaN(validCheck?.uptime?.failed);
+ const severityValue = validCheck?.severity || "-";
+ const statusHistoryList = validCheck?.checkStatuses;
+
+ const details = {
+ Name:
+ validCheck?.name || validCheck?.canaryName || validCheck?.endpoint || "-",
+ Type: validCheck?.type || "-",
+ Labels: (
+ <>
+ {validCheck?.labels &&
+ Object.entries(validCheck?.labels).map((entry) => {
+ const key = entry[0];
+ return ;
+ })}
+ >
+ ),
+ Owner: validCheck?.owner || "-",
+ Interval: validCheck?.interval || "-",
+ Location: validCheck?.location || "-",
+ Schedule: validCheck?.schedule || "-"
+ };
+
+ return (
+
+ {/* stats section */}
+
+
+
+ {validCheck?.uptime?.passed} passed
+
+
+ {validCheck?.uptime?.failed} failed
+
+
+ )
+ }
+ />
+
+
+
+ {/* chart section */}
+
+
+ Health overview
+ (time dropdown)
+
+
+
+
+ {statusHistoryList && statusHistoryList.length > 0 ? (
+
+ ) : (
+
+ No status history available
+
+ )}
+
+ ),
+ class: "flex flex-col overflow-y-hidden border-b border-gray-300"
+ },
+ checkDetails: {
+ label: "Check details",
+ content: (
+
+
+ {Object.entries(details).map(([label, value]) => (
+
+ ))}
+
+ }
+ />
+
+ ),
+ class: `flex flex-col overflow-y-auto border border-gray-300 ${styles.appleScrollbar}`
+ }
+ }}
+ />
+
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/CheckStat.js b/src/components/Canary/CanaryPopup/CheckStat.js
new file mode 100644
index 000000000..d400b87f1
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/CheckStat.js
@@ -0,0 +1,20 @@
+import React from "react";
+
+export function CheckStat({
+ title,
+ value,
+ append,
+ containerClass,
+ className,
+ ...rest
+}) {
+ return (
+
+
{title}
+
+ {value}
+ {append}
+
+
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/CheckTitle.js b/src/components/Canary/CanaryPopup/CheckTitle.js
new file mode 100644
index 000000000..a01f5b607
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/CheckTitle.js
@@ -0,0 +1,52 @@
+import React from "react";
+import { usePrevious } from "../../../utils/hooks";
+import { Badge } from "../../Badge";
+import { Icon } from "../../Icon";
+
+export function CheckTitle({ check, className, ...rest }) {
+ const prevCheck = usePrevious(check);
+ const validCheck = check || prevCheck;
+
+ return (
+
+
+
+
+
+
+
+ {validCheck?.name}
+
+
+
+
+
+ {true && (
+
+ {validCheck?.endpoint}
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/DetailField.js b/src/components/Canary/CanaryPopup/DetailField.js
new file mode 100644
index 000000000..f1c414485
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/DetailField.js
@@ -0,0 +1,14 @@
+import React from "react";
+
+export function DetailField({ label, value, className, ...rest }) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/StatusHistory.js b/src/components/Canary/CanaryPopup/StatusHistory.js
new file mode 100644
index 000000000..30783b1eb
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/StatusHistory.js
@@ -0,0 +1,44 @@
+import React from "react";
+import { format } from "timeago.js";
+import { CanaryStatus, Duration } from "../renderers";
+import { isEmpty } from "../utils";
+import { Table } from "../../Table";
+
+export function StatusHistory({ check, sticky = "false" }) {
+ const statii = check
+ ? check.checkStatuses != null
+ ? check.checkStatuses
+ : []
+ : [];
+ const data = [];
+ statii.forEach((status) => {
+ data.push({
+ key: `${check.key}.${check.description}`,
+ age: format(`${status.time} UTC`),
+ message: (
+ <>
+ {status.message}{" "}
+ {!isEmpty(status.error) &&
+ status.error.split("\n").map((item) => (
+ <>
+ {item}
+
+ >
+ ))}
+ >
+ ),
+ duration:
+ });
+ });
+
+ return (
+ check && (
+
+ )
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/index.js b/src/components/Canary/CanaryPopup/index.js
deleted file mode 100644
index 3b4dc80b8..000000000
--- a/src/components/Canary/CanaryPopup/index.js
+++ /dev/null
@@ -1,345 +0,0 @@
-import React, { useState } from "react";
-import { format } from "timeago.js";
-import { usePrevious } from "../../../utils/hooks";
-import { Badge } from "../../Badge";
-import { Icon } from "../../Icon";
-import { CanaryStatus, Duration, toFormattedDuration } from "../renderers";
-import { AccordionBox } from "../../AccordionBox";
-import { Table } from "../../Table";
-import { isEmpty } from "../utils";
-import {
- capitalizeFirstLetter,
- toFixedIfNecessary
-} from "../../../utils/common";
-import styles from "../index.module.css";
-
-export function CheckTitle({ check, className, ...rest }) {
- const prevCheck = usePrevious(check);
- const validCheck = check || prevCheck;
-
- return (
-
-
-
-
-
-
-
- {validCheck?.name}
-
-
-
-
-
- {true && (
-
- {validCheck?.endpoint}
-
- )}
-
-
-
-
-
- );
-}
-
-export function CheckDetails({ check, ...rest }) {
- const prevCheck = usePrevious(check);
- const validCheck = check || prevCheck;
-
- const [val, unit] = toFormattedDuration(validCheck?.latency?.rolling1h);
- const latencyValue = validCheck?.latency?.rolling1h ? `${val}${unit}` : "-";
- const uptimeValue = toFixedIfNecessary(getUptimePercentage(validCheck), 2);
- const validUptime =
- !Number.isNaN(validCheck?.uptime?.passed) &&
- !Number.isNaN(validCheck?.uptime?.failed);
- const severityValue = validCheck?.severity || "-";
- const statusHistoryList = validCheck?.checkStatuses;
-
- const details = {
- Name:
- validCheck?.name || validCheck?.canaryName || validCheck?.endpoint || "-",
- Type: validCheck?.type || "-",
- Labels: (
- <>
- {validCheck?.labels &&
- Object.entries(validCheck?.labels).map((entry) => {
- const key = entry[0];
- return ;
- })}
- >
- ),
- Owner: validCheck?.owner || "-",
- Interval: validCheck?.interval || "-",
- Location: validCheck?.location || "-",
- Schedule: validCheck?.schedule || "-"
- };
-
- return (
-
- {/* stats section */}
-
-
-
- {validCheck?.uptime?.passed} passed
-
-
- {validCheck?.uptime?.failed} failed
-
-
- )
- }
- />
-
-
-
- {/* chart section */}
-
-
- Health overview
- (time dropdown)
-
-
-
-
- {statusHistoryList && statusHistoryList.length > 0 ? (
-
- ) : (
-
- No status history available
-
- )}
-
- ),
- class: "flex flex-col overflow-y-hidden border-b border-gray-300"
- },
- checkDetails: {
- label: "Check details",
- content: (
-
-
- {Object.entries(details).map(([label, value]) => (
-
- ))}
-
- }
- />
-
- ),
- class: `flex flex-col overflow-y-auto border border-gray-300 ${styles.appleScrollbar}`
- }
- }}
- />
-
- );
-}
-
-function CheckStat({
- title,
- value,
- append,
- containerClass,
- className,
- ...rest
-}) {
- return (
-
-
{title}
-
- {value}
- {append}
-
-
- );
-}
-
-export function getUptimePercentage(check) {
- const uptime = check?.uptime;
- const passed = uptime?.passed;
- const failed = uptime?.failed;
- const valid = !Number.isNaN(passed) && !Number.isNaN(failed);
- return valid ? (passed / (passed + failed)) * 100 : null;
-}
-
-function DetailField({ label, value, className, ...rest }) {
- return (
-
-
- {label}
-
-
- {value}
-
-
- );
-}
-
-function StatusHistory({ check, sticky = "false" }) {
- const statii = check
- ? check.checkStatuses != null
- ? check.checkStatuses
- : []
- : [];
- const data = [];
- statii.forEach((status) => {
- data.push({
- key: `${check.key}.${check.description}`,
- age: format(`${status.time} UTC`),
- message: (
- <>
- {status.message}{" "}
- {!isEmpty(status.error) &&
- status.error.split("\n").map((item) => (
- <>
- {item}
-
- >
- ))}
- >
- ),
- duration:
- });
- });
-
- return (
- check && (
-
- )
- );
-}
-
-export function PopupTabs({
- tabs,
- contentStyle,
- contentClass,
- variant = "line",
- ...rest
-}) {
- const [selected, setSelected] = useState(Object.keys(tabs)[0]);
-
- const buttonStyles = {
- line: {
- container: "flex space-x-4 border-b border-gray-300 z-10",
- button: "border-b-2 font-medium text-sm py-2 px-1",
- active: "border-indigo-500 text-indigo-600",
- inactive:
- "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
- },
- simple: {
- container: "flex flex-wrap border-b border-gray-300 z-10",
- button:
- "-mb-px z-10 bg-white px-4 py-2 font-medium text-sm rounded-t-md border-gray-300 hover:text-gray-900",
- active: "text-gray-900 border",
- inactive: "text-gray-500 border-b",
- styles: {
- active: {
- borderBottomColor: "white"
- }
- }
- }
- };
- return (
-
-
- {Object.entries(tabs).map(([key, tab]) => (
-
- ))}
-
-
- {Object.entries(tabs).map(
- ([key, tab]) =>
- selected === key && (
-
- {tab.content}
-
- )
- )}
-
-
- );
-}
diff --git a/src/components/Canary/CanaryPopup/tabs.js b/src/components/Canary/CanaryPopup/tabs.js
new file mode 100644
index 000000000..0c020144f
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/tabs.js
@@ -0,0 +1,69 @@
+import React, { useState } from "react";
+
+export function PopupTabs({
+ tabs,
+ contentStyle,
+ contentClass,
+ variant = "line",
+ ...rest
+}) {
+ const [selected, setSelected] = useState(Object.keys(tabs)[0]);
+
+ const buttonStyles = {
+ line: {
+ container: "flex space-x-4 border-b border-gray-300 z-10",
+ button: "border-b-2 font-medium text-sm py-2 px-1",
+ active: "border-indigo-500 text-indigo-600",
+ inactive:
+ "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
+ },
+ simple: {
+ container: "flex flex-wrap border-b border-gray-300 z-10",
+ button:
+ "-mb-px z-10 bg-white px-4 py-2 font-medium text-sm rounded-t-md border-gray-300 hover:text-gray-900",
+ active: "text-gray-900 border",
+ inactive: "text-gray-500 border-b",
+ styles: {
+ active: {
+ borderBottomColor: "white"
+ }
+ }
+ }
+ };
+ return (
+
+
+ {Object.entries(tabs).map(([key, tab]) => (
+
+ ))}
+
+
+ {Object.entries(tabs).map(
+ ([key, tab]) =>
+ selected === key && (
+
+ {tab.content}
+
+ )
+ )}
+
+
+ );
+}
diff --git a/src/components/Canary/CanaryPopup/utils.js b/src/components/Canary/CanaryPopup/utils.js
new file mode 100644
index 000000000..56f4c5480
--- /dev/null
+++ b/src/components/Canary/CanaryPopup/utils.js
@@ -0,0 +1,7 @@
+export function getUptimePercentage(check) {
+ const uptime = check?.uptime;
+ const passed = uptime?.passed;
+ const failed = uptime?.failed;
+ const valid = !Number.isNaN(passed) && !Number.isNaN(failed);
+ return valid ? (passed / (passed + failed)) * 100 : null;
+}
diff --git a/src/components/Canary/index.js b/src/components/Canary/index.js
index fe9ccf0b8..f52c451a5 100644
--- a/src/components/Canary/index.js
+++ b/src/components/Canary/index.js
@@ -25,7 +25,8 @@ import { SidebarSubPanel } from "./SidebarSubPanel";
import { RefreshIntervalDropdown } from "../Dropdown/RefreshIntervalDropdown";
import { getLocalItem, setLocalItem } from "../../utils/storage";
import { Modal } from "../Modal";
-import { CheckDetails, CheckTitle } from "./CanaryPopup";
+import { CheckTitle } from "./CanaryPopup/CheckTitle";
+import { CheckDetails } from "./CanaryPopup/CheckDetails";
import styles from "./index.module.css";