diff --git a/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx b/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
index 85cfd2dcf..e9aef0e71 100644
--- a/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
+++ b/frontend/app/components/Header/HealthStatus/HealthModal/HealthModal.tsx
@@ -4,11 +4,12 @@ import slide from 'App/svg/cheers.svg';
import { Icon, Button } from 'UI';
import Footer from './Footer'
-function Category({ name, healthOk }: { name: string; healthOk: boolean }) {
+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}
@@ -18,7 +19,7 @@ function Category({ name, healthOk }: { name: string; healthOk: boolean }) {
)
}
-function HealthModal() {
+function HealthModal({ getHealth, isLoading, healthResponse }: { getHealth: () => void; isLoading: boolean; healthResponse: Record
}) {
return (
Installation Status
-
@@ -46,8 +47,8 @@ function HealthModal() {
-
-
+
+ {/**/}
= {
- overall: true,
- }
- const ingestHealth: Record
= {
- overall: true,
- }
- const backHealth: Record = {
- overall: true,
- }
- dbKeys.forEach(key => {
- const dbStatus = resp.databases[key].health
- if (!dbStatus) dbHealth.overall = false
- dbHealth[key] = resp.databases.key
- })
- ingestKeys.forEach(key => {
- const ingestStatus = resp.ingestionPipeline[key].health
- if (!ingestStatus) ingestHealth.overall = false
- ingestHealth[key] = resp.ingestionPipeline.key
- })
- backendKeys.forEach(key => {
- const backendStatus = resp.backendServices[key].health
- if (!backendStatus) backHealth.overall = false
- backHealth[key] = resp.backendServices.key
- })
- }
+const categoryKeyNames = {
+ backendServices: 'Backend Services',
+ databases: 'Databases',
+ ingestionPipeline: 'Ingestion Pipeline',
+ ssl: 'SSL',
}
-function HealthStatus() {
- const [healthOk, setHealth] = React.useState(false);
+function mapResponse(resp: Record) {
+ const services = Object.keys(resp);
+ const healthMap: Record = {}
+ services.forEach(service => {
+ healthMap[service] = {
+ // @ts-ignore
+ name: categoryKeyNames[service],
+ healthOk: true,
+ subservices: resp[service],
+ }
+ Object.values(healthMap[service].subservices).forEach((subservice: Record) => {
+ if (!subservice?.health) healthMap[service].healthOk = false;
+ })
+ })
- const icon = healthOk ? 'pulse' : ('exclamation-circle-fill' as const);
+ const overallHealth = Object.values(healthMap).every((service: Record) => service.healthOk);
+
+ return { overallHealth, healthMap }
+}
+
+
+function HealthStatus() {
+ const lastAskedKey = '__openreplay_health_status';
+ const healthResponseKey = '__openreplay_health_response';
+ const healthResponseSaved = localStorage.getItem(healthResponseKey) || '{}';
+ const [healthResponse, setHealthResponse] = React.useState(JSON.parse(healthResponseSaved));
+ const [isLoading, setIsLoading] = React.useState(false);
+ const lastAskedSaved = localStorage.getItem(lastAskedKey);
+ const [lastAsked, setLastAsked] = React.useState(lastAskedSaved);
+ const [showModal, setShowModal] = React.useState(false);
+
+ const getHealth = async () => {
+ if (isLoading) return;
+ try {
+ setIsLoading(true);
+ const r = await healthService.fetchStatus();
+ const healthMap = mapResponse(r)
+ setHealthResponse(healthMap);
+ const asked = new Date().getTime();
+ localStorage.setItem(healthResponseKey, JSON.stringify(healthMap))
+ localStorage.setItem(lastAskedKey, asked.toString());
+ setLastAsked(asked.toString());
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ 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);
+ if (Object.keys(healthResponse).length === 0 || !lastAskedDate || diffInMinutes > 10) {
+ void getHealth();
+ }
+ }, []);
+
+ const icon = healthResponse?.overallHealth ? 'pulse' : ('exclamation-circle-fill' as const);
return (
-
-
+
+ {showModal ? (
) : null}
);
}
-function HealthMenu({ healthOk, setHealth }: { healthOk: boolean; setHealth: any }) {
+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 22 mins. ago
-
setHealth(!healthOk)}>
+
Last checked {lastAskedDiff} mins. ago
+
getHealth()}
+ >
-
-
Version
-
- 123 123
-
-
+ {/*
*/}
+ {/*
Version
*/}
+ {/*
*/}
+ {/* 123 123*/}
+ {/*
*/}
+ {/*
*/}
- {healthOk ? (
+ {!healthOk ? (
<>
-
-
Sessions
-
- 10 000
-
-
-
+
Observed installation Issue with the following
+ {problematicServices.map(service =>
setShowModal(true)} healthOk={false} name={service.name} />)}
>
- ) : (
- Observed installation Issue with the following
- )}
+ ) : null}
diff --git a/frontend/app/services/HealthService.ts b/frontend/app/services/HealthService.ts
new file mode 100644
index 000000000..019863bb3
--- /dev/null
+++ b/frontend/app/services/HealthService.ts
@@ -0,0 +1,10 @@
+import BaseService from './BaseService';
+
+export default class HealthService extends BaseService {
+ fetchStatus(): Promise {
+ return this.client.get('/health')
+ .then(r => r.json())
+ .then(j => j.data || {})
+ .catch(Promise.reject)
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts
index 816113e68..32e216127 100644
--- a/frontend/app/services/index.ts
+++ b/frontend/app/services/index.ts
@@ -10,6 +10,7 @@ import RecordingsService from "./RecordingsService";
import ConfigService from './ConfigService'
import AlertsService from './AlertsService'
import WebhookService from './WebhookService'
+import HealthService from "./HealthService";
export const dashboardService = new DashboardService();
export const metricService = new MetricService();
@@ -24,6 +25,8 @@ export const configService = new ConfigService();
export const alertsService = new AlertsService();
export const webhookService = new WebhookService();
+export const healthService = new HealthService();
+
export const services = [
dashboardService,
metricService,
@@ -37,4 +40,5 @@ export const services = [
configService,
alertsService,
webhookService,
+ healthService,
]
\ No newline at end of file