change(ui): remove mock, connect api to health status comp

This commit is contained in:
nick-delirium 2023-03-13 16:06:58 +01:00 committed by Delirium
parent 6657a59cb9
commit 620489e57d
5 changed files with 141 additions and 255 deletions

View file

@ -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 (
<div
className={'p-4 flex items-center gap-2 border-b cursor-pointer hover:bg-active-blue'}
className={'px-4 py-2 flex items-center gap-2 border-b cursor-pointer hover:bg-active-blue'}
onClick={onClick}
>
<Icon name={icon} size={20} color={'green'} />
{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<string, any> }) {
return (
<div
@ -37,7 +38,7 @@ function HealthModal() {
}
>
<div className={'text-xl font-semibold'}>Installation Status</div>
<Button icon={'arrow-repeat'} variant={'text-primary'}>
<Button loading={isLoading} onClick={getHealth} icon={'arrow-repeat'} variant={'text-primary'}>
Recheck
</Button>
</div>
@ -46,8 +47,8 @@ function HealthModal() {
<div className={'flex flex-col h-full'} style={{ flex: 1 }}>
<Category name={'Databases'} healthOk={true} />
<Category name={'Ingestion Pipeline'} healthOk={false} />
<Category name={'Backend'} healthOk={false} />
<Category name={'SSL'} healthOk={true} />
<Category name={'Backend Services'} healthOk={false} />
{/*<Category name={'SSL'} healthOk={true} />*/}
</div>
<div
className={'bg-gray-lightest border-l w-fit border-figmaColors-divider'}
@ -66,4 +67,7 @@ function HealthModal() {
);
}
export default HealthModal;

View file

@ -1,187 +0,0 @@
export const response = {
databases: {
postgres: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
clickhouse: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
},
ingestionPipeline: {
redis: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
kafka: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
},
backendServices: {
alerts: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
assets: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
assist: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
chalice: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
db: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
ender: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
frontend: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
heuristics: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
http: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
'ingress-nginx': {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
integrations: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
peers: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
quickwit: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
sink: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
sourcemapreader: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
storage: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
utilities: {
health: true,
details: {
version: 'v1.13',
schema: 'v1.10',
lastUpdatedOn: '12jan2023',
},
},
},
overall: {
health: true,
details: {
numberOfEventCaptured: 123000,
numberOfSessionsCaptured: 25678,
},
labels: {
parent: 'information',
},
},
ssl: true,
};

View file

@ -1,46 +1,77 @@
import React from 'react';
import { Icon } from 'UI';
import cn from 'classnames';
import HealthModal from 'Components/Header/HealthStatus/HealthModal/HealthModal';
import { response } from './HealthModal/mock'
import HealthModal, { Category } from 'Components/Header/HealthStatus/HealthModal/HealthModal';
import { healthService } from 'App/services';
function mapResponse(resp) {
const dbKeys = Object.keys(resp.databases)
const ingestKeys = Object.keys(resp.ingestionPipeline)
const backendKeys = Object.keys(resp.backendServices)
if (!resp.overall.health) {
const dbHealth: Record<string, any> = {
overall: true,
}
const ingestHealth: Record<string, any> = {
overall: true,
}
const backHealth: Record<string, any> = {
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<string, any>) {
const services = Object.keys(resp);
const healthMap: Record<string, any> = {}
services.forEach(service => {
healthMap[service] = {
// @ts-ignore
name: categoryKeyNames[service],
healthOk: true,
subservices: resp[service],
}
Object.values(healthMap[service].subservices).forEach((subservice: Record<string, any>) => {
if (!subservice?.health) healthMap[service].healthOk = false;
})
})
const icon = healthOk ? 'pulse' : ('exclamation-circle-fill' as const);
const overallHealth = Object.values(healthMap).every((service: Record<string, any>) => 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 (
<div className={'relative group h-full'}>
<div
@ -53,15 +84,48 @@ function HealthStatus() {
</div>
</div>
<HealthMenu healthOk={healthOk} setHealth={setHealth} />
<HealthModal />
<HealthMenu
healthResponse={healthResponse}
getHealth={getHealth}
isLoading={isLoading}
lastAsked={lastAsked}
setShowModal={setShowModal}
/>
{showModal ? (<HealthModal healthResponse={healthResponse} getHealth={getHealth} isLoading={isLoading} lastAsked={lastAsked} />) : null}
</div>
);
}
function HealthMenu({ healthOk, setHealth }: { healthOk: boolean; setHealth: any }) {
function HealthMenu({
healthResponse,
getHealth,
isLoading,
lastAsked,
setShowModal,
}: {
healthResponse: Record<string, any>;
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<string, any>) => !service.healthOk
) as Record<string, any>[];
return (
<div
style={{ width: 220, top: '100%', right: '-30%', height: '110%' }}
@ -84,39 +148,30 @@ function HealthMenu({ healthOk, setHealth }: { healthOk: boolean; setHealth: any
<span>{title}</span>
</div>
<div className={'text-secondary flex w-full justify-between items-center text-sm'}>
<span>Last checked 22 mins. ago </span>
<div className={'cursor-pointer'} onClick={() => setHealth(!healthOk)}>
<span>Last checked {lastAskedDiff} mins. ago </span>
<div
className={cn('cursor-pointer', isLoading ? 'animate-spin' : '')}
onClick={() => getHealth()}
>
<Icon name={'arrow-repeat'} size={16} color={'main'} />
</div>
</div>
<div className={'divider w-full border border-b-light-gray'} />
<div className={'w-full'}>
<div className="flex items-center justify-between mt-2">
<div className="py-1 px-2 font-medium">Version</div>
<div className="code-font text-black rounded text-base bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip">
123 123
</div>
</div>
{/*<div className="flex items-center justify-between mt-2">*/}
{/* <div className="py-1 px-2 font-medium">Version</div>*/}
{/* <div className="code-font text-black rounded text-base bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip">*/}
{/* 123 123*/}
{/* </div>*/}
{/*</div>*/}
{healthOk ? (
{!healthOk ? (
<>
<div className="flex items-center justify-between mt-2">
<div className="py-1 px-2 font-medium">Sessions</div>
<div className="code-font text-black rounded text-base bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip">
10 000
</div>
</div>
<div className="flex items-center justify-between mt-2">
<div className="py-1 px-2 font-medium">Events</div>
<div className="code-font text-black rounded text-base bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip">
90 000
</div>
</div>
<div className={'text-secondary pb-2'}>Observed installation Issue with the following</div>
{problematicServices.map(service => <Category onClick={() => setShowModal(true)} healthOk={false} name={service.name} />)}
</>
) : (
<div>Observed installation Issue with the following</div>
)}
) : null}
</div>
</div>
</div>

View file

@ -0,0 +1,10 @@
import BaseService from './BaseService';
export default class HealthService extends BaseService {
fetchStatus(): Promise<any> {
return this.client.get('/health')
.then(r => r.json())
.then(j => j.data || {})
.catch(Promise.reject)
}
}

View file

@ -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,
]