diff --git a/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx b/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
index e9aef0e71..b0bbb8969 100644
--- a/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
+++ b/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
@@ -1,73 +1,131 @@
import React from 'react';
// @ts-ignore
import slide from 'App/svg/cheers.svg';
-import { Icon, Button } from 'UI';
-import Footer from './Footer'
+import { Button } from 'UI';
+import Footer from './Footer';
+import { getHighest } from 'App/constants/zindex';
+import Category from 'Components/Header/HealthStatus/ServiceCategory';
+import SubserviceHealth from 'Components/Header/HealthStatus/SubserviceHealth/SubserviceHealth';
+import { IServiceStats } from '../HealthStatus';
-export function Category({ name, healthOk, onClick }: { name: string; healthOk: boolean; onClick: (args: any) => void }) {
- const icon = healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const);
- return (
-
-
- {name}
+function HealthModal({
+ getHealth,
+ isLoading,
+ healthResponse,
+ setShowModal,
+}: {
+ getHealth: () => void;
+ isLoading: boolean;
+ healthResponse: { overallHealth: boolean; healthMap: Record };
+ setShowModal: (isOpen: boolean) => void;
+}) {
+ const [selectedService, setSelectedService] = React.useState('');
-
-
- )
-}
-
-function HealthModal({ getHealth, isLoading, healthResponse }: { getHealth: () => void; isLoading: boolean; healthResponse: Record }) {
+ React.useEffect(() => {
+ if (!healthResponse.overallHealth) {
+ setSelectedService(
+ Object.keys(healthResponse.healthMap).filter(
+ (s) => !healthResponse.healthMap[s].healthOk
+ )[0]
+ );
+ }
+ }, [healthResponse]);
+ const handleClose = () => {
+ setShowModal(false);
+ };
return (
e.stopPropagation()}
+ className={'flex flex-col bg-white rounded border border-figmaColors-divider'}
>
-
Installation Status
-
-
-
-
-
-
-
-
- {/**/}
-
-

+
Installation Status
+
+
+
+
+ {Object.keys(healthResponse.healthMap).map((service) => (
+
+ setSelectedService(service)}
+ healthOk={healthResponse.healthMap[service].healthOk}
+ name={healthResponse.healthMap[service].name}
+ isSelectable
+ isSelected={selectedService === service}
+ />
+
+ ))}
+
+
+ {selectedService ? (
+
+ ) : (
+

+ )}
+
+
+
+
+
+
-
-
-
-
);
}
-
-
+function ServiceStatus({ service }: { service: Record }) {
+ const { subservices } = service;
+ return (
+
+
+ {Object.keys(subservices).map((subservice: string) => (
+
+
+
+ ))}
+
+
+ );
+}
export default HealthModal;
diff --git a/frontend/app/components/Header/HealthStatus/HealthStatus.tsx b/frontend/app/components/Header/HealthStatus/HealthStatus.tsx
index 930419f63..7732bca9d 100644
--- a/frontend/app/components/Header/HealthStatus/HealthStatus.tsx
+++ b/frontend/app/components/Header/HealthStatus/HealthStatus.tsx
@@ -1,38 +1,46 @@
import React from 'react';
import { Icon } from 'UI';
-import cn from 'classnames';
-import HealthModal, { Category } from 'Components/Header/HealthStatus/HealthModal/HealthModal';
+import HealthModal from 'Components/Header/HealthStatus/HealthModal/HealthModal';
import { healthService } from 'App/services';
+import { categoryKeyNames } from './const';
+import HealthWidget from "Components/Header/HealthStatus/HealthWidget";
-
-const categoryKeyNames = {
- backendServices: 'Backend Services',
- databases: 'Databases',
- ingestionPipeline: 'Ingestion Pipeline',
- ssl: 'SSL',
+export interface IServiceStats {
+ name: 'backendServices' | 'databases' | 'ingestionPipeline' | 'ssl';
+ serviceName: string;
+ healthOk: boolean;
+ subservices: {
+ health: boolean;
+ details?: {
+ errors?: string[];
+ version?: string;
+ }
+ }[]
}
function mapResponse(resp: Record) {
const services = Object.keys(resp);
- const healthMap: Record = {}
- services.forEach(service => {
+ const healthMap: Record = {};
+ services.forEach((service) => {
healthMap[service] = {
// @ts-ignore
name: categoryKeyNames[service],
healthOk: true,
subservices: resp[service],
- }
+ serviceName: service,
+ };
Object.values(healthMap[service].subservices).forEach((subservice: Record) => {
if (!subservice?.health) healthMap[service].healthOk = false;
- })
- })
+ });
+ });
- const overallHealth = Object.values(healthMap).every((service: Record) => service.healthOk);
+ const overallHealth = Object.values(healthMap).every(
+ (service: Record) => service.healthOk
+ );
- return { overallHealth, healthMap }
+ return { overallHealth, healthMap };
}
-
function HealthStatus() {
const lastAskedKey = '__openreplay_health_status';
const healthResponseKey = '__openreplay_health_response';
@@ -48,10 +56,10 @@ function HealthStatus() {
try {
setIsLoading(true);
const r = await healthService.fetchStatus();
- const healthMap = mapResponse(r)
+ const healthMap = mapResponse(r);
setHealthResponse(healthMap);
const asked = new Date().getTime();
- localStorage.setItem(healthResponseKey, JSON.stringify(healthMap))
+ localStorage.setItem(healthResponseKey, JSON.stringify(healthMap));
localStorage.setItem(lastAskedKey, asked.toString());
setLastAsked(asked.toString());
} catch (e) {
@@ -73,109 +81,32 @@ function HealthStatus() {
const icon = healthResponse?.overallHealth ? 'pulse' : ('exclamation-circle-fill' as const);
return (
-
-
-
-
- {showModal ? (
) : null}
-
- );
-}
-
-function HealthMenu({
- healthResponse,
- getHealth,
- isLoading,
- lastAsked,
- setShowModal,
-}: {
- healthResponse: Record;
- getHealth: Function;
- isLoading: boolean;
- lastAsked: string | null;
- setShowModal: (visible: boolean) => void;
-}) {
- const [lastAskedDiff, setLastAskedDiff] = React.useState(0);
- const healthOk = healthResponse?.overallHealth;
-
- React.useEffect(() => {
- const now = new Date();
- const lastAskedDate = lastAsked ? new Date(parseInt(lastAsked, 10)) : null;
- const diff = lastAskedDate ? now.getTime() - lastAskedDate.getTime() : 0;
- const diffInMinutes = Math.round(diff / 1000 / 60);
- setLastAskedDiff(diffInMinutes);
- }, [lastAsked]);
-
- const title = healthOk ? 'All Systems Operational' : 'Service disruption';
- const icon = healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const);
-
- const problematicServices = Object.values(healthResponse?.healthMap || {}).filter(
- (service: Record) => !service.healthOk
- ) as Record[];
- return (
-
-
+ <>
+
-
- {title}
-
-
-
Last checked {lastAskedDiff} mins. ago
-
-
-
- {/*
*/}
- {/*
Version
*/}
- {/*
*/}
- {/* 123 123*/}
- {/*
*/}
- {/*
*/}
-
- {!healthOk ? (
- <>
-
Observed installation Issue with the following
- {problematicServices.map(service =>
setShowModal(true)} healthOk={false} name={service.name} />)}
- >
- ) : null}
-
+
-
+ {showModal ? (
+
+ ) : null}
+ >
);
}
+
export default HealthStatus;
diff --git a/frontend/app/components/Header/HealthStatus/HealthWidget.tsx b/frontend/app/components/Header/HealthStatus/HealthWidget.tsx
new file mode 100644
index 000000000..c6372540b
--- /dev/null
+++ b/frontend/app/components/Header/HealthStatus/HealthWidget.tsx
@@ -0,0 +1,93 @@
+import React from 'react'
+import { Icon } from "UI";
+import ServiceCategory from "Components/Header/HealthStatus/ServiceCategory";
+import cn from 'classnames'
+import { IServiceStats } from './HealthStatus'
+
+function HealthWidget({
+ healthResponse,
+ getHealth,
+ isLoading,
+ lastAsked,
+ setShowModal,
+}: {
+ healthResponse: { overallHealth: boolean; healthMap: Record
};
+ getHealth: Function;
+ isLoading: boolean;
+ lastAsked: string | null;
+ setShowModal: (visible: boolean) => void;
+}) {
+ const [lastAskedDiff, setLastAskedDiff] = React.useState(0);
+ const healthOk = healthResponse?.overallHealth;
+
+ React.useEffect(() => {
+ const now = new Date();
+ const lastAskedDate = lastAsked ? new Date(parseInt(lastAsked, 10)) : null;
+ const diff = lastAskedDate ? now.getTime() - lastAskedDate.getTime() : 0;
+ const diffInMinutes = Math.round(diff / 1000 / 60);
+ setLastAskedDiff(diffInMinutes);
+ }, [lastAsked]);
+
+ const title = healthOk ? 'All Systems Operational' : 'Service disruption';
+ const icon = healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const);
+
+ const problematicServices = Object.values(healthResponse?.healthMap || {}).filter(
+ (service: Record) => !service.healthOk
+ )
+
+ return (
+
+
+
+
+ {title}
+
+
+
Last checked {lastAskedDiff} mins. ago
+
getHealth()}
+ >
+
+
+
+
+
+
+ {!healthOk ? (
+ <>
+
+ Observed installation Issue with the following
+
+ {problematicServices.map((service) => (
+
+ setShowModal(true)}
+ healthOk={false}
+ name={service.name}
+ />
+
+ ))}
+ >
+ ) : null}
+
+
+
+ );
+}
+
+export default HealthWidget
\ No newline at end of file
diff --git a/frontend/app/components/Header/HealthStatus/ServiceCategory.tsx b/frontend/app/components/Header/HealthStatus/ServiceCategory.tsx
new file mode 100644
index 000000000..3c9259c39
--- /dev/null
+++ b/frontend/app/components/Header/HealthStatus/ServiceCategory.tsx
@@ -0,0 +1,43 @@
+import { Icon } from 'UI';
+import React from 'react';
+import cn from 'classnames';
+
+function Category({
+ name,
+ healthOk,
+ onClick,
+ isSelectable,
+ isExpandable,
+ isExpanded,
+ isSelected,
+}: {
+ name: string;
+ healthOk: boolean;
+ onClick: (args: any) => void;
+ isSelectable?: boolean;
+ isExpandable?: boolean;
+ isExpanded?: boolean;
+ isSelected?: boolean;
+}) {
+ const icon = healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const);
+ return (
+
+
+ {name}
+
+ {isSelectable ? : null}
+ {isExpandable ? (
+
+ ) : null}
+
+ );
+}
+
+export default Category
\ No newline at end of file
diff --git a/frontend/app/components/Header/HealthStatus/SubserviceHealth/SubserviceHealth.tsx b/frontend/app/components/Header/HealthStatus/SubserviceHealth/SubserviceHealth.tsx
new file mode 100644
index 000000000..4de64ffbe
--- /dev/null
+++ b/frontend/app/components/Header/HealthStatus/SubserviceHealth/SubserviceHealth.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import Category from 'Components/Header/HealthStatus/ServiceCategory';
+import cn from 'classnames';
+
+function SubserviceHealth({
+ subservice,
+ name,
+}: {
+ name: string;
+ subservice: { health: boolean; details: { errors?: string[]; version?: string } };
+}) {
+ const [isExpanded, setIsExpanded] = React.useState(!subservice?.health);
+
+ const isExpandable = subservice?.details && Object.keys(subservice?.details).length > 0;
+ return (
+
+
(isExpandable ? setIsExpanded(!isExpanded) : null)}
+ name={name}
+ healthOk={subservice?.health}
+ isExpandable={isExpandable}
+ isExpanded={isExpanded}
+ />
+ {isExpanded ? (
+
+ {subservice?.details?.version ? (
+
+
Version
+
+ {subservice?.details?.version}
+
+
+ ) : null}
+ {subservice?.details?.errors?.length ? (
+
+
Error log:
+ {subservice.details.errors.map((err: string, i) => (
+
+ {i + 1}. {err}
+
+ ))}
+
+ ) : subservice?.health ? null : (
+ 'Service not responding'
+ )}
+
+ ) : null}
+
+ );
+}
+
+export default SubserviceHealth;
diff --git a/frontend/app/components/Header/HealthStatus/const.ts b/frontend/app/components/Header/HealthStatus/const.ts
new file mode 100644
index 000000000..3c13c52dd
--- /dev/null
+++ b/frontend/app/components/Header/HealthStatus/const.ts
@@ -0,0 +1,6 @@
+export const categoryKeyNames = {
+ backendServices: 'Backend Services',
+ databases: 'Databases',
+ ingestionPipeline: 'Ingestion Pipeline',
+ ssl: 'SSL',
+} as const
\ No newline at end of file