From 9361ac2106945319c3843ffc3405016d071925fe Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Thu, 29 Aug 2024 05:58:52 +0000 Subject: [PATCH 01/55] feat: add service page --- web-server/pages/service.tsx | 78 +++++++++++++++++++ web-server/src/constants/routes.ts | 3 +- .../Sidebar/SidebarMenu/items.tsx | 8 +- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 web-server/pages/service.tsx diff --git a/web-server/pages/service.tsx b/web-server/pages/service.tsx new file mode 100644 index 00000000..5d6b5232 --- /dev/null +++ b/web-server/pages/service.tsx @@ -0,0 +1,78 @@ +import { Box } from '@mui/material'; +import { Authenticated } from 'src/components/Authenticated'; + +import { FlexBox } from '@/components/FlexBox'; +import { SystemLogs } from '@/components/Service/SystemLogs'; +import { useRedirectWithSession } from '@/constants/useRoute'; +import { PageWrapper } from '@/content/PullRequests/PageWrapper'; +import { useAuth } from '@/hooks/useAuth'; +import ExtendedSidebarLayout from '@/layouts/ExtendedSidebarLayout'; +import { serviceSlice } from '@/slices/service'; +import { useSelector } from '@/store'; +import { PageLayout } from '@/types/resources'; + +// Main System component +function Service() { + useRedirectWithSession(); + + const { + integrations: { github: isGithubIntegrated } + } = useAuth(); + + const initialState = serviceSlice.getInitialState(); + const currentState = useSelector( + (state: { service: { services: any } }) => state.service.services + ); + + const isLoading = currentState === initialState.services; + + return ( + + Service + + } + hideAllSelectors + pageTitle="Service" + showEvenIfNoTeamSelected={true} + isLoading={isLoading} + > + {isGithubIntegrated && } + + ); +} + +Service.getLayout = (page: PageLayout) => ( + + {page} + +); + +export default Service; + +// Content component centered and styled +const Content = () => { + return ( + + + + + + ); +}; diff --git a/web-server/src/constants/routes.ts b/web-server/src/constants/routes.ts index 9ff13d31..3354e2f1 100644 --- a/web-server/src/constants/routes.ts +++ b/web-server/src/constants/routes.ts @@ -68,7 +68,8 @@ export const ROUTES = { }; }, INTEGRATIONS: new RoutePath('INTEGRATIONS'), - SETTINGS: new RoutePath('SETTINGS') + SETTINGS: new RoutePath('SETTINGS'), + SERVICE: new RoutePath('SERVICE') }; export const DEFAULT_HOME_ROUTE = ROUTES.DORA_METRICS; diff --git a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx index 04695ffd..9a7be992 100644 --- a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx +++ b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx @@ -2,7 +2,8 @@ import { ExtensionTwoTone, GroupsTwoTone, Analytics, - Settings + Settings, + Dns } from '@mui/icons-material'; import { ROUTES } from '@/constants/routes'; @@ -65,6 +66,11 @@ const menuItems = (): MenuItems[] => [ name: 'Settings', icon: Settings, link: ROUTES.SETTINGS.PATH + }, + { + name: 'Services', + icon: Dns, + link: ROUTES.SERVICE.PATH } ] } From b1eadfe6b96f619d61aa0b4d773fa95aeca6715e Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Thu, 29 Aug 2024 21:41:44 +0000 Subject: [PATCH 02/55] feat: add reducer for status,logs --- web-server/src/slices/service.ts | 88 +++++++++++++++++++++++++++++ web-server/src/store/rootReducer.ts | 4 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 web-server/src/slices/service.ts diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts new file mode 100644 index 00000000..120ec0e6 --- /dev/null +++ b/web-server/src/slices/service.ts @@ -0,0 +1,88 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; + +import { handleApi } from '@/api-helpers/axios-api-instance'; +import { StateFetchConfig } from '@/types/redux'; + +export enum ServiceNames { + API_SERVER = 'api-server-service', + REDIS = 'redis-service', + POSTGRES = 'postgres-service', + SYNC_SERVER = 'sync-server-service' +} + +type ServiceStatus = { + isUp: boolean; + logs: string[]; +}; + +export type ServiceStatusState = { + [key in ServiceNames]: ServiceStatus; +} & { [key: string]: ServiceStatus }; + +type State = StateFetchConfig<{ + services: ServiceStatusState; + loading?: boolean; + error?: string; +}>; + +export type serviceSliceState = State; + +const getInitialState = (): State => { + return { + services: { + 'api-server-service': { isUp: false, logs: [] }, + 'redis-service': { isUp: false, logs: [] }, + 'postgres-service': { isUp: false, logs: [] }, + 'sync-server-service': { isUp: false, logs: [] } + } + }; +}; + +const initialState: State = getInitialState(); + +export const fetchServiceStatus = createAsyncThunk( + 'services/fetchServiceStatus', + async () => { + const response = await handleApi<{ + statuses: { [key in ServiceNames]: { isUp: boolean } }; + }>('/service/status', { + params: {} + }); + return { + statuses: response.statuses + }; + } +); + +export const fetchServiceLogs = createAsyncThunk( + 'services/fetchServiceLogs', + async (serviceName: string) => { + const response = await handleApi<{ logs: string[] }>('/service/log', { + params: { serviceName } + }); + return { serviceName, logs: response.logs }; + } +); + +export const serviceSlice = createSlice({ + name: 'services', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(fetchServiceStatus.fulfilled, (state, action) => { + state.loading = false; + const { statuses } = action.payload; + for (const [serviceName, { isUp }] of Object.entries(statuses)) { + state.services[serviceName].isUp = isUp; + } + }); + + builder.addCase(fetchServiceLogs.fulfilled, (state, action) => { + state.loading = false; + const { serviceName, logs } = action.payload; + state.services[serviceName].logs = logs; + }); + } +}); + +export default serviceSlice.reducer; diff --git a/web-server/src/store/rootReducer.ts b/web-server/src/store/rootReducer.ts index ed0c90f3..43bd7bbd 100644 --- a/web-server/src/store/rootReducer.ts +++ b/web-server/src/store/rootReducer.ts @@ -7,6 +7,7 @@ import { doraMetricsSlice } from '@/slices/dora_metrics'; import { loadLinkSlice } from '@/slices/loadLink'; import { orgSlice } from '@/slices/org'; import { reposSlice } from '@/slices/repos'; +import { serviceSlice } from '@/slices/service'; import { teamSlice } from '@/slices/team'; export const rootReducer = combineReducers({ @@ -17,5 +18,6 @@ export const rootReducer = combineReducers({ repos: reposSlice.reducer, org: orgSlice.reducer, doraMetrics: doraMetricsSlice.reducer, - loadLink: loadLinkSlice.reducer + loadLink: loadLinkSlice.reducer, + service: serviceSlice.reducer }); From c765c6e464db7ee1843c8f5d4162f82cd0bca4bc Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Thu, 29 Aug 2024 21:42:03 +0000 Subject: [PATCH 03/55] feat: api --- web-server/pages/api/service/log.ts | 64 ++++++++++++++++ web-server/pages/api/service/status.ts | 102 +++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 web-server/pages/api/service/log.ts create mode 100644 web-server/pages/api/service/status.ts diff --git a/web-server/pages/api/service/log.ts b/web-server/pages/api/service/log.ts new file mode 100644 index 00000000..173de8a7 --- /dev/null +++ b/web-server/pages/api/service/log.ts @@ -0,0 +1,64 @@ +import * as yup from 'yup'; + +import { exec } from 'child_process'; + +import { Endpoint, nullSchema } from '@/api-helpers/global'; + +const endpoint = new Endpoint(nullSchema); +const getLogsSchema = yup.object().shape({ + serviceName: yup.string().required('Service name is required') +}); + +const fetchDockerLogs = ( + command: string, + callback: (err: any, logs: string[]) => void +) => { + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(`Error executing Docker command: ${error.message}`); + callback(error, []); + return; + } + if (stderr) { + console.error(`Error in Docker command output: ${stderr}`); + callback(stderr, []); + return; + } + + const logs = stdout.split('\n').filter((line) => line.trim() !== ''); // Filter empty lines + callback(null, logs); + }); +}; + +endpoint.handle.GET(getLogsSchema, async (req, res) => { + console.log(req.payload); + const { serviceName } = req.payload; + console.log(`Fetching logs for ${serviceName}...`); + + const commandMap: Record = { + 'api-server-service': + 'cd ../../.. && cd /var/log/apiserver/ && tail -n 500 apiserver.log', + 'sync-server-service': + 'cd ../../.. && cd /var/log/sync_server/ && tail -n 500 sync_server.log', + 'redis-service': + 'cd ../../.. && cd /var/log/redis/ && tail -n 500 redis.log', + 'postgres-service': + 'cd ../../.. && cd /var/log/postgres/ && tail -n 500 postgres.log' + }; + + const command = commandMap[serviceName as keyof typeof commandMap]; + + if (!command) { + return res.status(400).json({ error: 'Invalid service name' }); + } + + fetchDockerLogs(command, (err, logs) => { + if (err) { + return res.status(500).json({ error: 'Failed to fetch logs' }); + } + + res.send({ logs }); + }); +}); + +export default endpoint.serve(); diff --git a/web-server/pages/api/service/status.ts b/web-server/pages/api/service/status.ts new file mode 100644 index 00000000..ee41199b --- /dev/null +++ b/web-server/pages/api/service/status.ts @@ -0,0 +1,102 @@ +import * as yup from 'yup'; + +import { exec } from 'child_process'; + +import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; +import { Endpoint, nullSchema } from '@/api-helpers/global'; +import { ServiceNames } from '@/slices/service'; +import { dbRaw } from '@/utils/db'; + +const getStatusSchema = yup.object().shape({}); + +const endpoint = new Endpoint(nullSchema); + +const execPromise = (command: string) => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +const checkServiceStatus = async (serviceName: string): Promise => { + let isUp = false; + switch (serviceName) { + case ServiceNames.API_SERVER: + try { + const response = await handleRequest(''); + if (response.message === 'hello world') { + isUp = true; + } + } catch (error) { + console.error('API Server service is down:', error); + isUp = false; + } + break; + case ServiceNames.REDIS: + try { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + + if (response.trim() === 'PONG') { + isUp = true; + } + } catch (error) { + console.error('Redis service is down:', error); + isUp = false; + } + break; + case ServiceNames.POSTGRES: + try { + await dbRaw.raw('SELECT 1'); + isUp = true; + } catch (error) { + console.error('PostgreSQL service is down:', error); + isUp = false; + } + break; + case ServiceNames.SYNC_SERVER: + try { + const response = await handleSyncServerRequest(''); + if (response.message === 'hello world') { + isUp = true; + } + } catch (error) { + console.error('Sync Server service is down:', error); + isUp = false; + } + break; + + default: + console.warn(`Service ${serviceName} not recognized.`); + break; + } + + return isUp; +}; + +endpoint.handle.GET(getStatusSchema, async (req, res) => { + console.log('Fetching service status...'); + + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + for (const service of services) { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp: isUp }; + } + + console.log(statuses); + + return res.send({ statuses }); +}); + +export default endpoint.serve(); From 8e1cf136dc0ee75e2d3d1ae513d301db01a72aeb Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 01:53:14 +0000 Subject: [PATCH 04/55] feat: add status --- web-server/pages/service.tsx | 34 +---- .../src/components/Service/SystemStatus.tsx | 130 ++++++++++++++++++ web-server/src/slices/service.ts | 15 +- 3 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 web-server/src/components/Service/SystemStatus.tsx diff --git a/web-server/pages/service.tsx b/web-server/pages/service.tsx index 5d6b5232..59297196 100644 --- a/web-server/pages/service.tsx +++ b/web-server/pages/service.tsx @@ -1,8 +1,7 @@ -import { Box } from '@mui/material'; import { Authenticated } from 'src/components/Authenticated'; - import { FlexBox } from '@/components/FlexBox'; -import { SystemLogs } from '@/components/Service/SystemLogs'; +import Loader from '@/components/Loader'; +import { SystemStatus } from '@/components/Service/SystemStatus'; import { useRedirectWithSession } from '@/constants/useRoute'; import { PageWrapper } from '@/content/PullRequests/PageWrapper'; import { useAuth } from '@/hooks/useAuth'; @@ -11,7 +10,6 @@ import { serviceSlice } from '@/slices/service'; import { useSelector } from '@/store'; import { PageLayout } from '@/types/resources'; -// Main System component function Service() { useRedirectWithSession(); @@ -38,7 +36,7 @@ function Service() { showEvenIfNoTeamSelected={true} isLoading={isLoading} > - {isGithubIntegrated && } + {isGithubIntegrated ? : } ); } @@ -50,29 +48,3 @@ Service.getLayout = (page: PageLayout) => ( ); export default Service; - -// Content component centered and styled -const Content = () => { - return ( - - - - - - ); -}; diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx new file mode 100644 index 00000000..84c15b2a --- /dev/null +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -0,0 +1,130 @@ +import { Box, Divider, Grid } from '@mui/material'; +import { FC, useEffect, useRef, useState } from 'react'; + +import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; +import { + ServiceStatusState, + fetchServiceLogs, + fetchServiceStatus, + serviceSlice +} from '@/slices/service'; +import { useDispatch, useSelector } from '@/store'; + +import { FlexBox } from '../FlexBox'; +import { useOverlayPage } from '../OverlayPageContext'; +import { Line } from '../Text'; + +export const SystemStatus: FC = () => { + const dispatch = useDispatch(); + const [error, setError] = useState(''); + const services = useSelector( + (state: { service: { services: ServiceStatusState } }) => + state.service.services + ); + const workerRef = useRef(); + + useEffect(() => { + const fetchAndHandleServiceStatus = async () => { + try { + await dispatch(fetchServiceStatus()).unwrap(); + if (services) { + Object.keys(services).forEach((serviceName) => { + dispatch(fetchServiceLogs(serviceName)).unwrap(); + }); + } + } catch (err) { + setError('Failed to fetch service status'); + } + }; + + fetchAndHandleServiceStatus(); + workerRef.current = new Worker('/workers/fetchStatusWorker.js'); + workerRef.current.onmessage = (event: MessageEvent) => { + console.log('WebWorker Response =>', event.data); + dispatch(fetchServiceStatus()); + }; + workerRef.current?.postMessage('fetchStatus'); + return () => { + workerRef.current?.terminate(); + }; + }, [dispatch]); + + const { addPage } = useOverlayPage(); + + const ServiceTitle: { [key: string]: string } = { + 'api-server-service': 'Backend Server', + 'redis-service': 'Redis DataBase', + 'postgres-service': 'Postgres DataBase', + 'sync-server-service': 'Sync Server' + }; + + return ( + + + System Status + + + + + {error && ( + +

{error}

+
+ )} + + + {services && + Object.keys(services).map((serviceName) => { + const { isUp } = services[serviceName]; + return ( + + { + dispatch( + serviceSlice.actions.setActiveService(serviceName) + ); + addPage({ + page: { + ui: 'system_logs', + title: ServiceTitle[serviceName] + ' Logs' + } + }); + }} + > + + + + + {`${ServiceTitle[serviceName]} `} + + + + + + + + + {isUp + ? 'Status: Healthy' + : 'Status: Not Operational'} + + + + + + + + + ); + })} + +
+ ); +}; diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 120ec0e6..9ecbcf0c 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -23,6 +23,7 @@ type State = StateFetchConfig<{ services: ServiceStatusState; loading?: boolean; error?: string; + active: string | null; // Add 'active' to store the active service name }>; export type serviceSliceState = State; @@ -34,7 +35,10 @@ const getInitialState = (): State => { 'redis-service': { isUp: false, logs: [] }, 'postgres-service': { isUp: false, logs: [] }, 'sync-server-service': { isUp: false, logs: [] } - } + }, + active: null, + loading: false, + error: undefined }; }; @@ -67,7 +71,14 @@ export const fetchServiceLogs = createAsyncThunk( export const serviceSlice = createSlice({ name: 'services', initialState, - reducers: {}, + reducers: { + setActiveService: (state, action) => { + console.log(action.payload, 'payload', typeof action.payload); + state.active = action.payload; + console.log(state.active, 'active'); + }, + resetState: () => getInitialState() + }, extraReducers: (builder) => { builder.addCase(fetchServiceStatus.fulfilled, (state, action) => { state.loading = false; From 4c701ee5c1480c89f59e61ffccea16ade570fe94 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 01:54:18 +0000 Subject: [PATCH 05/55] feat: add logs fetch --- web-server/src/constants/overlays.ts | 5 ++ web-server/src/content/Service/SystemLogs.tsx | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 web-server/src/content/Service/SystemLogs.tsx diff --git a/web-server/src/constants/overlays.ts b/web-server/src/constants/overlays.ts index 727a0f46..6cd90573 100644 --- a/web-server/src/constants/overlays.ts +++ b/web-server/src/constants/overlays.ts @@ -40,5 +40,10 @@ export const overlaysImportMap = { import('@/content/DoraMetrics/AIAnalysis/AIAnalysis').then((c) => ({ default: c.AIAnalysis })) + ), + system_logs: lazy(() => + import('@/content/Service/SystemLogs').then((c) => ({ + default: c.SystemLogs + })) ) }; diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx new file mode 100644 index 00000000..3d28875e --- /dev/null +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -0,0 +1,56 @@ +import { Box, Typography } from '@mui/material'; +import { useEffect, useRef } from 'react'; + +import { fetchServiceLogs, ServiceStatusState } from '@/slices/service'; +import { useDispatch, useSelector } from '@/store'; + +export const SystemLogs = () => { + const dispatch = useDispatch(); + const services = useSelector( + (state: { service: { services: ServiceStatusState } }) => + state.service.services + ); + const active = useSelector((s) => s.service.active); + const workerRef = useRef(); + + const logs = services[active].logs; + + useEffect(() => { + workerRef.current = new Worker('/workers/fetchLogsWorker.js'); + workerRef.current.onmessage = (event: MessageEvent) => { + dispatch(fetchServiceLogs(active)); + }; + workerRef.current?.postMessage('fetchStatus'); + return () => { + workerRef.current?.terminate(); + }; + }); + + return ( + + {services && + logs.map((log, i) => ( + + {log} + + ))} + + Hello, World! + + + ); +}; From 63738a4f0f4834c4de1102d957903ff71e08fb9e Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 01:54:40 +0000 Subject: [PATCH 06/55] feat: add webworkers --- web-server/package.json | 4 +++- web-server/public/workers/fetchLogsWorker.js | 8 ++++++++ web-server/public/workers/fetchLogsWorker.js.map | 1 + web-server/public/workers/fetchStatusWorker.js | 8 ++++++++ web-server/public/workers/fetchStatusWorker.js.map | 1 + web-server/public/workers/tsconfig.worker.tsbuildinfo | 1 + web-server/scripts/build.sh | 1 + web-server/src/workers/fetchLogsWorker.ts | 6 ++++++ web-server/src/workers/fetchStatusWorker.ts | 6 ++++++ web-server/tsconfig.worker.json | 9 +++++++++ 10 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 web-server/public/workers/fetchLogsWorker.js create mode 100644 web-server/public/workers/fetchLogsWorker.js.map create mode 100644 web-server/public/workers/fetchStatusWorker.js create mode 100644 web-server/public/workers/fetchStatusWorker.js.map create mode 100644 web-server/public/workers/tsconfig.worker.tsbuildinfo create mode 100644 web-server/src/workers/fetchLogsWorker.ts create mode 100644 web-server/src/workers/fetchStatusWorker.ts create mode 100644 web-server/tsconfig.worker.json diff --git a/web-server/package.json b/web-server/package.json index 82263af2..bee38a90 100644 --- a/web-server/package.json +++ b/web-server/package.json @@ -96,7 +96,9 @@ "clean": "rm -rf .next", "test": "jest --passWithNoTests --onlyChanged", "test:e2e": "playwright test --headed", - "env": "bash ./scripts/setup-env.sh" + "env": "bash ./scripts/setup-env.sh", + "build:workers": "tsc -p tsconfig.worker.json", + "watch:workers": "tsc -p tsconfig.worker.json -watch" }, "devDependencies": { "@next/bundle-analyzer": "^12.3.0", diff --git a/web-server/public/workers/fetchLogsWorker.js b/web-server/public/workers/fetchLogsWorker.js new file mode 100644 index 00000000..a37954d2 --- /dev/null +++ b/web-server/public/workers/fetchLogsWorker.js @@ -0,0 +1,8 @@ +"use strict"; +addEventListener('message', (_event) => { + // console.log('Worker "); + setInterval(() => { + postMessage('fetch'); + }, 2000); +}); +//# sourceMappingURL=fetchLogsWorker.js.map \ No newline at end of file diff --git a/web-server/public/workers/fetchLogsWorker.js.map b/web-server/public/workers/fetchLogsWorker.js.map new file mode 100644 index 00000000..a482e1b2 --- /dev/null +++ b/web-server/public/workers/fetchLogsWorker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetchLogsWorker.js","sourceRoot":"","sources":["../../src/workers/fetchLogsWorker.ts"],"names":[],"mappings":";AAAA,gBAAgB,CAAC,SAAS,EAAE,CAAC,MAA4B,EAAE,EAAE;IAC3D,0BAA0B;IAC1B,WAAW,CAAC,GAAG,EAAE;QACf,WAAW,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/web-server/public/workers/fetchStatusWorker.js b/web-server/public/workers/fetchStatusWorker.js new file mode 100644 index 00000000..7470d941 --- /dev/null +++ b/web-server/public/workers/fetchStatusWorker.js @@ -0,0 +1,8 @@ +"use strict"; +addEventListener('message', (_event) => { + // console.log('Worker "); + setInterval(() => { + postMessage('fetch'); + }, 3000); +}); +//# sourceMappingURL=fetchStatusWorker.js.map \ No newline at end of file diff --git a/web-server/public/workers/fetchStatusWorker.js.map b/web-server/public/workers/fetchStatusWorker.js.map new file mode 100644 index 00000000..2cf271c1 --- /dev/null +++ b/web-server/public/workers/fetchStatusWorker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetchStatusWorker.js","sourceRoot":"","sources":["../../src/workers/fetchStatusWorker.ts"],"names":[],"mappings":";AAAA,gBAAgB,CAAC,SAAS,EAAE,CAAC,MAA4B,EAAE,EAAE;IAC3D,0BAA0B;IAC1B,WAAW,CAAC,GAAG,EAAE;QACf,WAAW,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/web-server/public/workers/tsconfig.worker.tsbuildinfo b/web-server/public/workers/tsconfig.worker.tsbuildinfo new file mode 100644 index 00000000..e922096e --- /dev/null +++ b/web-server/public/workers/tsconfig.worker.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../src/workers/fetchLogsWorker.ts","../../src/workers/fetchStatusWorker.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/@types/ms/index.d.ts","../../node_modules/@types/debug/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/estree-jsx/index.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/dom-events.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/globals.global.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/graceful-fs/index.d.ts","../../node_modules/@types/unist/index.d.ts","../../node_modules/@types/hast/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/prop-types/index.d.ts","../../node_modules/@types/scheduler/tracing.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/hoist-non-react-statics/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@jest/expect-utils/build/index.d.ts","../../node_modules/jest-matcher-utils/node_modules/chalk/index.d.ts","../../node_modules/@sinclair/typebox/typebox.d.ts","../../node_modules/@jest/schemas/build/index.d.ts","../../node_modules/pretty-format/build/index.d.ts","../../node_modules/jest-diff/build/index.d.ts","../../node_modules/jest-matcher-utils/build/index.d.ts","../../node_modules/expect/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/js-cookie/index.d.ts","../../node_modules/parse5/dist/common/html.d.ts","../../node_modules/parse5/dist/common/token.d.ts","../../node_modules/parse5/dist/common/error-codes.d.ts","../../node_modules/parse5/dist/tokenizer/preprocessor.d.ts","../../node_modules/parse5/dist/tokenizer/index.d.ts","../../node_modules/parse5/dist/tree-adapters/interface.d.ts","../../node_modules/parse5/dist/parser/open-element-stack.d.ts","../../node_modules/parse5/dist/parser/formatting-element-list.d.ts","../../node_modules/parse5/dist/parser/index.d.ts","../../node_modules/parse5/dist/tree-adapters/default.d.ts","../../node_modules/parse5/dist/serializer/index.d.ts","../../node_modules/parse5/dist/common/foreign-content.d.ts","../../node_modules/parse5/dist/index.d.ts","../../node_modules/@types/tough-cookie/index.d.ts","../../node_modules/@types/jsdom/base.d.ts","../../node_modules/@types/jsdom/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/json5/index.d.ts","../../node_modules/@types/lodash/common/common.d.ts","../../node_modules/@types/lodash/common/array.d.ts","../../node_modules/@types/lodash/common/collection.d.ts","../../node_modules/@types/lodash/common/date.d.ts","../../node_modules/@types/lodash/common/function.d.ts","../../node_modules/@types/lodash/common/lang.d.ts","../../node_modules/@types/lodash/common/math.d.ts","../../node_modules/@types/lodash/common/number.d.ts","../../node_modules/@types/lodash/common/object.d.ts","../../node_modules/@types/lodash/common/seq.d.ts","../../node_modules/@types/lodash/common/string.d.ts","../../node_modules/@types/lodash/common/util.d.ts","../../node_modules/@types/lodash/index.d.ts","../../node_modules/@types/mdast/index.d.ts","../../node_modules/@types/nprogress/index.d.ts","../../node_modules/@types/parse-json/index.d.ts","../../node_modules/@types/pluralize/index.d.ts","../../node_modules/ts-toolbelt/out/index.d.ts","../../node_modules/@types/ramda/tools.d.ts","../../node_modules/@types/ramda/index.d.ts","../../node_modules/@types/react-copy-to-clipboard/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/redux/index.d.ts","../../node_modules/@types/react-redux/index.d.ts","../../node_modules/@types/react-transition-group/config.d.ts","../../node_modules/@types/react-transition-group/Transition.d.ts","../../node_modules/@types/react-transition-group/CSSTransition.d.ts","../../node_modules/@types/react-transition-group/SwitchTransition.d.ts","../../node_modules/@types/react-transition-group/TransitionGroup.d.ts","../../node_modules/@types/react-transition-group/index.d.ts","../../node_modules/@types/scheduler/index.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/ua-parser-js/index.d.ts","../../node_modules/@types/uuid/index.d.ts","../../node_modules/moment/ts3.1-typings/moment.d.ts","../../node_modules/@types/vis/index.d.ts","../../node_modules/@types/voca/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"2ac9cdcfb8f8875c18d14ec5796a8b029c426f73ad6dc3ffb580c228b58d1c44","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","dc48272d7c333ccf58034c0026162576b7d50ea0e69c3b9292f803fc20720fd5","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc",{"version":"0075fa5ceda385bcdf3488e37786b5a33be730e8bc4aa3cf1e78c63891752ce8","affectsGlobalScope":true},{"version":"35299ae4a62086698444a5aaee27fc7aa377c68cbb90b441c9ace246ffd05c97","affectsGlobalScope":true},{"version":"f296963760430fb65b4e5d91f0ed770a91c6e77455bacf8fa23a1501654ede0e","affectsGlobalScope":true},{"version":"09226e53d1cfda217317074a97724da3e71e2c545e18774484b61562afc53cd2","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"8b41361862022eb72fcc8a7f34680ac842aca802cf4bc1f915e8c620c9ce4331","affectsGlobalScope":true},{"version":"f7bd636ae3a4623c503359ada74510c4005df5b36de7f23e1db8a5c543fd176b","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"0c20f4d2358eb679e4ae8a4432bdd96c857a2960fd6800b21ec4008ec59d60ea","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"82d0d8e269b9eeac02c3bd1c9e884e85d483fcb2cd168bccd6bc54df663da031","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"b8deab98702588840be73d67f02412a2d45a417a3c097b2e96f7f3a42ac483d1","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"376d554d042fb409cb55b5cbaf0b2b4b7e669619493c5d18d5fa8bd67273f82a","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"c4138a3dd7cd6cf1f363ca0f905554e8d81b45844feea17786cdf1626cb8ea06","affectsGlobalScope":true},{"version":"6ff3e2452b055d8f0ec026511c6582b55d935675af67cdb67dd1dc671e8065df","affectsGlobalScope":true},{"version":"03de17b810f426a2f47396b0b99b53a82c1b60e9cba7a7edda47f9bb077882f4","affectsGlobalScope":true},{"version":"8184c6ddf48f0c98429326b428478ecc6143c27f79b79e85740f17e6feb090f1","affectsGlobalScope":true},{"version":"261c4d2cf86ac5a89ad3fb3fafed74cbb6f2f7c1d139b0540933df567d64a6ca","affectsGlobalScope":true},{"version":"6af1425e9973f4924fca986636ac19a0cf9909a7e0d9d3009c349e6244e957b6","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"15a630d6817718a2ddd7088c4f83e4673fde19fa992d2eae2cf51132a302a5d3","affectsGlobalScope":true},{"version":"b7e9f95a7387e3f66be0ed6db43600c49cec33a3900437ce2fd350d9b7cb16f2","affectsGlobalScope":true},{"version":"01e0ee7e1f661acedb08b51f8a9b7d7f959e9cdb6441360f06522cc3aea1bf2e","affectsGlobalScope":true},{"version":"ac17a97f816d53d9dd79b0d235e1c0ed54a8cc6a0677e9a3d61efb480b2a3e4e","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"ec0104fee478075cb5171e5f4e3f23add8e02d845ae0165bfa3f1099241fa2aa","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"9cc66b0513ad41cb5f5372cca86ef83a0d37d1c1017580b7dace3ea5661836df","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"709efdae0cb5df5f49376cde61daacc95cdd44ae4671da13a540da5088bf3f30","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"bc496ef4377553e461efcf7cc5a5a57cf59f9962aea06b5e722d54a36bf66ea1","affectsGlobalScope":true},{"version":"038a2f66a34ee7a9c2fbc3584c8ab43dff2995f8c68e3f566f4c300d2175e31e","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"f5c92f2c27b06c1a41b88f6db8299205aee52c2a2943f7ed29bd585977f254e8","affectsGlobalScope":true},{"version":"930b0e15811f84e203d3c23508674d5ded88266df4b10abee7b31b2ac77632d2","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"b9ea5778ff8b50d7c04c9890170db34c26a5358cccba36844fe319f50a43a61a","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"307c8b7ebbd7f23a92b73a4c6c0a697beca05b06b036c23a34553e5fe65e4fdc","affectsGlobalScope":true},{"version":"f35a831e4f0fe3b3697f4a0fe0e3caa7624c92b78afbecaf142c0f93abfaf379","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"a7bcdaff8027296d87a436b6dcd66c1aa7f7848cbe4457f0b6132de372d80f6e","affectsGlobalScope":true},{"version":"3611f0603c67df2500cd0db5c568aec9938cbeb1200eaf6841459609f2d43873","affectsGlobalScope":true},"e74998d5cefc2f29d583c10b99c1478fb810f1e46fbb06535bfb0bbba3c84aa5","cc957354aa3c94c9961ebf46282cfde1e81d107fc5785a61f62c67f1dd3ac2eb","43d058146b002d075f5d0033a6870321048297f1658eb0db559ba028383803a6","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","6704f0b54df85640baaeebd86c9d4a1dbb661d5a4d57a75bc84162f562f6531d","9d255af1b09c6697089d3c9bf438292a298d8b7a95c68793c9aae80afc9e5ca7","68cc8d6fcc2f270d7108f02f3ebc59480a54615be3e09a47e14527f349e9d53e","3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","5b3cd03ae354ea96eff1f74d7c410fe4852e6382227e8b0ecf87ab5e3a5bbcd4","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"056097110efd16869ec118cedb44ecbac9a019576eee808d61304ca6d5cb2cbe","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"6fb8358e10ed92a7f515b7d79da3904c955a3ffd4e14aa9df6f0ea113041f1cf","affectsGlobalScope":true},"45c831238c6dac21c72da5f335747736a56a3847192bf03c84b958a7e9ec93e2","661a11d16ad2e3543a77c53bcd4017ee9a450f47ab7def3ab493a86eae4d550c",{"version":"8cdc646cec7819581ef343b83855b1bfe4fe674f2c84f4fb8dc90d82fb56bd3a","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","9dd56225cc2d8cb8fe5ceb0043ff386987637e12fecc6078896058a99deae284","2375ed4b439215aa3b6d0c6fd175c78a4384b30cb43cbadaecbf0a18954c98cb","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","41231da15bb5e3e806a8395bd15c7befd2ec90f9f4e3c9d0ae1356bccb76dbb0","fccfef201d057cb407fa515311bd608549bab6c7b8adcf8f2df31f5d3b796478",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},"5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"464762c6213566d072f1ced5e8e9a954785ec5e53883b7397198abb5ef5b8f71","affectsGlobalScope":true},"6387920dc3e18927335b086deec75bf8e50f879a5e273d32ee7bb7a55ba50572","9bba37424094688c4663c177a1379b229f919b8912889a472f32fdc5f08ddb4d","29a4be13b3a30d3e66667b75c58ec61fb2df8fa0422534fdee3cfb30c5dbf450","83366d901beda79d6eb37aaaf6ca248dcd88946302b2a7d975590783be51e88e","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","43ec77c369473e92e2ecebf0554a0fdaa9c256644a6070f28228dfcceec77351",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"95ed02bacb4502c985b69742ec82a4576d4ff4a6620ecc91593f611d502ae546","bf755525c4e6f85a970b98c4755d98e8aa1b6dbd83a5d8fcc57d3d497351b936","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"28084e15b63e6211769db2fe646d8bc5c4c6776321e0deffe2d12eefd52cb6b9","affectsGlobalScope":true},{"version":"aed37dabf86c99d6c8508700576ecede86688397bc12523541858705a0c737c2","affectsGlobalScope":true},"cc6ef5733d4ea6d2e06310a32dffd2c16418b467c5033d49cecc4f3a25de7497","94768454c3348b6ebe48e45fbad8c92e2bb7af4a35243edbe2b90823d0bd7f9a","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","84da3c28344e621fd1d591f2c09e9595292d2b70018da28a553268ac122597d4","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","6e191fea1db6e9e4fa828259cf489e820ec9170effff57fb081a2f3295db4722","aed943465fbce1efe49ee16b5ea409050f15cd8eaf116f6fadb64ef0772e7d95","70d08483a67bf7050dbedace398ef3fee9f436fcd60517c97c4c1e22e3c6f3e8","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"e933de8143e1d12dd51d89b398760fd5a9081896be366dad88a922d0b29f3c69","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b38d55d08708c2410a3039687db70b4a5bfa69fc4845617c313b5a10d9c5c637","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"a05e2d784c9be7051c4ac87a407c66d2106e23490c18c038bbd0712bde7602fd","affectsGlobalScope":true},{"version":"df90b9d0e9980762da8daf8adf6ffa0c853e76bfd269c377be0d07a9ad87acd2","affectsGlobalScope":true},"cf434b5c04792f62d6f4bdd5e2c8673f36e638e910333c172614d5def9b17f98","1d65d4798df9c2df008884035c41d3e67731f29db5ecb64cd7378797c7c53a2f","0faee6b555890a1cb106e2adc5d3ffd89545b1da894d474e9d436596d654998f","c6c01ea1c42508edf11a36d13b70f6e35774f74355ba5d358354d4a77cc67ea1","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"b0297b09e607bec9698cac7cf55463d6731406efb1161ee4d448293b47397c84","bf88ef4208a770ca39a844b182b3695df536326ea566893fdc5b8418702a331e","89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6",{"version":"3b75495c77f85fef76a898491b2eff2e4eb80a37d798a8ad8b39a578c2303859","affectsGlobalScope":true},"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","247a952efd811d780e5630f8cfd76f495196f5fa74f6f0fee39ac8ba4a3c9800","f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",{"version":"7e6f46ed7dbebb2b1a5bdfac5a41582dfd266217a30387d9cbdeb2af84a39c52","affectsGlobalScope":true},"bfe1b52cf71aea9bf8815810cc5d9490fa9617313e3d3c2ee3809a28b80d0bb4","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec",{"version":"5ab630d466ac55baa6d32820378098404fc18ba9da6f7bc5df30c5dbb1cffae8","affectsGlobalScope":true},"15418e0b2cb1655d7503fd57bd55d761764d9d1d5b7c4941bf8bca0e3831a921","3411c785dbe8fd42f7d644d1e05a7e72b624774a08a9356479754999419c3c5a","8fb8fdda477cd7382477ffda92c2bb7d9f7ef583b1aa531eb6b2dc2f0a206c10","66995b0c991b5c5d42eff1d950733f85482c7419f7296ab8952e03718169e379","33f3795a4617f98b1bb8dac36312119d02f31897ae75436a1e109ce042b48ee8","2850c9c5dc28d34ad5f354117d0419f325fc8932d2a62eadc4dc52c018cd569b","c753948f7e0febe7aa1a5b71a714001a127a68861309b2c4127775aa9b6d4f24","3e7a40e023e1d4a9eef1a6f08a3ded8edacb67ae5fce072014205d730f717ba5","a77be6fc44c876bc10c897107f84eaba10790913ebdcad40fcda7e47469b2160","382100b010774614310d994bbf16cc9cd291c14f0d417126c7a7cfad1dc1d3f8","91f5dbcdb25d145a56cffe957ec665256827892d779ef108eb2f3864faff523b","4fdf56315340bd1770eb52e1601c3a98e45b1d207202831357e99ce29c35b55c","927955a3de5857e0a1c575ced5a4245e74e6821d720ed213141347dd1870197f","be6fd74528b32986fbf0cd2cfa9192a5ed7f369060b32a7adcb0c8d055708e61","03c258e060b7da220973f84b89615e4e9850e9b5d30b3a8e4840b3e3268ae8eb","fd0589ca571ad090b531d8c095e26caa53d4825c64d3ff2b2b1ab95d72294175",{"version":"669843ecafb89ae1e944df06360e8966219e4c1c34c0d28aa2503272cdd444a7","affectsGlobalScope":true},"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8","fe4a2042d087990ebfc7dc0142d5aaf5a152e4baea86b45f283f103ec1e871ea","d70c026dd2eeaa974f430ea229230a1897fdb897dc74659deebe2afd4feeb08f","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","c24ad9be9adf28f0927e3d9d9e9cec1c677022356f241ccbbfb97bfe8fb3d1a1","ca59fe42b81228a317812e95a2e72ccc8c7f1911b5f0c2a032adf41a0161ec5d","9364c7566b0be2f7b70ff5285eb34686f83ccb01bda529b82d23b2a844653bfb","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","ae9930989ed57478eb03b9b80ad3efa7a3eacdfeff0f78ecf7894c4963a64f93","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","3e59f00ab03c33717b3130066d4debb272da90eeded4935ff0604c2bc25a5cae","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",{"version":"0714e2046df66c0e93c3330d30dbc0565b3e8cd3ee302cf99e4ede6220e5fec8","affectsGlobalScope":true},"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","3c17de487f67fd2ce7a70090b19e791b0388ed9cb60cdbdc7a49277094ffc413","2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b","8f7a2387ecc680872d09a6edbca1612d699f77ee5a5129944935c3798a613d04","9df147746b0cbd11d022b564e6fdd43ac79b643dc579d2123317ee01cc4f0d70","32ee1994c2640a8a4ebd01f61960add137006eb59f78fc4c3c0743b54490605a","0e8a832a1e01424254356952245fd3c60179d7b48deac3f757a2a3147ecf5d44","a08619a14fedff16b7f7baa63698ce9be84fcff20462a52aba1ed7675965742f","8a02143a323c05741ec49533d54c4928dbf6618d297d855f12114304c3b69846",{"version":"8f19251323456195ec9236df857bac3b8f97b73b540ef06ead2ceccc3884954f","affectsGlobalScope":true},"4370c91e46f6992a89e3979c50434cf932088e1b5ae6e3a5214d778634dea768","960a68ced7820108787135bdae5265d2cc4b511b7dcfd5b8f213432a8483daf1","e27ecc0d7bbbb4b12c9688e2f728e09c0be5a73dff4257008790f60cc6df5d54","2e7ebdc7d8af978c263890bbde991e88d6aa31cc29d46735c9c5f45f0a41243b","b57fd1c0a680d220e714b76d83eff51a08670f56efcc5d68abc82f5a2684f0c0","53e37f594066c9a083bb1a3ff2d2fa1be5fa617fcfad963a22841648e9139378","1084565c68b2aed5d6d5cea394799bd688afdf4dc99f4e3615957857c15bb231","74b0245c42990ed8a849df955db3f4362c81b13f799ebc981b7bec2d5b414a57","cc0700b1b97e18a3d5d9184470502d8762ec85158819d662730c3a8c5d702584","9871b7ee672bc16c78833bdab3052615834b08375cb144e4d2cba74473f4a589","c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","86c73f2ee1752bac8eeeece234fd05dfcf0637a4fbd8032e4f5f43102faa8eec","42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","f4e9bf9103191ef3b3612d3ec0044ca4044ca5be27711fe648ada06fad4bcc85","0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","f77ff4cd234d3fd18ddd5aeadb6f94374511931976d41f4b9f594cb71f7ce6f3","4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","4f18b4e6081e5e980ef53ddf57b9c959d36cffe1eb153865f512a01aeffb5e1e","7f17d4846a88eca5fe71c4474ef687ee89c4acf9b5372ab9b2ee68644b7e0fe0","b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","85f8ebd7f245e8bf29da270e8b53dcdd17528826ffd27176c5fc7e426213ef5a","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","c5e775cc64dcc3b16c41f28b9aee23bb2854792114ea49a8b2bdc35dc9549ca0","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","efdffb306ef0a6c0ac375064d190bc93c7c2108004064529ca7d5e7f38d71285","6d6e06d8269ca289d16f53844f23c254c3944a524bc67382d9b4691ec8c486b9","70e9a18da08294f75bf23e46c7d69e67634c0765d355887b9b41f0d959e1426e","e9eb1b173aa166892f3eddab182e49cfe59aa2e14d33aedb6b49d175ed6a3750"],"root":[60,61],"options":{"allowSyntheticDefaultImports":true,"esModuleInterop":true,"jsx":1,"module":5,"noFallthroughCasesInSwitch":true,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"./","preserveConstEnums":true,"removeComments":false,"skipLibCheck":true,"sourceMap":true,"strict":true,"strictNullChecks":false,"strictPropertyInitialization":false,"target":99},"fileIdsList":[[62],[139],[62,63,64,65,66],[62,64],[68],[70,71],[89,124],[126],[132],[134],[135],[141,144],[88,119,124,159,160,162],[161],[165,167,168,169,170,171,172,173,174,175,176,177],[165,166,168,169,170,171,172,173,174,175,176,177],[166,167,168,169,170,171,172,173,174,175,176,177],[165,166,167,169,170,171,172,173,174,175,176,177],[165,166,167,168,170,171,172,173,174,175,176,177],[165,166,167,168,169,171,172,173,174,175,176,177],[165,166,167,168,169,170,172,173,174,175,176,177],[165,166,167,168,169,170,171,173,174,175,176,177],[165,166,167,168,169,170,171,172,174,175,176,177],[165,166,167,168,169,170,171,172,173,175,176,177],[165,166,167,168,169,170,171,172,173,174,176,177],[165,166,167,168,169,170,171,172,173,174,175,177],[165,166,167,168,169,170,171,172,173,174,175,176],[72],[75],[76,81,108],[77,88,89,96,105,116],[77,78,88,96],[79,117],[80,81,89,97],[81,105,113],[82,84,88,96],[83],[84,85],[88],[87,88],[75,88],[88,89,90,105,116],[88,89,90,105],[88,91,96,105,116],[88,89,91,92,96,105,113,116],[91,93,105,113,116],[72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],[88,94],[95,116,121],[84,88,96,105],[97],[98],[75,99],[100,115,121],[101],[102],[88,103],[103,104,117,119],[76,88,105,106,107],[76,105,107],[105,106],[108],[109],[88,111,112],[111,112],[81,96,105,113],[114],[96,115],[76,91,102,116],[81,117],[105,118],[119],[120],[76,81,88,90,99,105,116,119,121],[105,122],[182,183],[182],[132,133,187],[132,190],[189,190,191,192,193],[128,129,130,131],[196,235],[196,220,235],[235],[196],[196,221,235],[196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234],[221,235],[239],[242],[137,143],[141],[138,142],[148],[147,148],[147],[147,148,149,151,152,155,156,157,158],[148,152],[147,148,149,151,152,153,154],[147,152],[152,156],[148,149,150],[149],[147,148,152],[140]],"referencedMap":[[64,1],[140,2],[67,3],[63,1],[65,4],[66,1],[69,5],[71,6],[125,7],[127,8],[133,9],[135,10],[136,11],[145,12],[161,13],[162,14],[166,15],[167,16],[165,17],[168,18],[169,19],[170,20],[171,21],[172,22],[173,23],[174,24],[175,25],[176,26],[177,27],[178,8],[72,28],[73,28],[75,29],[76,30],[77,31],[78,32],[79,33],[80,34],[81,35],[82,36],[83,37],[84,38],[85,38],[86,39],[87,40],[88,41],[89,42],[90,43],[91,44],[92,45],[93,46],[124,47],[94,48],[95,49],[96,50],[97,51],[98,52],[99,53],[100,54],[101,55],[102,56],[103,57],[104,58],[105,59],[107,60],[106,61],[108,62],[109,63],[111,64],[112,65],[113,66],[114,67],[115,68],[116,69],[117,70],[118,71],[119,72],[120,73],[121,74],[122,75],[184,76],[183,77],[185,9],[186,9],[188,78],[191,79],[192,9],[190,9],[193,79],[194,80],[132,81],[220,82],[221,83],[196,84],[199,84],[218,82],[219,82],[209,82],[208,85],[206,82],[201,82],[214,82],[212,82],[216,82],[200,82],[213,82],[217,82],[202,82],[203,82],[215,82],[197,82],[204,82],[205,82],[207,82],[211,82],[222,86],[210,82],[198,82],[235,87],[229,86],[231,88],[230,86],[223,86],[224,86],[226,86],[228,86],[232,88],[233,88],[225,88],[227,88],[240,89],[243,90],[144,91],[142,92],[143,93],[149,94],[158,95],[148,96],[159,97],[154,98],[155,99],[153,100],[157,101],[151,102],[150,103],[156,104],[152,95],[141,105],[182,77]],"exportedModulesMap":[[64,1],[140,2],[67,3],[63,1],[65,4],[66,1],[69,5],[71,6],[125,7],[127,8],[133,9],[135,10],[136,11],[145,12],[161,13],[162,14],[166,15],[167,16],[165,17],[168,18],[169,19],[170,20],[171,21],[172,22],[173,23],[174,24],[175,25],[176,26],[177,27],[178,8],[72,28],[73,28],[75,29],[76,30],[77,31],[78,32],[79,33],[80,34],[81,35],[82,36],[83,37],[84,38],[85,38],[86,39],[87,40],[88,41],[89,42],[90,43],[91,44],[92,45],[93,46],[124,47],[94,48],[95,49],[96,50],[97,51],[98,52],[99,53],[100,54],[101,55],[102,56],[103,57],[104,58],[105,59],[107,60],[106,61],[108,62],[109,63],[111,64],[112,65],[113,66],[114,67],[115,68],[116,69],[117,70],[118,71],[119,72],[120,73],[121,74],[122,75],[184,76],[183,77],[185,9],[186,9],[188,78],[191,79],[192,9],[190,9],[193,79],[194,80],[132,81],[220,82],[221,83],[196,84],[199,84],[218,82],[219,82],[209,82],[208,85],[206,82],[201,82],[214,82],[212,82],[216,82],[200,82],[213,82],[217,82],[202,82],[203,82],[215,82],[197,82],[204,82],[205,82],[207,82],[211,82],[222,86],[210,82],[198,82],[235,87],[229,86],[231,88],[230,86],[223,86],[224,86],[226,86],[228,86],[232,88],[233,88],[225,88],[227,88],[240,89],[243,90],[144,91],[142,92],[143,93],[149,94],[158,95],[148,96],[159,97],[154,98],[155,99],[153,100],[157,101],[151,102],[150,103],[156,104],[152,95],[141,105],[182,77]],"semanticDiagnosticsPerFile":[64,62,137,140,139,67,63,65,66,69,71,70,125,127,133,134,135,136,145,146,161,162,163,164,166,167,165,168,169,170,171,172,173,174,175,176,177,178,68,72,73,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,74,123,91,92,93,124,94,95,96,97,98,99,100,101,102,103,104,105,107,106,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,179,180,181,130,184,183,185,186,188,191,192,190,193,189,194,128,132,195,131,220,221,196,199,218,219,209,208,206,201,214,212,216,200,213,217,202,203,215,197,204,205,207,211,222,210,198,235,234,229,231,230,223,224,226,228,232,233,225,227,236,160,237,126,238,240,241,242,243,129,144,142,143,138,239,149,158,147,148,159,154,155,153,157,151,150,156,152,141,187,182,58,59,10,11,13,12,2,14,15,16,17,18,19,20,21,3,4,22,26,23,24,25,27,28,29,5,30,31,32,33,6,37,34,35,36,38,7,39,44,45,40,41,42,43,8,49,46,47,48,50,9,51,52,53,56,54,55,1,57,60,61]},"version":"5.2.2"} \ No newline at end of file diff --git a/web-server/scripts/build.sh b/web-server/scripts/build.sh index 1a4d53df..3bcacd4f 100755 --- a/web-server/scripts/build.sh +++ b/web-server/scripts/build.sh @@ -9,6 +9,7 @@ is_project_root NEXT_MANUAL_SIG_HANDLE=true yarn run next build +yarn run build:workers echo "EXITED $?" diff --git a/web-server/src/workers/fetchLogsWorker.ts b/web-server/src/workers/fetchLogsWorker.ts new file mode 100644 index 00000000..407da6a6 --- /dev/null +++ b/web-server/src/workers/fetchLogsWorker.ts @@ -0,0 +1,6 @@ +addEventListener('message', (_event: MessageEvent) => { + // console.log('Worker "); + setInterval(() => { + postMessage('fetch'); + }, 2000); +}); diff --git a/web-server/src/workers/fetchStatusWorker.ts b/web-server/src/workers/fetchStatusWorker.ts new file mode 100644 index 00000000..465499ad --- /dev/null +++ b/web-server/src/workers/fetchStatusWorker.ts @@ -0,0 +1,6 @@ +addEventListener('message', (_event: MessageEvent) => { + // console.log('Worker "); + setInterval(() => { + postMessage('fetch'); + }, 3000); +}); diff --git a/web-server/tsconfig.worker.json b/web-server/tsconfig.worker.json new file mode 100644 index 00000000..8bbc4aae --- /dev/null +++ b/web-server/tsconfig.worker.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./public/workers", + "module": "ES6", + "noEmit": false + }, + "include": ["src/workers/**/*.ts"] +} From 6d53a2bc63b46d491f2a5777c96f35aeaeb77764 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 01:57:58 +0000 Subject: [PATCH 07/55] refactor: --- web-server/src/content/Service/SystemLogs.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 3d28875e..f23785fc 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -48,9 +48,6 @@ export const SystemLogs = () => { {log} ))} - - Hello, World! - ); }; From 4de238b12710c94fd8fae5769a26a622d124a183 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 02:01:00 +0000 Subject: [PATCH 08/55] refactor: remove logs --- web-server/pages/api/service/log.ts | 2 -- web-server/pages/api/service/status.ts | 4 ---- web-server/src/components/Service/SystemStatus.tsx | 1 - web-server/src/slices/service.ts | 2 -- web-server/src/workers/fetchLogsWorker.ts | 1 - web-server/src/workers/fetchStatusWorker.ts | 1 - 6 files changed, 11 deletions(-) diff --git a/web-server/pages/api/service/log.ts b/web-server/pages/api/service/log.ts index 173de8a7..138e8b2d 100644 --- a/web-server/pages/api/service/log.ts +++ b/web-server/pages/api/service/log.ts @@ -31,9 +31,7 @@ const fetchDockerLogs = ( }; endpoint.handle.GET(getLogsSchema, async (req, res) => { - console.log(req.payload); const { serviceName } = req.payload; - console.log(`Fetching logs for ${serviceName}...`); const commandMap: Record = { 'api-server-service': diff --git a/web-server/pages/api/service/status.ts b/web-server/pages/api/service/status.ts index ee41199b..f1514f2c 100644 --- a/web-server/pages/api/service/status.ts +++ b/web-server/pages/api/service/status.ts @@ -79,8 +79,6 @@ const checkServiceStatus = async (serviceName: string): Promise => { }; endpoint.handle.GET(getStatusSchema, async (req, res) => { - console.log('Fetching service status...'); - const services = Object.values(ServiceNames); const statuses: { [key in ServiceNames]: { isUp: boolean } } = { [ServiceNames.API_SERVER]: { isUp: false }, @@ -94,8 +92,6 @@ endpoint.handle.GET(getStatusSchema, async (req, res) => { statuses[service] = { isUp: isUp }; } - console.log(statuses); - return res.send({ statuses }); }); diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 84c15b2a..b481144f 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -40,7 +40,6 @@ export const SystemStatus: FC = () => { fetchAndHandleServiceStatus(); workerRef.current = new Worker('/workers/fetchStatusWorker.js'); workerRef.current.onmessage = (event: MessageEvent) => { - console.log('WebWorker Response =>', event.data); dispatch(fetchServiceStatus()); }; workerRef.current?.postMessage('fetchStatus'); diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 9ecbcf0c..dfd61033 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -73,9 +73,7 @@ export const serviceSlice = createSlice({ initialState, reducers: { setActiveService: (state, action) => { - console.log(action.payload, 'payload', typeof action.payload); state.active = action.payload; - console.log(state.active, 'active'); }, resetState: () => getInitialState() }, diff --git a/web-server/src/workers/fetchLogsWorker.ts b/web-server/src/workers/fetchLogsWorker.ts index 407da6a6..56515f73 100644 --- a/web-server/src/workers/fetchLogsWorker.ts +++ b/web-server/src/workers/fetchLogsWorker.ts @@ -1,5 +1,4 @@ addEventListener('message', (_event: MessageEvent) => { - // console.log('Worker "); setInterval(() => { postMessage('fetch'); }, 2000); diff --git a/web-server/src/workers/fetchStatusWorker.ts b/web-server/src/workers/fetchStatusWorker.ts index 465499ad..7e154f88 100644 --- a/web-server/src/workers/fetchStatusWorker.ts +++ b/web-server/src/workers/fetchStatusWorker.ts @@ -1,5 +1,4 @@ addEventListener('message', (_event: MessageEvent) => { - // console.log('Worker "); setInterval(() => { postMessage('fetch'); }, 3000); From cf84f106383ffb719e7ea4f2795cd5b13ce78c48 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 02:54:11 +0000 Subject: [PATCH 09/55] refactor: rename to system --- web-server/pages/{service.tsx => system.tsx} | 6 +++--- web-server/src/constants/routes.ts | 2 +- .../ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename web-server/pages/{service.tsx => system.tsx} (92%) diff --git a/web-server/pages/service.tsx b/web-server/pages/system.tsx similarity index 92% rename from web-server/pages/service.tsx rename to web-server/pages/system.tsx index 59297196..78a4286f 100644 --- a/web-server/pages/service.tsx +++ b/web-server/pages/system.tsx @@ -1,7 +1,7 @@ import { Authenticated } from 'src/components/Authenticated'; import { FlexBox } from '@/components/FlexBox'; import Loader from '@/components/Loader'; -import { SystemStatus } from '@/components/Service/SystemStatus'; +import { SystemStatus } from '@/components/Service/SystemStatus'; import { useRedirectWithSession } from '@/constants/useRoute'; import { PageWrapper } from '@/content/PullRequests/PageWrapper'; import { useAuth } from '@/hooks/useAuth'; @@ -28,11 +28,11 @@ function Service() { - Service + System } hideAllSelectors - pageTitle="Service" + pageTitle="System" showEvenIfNoTeamSelected={true} isLoading={isLoading} > diff --git a/web-server/src/constants/routes.ts b/web-server/src/constants/routes.ts index 3354e2f1..dac0f165 100644 --- a/web-server/src/constants/routes.ts +++ b/web-server/src/constants/routes.ts @@ -69,7 +69,7 @@ export const ROUTES = { }, INTEGRATIONS: new RoutePath('INTEGRATIONS'), SETTINGS: new RoutePath('SETTINGS'), - SERVICE: new RoutePath('SERVICE') + SYSTEM: new RoutePath('SYSTEM') }; export const DEFAULT_HOME_ROUTE = ROUTES.DORA_METRICS; diff --git a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx index 9a7be992..139ec6c7 100644 --- a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx +++ b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx @@ -68,9 +68,9 @@ const menuItems = (): MenuItems[] => [ link: ROUTES.SETTINGS.PATH }, { - name: 'Services', + name: 'System', icon: Dns, - link: ROUTES.SERVICE.PATH + link: ROUTES.SYSTEM.PATH } ] } From fb99b269e5da7315cf79a89d81e12a054791abe1 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 11:09:45 +0000 Subject: [PATCH 10/55] refactor: --- web-server/src/slices/service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index dfd61033..8d82e701 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -23,7 +23,7 @@ type State = StateFetchConfig<{ services: ServiceStatusState; loading?: boolean; error?: string; - active: string | null; // Add 'active' to store the active service name + active: string | null; }>; export type serviceSliceState = State; @@ -31,10 +31,10 @@ export type serviceSliceState = State; const getInitialState = (): State => { return { services: { - 'api-server-service': { isUp: false, logs: [] }, - 'redis-service': { isUp: false, logs: [] }, - 'postgres-service': { isUp: false, logs: [] }, - 'sync-server-service': { isUp: false, logs: [] } + [ServiceNames.API_SERVER]: { isUp: false, logs: [] }, + [ServiceNames.REDIS]: { isUp: false, logs: [] }, + [ServiceNames.POSTGRES]: { isUp: false, logs: [] }, + [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } }, active: null, loading: false, From 10beb5fdcc019cce73ac12ea29605e084cb8a0d4 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 30 Aug 2024 13:19:34 +0000 Subject: [PATCH 11/55] style: --- web-server/pages/api/service/log.ts | 9 +- web-server/pages/api/service/status.ts | 2 +- .../src/components/Service/SystemStatus.tsx | 96 ++++++++++++------- web-server/src/constants/service.ts | 6 ++ web-server/src/content/Service/SystemLogs.tsx | 32 ++++++- web-server/src/slices/service.ts | 8 +- 6 files changed, 103 insertions(+), 50 deletions(-) create mode 100644 web-server/src/constants/service.ts diff --git a/web-server/pages/api/service/log.ts b/web-server/pages/api/service/log.ts index 138e8b2d..2c3f41e5 100644 --- a/web-server/pages/api/service/log.ts +++ b/web-server/pages/api/service/log.ts @@ -3,6 +3,7 @@ import * as yup from 'yup'; import { exec } from 'child_process'; import { Endpoint, nullSchema } from '@/api-helpers/global'; +import { ServiceNames } from '@/constants/service'; const endpoint = new Endpoint(nullSchema); const getLogsSchema = yup.object().shape({ @@ -34,13 +35,13 @@ endpoint.handle.GET(getLogsSchema, async (req, res) => { const { serviceName } = req.payload; const commandMap: Record = { - 'api-server-service': + [ServiceNames.API_SERVER]: 'cd ../../.. && cd /var/log/apiserver/ && tail -n 500 apiserver.log', - 'sync-server-service': + [ServiceNames.SYNC_SERVER]: 'cd ../../.. && cd /var/log/sync_server/ && tail -n 500 sync_server.log', - 'redis-service': + [ServiceNames.REDIS]: 'cd ../../.. && cd /var/log/redis/ && tail -n 500 redis.log', - 'postgres-service': + [ServiceNames.POSTGRES]: 'cd ../../.. && cd /var/log/postgres/ && tail -n 500 postgres.log' }; diff --git a/web-server/pages/api/service/status.ts b/web-server/pages/api/service/status.ts index f1514f2c..1c81b724 100644 --- a/web-server/pages/api/service/status.ts +++ b/web-server/pages/api/service/status.ts @@ -4,7 +4,7 @@ import { exec } from 'child_process'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { Endpoint, nullSchema } from '@/api-helpers/global'; -import { ServiceNames } from '@/slices/service'; +import { ServiceNames } from '@/constants/service'; import { dbRaw } from '@/utils/db'; const getStatusSchema = yup.object().shape({}); diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index b481144f..05e0a1f5 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,6 +1,7 @@ import { Box, Divider, Grid } from '@mui/material'; import { FC, useEffect, useRef, useState } from 'react'; +import { ServiceNames } from '@/constants/service'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; import { ServiceStatusState, @@ -51,32 +52,31 @@ export const SystemStatus: FC = () => { const { addPage } = useOverlayPage(); const ServiceTitle: { [key: string]: string } = { - 'api-server-service': 'Backend Server', - 'redis-service': 'Redis DataBase', - 'postgres-service': 'Postgres DataBase', - 'sync-server-service': 'Sync Server' + [ServiceNames.API_SERVER]: 'Backend Server', + [ServiceNames.REDIS]: 'Redis DataBase', + [ServiceNames.POSTGRES]: 'Postgres DataBase', + [ServiceNames.SYNC_SERVER]: 'Sync Server' }; - return ( - - + + System Status - + {error && ( - +

{error}

)} - + {services && Object.keys(services).map((serviceName) => { const { isUp } = services[serviceName]; return ( - + { dispatch( @@ -85,37 +85,67 @@ export const SystemStatus: FC = () => { addPage({ page: { ui: 'system_logs', - title: ServiceTitle[serviceName] + ' Logs' + title: `${ServiceTitle[serviceName]} Logs` } }); }} + sx={{ + transition: 'box-shadow 0.2s ease', + '&:hover': { + boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)' + }, + backgroundColor: 'rgba(255, 255, 255, 0.05)', + borderRadius: '12px', + border: `1px solid ${ + isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' + }`, + padding: '16px', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden' + }} > - - - - - {`${ServiceTitle[serviceName]} `} - - + + + + {ServiceTitle[serviceName]} + + - - - {isUp - ? 'Status: Healthy' - : 'Status: Not Operational'} - - - + + {isUp ? 'Status: Healthy' : 'Status: Not Operational'} + diff --git a/web-server/src/constants/service.ts b/web-server/src/constants/service.ts new file mode 100644 index 00000000..d634e142 --- /dev/null +++ b/web-server/src/constants/service.ts @@ -0,0 +1,6 @@ +export enum ServiceNames { + API_SERVER = 'api-server-service', + REDIS = 'redis-service', + POSTGRES = 'postgres-service', + SYNC_SERVER = 'sync-server-service' +} diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index f23785fc..3739fbd7 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -29,10 +29,8 @@ export const SystemLogs = () => { return ( { - {log} + + {i + 1} + + + {log} + ))} diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 8d82e701..7e2a2e4d 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -1,15 +1,9 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { handleApi } from '@/api-helpers/axios-api-instance'; +import { ServiceNames } from '@/constants/service'; import { StateFetchConfig } from '@/types/redux'; -export enum ServiceNames { - API_SERVER = 'api-server-service', - REDIS = 'redis-service', - POSTGRES = 'postgres-service', - SYNC_SERVER = 'sync-server-service' -} - type ServiceStatus = { isUp: boolean; logs: string[]; From 093e1d98002c804fe38abbd987e0aeaa90d9bd28 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 10:07:47 +0000 Subject: [PATCH 12/55] remove workers --- web-server/package.json | 4 +--- web-server/public/workers/fetchLogsWorker.js | 8 -------- web-server/public/workers/fetchLogsWorker.js.map | 1 - web-server/public/workers/fetchStatusWorker.js | 8 -------- web-server/public/workers/fetchStatusWorker.js.map | 1 - web-server/public/workers/tsconfig.worker.tsbuildinfo | 1 - web-server/src/workers/fetchLogsWorker.ts | 5 ----- web-server/src/workers/fetchStatusWorker.ts | 5 ----- web-server/tsconfig.worker.json | 9 --------- 9 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 web-server/public/workers/fetchLogsWorker.js delete mode 100644 web-server/public/workers/fetchLogsWorker.js.map delete mode 100644 web-server/public/workers/fetchStatusWorker.js delete mode 100644 web-server/public/workers/fetchStatusWorker.js.map delete mode 100644 web-server/public/workers/tsconfig.worker.tsbuildinfo delete mode 100644 web-server/src/workers/fetchLogsWorker.ts delete mode 100644 web-server/src/workers/fetchStatusWorker.ts delete mode 100644 web-server/tsconfig.worker.json diff --git a/web-server/package.json b/web-server/package.json index bee38a90..82263af2 100644 --- a/web-server/package.json +++ b/web-server/package.json @@ -96,9 +96,7 @@ "clean": "rm -rf .next", "test": "jest --passWithNoTests --onlyChanged", "test:e2e": "playwright test --headed", - "env": "bash ./scripts/setup-env.sh", - "build:workers": "tsc -p tsconfig.worker.json", - "watch:workers": "tsc -p tsconfig.worker.json -watch" + "env": "bash ./scripts/setup-env.sh" }, "devDependencies": { "@next/bundle-analyzer": "^12.3.0", diff --git a/web-server/public/workers/fetchLogsWorker.js b/web-server/public/workers/fetchLogsWorker.js deleted file mode 100644 index a37954d2..00000000 --- a/web-server/public/workers/fetchLogsWorker.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -addEventListener('message', (_event) => { - // console.log('Worker "); - setInterval(() => { - postMessage('fetch'); - }, 2000); -}); -//# sourceMappingURL=fetchLogsWorker.js.map \ No newline at end of file diff --git a/web-server/public/workers/fetchLogsWorker.js.map b/web-server/public/workers/fetchLogsWorker.js.map deleted file mode 100644 index a482e1b2..00000000 --- a/web-server/public/workers/fetchLogsWorker.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fetchLogsWorker.js","sourceRoot":"","sources":["../../src/workers/fetchLogsWorker.ts"],"names":[],"mappings":";AAAA,gBAAgB,CAAC,SAAS,EAAE,CAAC,MAA4B,EAAE,EAAE;IAC3D,0BAA0B;IAC1B,WAAW,CAAC,GAAG,EAAE;QACf,WAAW,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/web-server/public/workers/fetchStatusWorker.js b/web-server/public/workers/fetchStatusWorker.js deleted file mode 100644 index 7470d941..00000000 --- a/web-server/public/workers/fetchStatusWorker.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -addEventListener('message', (_event) => { - // console.log('Worker "); - setInterval(() => { - postMessage('fetch'); - }, 3000); -}); -//# sourceMappingURL=fetchStatusWorker.js.map \ No newline at end of file diff --git a/web-server/public/workers/fetchStatusWorker.js.map b/web-server/public/workers/fetchStatusWorker.js.map deleted file mode 100644 index 2cf271c1..00000000 --- a/web-server/public/workers/fetchStatusWorker.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fetchStatusWorker.js","sourceRoot":"","sources":["../../src/workers/fetchStatusWorker.ts"],"names":[],"mappings":";AAAA,gBAAgB,CAAC,SAAS,EAAE,CAAC,MAA4B,EAAE,EAAE;IAC3D,0BAA0B;IAC1B,WAAW,CAAC,GAAG,EAAE;QACf,WAAW,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/web-server/public/workers/tsconfig.worker.tsbuildinfo b/web-server/public/workers/tsconfig.worker.tsbuildinfo deleted file mode 100644 index e922096e..00000000 --- a/web-server/public/workers/tsconfig.worker.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"program":{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../src/workers/fetchLogsWorker.ts","../../src/workers/fetchStatusWorker.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/@types/ms/index.d.ts","../../node_modules/@types/debug/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/estree-jsx/index.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/dom-events.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/globals.global.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/graceful-fs/index.d.ts","../../node_modules/@types/unist/index.d.ts","../../node_modules/@types/hast/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/prop-types/index.d.ts","../../node_modules/@types/scheduler/tracing.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/hoist-non-react-statics/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@jest/expect-utils/build/index.d.ts","../../node_modules/jest-matcher-utils/node_modules/chalk/index.d.ts","../../node_modules/@sinclair/typebox/typebox.d.ts","../../node_modules/@jest/schemas/build/index.d.ts","../../node_modules/pretty-format/build/index.d.ts","../../node_modules/jest-diff/build/index.d.ts","../../node_modules/jest-matcher-utils/build/index.d.ts","../../node_modules/expect/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/js-cookie/index.d.ts","../../node_modules/parse5/dist/common/html.d.ts","../../node_modules/parse5/dist/common/token.d.ts","../../node_modules/parse5/dist/common/error-codes.d.ts","../../node_modules/parse5/dist/tokenizer/preprocessor.d.ts","../../node_modules/parse5/dist/tokenizer/index.d.ts","../../node_modules/parse5/dist/tree-adapters/interface.d.ts","../../node_modules/parse5/dist/parser/open-element-stack.d.ts","../../node_modules/parse5/dist/parser/formatting-element-list.d.ts","../../node_modules/parse5/dist/parser/index.d.ts","../../node_modules/parse5/dist/tree-adapters/default.d.ts","../../node_modules/parse5/dist/serializer/index.d.ts","../../node_modules/parse5/dist/common/foreign-content.d.ts","../../node_modules/parse5/dist/index.d.ts","../../node_modules/@types/tough-cookie/index.d.ts","../../node_modules/@types/jsdom/base.d.ts","../../node_modules/@types/jsdom/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/json5/index.d.ts","../../node_modules/@types/lodash/common/common.d.ts","../../node_modules/@types/lodash/common/array.d.ts","../../node_modules/@types/lodash/common/collection.d.ts","../../node_modules/@types/lodash/common/date.d.ts","../../node_modules/@types/lodash/common/function.d.ts","../../node_modules/@types/lodash/common/lang.d.ts","../../node_modules/@types/lodash/common/math.d.ts","../../node_modules/@types/lodash/common/number.d.ts","../../node_modules/@types/lodash/common/object.d.ts","../../node_modules/@types/lodash/common/seq.d.ts","../../node_modules/@types/lodash/common/string.d.ts","../../node_modules/@types/lodash/common/util.d.ts","../../node_modules/@types/lodash/index.d.ts","../../node_modules/@types/mdast/index.d.ts","../../node_modules/@types/nprogress/index.d.ts","../../node_modules/@types/parse-json/index.d.ts","../../node_modules/@types/pluralize/index.d.ts","../../node_modules/ts-toolbelt/out/index.d.ts","../../node_modules/@types/ramda/tools.d.ts","../../node_modules/@types/ramda/index.d.ts","../../node_modules/@types/react-copy-to-clipboard/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/redux/index.d.ts","../../node_modules/@types/react-redux/index.d.ts","../../node_modules/@types/react-transition-group/config.d.ts","../../node_modules/@types/react-transition-group/Transition.d.ts","../../node_modules/@types/react-transition-group/CSSTransition.d.ts","../../node_modules/@types/react-transition-group/SwitchTransition.d.ts","../../node_modules/@types/react-transition-group/TransitionGroup.d.ts","../../node_modules/@types/react-transition-group/index.d.ts","../../node_modules/@types/scheduler/index.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/ua-parser-js/index.d.ts","../../node_modules/@types/uuid/index.d.ts","../../node_modules/moment/ts3.1-typings/moment.d.ts","../../node_modules/@types/vis/index.d.ts","../../node_modules/@types/voca/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"2ac9cdcfb8f8875c18d14ec5796a8b029c426f73ad6dc3ffb580c228b58d1c44","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","dc48272d7c333ccf58034c0026162576b7d50ea0e69c3b9292f803fc20720fd5","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc",{"version":"0075fa5ceda385bcdf3488e37786b5a33be730e8bc4aa3cf1e78c63891752ce8","affectsGlobalScope":true},{"version":"35299ae4a62086698444a5aaee27fc7aa377c68cbb90b441c9ace246ffd05c97","affectsGlobalScope":true},{"version":"f296963760430fb65b4e5d91f0ed770a91c6e77455bacf8fa23a1501654ede0e","affectsGlobalScope":true},{"version":"09226e53d1cfda217317074a97724da3e71e2c545e18774484b61562afc53cd2","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"8b41361862022eb72fcc8a7f34680ac842aca802cf4bc1f915e8c620c9ce4331","affectsGlobalScope":true},{"version":"f7bd636ae3a4623c503359ada74510c4005df5b36de7f23e1db8a5c543fd176b","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"0c20f4d2358eb679e4ae8a4432bdd96c857a2960fd6800b21ec4008ec59d60ea","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"82d0d8e269b9eeac02c3bd1c9e884e85d483fcb2cd168bccd6bc54df663da031","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"b8deab98702588840be73d67f02412a2d45a417a3c097b2e96f7f3a42ac483d1","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"376d554d042fb409cb55b5cbaf0b2b4b7e669619493c5d18d5fa8bd67273f82a","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"c4138a3dd7cd6cf1f363ca0f905554e8d81b45844feea17786cdf1626cb8ea06","affectsGlobalScope":true},{"version":"6ff3e2452b055d8f0ec026511c6582b55d935675af67cdb67dd1dc671e8065df","affectsGlobalScope":true},{"version":"03de17b810f426a2f47396b0b99b53a82c1b60e9cba7a7edda47f9bb077882f4","affectsGlobalScope":true},{"version":"8184c6ddf48f0c98429326b428478ecc6143c27f79b79e85740f17e6feb090f1","affectsGlobalScope":true},{"version":"261c4d2cf86ac5a89ad3fb3fafed74cbb6f2f7c1d139b0540933df567d64a6ca","affectsGlobalScope":true},{"version":"6af1425e9973f4924fca986636ac19a0cf9909a7e0d9d3009c349e6244e957b6","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"15a630d6817718a2ddd7088c4f83e4673fde19fa992d2eae2cf51132a302a5d3","affectsGlobalScope":true},{"version":"b7e9f95a7387e3f66be0ed6db43600c49cec33a3900437ce2fd350d9b7cb16f2","affectsGlobalScope":true},{"version":"01e0ee7e1f661acedb08b51f8a9b7d7f959e9cdb6441360f06522cc3aea1bf2e","affectsGlobalScope":true},{"version":"ac17a97f816d53d9dd79b0d235e1c0ed54a8cc6a0677e9a3d61efb480b2a3e4e","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"ec0104fee478075cb5171e5f4e3f23add8e02d845ae0165bfa3f1099241fa2aa","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"9cc66b0513ad41cb5f5372cca86ef83a0d37d1c1017580b7dace3ea5661836df","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"709efdae0cb5df5f49376cde61daacc95cdd44ae4671da13a540da5088bf3f30","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"bc496ef4377553e461efcf7cc5a5a57cf59f9962aea06b5e722d54a36bf66ea1","affectsGlobalScope":true},{"version":"038a2f66a34ee7a9c2fbc3584c8ab43dff2995f8c68e3f566f4c300d2175e31e","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"f5c92f2c27b06c1a41b88f6db8299205aee52c2a2943f7ed29bd585977f254e8","affectsGlobalScope":true},{"version":"930b0e15811f84e203d3c23508674d5ded88266df4b10abee7b31b2ac77632d2","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"b9ea5778ff8b50d7c04c9890170db34c26a5358cccba36844fe319f50a43a61a","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"307c8b7ebbd7f23a92b73a4c6c0a697beca05b06b036c23a34553e5fe65e4fdc","affectsGlobalScope":true},{"version":"f35a831e4f0fe3b3697f4a0fe0e3caa7624c92b78afbecaf142c0f93abfaf379","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"a7bcdaff8027296d87a436b6dcd66c1aa7f7848cbe4457f0b6132de372d80f6e","affectsGlobalScope":true},{"version":"3611f0603c67df2500cd0db5c568aec9938cbeb1200eaf6841459609f2d43873","affectsGlobalScope":true},"e74998d5cefc2f29d583c10b99c1478fb810f1e46fbb06535bfb0bbba3c84aa5","cc957354aa3c94c9961ebf46282cfde1e81d107fc5785a61f62c67f1dd3ac2eb","43d058146b002d075f5d0033a6870321048297f1658eb0db559ba028383803a6","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","6704f0b54df85640baaeebd86c9d4a1dbb661d5a4d57a75bc84162f562f6531d","9d255af1b09c6697089d3c9bf438292a298d8b7a95c68793c9aae80afc9e5ca7","68cc8d6fcc2f270d7108f02f3ebc59480a54615be3e09a47e14527f349e9d53e","3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","5b3cd03ae354ea96eff1f74d7c410fe4852e6382227e8b0ecf87ab5e3a5bbcd4","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"056097110efd16869ec118cedb44ecbac9a019576eee808d61304ca6d5cb2cbe","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"6fb8358e10ed92a7f515b7d79da3904c955a3ffd4e14aa9df6f0ea113041f1cf","affectsGlobalScope":true},"45c831238c6dac21c72da5f335747736a56a3847192bf03c84b958a7e9ec93e2","661a11d16ad2e3543a77c53bcd4017ee9a450f47ab7def3ab493a86eae4d550c",{"version":"8cdc646cec7819581ef343b83855b1bfe4fe674f2c84f4fb8dc90d82fb56bd3a","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","9dd56225cc2d8cb8fe5ceb0043ff386987637e12fecc6078896058a99deae284","2375ed4b439215aa3b6d0c6fd175c78a4384b30cb43cbadaecbf0a18954c98cb","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","41231da15bb5e3e806a8395bd15c7befd2ec90f9f4e3c9d0ae1356bccb76dbb0","fccfef201d057cb407fa515311bd608549bab6c7b8adcf8f2df31f5d3b796478",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},"5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"464762c6213566d072f1ced5e8e9a954785ec5e53883b7397198abb5ef5b8f71","affectsGlobalScope":true},"6387920dc3e18927335b086deec75bf8e50f879a5e273d32ee7bb7a55ba50572","9bba37424094688c4663c177a1379b229f919b8912889a472f32fdc5f08ddb4d","29a4be13b3a30d3e66667b75c58ec61fb2df8fa0422534fdee3cfb30c5dbf450","83366d901beda79d6eb37aaaf6ca248dcd88946302b2a7d975590783be51e88e","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","43ec77c369473e92e2ecebf0554a0fdaa9c256644a6070f28228dfcceec77351",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"95ed02bacb4502c985b69742ec82a4576d4ff4a6620ecc91593f611d502ae546","bf755525c4e6f85a970b98c4755d98e8aa1b6dbd83a5d8fcc57d3d497351b936","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"28084e15b63e6211769db2fe646d8bc5c4c6776321e0deffe2d12eefd52cb6b9","affectsGlobalScope":true},{"version":"aed37dabf86c99d6c8508700576ecede86688397bc12523541858705a0c737c2","affectsGlobalScope":true},"cc6ef5733d4ea6d2e06310a32dffd2c16418b467c5033d49cecc4f3a25de7497","94768454c3348b6ebe48e45fbad8c92e2bb7af4a35243edbe2b90823d0bd7f9a","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","84da3c28344e621fd1d591f2c09e9595292d2b70018da28a553268ac122597d4","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","6e191fea1db6e9e4fa828259cf489e820ec9170effff57fb081a2f3295db4722","aed943465fbce1efe49ee16b5ea409050f15cd8eaf116f6fadb64ef0772e7d95","70d08483a67bf7050dbedace398ef3fee9f436fcd60517c97c4c1e22e3c6f3e8","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"e933de8143e1d12dd51d89b398760fd5a9081896be366dad88a922d0b29f3c69","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b38d55d08708c2410a3039687db70b4a5bfa69fc4845617c313b5a10d9c5c637","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"a05e2d784c9be7051c4ac87a407c66d2106e23490c18c038bbd0712bde7602fd","affectsGlobalScope":true},{"version":"df90b9d0e9980762da8daf8adf6ffa0c853e76bfd269c377be0d07a9ad87acd2","affectsGlobalScope":true},"cf434b5c04792f62d6f4bdd5e2c8673f36e638e910333c172614d5def9b17f98","1d65d4798df9c2df008884035c41d3e67731f29db5ecb64cd7378797c7c53a2f","0faee6b555890a1cb106e2adc5d3ffd89545b1da894d474e9d436596d654998f","c6c01ea1c42508edf11a36d13b70f6e35774f74355ba5d358354d4a77cc67ea1","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"b0297b09e607bec9698cac7cf55463d6731406efb1161ee4d448293b47397c84","bf88ef4208a770ca39a844b182b3695df536326ea566893fdc5b8418702a331e","89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6",{"version":"3b75495c77f85fef76a898491b2eff2e4eb80a37d798a8ad8b39a578c2303859","affectsGlobalScope":true},"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","247a952efd811d780e5630f8cfd76f495196f5fa74f6f0fee39ac8ba4a3c9800","f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",{"version":"7e6f46ed7dbebb2b1a5bdfac5a41582dfd266217a30387d9cbdeb2af84a39c52","affectsGlobalScope":true},"bfe1b52cf71aea9bf8815810cc5d9490fa9617313e3d3c2ee3809a28b80d0bb4","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec",{"version":"5ab630d466ac55baa6d32820378098404fc18ba9da6f7bc5df30c5dbb1cffae8","affectsGlobalScope":true},"15418e0b2cb1655d7503fd57bd55d761764d9d1d5b7c4941bf8bca0e3831a921","3411c785dbe8fd42f7d644d1e05a7e72b624774a08a9356479754999419c3c5a","8fb8fdda477cd7382477ffda92c2bb7d9f7ef583b1aa531eb6b2dc2f0a206c10","66995b0c991b5c5d42eff1d950733f85482c7419f7296ab8952e03718169e379","33f3795a4617f98b1bb8dac36312119d02f31897ae75436a1e109ce042b48ee8","2850c9c5dc28d34ad5f354117d0419f325fc8932d2a62eadc4dc52c018cd569b","c753948f7e0febe7aa1a5b71a714001a127a68861309b2c4127775aa9b6d4f24","3e7a40e023e1d4a9eef1a6f08a3ded8edacb67ae5fce072014205d730f717ba5","a77be6fc44c876bc10c897107f84eaba10790913ebdcad40fcda7e47469b2160","382100b010774614310d994bbf16cc9cd291c14f0d417126c7a7cfad1dc1d3f8","91f5dbcdb25d145a56cffe957ec665256827892d779ef108eb2f3864faff523b","4fdf56315340bd1770eb52e1601c3a98e45b1d207202831357e99ce29c35b55c","927955a3de5857e0a1c575ced5a4245e74e6821d720ed213141347dd1870197f","be6fd74528b32986fbf0cd2cfa9192a5ed7f369060b32a7adcb0c8d055708e61","03c258e060b7da220973f84b89615e4e9850e9b5d30b3a8e4840b3e3268ae8eb","fd0589ca571ad090b531d8c095e26caa53d4825c64d3ff2b2b1ab95d72294175",{"version":"669843ecafb89ae1e944df06360e8966219e4c1c34c0d28aa2503272cdd444a7","affectsGlobalScope":true},"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8","fe4a2042d087990ebfc7dc0142d5aaf5a152e4baea86b45f283f103ec1e871ea","d70c026dd2eeaa974f430ea229230a1897fdb897dc74659deebe2afd4feeb08f","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","c24ad9be9adf28f0927e3d9d9e9cec1c677022356f241ccbbfb97bfe8fb3d1a1","ca59fe42b81228a317812e95a2e72ccc8c7f1911b5f0c2a032adf41a0161ec5d","9364c7566b0be2f7b70ff5285eb34686f83ccb01bda529b82d23b2a844653bfb","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","ae9930989ed57478eb03b9b80ad3efa7a3eacdfeff0f78ecf7894c4963a64f93","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","3e59f00ab03c33717b3130066d4debb272da90eeded4935ff0604c2bc25a5cae","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",{"version":"0714e2046df66c0e93c3330d30dbc0565b3e8cd3ee302cf99e4ede6220e5fec8","affectsGlobalScope":true},"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","3c17de487f67fd2ce7a70090b19e791b0388ed9cb60cdbdc7a49277094ffc413","2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b","8f7a2387ecc680872d09a6edbca1612d699f77ee5a5129944935c3798a613d04","9df147746b0cbd11d022b564e6fdd43ac79b643dc579d2123317ee01cc4f0d70","32ee1994c2640a8a4ebd01f61960add137006eb59f78fc4c3c0743b54490605a","0e8a832a1e01424254356952245fd3c60179d7b48deac3f757a2a3147ecf5d44","a08619a14fedff16b7f7baa63698ce9be84fcff20462a52aba1ed7675965742f","8a02143a323c05741ec49533d54c4928dbf6618d297d855f12114304c3b69846",{"version":"8f19251323456195ec9236df857bac3b8f97b73b540ef06ead2ceccc3884954f","affectsGlobalScope":true},"4370c91e46f6992a89e3979c50434cf932088e1b5ae6e3a5214d778634dea768","960a68ced7820108787135bdae5265d2cc4b511b7dcfd5b8f213432a8483daf1","e27ecc0d7bbbb4b12c9688e2f728e09c0be5a73dff4257008790f60cc6df5d54","2e7ebdc7d8af978c263890bbde991e88d6aa31cc29d46735c9c5f45f0a41243b","b57fd1c0a680d220e714b76d83eff51a08670f56efcc5d68abc82f5a2684f0c0","53e37f594066c9a083bb1a3ff2d2fa1be5fa617fcfad963a22841648e9139378","1084565c68b2aed5d6d5cea394799bd688afdf4dc99f4e3615957857c15bb231","74b0245c42990ed8a849df955db3f4362c81b13f799ebc981b7bec2d5b414a57","cc0700b1b97e18a3d5d9184470502d8762ec85158819d662730c3a8c5d702584","9871b7ee672bc16c78833bdab3052615834b08375cb144e4d2cba74473f4a589","c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","86c73f2ee1752bac8eeeece234fd05dfcf0637a4fbd8032e4f5f43102faa8eec","42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","f4e9bf9103191ef3b3612d3ec0044ca4044ca5be27711fe648ada06fad4bcc85","0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","f77ff4cd234d3fd18ddd5aeadb6f94374511931976d41f4b9f594cb71f7ce6f3","4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","4f18b4e6081e5e980ef53ddf57b9c959d36cffe1eb153865f512a01aeffb5e1e","7f17d4846a88eca5fe71c4474ef687ee89c4acf9b5372ab9b2ee68644b7e0fe0","b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","85f8ebd7f245e8bf29da270e8b53dcdd17528826ffd27176c5fc7e426213ef5a","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","c5e775cc64dcc3b16c41f28b9aee23bb2854792114ea49a8b2bdc35dc9549ca0","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","efdffb306ef0a6c0ac375064d190bc93c7c2108004064529ca7d5e7f38d71285","6d6e06d8269ca289d16f53844f23c254c3944a524bc67382d9b4691ec8c486b9","70e9a18da08294f75bf23e46c7d69e67634c0765d355887b9b41f0d959e1426e","e9eb1b173aa166892f3eddab182e49cfe59aa2e14d33aedb6b49d175ed6a3750"],"root":[60,61],"options":{"allowSyntheticDefaultImports":true,"esModuleInterop":true,"jsx":1,"module":5,"noFallthroughCasesInSwitch":true,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"./","preserveConstEnums":true,"removeComments":false,"skipLibCheck":true,"sourceMap":true,"strict":true,"strictNullChecks":false,"strictPropertyInitialization":false,"target":99},"fileIdsList":[[62],[139],[62,63,64,65,66],[62,64],[68],[70,71],[89,124],[126],[132],[134],[135],[141,144],[88,119,124,159,160,162],[161],[165,167,168,169,170,171,172,173,174,175,176,177],[165,166,168,169,170,171,172,173,174,175,176,177],[166,167,168,169,170,171,172,173,174,175,176,177],[165,166,167,169,170,171,172,173,174,175,176,177],[165,166,167,168,170,171,172,173,174,175,176,177],[165,166,167,168,169,171,172,173,174,175,176,177],[165,166,167,168,169,170,172,173,174,175,176,177],[165,166,167,168,169,170,171,173,174,175,176,177],[165,166,167,168,169,170,171,172,174,175,176,177],[165,166,167,168,169,170,171,172,173,175,176,177],[165,166,167,168,169,170,171,172,173,174,176,177],[165,166,167,168,169,170,171,172,173,174,175,177],[165,166,167,168,169,170,171,172,173,174,175,176],[72],[75],[76,81,108],[77,88,89,96,105,116],[77,78,88,96],[79,117],[80,81,89,97],[81,105,113],[82,84,88,96],[83],[84,85],[88],[87,88],[75,88],[88,89,90,105,116],[88,89,90,105],[88,91,96,105,116],[88,89,91,92,96,105,113,116],[91,93,105,113,116],[72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],[88,94],[95,116,121],[84,88,96,105],[97],[98],[75,99],[100,115,121],[101],[102],[88,103],[103,104,117,119],[76,88,105,106,107],[76,105,107],[105,106],[108],[109],[88,111,112],[111,112],[81,96,105,113],[114],[96,115],[76,91,102,116],[81,117],[105,118],[119],[120],[76,81,88,90,99,105,116,119,121],[105,122],[182,183],[182],[132,133,187],[132,190],[189,190,191,192,193],[128,129,130,131],[196,235],[196,220,235],[235],[196],[196,221,235],[196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234],[221,235],[239],[242],[137,143],[141],[138,142],[148],[147,148],[147],[147,148,149,151,152,155,156,157,158],[148,152],[147,148,149,151,152,153,154],[147,152],[152,156],[148,149,150],[149],[147,148,152],[140]],"referencedMap":[[64,1],[140,2],[67,3],[63,1],[65,4],[66,1],[69,5],[71,6],[125,7],[127,8],[133,9],[135,10],[136,11],[145,12],[161,13],[162,14],[166,15],[167,16],[165,17],[168,18],[169,19],[170,20],[171,21],[172,22],[173,23],[174,24],[175,25],[176,26],[177,27],[178,8],[72,28],[73,28],[75,29],[76,30],[77,31],[78,32],[79,33],[80,34],[81,35],[82,36],[83,37],[84,38],[85,38],[86,39],[87,40],[88,41],[89,42],[90,43],[91,44],[92,45],[93,46],[124,47],[94,48],[95,49],[96,50],[97,51],[98,52],[99,53],[100,54],[101,55],[102,56],[103,57],[104,58],[105,59],[107,60],[106,61],[108,62],[109,63],[111,64],[112,65],[113,66],[114,67],[115,68],[116,69],[117,70],[118,71],[119,72],[120,73],[121,74],[122,75],[184,76],[183,77],[185,9],[186,9],[188,78],[191,79],[192,9],[190,9],[193,79],[194,80],[132,81],[220,82],[221,83],[196,84],[199,84],[218,82],[219,82],[209,82],[208,85],[206,82],[201,82],[214,82],[212,82],[216,82],[200,82],[213,82],[217,82],[202,82],[203,82],[215,82],[197,82],[204,82],[205,82],[207,82],[211,82],[222,86],[210,82],[198,82],[235,87],[229,86],[231,88],[230,86],[223,86],[224,86],[226,86],[228,86],[232,88],[233,88],[225,88],[227,88],[240,89],[243,90],[144,91],[142,92],[143,93],[149,94],[158,95],[148,96],[159,97],[154,98],[155,99],[153,100],[157,101],[151,102],[150,103],[156,104],[152,95],[141,105],[182,77]],"exportedModulesMap":[[64,1],[140,2],[67,3],[63,1],[65,4],[66,1],[69,5],[71,6],[125,7],[127,8],[133,9],[135,10],[136,11],[145,12],[161,13],[162,14],[166,15],[167,16],[165,17],[168,18],[169,19],[170,20],[171,21],[172,22],[173,23],[174,24],[175,25],[176,26],[177,27],[178,8],[72,28],[73,28],[75,29],[76,30],[77,31],[78,32],[79,33],[80,34],[81,35],[82,36],[83,37],[84,38],[85,38],[86,39],[87,40],[88,41],[89,42],[90,43],[91,44],[92,45],[93,46],[124,47],[94,48],[95,49],[96,50],[97,51],[98,52],[99,53],[100,54],[101,55],[102,56],[103,57],[104,58],[105,59],[107,60],[106,61],[108,62],[109,63],[111,64],[112,65],[113,66],[114,67],[115,68],[116,69],[117,70],[118,71],[119,72],[120,73],[121,74],[122,75],[184,76],[183,77],[185,9],[186,9],[188,78],[191,79],[192,9],[190,9],[193,79],[194,80],[132,81],[220,82],[221,83],[196,84],[199,84],[218,82],[219,82],[209,82],[208,85],[206,82],[201,82],[214,82],[212,82],[216,82],[200,82],[213,82],[217,82],[202,82],[203,82],[215,82],[197,82],[204,82],[205,82],[207,82],[211,82],[222,86],[210,82],[198,82],[235,87],[229,86],[231,88],[230,86],[223,86],[224,86],[226,86],[228,86],[232,88],[233,88],[225,88],[227,88],[240,89],[243,90],[144,91],[142,92],[143,93],[149,94],[158,95],[148,96],[159,97],[154,98],[155,99],[153,100],[157,101],[151,102],[150,103],[156,104],[152,95],[141,105],[182,77]],"semanticDiagnosticsPerFile":[64,62,137,140,139,67,63,65,66,69,71,70,125,127,133,134,135,136,145,146,161,162,163,164,166,167,165,168,169,170,171,172,173,174,175,176,177,178,68,72,73,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,74,123,91,92,93,124,94,95,96,97,98,99,100,101,102,103,104,105,107,106,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,179,180,181,130,184,183,185,186,188,191,192,190,193,189,194,128,132,195,131,220,221,196,199,218,219,209,208,206,201,214,212,216,200,213,217,202,203,215,197,204,205,207,211,222,210,198,235,234,229,231,230,223,224,226,228,232,233,225,227,236,160,237,126,238,240,241,242,243,129,144,142,143,138,239,149,158,147,148,159,154,155,153,157,151,150,156,152,141,187,182,58,59,10,11,13,12,2,14,15,16,17,18,19,20,21,3,4,22,26,23,24,25,27,28,29,5,30,31,32,33,6,37,34,35,36,38,7,39,44,45,40,41,42,43,8,49,46,47,48,50,9,51,52,53,56,54,55,1,57,60,61]},"version":"5.2.2"} \ No newline at end of file diff --git a/web-server/src/workers/fetchLogsWorker.ts b/web-server/src/workers/fetchLogsWorker.ts deleted file mode 100644 index 56515f73..00000000 --- a/web-server/src/workers/fetchLogsWorker.ts +++ /dev/null @@ -1,5 +0,0 @@ -addEventListener('message', (_event: MessageEvent) => { - setInterval(() => { - postMessage('fetch'); - }, 2000); -}); diff --git a/web-server/src/workers/fetchStatusWorker.ts b/web-server/src/workers/fetchStatusWorker.ts deleted file mode 100644 index 7e154f88..00000000 --- a/web-server/src/workers/fetchStatusWorker.ts +++ /dev/null @@ -1,5 +0,0 @@ -addEventListener('message', (_event: MessageEvent) => { - setInterval(() => { - postMessage('fetch'); - }, 3000); -}); diff --git a/web-server/tsconfig.worker.json b/web-server/tsconfig.worker.json deleted file mode 100644 index 8bbc4aae..00000000 --- a/web-server/tsconfig.worker.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./public/workers", - "module": "ES6", - "noEmit": false - }, - "include": ["src/workers/**/*.ts"] -} From b6617e5ed8a6588f0c5e3c4c5e2968f8cda39f23 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 10:08:32 +0000 Subject: [PATCH 13/55] remove worker build --- web-server/scripts/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/web-server/scripts/build.sh b/web-server/scripts/build.sh index 3bcacd4f..1a4d53df 100755 --- a/web-server/scripts/build.sh +++ b/web-server/scripts/build.sh @@ -9,7 +9,6 @@ is_project_root NEXT_MANUAL_SIG_HANDLE=true yarn run next build -yarn run build:workers echo "EXITED $?" From 6a9b1fafbabc8bf2fd5cf53dbfdb92b358647360 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 14:33:57 +0000 Subject: [PATCH 14/55] feat: stream status --- web-server/pages/api/service/stream.ts | 120 +++++++ .../src/components/Service/SystemStatus.tsx | 305 ++++++++++++------ 2 files changed, 324 insertions(+), 101 deletions(-) create mode 100644 web-server/pages/api/service/stream.ts diff --git a/web-server/pages/api/service/stream.ts b/web-server/pages/api/service/stream.ts new file mode 100644 index 00000000..8d67a898 --- /dev/null +++ b/web-server/pages/api/service/stream.ts @@ -0,0 +1,120 @@ +import { exec } from 'child_process'; + +import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; +import { ServiceNames } from '@/constants/service'; +import { ApiRequest, ApiResponse } from '@/types/request'; +import { dbRaw } from '@/utils/db'; + +export default async function handler( + request: ApiRequest, + response: ApiResponse +) { + console.log('SSE handler initialized'); + + response.setHeader('Content-Type', 'text/event-stream'); + response.setHeader('Cache-Control', 'no-cache'); + response.setHeader('Connection', 'keep-alive'); + response.setHeader('content-encoding', 'none'); + response.setHeader('Access-Control-Allow-Origin', '*'); // Enable CORS + + response.flushHeaders(); + + // Send an initial message to establish the connection + // response.write('data: Connected\n\n'); + + const statuses = await getStatus(); + const Status = { + statuses: statuses + }; + response.write(`data: ${JSON.stringify(Status)}\n\n`); + + response.on('close', () => { + console.log('Client disconnected'); + // clearInterval(intervalId); + response.end(); + }); +} + +const getStatus = async () => { + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + for (const service of services) { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp: isUp }; + } + + return statuses; +}; + +const execPromise = (command: string) => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +const checkServiceStatus = async (serviceName: string): Promise => { + let isUp = false; + switch (serviceName) { + case ServiceNames.API_SERVER: + try { + const response = await handleRequest(''); + if (response.message === 'hello world') { + isUp = true; + } + } catch (error) { + console.error('API Server service is down:', error); + isUp = false; + } + break; + case ServiceNames.REDIS: + try { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + + if (response.trim() === 'PONG') { + isUp = true; + } + } catch (error) { + console.error('Redis service is down:', error); + isUp = false; + } + break; + case ServiceNames.POSTGRES: + try { + await dbRaw.raw('SELECT 1'); + isUp = true; + } catch (error) { + console.error('PostgreSQL service is down:', error); + isUp = false; + } + break; + case ServiceNames.SYNC_SERVER: + try { + const response = await handleSyncServerRequest(''); + if (response.message === 'hello world') { + isUp = true; + } + } catch (error) { + console.error('Sync Server service is down:', error); + isUp = false; + } + break; + + default: + console.warn(`Service ${serviceName} not recognized.`); + break; + } + + return isUp; +}; diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 05e0a1f5..3eb7d409 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,14 +1,8 @@ -import { Box, Divider, Grid } from '@mui/material'; -import { FC, useEffect, useRef, useState } from 'react'; +import { Box, Button, Divider, Typography } from '@mui/material'; +import { FC, useEffect, useState } from 'react'; import { ServiceNames } from '@/constants/service'; -import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; -import { - ServiceStatusState, - fetchServiceLogs, - fetchServiceStatus, - serviceSlice -} from '@/slices/service'; +import service, { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; import { FlexBox } from '../FlexBox'; @@ -22,39 +16,63 @@ export const SystemStatus: FC = () => { (state: { service: { services: ServiceStatusState } }) => state.service.services ); - const workerRef = useRef(); + const loading = useSelector((s) => s.service.loading); + + // useEffect(() => { + // // const fetchAndHandleServiceStatus = async () => { + // // try { + // // await dispatch(fetchServiceStatus()).unwrap(); + // // dispatch(serviceSlice.actions.setLoading(true)); + + // // } catch (err) { + // // setError('Failed to fetch service status'); + // // } + // // }; + + // // fetchAndHandleServiceStatus(); + + // }, [dispatch]); useEffect(() => { - const fetchAndHandleServiceStatus = async () => { - try { - await dispatch(fetchServiceStatus()).unwrap(); - if (services) { - Object.keys(services).forEach((serviceName) => { - dispatch(fetchServiceLogs(serviceName)).unwrap(); - }); - } - } catch (err) { - setError('Failed to fetch service status'); - } + const eventSource = new EventSource('/api/service/stream'); + + eventSource.onopen = (event) => { + console.log('Connection opened', event); }; - fetchAndHandleServiceStatus(); - workerRef.current = new Worker('/workers/fetchStatusWorker.js'); - workerRef.current.onmessage = (event: MessageEvent) => { - dispatch(fetchServiceStatus()); + // eventSource.addEventListener("message", (event) => { console.log(event) }) + eventSource.onmessage = (event) => { + console.log('Message received', event); + // Parse the data and update your component state here + const data = JSON.parse(event.data); + console.log(data); + + dispatch(serviceSlice.actions.setStatus(data)); + + // For example: setLastUpdate(data.time); + }; + + eventSource.addEventListener('state', function (event) { + const data = JSON.parse(event.data); + console.log('State:', data); + }); + + eventSource.onerror = (event) => { + console.error('EventSource failed:', event); }; - workerRef.current?.postMessage('fetchStatus'); + return () => { - workerRef.current?.terminate(); + eventSource.close(); + console.log('EventSource closed'); }; - }, [dispatch]); + }, []); const { addPage } = useOverlayPage(); const ServiceTitle: { [key: string]: string } = { [ServiceNames.API_SERVER]: 'Backend Server', - [ServiceNames.REDIS]: 'Redis DataBase', - [ServiceNames.POSTGRES]: 'Postgres DataBase', + [ServiceNames.REDIS]: 'Redis Database', + [ServiceNames.POSTGRES]: 'Postgres Database', [ServiceNames.SYNC_SERVER]: 'Sync Server' }; return ( @@ -70,90 +88,175 @@ export const SystemStatus: FC = () => {

{error}

)} + - - {services && + + {/* {services && loading && Object.keys(services).map((serviceName) => { const { isUp } = services[serviceName]; return ( - - { - dispatch( - serviceSlice.actions.setActiveService(serviceName) - ); - addPage({ - page: { - ui: 'system_logs', - title: `${ServiceTitle[serviceName]} Logs` - } - }); - }} - sx={{ - transition: 'box-shadow 0.2s ease', - '&:hover': { - boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)' - }, - backgroundColor: 'rgba(255, 255, 255, 0.05)', - borderRadius: '12px', - border: `1px solid ${ - isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' + { + dispatch(serviceSlice.actions.setActiveService(serviceName)); + dispatch(serviceSlice.actions.setLoading(true)); + + addPage({ + page: { + ui: 'system_logs', + title: `${ServiceTitle[serviceName]} Logs` + } + }); + }} + sx={{ + transition: 'box-shadow 0.2s ease', + '&:hover': { + boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)' + }, + backgroundColor: 'rgba(255, 255, 255, 0.05)', + borderRadius: '12px', + border: `1px solid ${isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' }`, - padding: '16px', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden' - }} - > - - + padding: '16px', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden' + }} + > + + + + {ServiceTitle[serviceName]} + + + + + + - {ServiceTitle[serviceName]} - + {isUp ? 'Status: Healthy' : 'Status: Not Operational'} - - - - - {isUp ? 'Status: Healthy' : 'Status: Not Operational'} - - - - - + + ); - })} - + })} */} + + {Object.entries(services).map(([serviceName, serviceData]) => ( + + + {serviceName} + + + Status: {serviceData.isUp ? 'Up' : 'Down'} + + + {serviceData.logs.length > 0 ? ( + serviceData.logs.map((log, index) => ( + + {log} + + )) + ) : ( + + No logs available + + )} + + + ))} + +
); }; From 222103253752ba1b9dca9c3f9b079bc0d45c9339 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 18:14:57 +0000 Subject: [PATCH 15/55] refactor: keep alive version --- web-server/pages/api/service/stream.ts | 82 +++++++++++++++++-- .../src/components/Service/SystemStatus.tsx | 45 +++++++--- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/web-server/pages/api/service/stream.ts b/web-server/pages/api/service/stream.ts index 8d67a898..a1a07b91 100644 --- a/web-server/pages/api/service/stream.ts +++ b/web-server/pages/api/service/stream.ts @@ -1,13 +1,15 @@ import { exec } from 'child_process'; +import { watch, createReadStream, FSWatcher } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; -import { ApiRequest, ApiResponse } from '@/types/request'; import { dbRaw } from '@/utils/db'; +import type { NextApiRequest, NextApiResponse } from 'next/types'; + export default async function handler( - request: ApiRequest, - response: ApiResponse + request: NextApiRequest, + response: NextApiResponse ) { console.log('SSE handler initialized'); @@ -22,16 +24,78 @@ export default async function handler( // Send an initial message to establish the connection // response.write('data: Connected\n\n'); - const statuses = await getStatus(); - const Status = { - statuses: statuses + // --------------------------- + // const sendStatuses = async () => { + // // Fetch the statuses + // const statuses = await getStatus(); + // const statusData = { type: 'status-update', statuses: statuses }; + + // // Send the data to the client + // response.write(`data: ${JSON.stringify(statusData)}\n\n`); + + // // Call this function again after 15 seconds + // timeoutId = setTimeout(sendStatuses, 15000); + // }; + + // Start the recursive process + // let timeoutId = setTimeout(sendStatuses, 15000); // Set initial timeout + + // ----------------------------- + const fullPath = '/var/log/apiserver/apiserver.log'; + let lastPosition = 0; + + const sendFileContent = async () => { + console.log('SEnd file content'); + return new Promise((resolve, reject) => { + const stream = createReadStream(fullPath, { + start: lastPosition, + encoding: 'utf8' + }); + stream.on('data', (chunk) => { + const data = { + type: 'log-update', + serviceName: ServiceNames.API_SERVER, + content: chunk + }; + response.write(`data: ${JSON.stringify(data)}\n\n`); + lastPosition += Buffer.byteLength(chunk); + }); + stream.on('end', () => { + response.write(`data: ${JSON.stringify({ type: 'end' })}\n\n`); + resolve(); + }); + stream.on('error', (error) => { + reject(error); + }); + }); }; - response.write(`data: ${JSON.stringify(Status)}\n\n`); + + let watcher: FSWatcher | null = null; + + if (!watcher) { + watcher = watch(fullPath, async (eventType, filename) => { + if (eventType === 'change') { + console.log(`File ${filename} has been changed`); + await sendFileContent(); + } + }); + console.log('Watcher created for', fullPath); + } else { + console.log('Watcher already exists for', fullPath); + } + + // Initial read of the file + await sendFileContent(); response.on('close', () => { console.log('Client disconnected'); - // clearInterval(intervalId); - response.end(); + watcher.close(); // Ensure the watcher is closed + response.end(); // End the response + }); + + response.on('finish', () => { + console.log('Response finished'); + watcher.close(); // Close the watcher when response is finished }); } diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 3eb7d409..ec49f7fd 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -2,7 +2,7 @@ import { Box, Button, Divider, Typography } from '@mui/material'; import { FC, useEffect, useState } from 'react'; import { ServiceNames } from '@/constants/service'; -import service, { serviceSlice, ServiceStatusState } from '@/slices/service'; +import { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; import { FlexBox } from '../FlexBox'; @@ -42,26 +42,43 @@ export const SystemStatus: FC = () => { // eventSource.addEventListener("message", (event) => { console.log(event) }) eventSource.onmessage = (event) => { - console.log('Message received', event); + // console.log('Message received', event); // Parse the data and update your component state here const data = JSON.parse(event.data); - console.log(data); - - dispatch(serviceSlice.actions.setStatus(data)); - - // For example: setLastUpdate(data.time); + // console.log(data); + if (data.type === 'status-update') { + dispatch(serviceSlice.actions.setStatus({ statuses: data.statuses })); + } + if (data.type === 'log-update') { + console.log(data.serviceName); + const newLines = data.content.split('\n'); // Split new content into lines + const trimmedLines = newLines.filter( + (line: string) => line.trim() !== '' + ); + dispatch( + serviceSlice.actions.setServiceLogs({ + serviceName: data.serviceName, + serviceLog: trimmedLines + }) + ); + // } + // For example: setLastUpdate(data.time); + } }; - eventSource.addEventListener('state', function (event) { - const data = JSON.parse(event.data); - console.log('State:', data); - }); - eventSource.onerror = (event) => { console.error('EventSource failed:', event); + eventSource.close(); }; + setTimeout(() => { + console.log('Cleaning up EventSource...'); + eventSource.close(); + console.log('EventSource closed'); + }, 60000); + return () => { + console.log('Cleaning up EventSource...'); eventSource.close(); console.log('EventSource closed'); }; @@ -97,7 +114,9 @@ export const SystemStatus: FC = () => { } }); }} - > + > + LOG + {/* {services && loading && From 6c5723aeed6ab29d2eae4a3c33be78fd2f483e4e Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 22:20:35 +0000 Subject: [PATCH 16/55] feat: server side events --- web-server/pages/api/service/log.ts | 63 --------- web-server/pages/api/service/status.ts | 98 -------------- web-server/pages/api/service/stream.ts | 109 ++++++++++----- .../src/components/Service/SystemStatus.tsx | 128 ++---------------- web-server/src/content/Service/SystemLogs.tsx | 12 -- web-server/src/slices/service.ts | 104 ++++++-------- 6 files changed, 126 insertions(+), 388 deletions(-) delete mode 100644 web-server/pages/api/service/log.ts delete mode 100644 web-server/pages/api/service/status.ts diff --git a/web-server/pages/api/service/log.ts b/web-server/pages/api/service/log.ts deleted file mode 100644 index 2c3f41e5..00000000 --- a/web-server/pages/api/service/log.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as yup from 'yup'; - -import { exec } from 'child_process'; - -import { Endpoint, nullSchema } from '@/api-helpers/global'; -import { ServiceNames } from '@/constants/service'; - -const endpoint = new Endpoint(nullSchema); -const getLogsSchema = yup.object().shape({ - serviceName: yup.string().required('Service name is required') -}); - -const fetchDockerLogs = ( - command: string, - callback: (err: any, logs: string[]) => void -) => { - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(`Error executing Docker command: ${error.message}`); - callback(error, []); - return; - } - if (stderr) { - console.error(`Error in Docker command output: ${stderr}`); - callback(stderr, []); - return; - } - - const logs = stdout.split('\n').filter((line) => line.trim() !== ''); // Filter empty lines - callback(null, logs); - }); -}; - -endpoint.handle.GET(getLogsSchema, async (req, res) => { - const { serviceName } = req.payload; - - const commandMap: Record = { - [ServiceNames.API_SERVER]: - 'cd ../../.. && cd /var/log/apiserver/ && tail -n 500 apiserver.log', - [ServiceNames.SYNC_SERVER]: - 'cd ../../.. && cd /var/log/sync_server/ && tail -n 500 sync_server.log', - [ServiceNames.REDIS]: - 'cd ../../.. && cd /var/log/redis/ && tail -n 500 redis.log', - [ServiceNames.POSTGRES]: - 'cd ../../.. && cd /var/log/postgres/ && tail -n 500 postgres.log' - }; - - const command = commandMap[serviceName as keyof typeof commandMap]; - - if (!command) { - return res.status(400).json({ error: 'Invalid service name' }); - } - - fetchDockerLogs(command, (err, logs) => { - if (err) { - return res.status(500).json({ error: 'Failed to fetch logs' }); - } - - res.send({ logs }); - }); -}); - -export default endpoint.serve(); diff --git a/web-server/pages/api/service/status.ts b/web-server/pages/api/service/status.ts deleted file mode 100644 index 1c81b724..00000000 --- a/web-server/pages/api/service/status.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as yup from 'yup'; - -import { exec } from 'child_process'; - -import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; -import { Endpoint, nullSchema } from '@/api-helpers/global'; -import { ServiceNames } from '@/constants/service'; -import { dbRaw } from '@/utils/db'; - -const getStatusSchema = yup.object().shape({}); - -const endpoint = new Endpoint(nullSchema); - -const execPromise = (command: string) => { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - return reject(error); - } - resolve(stdout); - }); - }); -}; - -const checkServiceStatus = async (serviceName: string): Promise => { - let isUp = false; - switch (serviceName) { - case ServiceNames.API_SERVER: - try { - const response = await handleRequest(''); - if (response.message === 'hello world') { - isUp = true; - } - } catch (error) { - console.error('API Server service is down:', error); - isUp = false; - } - break; - case ServiceNames.REDIS: - try { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - - if (response.trim() === 'PONG') { - isUp = true; - } - } catch (error) { - console.error('Redis service is down:', error); - isUp = false; - } - break; - case ServiceNames.POSTGRES: - try { - await dbRaw.raw('SELECT 1'); - isUp = true; - } catch (error) { - console.error('PostgreSQL service is down:', error); - isUp = false; - } - break; - case ServiceNames.SYNC_SERVER: - try { - const response = await handleSyncServerRequest(''); - if (response.message === 'hello world') { - isUp = true; - } - } catch (error) { - console.error('Sync Server service is down:', error); - isUp = false; - } - break; - - default: - console.warn(`Service ${serviceName} not recognized.`); - break; - } - - return isUp; -}; - -endpoint.handle.GET(getStatusSchema, async (req, res) => { - const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; - - for (const service of services) { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp: isUp }; - } - - return res.send({ statuses }); -}); - -export default endpoint.serve(); diff --git a/web-server/pages/api/service/stream.ts b/web-server/pages/api/service/stream.ts index a1a07b91..319a0dc9 100644 --- a/web-server/pages/api/service/stream.ts +++ b/web-server/pages/api/service/stream.ts @@ -6,6 +6,7 @@ import { ServiceNames } from '@/constants/service'; import { dbRaw } from '@/utils/db'; import type { NextApiRequest, NextApiResponse } from 'next/types'; +export const dynamic = 'force-dynamic'; export default async function handler( request: NextApiRequest, @@ -14,7 +15,7 @@ export default async function handler( console.log('SSE handler initialized'); response.setHeader('Content-Type', 'text/event-stream'); - response.setHeader('Cache-Control', 'no-cache'); + response.setHeader('Cache-Control', 'no-cache, no-transform'); response.setHeader('Connection', 'keep-alive'); response.setHeader('content-encoding', 'none'); response.setHeader('Access-Control-Allow-Origin', '*'); // Enable CORS @@ -25,80 +26,116 @@ export default async function handler( // response.write('data: Connected\n\n'); // --------------------------- - // const sendStatuses = async () => { - // // Fetch the statuses - // const statuses = await getStatus(); - // const statusData = { type: 'status-update', statuses: statuses }; + const sendStatuses = async () => { + console.log('Set STatus'); + // Fetch the statuses + const statuses = await getStatus(); + const statusData = { type: 'status-update', statuses: statuses }; - // // Send the data to the client - // response.write(`data: ${JSON.stringify(statusData)}\n\n`); + // Send the data to the client + response.write(`data: ${JSON.stringify(statusData)}\n\n`); - // // Call this function again after 15 seconds - // timeoutId = setTimeout(sendStatuses, 15000); - // }; + // Call this function again after 15 seconds + timeoutId = setTimeout(sendStatuses, 15000); + }; // Start the recursive process - // let timeoutId = setTimeout(sendStatuses, 15000); // Set initial timeout + let timeoutId = setTimeout(sendStatuses, 15000); // Set initial timeout // ----------------------------- - const fullPath = '/var/log/apiserver/apiserver.log'; - let lastPosition = 0; + const logFiles = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { + path: '/var/log/redis/redis.log', + serviceName: ServiceNames.REDIS + }, + { + path: '/var/log/postgres/postgres.log', + serviceName: ServiceNames.POSTGRES + } + ]; + + let watchers: FSWatcher[] = []; + let lastPositions: { [key: string]: number } = {}; + + const sendFileContent = async (filePath: string, serviceName: string) => { + console.log(`Sending file content for ${serviceName}`); - const sendFileContent = async () => { - console.log('SEnd file content'); return new Promise((resolve, reject) => { - const stream = createReadStream(fullPath, { - start: lastPosition, + const stream = createReadStream(filePath, { + start: lastPositions[filePath] || 0, encoding: 'utf8' }); + stream.on('data', (chunk) => { const data = { type: 'log-update', - serviceName: ServiceNames.API_SERVER, + serviceName, content: chunk }; response.write(`data: ${JSON.stringify(data)}\n\n`); - lastPosition += Buffer.byteLength(chunk); + lastPositions[filePath] = + (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); }); + stream.on('end', () => { - response.write(`data: ${JSON.stringify({ type: 'end' })}\n\n`); resolve(); }); + stream.on('error', (error) => { reject(error); }); }); }; - let watcher: FSWatcher | null = null; + const startWatchers = () => { + logFiles.forEach(({ path, serviceName }) => { + const watcher = watch(path, async (eventType) => { + if (eventType === 'change') { + console.log(`File ${path} (${serviceName}) has been changed`); + await sendFileContent(path, serviceName); + } + }); - if (!watcher) { - watcher = watch(fullPath, async (eventType, filename) => { - if (eventType === 'change') { - console.log(`File ${filename} has been changed`); - await sendFileContent(); - } + watchers.push(watcher); + console.log(`Watcher created for ${path}`); }); - console.log('Watcher created for', fullPath); - } else { - console.log('Watcher already exists for', fullPath); + }; + + // Start watchers for all log files + startWatchers(); + sendStatuses(); + + // Initial read of all files + for (const { path, serviceName } of logFiles) { + await sendFileContent(path, serviceName); } - // Initial read of the file - await sendFileContent(); + const cleanupWatchers = () => { + watchers.forEach((watcher) => watcher.close()); + watchers = []; + }; response.on('close', () => { console.log('Client disconnected'); - watcher.close(); // Ensure the watcher is closed - response.end(); // End the response + cleanupWatchers(); + clearTimeout(timeoutId); + response.end(); }); response.on('finish', () => { console.log('Response finished'); - watcher.close(); // Close the watcher when response is finished + clearTimeout(timeoutId); + cleanupWatchers(); }); } - const getStatus = async () => { const services = Object.values(ServiceNames); const statuses: { [key in ServiceNames]: { isUp: boolean } } = { diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index ec49f7fd..8c479720 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,7 +1,8 @@ -import { Box, Button, Divider, Typography } from '@mui/material'; +import { Box, Divider } from '@mui/material'; import { FC, useEffect, useState } from 'react'; import { ServiceNames } from '@/constants/service'; +import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; import { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; @@ -18,20 +19,7 @@ export const SystemStatus: FC = () => { ); const loading = useSelector((s) => s.service.loading); - // useEffect(() => { - // // const fetchAndHandleServiceStatus = async () => { - // // try { - // // await dispatch(fetchServiceStatus()).unwrap(); - // // dispatch(serviceSlice.actions.setLoading(true)); - - // // } catch (err) { - // // setError('Failed to fetch service status'); - // // } - // // }; - - // // fetchAndHandleServiceStatus(); - - // }, [dispatch]); + console.log(services); useEffect(() => { const eventSource = new EventSource('/api/service/stream'); @@ -40,10 +28,7 @@ export const SystemStatus: FC = () => { console.log('Connection opened', event); }; - // eventSource.addEventListener("message", (event) => { console.log(event) }) eventSource.onmessage = (event) => { - // console.log('Message received', event); - // Parse the data and update your component state here const data = JSON.parse(event.data); // console.log(data); if (data.type === 'status-update') { @@ -61,8 +46,6 @@ export const SystemStatus: FC = () => { serviceLog: trimmedLines }) ); - // } - // For example: setLastUpdate(data.time); } }; @@ -71,14 +54,7 @@ export const SystemStatus: FC = () => { eventSource.close(); }; - setTimeout(() => { - console.log('Cleaning up EventSource...'); - eventSource.close(); - console.log('EventSource closed'); - }, 60000); - return () => { - console.log('Cleaning up EventSource...'); eventSource.close(); console.log('EventSource closed'); }; @@ -105,21 +81,9 @@ export const SystemStatus: FC = () => {

{error}

)} - - {/* {services && loading && + {services && Object.keys(services).map((serviceName) => { const { isUp } = services[serviceName]; return ( @@ -143,8 +107,9 @@ export const SystemStatus: FC = () => { }, backgroundColor: 'rgba(255, 255, 255, 0.05)', borderRadius: '12px', - border: `1px solid ${isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' - }`, + border: `1px solid ${ + isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' + }`, padding: '16px', cursor: 'pointer', position: 'relative', @@ -197,84 +162,7 @@ export const SystemStatus: FC = () => { ); - })} */} - - {Object.entries(services).map(([serviceName, serviceData]) => ( - - - {serviceName} - - - Status: {serviceData.isUp ? 'Up' : 'Down'} - - - {serviceData.logs.length > 0 ? ( - serviceData.logs.map((log, index) => ( - - {log} - - )) - ) : ( - - No logs available - - )} - - - ))} - + })}
); diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 3739fbd7..08af9f1f 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -14,18 +14,6 @@ export const SystemLogs = () => { const workerRef = useRef(); const logs = services[active].logs; - - useEffect(() => { - workerRef.current = new Worker('/workers/fetchLogsWorker.js'); - workerRef.current.onmessage = (event: MessageEvent) => { - dispatch(fetchServiceLogs(active)); - }; - workerRef.current?.postMessage('fetchStatus'); - return () => { - workerRef.current?.terminate(); - }; - }); - return ( ; - -export type serviceSliceState = State; - -const getInitialState = (): State => { - return { - services: { - [ServiceNames.API_SERVER]: { isUp: false, logs: [] }, - [ServiceNames.REDIS]: { isUp: false, logs: [] }, - [ServiceNames.POSTGRES]: { isUp: false, logs: [] }, - [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } - }, - active: null, - loading: false, - error: undefined - }; }; -const initialState: State = getInitialState(); - -export const fetchServiceStatus = createAsyncThunk( - 'services/fetchServiceStatus', - async () => { - const response = await handleApi<{ - statuses: { [key in ServiceNames]: { isUp: boolean } }; - }>('/service/status', { - params: {} - }); - return { - statuses: response.statuses - }; - } -); +const initialState: State = { + services: { + [ServiceNames.API_SERVER]: { isUp: false, logs: [] }, + [ServiceNames.REDIS]: { isUp: false, logs: [] }, + [ServiceNames.POSTGRES]: { isUp: false, logs: [] }, + [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } + }, + active: null, + loading: false, + error: undefined +}; -export const fetchServiceLogs = createAsyncThunk( - 'services/fetchServiceLogs', - async (serviceName: string) => { - const response = await handleApi<{ logs: string[] }>('/service/log', { - params: { serviceName } - }); - return { serviceName, logs: response.logs }; - } -); +type SetStatusPayload = { + statuses: { [key in ServiceNames]: Status }; +}; export const serviceSlice = createSlice({ name: 'services', initialState, reducers: { - setActiveService: (state, action) => { + setActiveService: (state, action: PayloadAction) => { state.active = action.payload; }, - resetState: () => getInitialState() - }, - extraReducers: (builder) => { - builder.addCase(fetchServiceStatus.fulfilled, (state, action) => { + setLoading: (state, action: PayloadAction) => { + state.loading = action.payload; + }, + setStatus: (state, action: PayloadAction) => { state.loading = false; + console.log(action.payload, 'state set'); const { statuses } = action.payload; + for (const [serviceName, { isUp }] of Object.entries(statuses)) { - state.services[serviceName].isUp = isUp; + if (state.services[serviceName as ServiceNames]) { + state.services[serviceName as ServiceNames].isUp = isUp; + } } - }); - - builder.addCase(fetchServiceLogs.fulfilled, (state, action) => { - state.loading = false; - const { serviceName, logs } = action.payload; - state.services[serviceName].logs = logs; - }); + }, + setServiceLogs: ( + state, + action: PayloadAction<{ serviceName: ServiceNames; serviceLog: string[] }> + ) => { + const { serviceName, serviceLog } = action.payload; + console.log(action, 'action '); + state.services[serviceName].logs = [ + ...state.services[serviceName].logs, + ...serviceLog + ]; + }, + resetState: () => initialState } }); From 19588c8efb002c61592c4ca9ffda16b8f7f955df Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sat, 31 Aug 2024 23:01:50 +0000 Subject: [PATCH 17/55] refactor: --- web-server/src/slices/service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 03253efd..3730ff7e 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -50,7 +50,6 @@ export const serviceSlice = createSlice({ }, setStatus: (state, action: PayloadAction) => { state.loading = false; - console.log(action.payload, 'state set'); const { statuses } = action.payload; for (const [serviceName, { isUp }] of Object.entries(statuses)) { @@ -64,7 +63,6 @@ export const serviceSlice = createSlice({ action: PayloadAction<{ serviceName: ServiceNames; serviceLog: string[] }> ) => { const { serviceName, serviceLog } = action.payload; - console.log(action, 'action '); state.services[serviceName].logs = [ ...state.services[serviceName].logs, ...serviceLog From 7d2a7f2a6614af92720862832758e99d461a6dd9 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Sun, 1 Sep 2024 21:22:59 +0000 Subject: [PATCH 18/55] feat: Abort Works --- web-server/app/api/stream/route.ts | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 web-server/app/api/stream/route.ts diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts new file mode 100644 index 00000000..71394fc0 --- /dev/null +++ b/web-server/app/api/stream/route.ts @@ -0,0 +1,38 @@ +export function GET(req: Request) { + const encoder = new TextEncoder(); + let count = 1; + return new Response( + new ReadableStream({ + start(controller) { + var interval = setInterval(() => { + console.log('doing something new1', Date.now()); + controller.enqueue(encoder.encode(String(Date.now() + '\n' + count))); + count++; + + if (count > 10) { + clearInterval(interval); + controller.close(); + } + }, 1000); + + req.signal.onabort = () => { + console.log('request closed'); + clearInterval(interval); + controller.close(); + }; + req.signal.addEventListener('abort', async () => { + console.log('abort'); + clearInterval(interval); + controller.close(); + // req.close(); + // await writer.close(); + }); + } + }), + { + headers: { + 'content-type': 'text/plain' + } + } + ); +} From ea77c61f44031f46ef60727c562da4fd625d9028 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 05:58:42 +0000 Subject: [PATCH 19/55] stream route implemenation --- web-server/app/api/stream/route.ts | 86 ++++++++++++++++++------------ 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 71394fc0..9e85df81 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,38 +1,56 @@ -export function GET(req: Request) { + +import { NextRequest } from 'next/server'; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +export async function GET(request: NextRequest) { + let responseStream = new TransformStream(); + const writer = responseStream.writable.getWriter(); const encoder = new TextEncoder(); - let count = 1; - return new Response( - new ReadableStream({ - start(controller) { - var interval = setInterval(() => { - console.log('doing something new1', Date.now()); - controller.enqueue(encoder.encode(String(Date.now() + '\n' + count))); - count++; - - if (count > 10) { - clearInterval(interval); - controller.close(); - } - }, 1000); - - req.signal.onabort = () => { - console.log('request closed'); - clearInterval(interval); - controller.close(); - }; - req.signal.addEventListener('abort', async () => { - console.log('abort'); - clearInterval(interval); - controller.close(); - // req.close(); - // await writer.close(); - }); - } - }), - { - headers: { - 'content-type': 'text/plain' + let count = 0; + let timeoutId: NodeJS.Timeout | null = null; + let streamClosed = false; + + const closeStream = () => { + if (!streamClosed) { + streamClosed = true; + writer.close().catch((error) => { + console.error('Error closing writer:', error); + }); + if (timeoutId !== null) { + clearTimeout(timeoutId); } } - ); + }; + + function repeat() { + if (streamClosed) return; + + console.log(count); + count += 1; + + if (count > 10) { + console.log('Complete timeout'); + closeStream(); + } else { + timeoutId = setTimeout(repeat, 3000); // Recursively call repeat after 3000ms + } + } + + // Start the first iteration + timeoutId = setTimeout(repeat, 3000); + + // Close the stream if the client disconnects and stop the timeout + request.signal.onabort = () => { + console.log('Client disconnected. Closing writer.'); + closeStream(); + }; + + return new Response(responseStream.readable, { + headers: { + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache, no-transform' + } + }); } From 67a1ee3c375112a98d53144ef0165aeb2596e5bf Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 07:05:31 +0000 Subject: [PATCH 20/55] status stream --- web-server/app/api/stream/route.ts | 152 ++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 24 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 9e85df81..394a1707 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,5 +1,10 @@ - import { NextRequest } from 'next/server'; + +import { exec } from 'child_process'; + +import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; +import { ServiceNames } from '@/constants/service'; +// import { dbRaw } from '@/utils/db'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -11,6 +16,32 @@ export async function GET(request: NextRequest) { let timeoutId: NodeJS.Timeout | null = null; let streamClosed = false; + + const sendStatuses = async () => { + console.log('Set Status'); + // Fetch the statuses + const statuses = await getStatus(); + const statusData = { type: 'status-update', statuses: statuses }; + + // await writer.write(encoder.encode(`data: ${data}\n\n`)); + + // Send the data to the client + // response.write(`data: ${JSON.stringify(statusData)}\n\n`); + writer.write(encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`)); + + // Call this function again after 15 seconds + timeoutId = setTimeout(sendStatuses, 15000); + }; + + // Start the recursive process + sendStatuses(); // Set initial timeout + + // Close the stream if the client disconnects and stop the timeout + request.signal.onabort = () => { + console.log('Client disconnected. Closing writer.'); + closeStream(); + }; + const closeStream = () => { if (!streamClosed) { streamClosed = true; @@ -23,29 +54,6 @@ export async function GET(request: NextRequest) { } }; - function repeat() { - if (streamClosed) return; - - console.log(count); - count += 1; - - if (count > 10) { - console.log('Complete timeout'); - closeStream(); - } else { - timeoutId = setTimeout(repeat, 3000); // Recursively call repeat after 3000ms - } - } - - // Start the first iteration - timeoutId = setTimeout(repeat, 3000); - - // Close the stream if the client disconnects and stop the timeout - request.signal.onabort = () => { - console.log('Client disconnected. Closing writer.'); - closeStream(); - }; - return new Response(responseStream.readable, { headers: { 'Content-Type': 'text/event-stream', @@ -54,3 +62,99 @@ export async function GET(request: NextRequest) { } }); } + +const getStatus = async () => { + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + for (const service of services) { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp: isUp }; + } + + return statuses; +}; + +const execPromise = (command: string) => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +const checkServiceStatus = async (serviceName: string): Promise => { + let isUp = false; + switch (serviceName) { + case ServiceNames.API_SERVER: + try { + const response = await handleRequest(''); + if (response.message.includes('hello world')) { + isUp = true; + } + } catch (error) { + console.error('API Server service is down:', error); + isUp = false; + } + break; + case ServiceNames.REDIS: + try { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + + if (response.trim().includes('PONG')) { + isUp = true; + } + } catch (error) { + console.error('Redis service is down:', error); + isUp = false; + } + break; + case ServiceNames.POSTGRES: + try { + // await dbRaw.raw('SELECT NOW()'); + const POSTGRES_PORT = process.env.DB_PORT; // Default port for PostgreSQL + const POSTGRES_HOST = process.env.DB_HOST; + + // Using pg_isready to check if the server is up + const response = await execPromise( + `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` + ); + + if (response.includes('accepting connections')) { + isUp = true; + } else { + isUp = false; + } + } catch (error) { + console.error('PostgreSQL service is down:', error); + isUp = false; + } + break; + case ServiceNames.SYNC_SERVER: + try { + const response = await handleSyncServerRequest(''); + if (response.message.includes('hello world')) { + isUp = true; + } + } catch (error) { + console.error('Sync Server service is down:', error); + isUp = false; + } + break; + + default: + console.warn(`Service ${serviceName} not recognized.`); + break; + } + + return isUp; +}; From dd361a6ab764b46a86c91b91e7deff77b9e153f7 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 07:12:33 +0000 Subject: [PATCH 21/55] refactor stream --- web-server/app/api/stream/route.ts | 214 +++++++++++++---------------- 1 file changed, 96 insertions(+), 118 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 394a1707..edd0eb26 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -8,52 +8,126 @@ import { ServiceNames } from '@/constants/service'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; -export async function GET(request: NextRequest) { - let responseStream = new TransformStream(); +// Utility function to execute shell commands as promises +const execPromise = (command: string): Promise => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +// Utility function to check if a service is up +const checkServiceStatus = async (serviceName: string): Promise => { + try { + switch (serviceName) { + case ServiceNames.API_SERVER: { + const response = await handleRequest(''); + return response.message.includes('hello world'); + } + + case ServiceNames.REDIS: { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + return response.trim().includes('PONG'); + } + + case ServiceNames.POSTGRES: { + const POSTGRES_PORT = process.env.DB_PORT; + const POSTGRES_HOST = process.env.DB_HOST; + const response = await execPromise( + `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` + ); + return response.includes('accepting connections'); + } + + case ServiceNames.SYNC_SERVER: { + const response = await handleSyncServerRequest(''); + return response.message.includes('hello world'); + } + + default: + console.warn(`Service ${serviceName} not recognized.`); + return false; + } + } catch (error) { + console.error(`${serviceName} service is down:`, error); + return false; + } +}; + +// Function to get the status of all services +const getStatus = async (): Promise<{ + [key in ServiceNames]: { isUp: boolean }; +}> => { + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + await Promise.all( + services.map(async (service) => { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp }; + }) + ); + + return statuses; +}; + +// Stream handling function +export async function GET(request: NextRequest): Promise { + const responseStream = new TransformStream(); const writer = responseStream.writable.getWriter(); const encoder = new TextEncoder(); - let count = 0; let timeoutId: NodeJS.Timeout | null = null; let streamClosed = false; - const sendStatuses = async () => { - console.log('Set Status'); - // Fetch the statuses - const statuses = await getStatus(); - const statusData = { type: 'status-update', statuses: statuses }; - - // await writer.write(encoder.encode(`data: ${data}\n\n`)); - - // Send the data to the client - // response.write(`data: ${JSON.stringify(statusData)}\n\n`); - writer.write(encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`)); + try { + console.log('Fetching service statuses...'); + const statuses = await getStatus(); + const statusData = { type: 'status-update', statuses }; + await writer.write( + encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) + ); + } catch (error) { + console.error('Error sending statuses:', error); + } - // Call this function again after 15 seconds + // Schedule the next status update timeoutId = setTimeout(sendStatuses, 15000); }; - // Start the recursive process - sendStatuses(); // Set initial timeout + // Start the initial status send + sendStatuses(); - // Close the stream if the client disconnects and stop the timeout + // Handle client disconnect request.signal.onabort = () => { console.log('Client disconnected. Closing writer.'); closeStream(); }; + // Function to close the stream and clear timeout const closeStream = () => { if (!streamClosed) { streamClosed = true; - writer.close().catch((error) => { - console.error('Error closing writer:', error); - }); - if (timeoutId !== null) { + writer + .close() + .catch((error) => console.error('Error closing writer:', error)); + if (timeoutId) { clearTimeout(timeoutId); } } }; + // Return the response stream return new Response(responseStream.readable, { headers: { 'Content-Type': 'text/event-stream', @@ -62,99 +136,3 @@ export async function GET(request: NextRequest) { } }); } - -const getStatus = async () => { - const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; - - for (const service of services) { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp: isUp }; - } - - return statuses; -}; - -const execPromise = (command: string) => { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - return reject(error); - } - resolve(stdout); - }); - }); -}; - -const checkServiceStatus = async (serviceName: string): Promise => { - let isUp = false; - switch (serviceName) { - case ServiceNames.API_SERVER: - try { - const response = await handleRequest(''); - if (response.message.includes('hello world')) { - isUp = true; - } - } catch (error) { - console.error('API Server service is down:', error); - isUp = false; - } - break; - case ServiceNames.REDIS: - try { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - - if (response.trim().includes('PONG')) { - isUp = true; - } - } catch (error) { - console.error('Redis service is down:', error); - isUp = false; - } - break; - case ServiceNames.POSTGRES: - try { - // await dbRaw.raw('SELECT NOW()'); - const POSTGRES_PORT = process.env.DB_PORT; // Default port for PostgreSQL - const POSTGRES_HOST = process.env.DB_HOST; - - // Using pg_isready to check if the server is up - const response = await execPromise( - `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` - ); - - if (response.includes('accepting connections')) { - isUp = true; - } else { - isUp = false; - } - } catch (error) { - console.error('PostgreSQL service is down:', error); - isUp = false; - } - break; - case ServiceNames.SYNC_SERVER: - try { - const response = await handleSyncServerRequest(''); - if (response.message.includes('hello world')) { - isUp = true; - } - } catch (error) { - console.error('Sync Server service is down:', error); - isUp = false; - } - break; - - default: - console.warn(`Service ${serviceName} not recognized.`); - break; - } - - return isUp; -}; From d0b7f393097aa590ef432fd86e177d802f72f7dd Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 11:14:18 +0000 Subject: [PATCH 22/55] save --- web-server/app/api/stream/route.ts | 6 +- web-server/pages/api/service/stream.ts | 221 ------------------ .../src/components/Service/SystemStatus.tsx | 42 +--- 3 files changed, 16 insertions(+), 253 deletions(-) delete mode 100644 web-server/pages/api/service/stream.ts diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index edd0eb26..0bf9252a 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -4,7 +4,6 @@ import { exec } from 'child_process'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; -// import { dbRaw } from '@/utils/db'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -113,6 +112,11 @@ export async function GET(request: NextRequest): Promise { console.log('Client disconnected. Closing writer.'); closeStream(); }; + request.signal.addEventListener('abort', () => { + // removeClient(clientId) + console.log('event listner client dis'); + closeStream(); + }); // Function to close the stream and clear timeout const closeStream = () => { diff --git a/web-server/pages/api/service/stream.ts b/web-server/pages/api/service/stream.ts deleted file mode 100644 index 319a0dc9..00000000 --- a/web-server/pages/api/service/stream.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { exec } from 'child_process'; -import { watch, createReadStream, FSWatcher } from 'fs'; - -import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; -import { ServiceNames } from '@/constants/service'; -import { dbRaw } from '@/utils/db'; - -import type { NextApiRequest, NextApiResponse } from 'next/types'; -export const dynamic = 'force-dynamic'; - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse -) { - console.log('SSE handler initialized'); - - response.setHeader('Content-Type', 'text/event-stream'); - response.setHeader('Cache-Control', 'no-cache, no-transform'); - response.setHeader('Connection', 'keep-alive'); - response.setHeader('content-encoding', 'none'); - response.setHeader('Access-Control-Allow-Origin', '*'); // Enable CORS - - response.flushHeaders(); - - // Send an initial message to establish the connection - // response.write('data: Connected\n\n'); - - // --------------------------- - const sendStatuses = async () => { - console.log('Set STatus'); - // Fetch the statuses - const statuses = await getStatus(); - const statusData = { type: 'status-update', statuses: statuses }; - - // Send the data to the client - response.write(`data: ${JSON.stringify(statusData)}\n\n`); - - // Call this function again after 15 seconds - timeoutId = setTimeout(sendStatuses, 15000); - }; - - // Start the recursive process - let timeoutId = setTimeout(sendStatuses, 15000); // Set initial timeout - - // ----------------------------- - const logFiles = [ - { - path: '/var/log/apiserver/apiserver.log', - serviceName: ServiceNames.API_SERVER - }, - { - path: '/var/log/sync_server/sync_server.log', - serviceName: ServiceNames.SYNC_SERVER - }, - { - path: '/var/log/redis/redis.log', - serviceName: ServiceNames.REDIS - }, - { - path: '/var/log/postgres/postgres.log', - serviceName: ServiceNames.POSTGRES - } - ]; - - let watchers: FSWatcher[] = []; - let lastPositions: { [key: string]: number } = {}; - - const sendFileContent = async (filePath: string, serviceName: string) => { - console.log(`Sending file content for ${serviceName}`); - - return new Promise((resolve, reject) => { - const stream = createReadStream(filePath, { - start: lastPositions[filePath] || 0, - encoding: 'utf8' - }); - - stream.on('data', (chunk) => { - const data = { - type: 'log-update', - serviceName, - content: chunk - }; - response.write(`data: ${JSON.stringify(data)}\n\n`); - lastPositions[filePath] = - (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); - }); - - stream.on('end', () => { - resolve(); - }); - - stream.on('error', (error) => { - reject(error); - }); - }); - }; - - const startWatchers = () => { - logFiles.forEach(({ path, serviceName }) => { - const watcher = watch(path, async (eventType) => { - if (eventType === 'change') { - console.log(`File ${path} (${serviceName}) has been changed`); - await sendFileContent(path, serviceName); - } - }); - - watchers.push(watcher); - console.log(`Watcher created for ${path}`); - }); - }; - - // Start watchers for all log files - startWatchers(); - sendStatuses(); - - // Initial read of all files - for (const { path, serviceName } of logFiles) { - await sendFileContent(path, serviceName); - } - - const cleanupWatchers = () => { - watchers.forEach((watcher) => watcher.close()); - watchers = []; - }; - - response.on('close', () => { - console.log('Client disconnected'); - cleanupWatchers(); - clearTimeout(timeoutId); - response.end(); - }); - - response.on('finish', () => { - console.log('Response finished'); - clearTimeout(timeoutId); - cleanupWatchers(); - }); -} -const getStatus = async () => { - const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; - - for (const service of services) { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp: isUp }; - } - - return statuses; -}; - -const execPromise = (command: string) => { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - return reject(error); - } - resolve(stdout); - }); - }); -}; - -const checkServiceStatus = async (serviceName: string): Promise => { - let isUp = false; - switch (serviceName) { - case ServiceNames.API_SERVER: - try { - const response = await handleRequest(''); - if (response.message === 'hello world') { - isUp = true; - } - } catch (error) { - console.error('API Server service is down:', error); - isUp = false; - } - break; - case ServiceNames.REDIS: - try { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - - if (response.trim() === 'PONG') { - isUp = true; - } - } catch (error) { - console.error('Redis service is down:', error); - isUp = false; - } - break; - case ServiceNames.POSTGRES: - try { - await dbRaw.raw('SELECT 1'); - isUp = true; - } catch (error) { - console.error('PostgreSQL service is down:', error); - isUp = false; - } - break; - case ServiceNames.SYNC_SERVER: - try { - const response = await handleSyncServerRequest(''); - if (response.message === 'hello world') { - isUp = true; - } - } catch (error) { - console.error('Sync Server service is down:', error); - isUp = false; - } - break; - - default: - console.warn(`Service ${serviceName} not recognized.`); - break; - } - - return isUp; -}; diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 8c479720..4ef9bd1b 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,5 +1,5 @@ -import { Box, Divider } from '@mui/material'; -import { FC, useEffect, useState } from 'react'; +import { Box, Button, Divider } from '@mui/material'; +import { FC, useEffect, useState, useRef } from 'react'; import { ServiceNames } from '@/constants/service'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; @@ -17,46 +17,26 @@ export const SystemStatus: FC = () => { (state: { service: { services: ServiceStatusState } }) => state.service.services ); - const loading = useSelector((s) => s.service.loading); - - console.log(services); + const [status, setStatus] = useState('loading'); useEffect(() => { - const eventSource = new EventSource('/api/service/stream'); - - eventSource.onopen = (event) => { - console.log('Connection opened', event); - }; + const eventSource = new EventSource(`/api/stream`); //configure this based on your user case, for demo purpose I'm using static value eventSource.onmessage = (event) => { - const data = JSON.parse(event.data); - // console.log(data); - if (data.type === 'status-update') { - dispatch(serviceSlice.actions.setStatus({ statuses: data.statuses })); - } - if (data.type === 'log-update') { - console.log(data.serviceName); - const newLines = data.content.split('\n'); // Split new content into lines - const trimmedLines = newLines.filter( - (line: string) => line.trim() !== '' - ); - dispatch( - serviceSlice.actions.setServiceLogs({ - serviceName: data.serviceName, - serviceLog: trimmedLines - }) - ); + const data = event.data && JSON.parse(event?.data); + + if (data.success) { + setStatus('success'); + eventSource.close(); } }; - eventSource.onerror = (event) => { - console.error('EventSource failed:', event); + eventSource.onerror = () => { eventSource.close(); }; return () => { eventSource.close(); - console.log('EventSource closed'); }; }, []); @@ -73,7 +53,7 @@ export const SystemStatus: FC = () => { System Status - + {status} {error && ( From 78d124b4f76b8496267def2b909d686e444ebe2f Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 11:27:41 +0000 Subject: [PATCH 23/55] feat: stream statsu --- web-server/app/api/stream/route.ts | 97 ++++++++++++++++--- .../src/components/Service/SystemStatus.tsx | 12 ++- 2 files changed, 91 insertions(+), 18 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 0bf9252a..a89acc98 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,7 +1,5 @@ import { NextRequest } from 'next/server'; - import { exec } from 'child_process'; - import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; export const runtime = 'nodejs'; @@ -80,6 +78,86 @@ const getStatus = async (): Promise<{ return statuses; }; +// Stream handling function +export async function GET(request: NextRequest): Promise { + const responseStream = new TransformStream(); + +// Increase the max listeners limit to avoid warnings +process.setMaxListeners(20); + +// Utility function to execute shell commands as promises +const execPromise = (command: string): Promise => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +// Utility function to check if a service is up +const checkServiceStatus = async (serviceName: string): Promise => { + try { + switch (serviceName) { + case ServiceNames.API_SERVER: { + const response = await handleRequest(''); + return response.message.includes('hello world'); + } + + case ServiceNames.REDIS: { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + return response.trim().includes('PONG'); + } + + case ServiceNames.POSTGRES: { + const POSTGRES_PORT = process.env.DB_PORT; + const POSTGRES_HOST = process.env.DB_HOST; + const response = await execPromise( + `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` + ); + return response.includes('accepting connections'); + } + + case ServiceNames.SYNC_SERVER: { + const response = await handleSyncServerRequest(''); + return response.message.includes('hello world'); + } + + default: + console.warn(`Service ${serviceName} not recognized.`); + return false; + } + } catch (error) { + console.error(`${serviceName} service is down:`, error); + return false; + } +}; + +// Function to get the status of all services +const getStatus = async (): Promise<{ + [key in ServiceNames]: { isUp: boolean }; +}> => { + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + await Promise.all( + services.map(async (service) => { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp }; + }) + ); + + return statuses; +}; + // Stream handling function export async function GET(request: NextRequest): Promise { const responseStream = new TransformStream(); @@ -88,7 +166,10 @@ export async function GET(request: NextRequest): Promise { let timeoutId: NodeJS.Timeout | null = null; let streamClosed = false; + // Function to send statuses to the client const sendStatuses = async () => { + if (streamClosed) return; // Prevent sending if the stream is closed + try { console.log('Fetching service statuses...'); const statuses = await getStatus(); @@ -107,19 +188,9 @@ export async function GET(request: NextRequest): Promise { // Start the initial status send sendStatuses(); - // Handle client disconnect - request.signal.onabort = () => { - console.log('Client disconnected. Closing writer.'); - closeStream(); - }; - request.signal.addEventListener('abort', () => { - // removeClient(clientId) - console.log('event listner client dis'); - closeStream(); - }); - // Function to close the stream and clear timeout const closeStream = () => { + console.log('CLIENT DISCONNECTED'); if (!streamClosed) { streamClosed = true; writer diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 4ef9bd1b..85ec020d 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -20,14 +20,16 @@ export const SystemStatus: FC = () => { const [status, setStatus] = useState('loading'); useEffect(() => { - const eventSource = new EventSource(`/api/stream`); //configure this based on your user case, for demo purpose I'm using static value + const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { - const data = event.data && JSON.parse(event?.data); + const data = JSON.parse(event.data); + console.log(data); - if (data.success) { - setStatus('success'); - eventSource.close(); + if (data.type.includes('status-update')) { + const statuses = { statuses: data.statuses }; + console.log(statuses); + dispatch(serviceSlice.actions.setStatus(statuses)); } }; From cc3ab077e41ea02dcc8570107d1bc9f447a9e51c Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 13:39:02 +0000 Subject: [PATCH 24/55] complete System Status --- web-server/app/api/stream/route.ts | 340 +++++++++++++++++- .../src/components/Service/SystemStatus.tsx | 44 ++- web-server/src/content/Service/SystemLogs.tsx | 14 +- 3 files changed, 375 insertions(+), 23 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index a89acc98..dcf06e6a 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,5 +1,8 @@ import { NextRequest } from 'next/server'; + import { exec } from 'child_process'; +import { FSWatcher, createReadStream, watch } from 'fs'; + import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; export const runtime = 'nodejs'; @@ -82,7 +85,132 @@ const getStatus = async (): Promise<{ export async function GET(request: NextRequest): Promise { const responseStream = new TransformStream(); -// Increase the max listeners limit to avoid warnings +export async function GET(request: NextRequest): Promise { + const responseStream = new TransformStream(); + const writer = responseStream.writable.getWriter(); + const encoder = new TextEncoder(); + let timeoutId: NodeJS.Timeout | null = null; + let streamClosed = false; + + const sendStatuses = async () => { + if (streamClosed) return; + + try { + console.log('Fetching service statuses...'); + const statuses = await getStatus(); + const statusData = { type: 'status-update', statuses }; + + if (!streamClosed) { + await writer.write( + encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) + ); + } + } catch (error) { + console.error('Error sending statuses:', error); + } + + // Schedule the next status update + if (!streamClosed) { + timeoutId = setTimeout(sendStatuses, 10000); + } + }; + + const logFiles = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, + { + path: '/var/log/postgres/postgres.log', + serviceName: ServiceNames.POSTGRES + } + ]; + + let watchers: FSWatcher[] = []; + let lastPositions: { [key: string]: number } = {}; + + const sendFileContent = async (filePath: string, serviceName: string) => { + if (streamClosed) return; // Prevent sending if the stream is closed + console.log(`Sending file content for ${serviceName}`); + + return new Promise((resolve, reject) => { + const stream = createReadStream(filePath, { + start: lastPositions[filePath] || 0, + encoding: 'utf8' + }); + + stream.on('data', (chunk) => { + if (streamClosed) return; + const data = { + type: 'log-update', + serviceName, + content: chunk + }; + + writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + lastPositions[filePath] = + (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); + }); + + stream.on('end', () => resolve()); + stream.on('error', (error) => reject(error)); + }); + }; + + const startWatchers = () => { + logFiles.forEach(async ({ path, serviceName }) => { + await sendFileContent(path, serviceName); + + const watcher = watch(path, async (eventType) => { + if (eventType === 'change') { + console.log(`File ${path} (${serviceName}) has been changed`); + await sendFileContent(path, serviceName); + } + }); + + watchers.push(watcher); + console.log(`Watcher created for ${path}`); + }); + }; + + const cleanupWatchers = () => { + watchers.forEach((watcher) => watcher.close()); + watchers = []; + }; + + sendStatuses(); + startWatchers(); + + const closeStream = () => { + console.log('Client Disconnected'); + if (!streamClosed) { + streamClosed = true; + writer + .close() + .catch((error) => console.error('Error closing writer:', error)); + if (timeoutId) { + clearTimeout(timeoutId); + } + } + cleanupWatchers(); + }; + + request.signal.onabort = closeStream; + + return new Response(responseStream.readable, { + headers: { + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache, no-transform' + } + }); +} + process.setMaxListeners(20); // Utility function to execute shell commands as promises @@ -97,7 +225,6 @@ const execPromise = (command: string): Promise => { }); }; -// Utility function to check if a service is up const checkServiceStatus = async (serviceName: string): Promise => { try { switch (serviceName) { @@ -136,7 +263,214 @@ const checkServiceStatus = async (serviceName: string): Promise => { } }; -// Function to get the status of all services +=const getStatus = async (): Promise<{ + [key in ServiceNames]: { isUp: boolean }; +}> => { + const services = Object.values(ServiceNames); + const statuses: { [key in ServiceNames]: { isUp: boolean } } = { + [ServiceNames.API_SERVER]: { isUp: false }, + [ServiceNames.REDIS]: { isUp: false }, + [ServiceNames.POSTGRES]: { isUp: false }, + [ServiceNames.SYNC_SERVER]: { isUp: false } + }; + + await Promise.all( + services.map(async (service) => { + const isUp = await checkServiceStatus(service); + statuses[service] = { isUp }; + }) + ); + + return statuses; +}; + +// Stream handling function +export async function GET(request: NextRequest): Promise { + const responseStream = new TransformStream(); + +export async function GET(request: NextRequest): Promise { + const responseStream = new TransformStream(); + const writer = responseStream.writable.getWriter(); + const encoder = new TextEncoder(); + let timeoutId: NodeJS.Timeout | null = null; + let streamClosed = false; + + const sendStatuses = async () => { + if (streamClosed) return; + + try { + console.log('Fetching service statuses...'); + const statuses = await getStatus(); + const statusData = { type: 'status-update', statuses }; + + if (!streamClosed) { + await writer.write( + encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) + ); + } + } catch (error) { + console.error('Error sending statuses:', error); + } + + // Schedule the next status update + if (!streamClosed) { + timeoutId = setTimeout(sendStatuses, 10000); + } + }; + + const logFiles = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, + { + path: '/var/log/postgres/postgres.log', + serviceName: ServiceNames.POSTGRES + } + ]; + + let watchers: FSWatcher[] = []; + let lastPositions: { [key: string]: number } = {}; + + const sendFileContent = async (filePath: string, serviceName: string) => { + if (streamClosed) return; // Prevent sending if the stream is closed + console.log(`Sending file content for ${serviceName}`); + + return new Promise((resolve, reject) => { + const stream = createReadStream(filePath, { + start: lastPositions[filePath] || 0, + encoding: 'utf8' + }); + + stream.on('data', (chunk) => { + if (streamClosed) return; + const data = { + type: 'log-update', + serviceName, + content: chunk + }; + + writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + lastPositions[filePath] = + (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); + }); + + stream.on('end', () => resolve()); + stream.on('error', (error) => reject(error)); + }); + }; + + const startWatchers = () => { + logFiles.forEach(async ({ path, serviceName }) => { + await sendFileContent(path, serviceName); + + const watcher = watch(path, async (eventType) => { + if (eventType === 'change') { + console.log(`File ${path} (${serviceName}) has been changed`); + await sendFileContent(path, serviceName); + } + }); + + watchers.push(watcher); + console.log(`Watcher created for ${path}`); + }); + }; + + const cleanupWatchers = () => { + watchers.forEach((watcher) => watcher.close()); + watchers = []; + }; + + sendStatuses(); + startWatchers(); + + const closeStream = () => { + console.log('Client Disconnected'); + if (!streamClosed) { + streamClosed = true; + writer + .close() + .catch((error) => console.error('Error closing writer:', error)); + if (timeoutId) { + clearTimeout(timeoutId); + } + } + cleanupWatchers(); + }; + + request.signal.onabort = closeStream; + + return new Response(responseStream.readable, { + headers: { + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + 'Cache-Control': 'no-cache, no-transform' + } + }); +} + +process.setMaxListeners(20); +if (!process.listenerCount('SIGINT')) { + process.on('SIGINT', () => { + console.log('SIGINT received. Performing cleanup.'); + process.exit(); + }); +} + +const execPromise = (command: string): Promise => { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, _stderr) => { + if (error) { + return reject(error); + } + resolve(stdout); + }); + }); +}; + +const checkServiceStatus = async (serviceName: string): Promise => { + try { + switch (serviceName) { + case ServiceNames.API_SERVER: { + const response = await handleRequest(''); + return response.message.includes('hello world'); + } + + case ServiceNames.REDIS: { + const REDIS_PORT = process.env.REDIS_PORT; + const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); + return response.trim().includes('PONG'); + } + + case ServiceNames.POSTGRES: { + const POSTGRES_PORT = process.env.DB_PORT; + const POSTGRES_HOST = process.env.DB_HOST; + const response = await execPromise( + `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` + ); + return response.includes('accepting connections'); + } + + case ServiceNames.SYNC_SERVER: { + const response = await handleSyncServerRequest(''); + return response.message.includes('hello world'); + } + + default: + console.warn(`Service ${serviceName} not recognized.`); + return false; + } + } catch (error) { + console.error(`${serviceName} service is down:`, error); + return false; + } +}; + const getStatus = async (): Promise<{ [key in ServiceNames]: { isUp: boolean }; }> => { diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 85ec020d..34f44dcd 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,5 +1,5 @@ -import { Box, Button, Divider } from '@mui/material'; -import { FC, useEffect, useState, useRef } from 'react'; +import { Box, CircularProgress, Divider } from '@mui/material'; +import { FC, useEffect, useState } from 'react'; import { ServiceNames } from '@/constants/service'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; @@ -12,25 +12,38 @@ import { Line } from '../Text'; export const SystemStatus: FC = () => { const dispatch = useDispatch(); - const [error, setError] = useState(''); + const [loading, setLoading] = useState(true); const services = useSelector( (state: { service: { services: ServiceStatusState } }) => state.service.services ); - const [status, setStatus] = useState('loading'); useEffect(() => { const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); - console.log(data); + setLoading(false); if (data.type.includes('status-update')) { const statuses = { statuses: data.statuses }; - console.log(statuses); dispatch(serviceSlice.actions.setStatus(statuses)); } + if (data.type.includes('log-update')) { + const { serviceName, content } = data; + + const newLines = content.split('\n'); + const trimmedLines = newLines.filter( + (line: string) => line.trim() !== '' + ); + + dispatch( + serviceSlice.actions.setServiceLogs({ + serviceName, + serviceLog: trimmedLines + }) + ); + } }; eventSource.onerror = () => { @@ -40,7 +53,7 @@ export const SystemStatus: FC = () => { return () => { eventSource.close(); }; - }, []); + }, [dispatch]); const { addPage } = useOverlayPage(); @@ -55,19 +68,26 @@ export const SystemStatus: FC = () => { System Status - {status} - {error && ( - -

{error}

+ {loading && ( + + )} {services && + !loading && Object.keys(services).map((serviceName) => { - const { isUp } = services[serviceName]; + const ServiceName = serviceName as ServiceNames; + const { isUp } = services[ServiceName]; return ( { - const dispatch = useDispatch(); const services = useSelector( (state: { service: { services: ServiceStatusState } }) => state.service.services ); - const active = useSelector((s) => s.service.active); - const workerRef = useRef(); - - const logs = services[active].logs; + const active = useSelector((s) => s.service.active) as ServiceNames; + const logs: string[] = services[active].logs; + return ( Date: Mon, 2 Sep 2024 14:12:07 +0000 Subject: [PATCH 25/55] remove console logs --- web-server/app/api/stream/route.ts | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index dcf06e6a..afd6e3ad 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -296,10 +296,9 @@ export async function GET(request: NextRequest): Promise { let streamClosed = false; const sendStatuses = async () => { - if (streamClosed) return; + if (streamClosed) return; try { - console.log('Fetching service statuses...'); const statuses = await getStatus(); const statusData = { type: 'status-update', statuses }; @@ -312,7 +311,6 @@ export async function GET(request: NextRequest): Promise { console.error('Error sending statuses:', error); } - // Schedule the next status update if (!streamClosed) { timeoutId = setTimeout(sendStatuses, 10000); } @@ -338,8 +336,7 @@ export async function GET(request: NextRequest): Promise { let lastPositions: { [key: string]: number } = {}; const sendFileContent = async (filePath: string, serviceName: string) => { - if (streamClosed) return; // Prevent sending if the stream is closed - console.log(`Sending file content for ${serviceName}`); + if (streamClosed) return; return new Promise((resolve, reject) => { const stream = createReadStream(filePath, { @@ -348,7 +345,7 @@ export async function GET(request: NextRequest): Promise { }); stream.on('data', (chunk) => { - if (streamClosed) return; + if (streamClosed) return; const data = { type: 'log-update', serviceName, @@ -371,13 +368,11 @@ export async function GET(request: NextRequest): Promise { const watcher = watch(path, async (eventType) => { if (eventType === 'change') { - console.log(`File ${path} (${serviceName}) has been changed`); await sendFileContent(path, serviceName); } }); watchers.push(watcher); - console.log(`Watcher created for ${path}`); }); }; @@ -390,7 +385,6 @@ export async function GET(request: NextRequest): Promise { startWatchers(); const closeStream = () => { - console.log('Client Disconnected'); if (!streamClosed) { streamClosed = true; writer @@ -414,14 +408,6 @@ export async function GET(request: NextRequest): Promise { }); } -process.setMaxListeners(20); -if (!process.listenerCount('SIGINT')) { - process.on('SIGINT', () => { - console.log('SIGINT received. Performing cleanup.'); - process.exit(); - }); -} - const execPromise = (command: string): Promise => { return new Promise((resolve, reject) => { exec(command, (error, stdout, _stderr) => { From 599f6f3a06c2358b8ada3486621f742ea582412b Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 17:37:20 +0000 Subject: [PATCH 26/55] UI updates --- .../src/components/Service/SystemStatus.tsx | 24 ++++++++--------- web-server/src/content/Service/SystemLogs.tsx | 27 ++++++++++++++++--- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 34f44dcd..c91ca936 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,8 +1,9 @@ import { Box, CircularProgress, Divider } from '@mui/material'; -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect } from 'react'; import { ServiceNames } from '@/constants/service'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; +import { useBoolState } from '@/hooks/useEasyState'; import { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; @@ -12,7 +13,8 @@ import { Line } from '../Text'; export const SystemStatus: FC = () => { const dispatch = useDispatch(); - const [loading, setLoading] = useState(true); + const loading = useBoolState(true); + const services = useSelector( (state: { service: { services: ServiceStatusState } }) => state.service.services @@ -23,11 +25,11 @@ export const SystemStatus: FC = () => { eventSource.onmessage = (event) => { const data = JSON.parse(event.data); - setLoading(false); if (data.type.includes('status-update')) { const statuses = { statuses: data.statuses }; dispatch(serviceSlice.actions.setStatus(statuses)); + loading.set(false); } if (data.type.includes('log-update')) { const { serviceName, content } = data; @@ -53,7 +55,7 @@ export const SystemStatus: FC = () => { return () => { eventSource.close(); }; - }, [dispatch]); + }, [dispatch, loading]); const { addPage } = useOverlayPage(); @@ -70,7 +72,7 @@ export const SystemStatus: FC = () => { - {loading && ( + {loading.value ? ( { > - )} - - - {services && - !loading && - Object.keys(services).map((serviceName) => { + ) : ( + + {Object.keys(services).map((serviceName) => { const ServiceName = serviceName as ServiceNames; const { isUp } = services[ServiceName]; return ( @@ -165,7 +164,8 @@ export const SystemStatus: FC = () => { ); })} - + + )} ); }; diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 2a1147d7..18ab4903 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -1,25 +1,46 @@ import { Box, Typography } from '@mui/material'; +import { useRouter } from 'next/router'; +import { useEffect, useRef, useMemo } from 'react'; import { ServiceNames } from '@/constants/service'; import { ServiceStatusState } from '@/slices/service'; import { useSelector } from '@/store'; export const SystemLogs = () => { + const router = useRouter(); + const containerRef = useRef(null); + const services = useSelector( (state: { service: { services: ServiceStatusState } }) => state.service.services ); const active = useSelector((s) => s.service.active) as ServiceNames; - const logs: string[] = services[active].logs; - + + const logs = useMemo(() => { + return active ? services[active]?.logs || [] : []; + }, [active, services]); + + useEffect(() => { + if (!active) { + router.push('/system'); + } + }, [active, router]); + + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [logs]); + return ( {services && From 4d6a8037b6bf190788d8c440c553e934f23f4730 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 17:43:16 +0000 Subject: [PATCH 27/55] refactor --- web-server/app/api/stream/route.ts | 108 ++++++++++------------------- 1 file changed, 38 insertions(+), 70 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index afd6e3ad..0662dc56 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -289,117 +289,85 @@ export async function GET(request: NextRequest): Promise { const responseStream = new TransformStream(); export async function GET(request: NextRequest): Promise { - const responseStream = new TransformStream(); - const writer = responseStream.writable.getWriter(); + const { writable, readable } = new TransformStream(); + const writer = writable.getWriter(); const encoder = new TextEncoder(); + let timeoutId: NodeJS.Timeout | null = null; let streamClosed = false; + let watchers: FSWatcher[] = []; + let lastPositions: { [key: string]: number } = {}; - const sendStatuses = async () => { + const sendEvent = async (eventType: string, data: any) => { if (streamClosed) return; + await writer.write( + encoder.encode( + `data: ${JSON.stringify({ type: eventType, ...data })}\n\n` + ) + ); + }; + const sendStatuses = async () => { + if (streamClosed) return; try { const statuses = await getStatus(); - const statusData = { type: 'status-update', statuses }; - - if (!streamClosed) { - await writer.write( - encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) - ); - } + await sendEvent('status-update', { statuses }); } catch (error) { console.error('Error sending statuses:', error); } - if (!streamClosed) { - timeoutId = setTimeout(sendStatuses, 10000); + timeoutId = setTimeout(sendStatuses, UPDATE_INTERVAL); } }; - const logFiles = [ - { - path: '/var/log/apiserver/apiserver.log', - serviceName: ServiceNames.API_SERVER - }, - { - path: '/var/log/sync_server/sync_server.log', - serviceName: ServiceNames.SYNC_SERVER - }, - { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, - { - path: '/var/log/postgres/postgres.log', - serviceName: ServiceNames.POSTGRES - } - ]; - - let watchers: FSWatcher[] = []; - let lastPositions: { [key: string]: number } = {}; - - const sendFileContent = async (filePath: string, serviceName: string) => { + const sendFileContent = async ({ path, serviceName }: LogFile) => { if (streamClosed) return; - return new Promise((resolve, reject) => { - const stream = createReadStream(filePath, { - start: lastPositions[filePath] || 0, + const stream = createReadStream(path, { + start: lastPositions[path] || 0, encoding: 'utf8' }); - - stream.on('data', (chunk) => { + stream.on('data', async (chunk) => { if (streamClosed) return; - const data = { - type: 'log-update', - serviceName, - content: chunk - }; - - writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); - lastPositions[filePath] = - (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); + await sendEvent('log-update', { serviceName, content: chunk }); + lastPositions[path] = + (lastPositions[path] || 0) + Buffer.byteLength(chunk); }); - - stream.on('end', () => resolve()); - stream.on('error', (error) => reject(error)); + stream.on('end', resolve); + stream.on('error', reject); }); }; const startWatchers = () => { - logFiles.forEach(async ({ path, serviceName }) => { - await sendFileContent(path, serviceName); - - const watcher = watch(path, async (eventType) => { + LOG_FILES.forEach(async (logFile) => { + await sendFileContent(logFile); + const watcher = watch(logFile.path, async (eventType) => { if (eventType === 'change') { - await sendFileContent(path, serviceName); + await sendFileContent(logFile); } }); - watchers.push(watcher); }); }; - const cleanupWatchers = () => { - watchers.forEach((watcher) => watcher.close()); - watchers = []; - }; - - sendStatuses(); - startWatchers(); - - const closeStream = () => { + const cleanup = () => { if (!streamClosed) { streamClosed = true; writer .close() .catch((error) => console.error('Error closing writer:', error)); - if (timeoutId) { - clearTimeout(timeoutId); - } + if (timeoutId) clearTimeout(timeoutId); } - cleanupWatchers(); + watchers.forEach((watcher) => watcher.close()); + watchers = []; }; - request.signal.onabort = closeStream; + sendStatuses(); + startWatchers(); - return new Response(responseStream.readable, { + request.signal.onabort = cleanup; + + return new Response(readable, { headers: { 'Content-Type': 'text/event-stream', Connection: 'keep-alive', From a50ff4488b373c274d1764d24ad337484e4c3d16 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 17:52:53 +0000 Subject: [PATCH 28/55] refactor --- web-server/app/api/stream/route.ts | 131 ++--------------------------- web-server/src/constants/stream.ts | 23 +++++ 2 files changed, 29 insertions(+), 125 deletions(-) create mode 100644 web-server/src/constants/stream.ts diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 0662dc56..c9cb6cc9 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -5,131 +5,12 @@ import { FSWatcher, createReadStream, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; -export const runtime = 'nodejs'; -export const dynamic = 'force-dynamic'; - -// Utility function to execute shell commands as promises -const execPromise = (command: string): Promise => { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - return reject(error); - } - resolve(stdout); - }); - }); -}; - -// Utility function to check if a service is up -const checkServiceStatus = async (serviceName: string): Promise => { - try { - switch (serviceName) { - case ServiceNames.API_SERVER: { - const response = await handleRequest(''); - return response.message.includes('hello world'); - } - - case ServiceNames.REDIS: { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - return response.trim().includes('PONG'); - } - - case ServiceNames.POSTGRES: { - const POSTGRES_PORT = process.env.DB_PORT; - const POSTGRES_HOST = process.env.DB_HOST; - const response = await execPromise( - `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` - ); - return response.includes('accepting connections'); - } - - case ServiceNames.SYNC_SERVER: { - const response = await handleSyncServerRequest(''); - return response.message.includes('hello world'); - } - - default: - console.warn(`Service ${serviceName} not recognized.`); - return false; - } - } catch (error) { - console.error(`${serviceName} service is down:`, error); - return false; - } -}; - -// Function to get the status of all services -const getStatus = async (): Promise<{ - [key in ServiceNames]: { isUp: boolean }; -}> => { - const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; - - await Promise.all( - services.map(async (service) => { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp }; - }) - ); - - return statuses; -}; - -// Stream handling function -export async function GET(request: NextRequest): Promise { - const responseStream = new TransformStream(); - -export async function GET(request: NextRequest): Promise { - const responseStream = new TransformStream(); - const writer = responseStream.writable.getWriter(); - const encoder = new TextEncoder(); - let timeoutId: NodeJS.Timeout | null = null; - let streamClosed = false; - - const sendStatuses = async () => { - if (streamClosed) return; - - try { - console.log('Fetching service statuses...'); - const statuses = await getStatus(); - const statusData = { type: 'status-update', statuses }; - - if (!streamClosed) { - await writer.write( - encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) - ); - } - } catch (error) { - console.error('Error sending statuses:', error); - } - - // Schedule the next status update - if (!streamClosed) { - timeoutId = setTimeout(sendStatuses, 10000); - } - }; - - const logFiles = [ - { - path: '/var/log/apiserver/apiserver.log', - serviceName: ServiceNames.API_SERVER - }, - { - path: '/var/log/sync_server/sync_server.log', - serviceName: ServiceNames.SYNC_SERVER - }, - { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, - { - path: '/var/log/postgres/postgres.log', - serviceName: ServiceNames.POSTGRES - } - ]; +import { + ServiceStatus, + UPDATE_INTERVAL, + LOG_FILES, + LogFile +} from '@/constants/stream'; let watchers: FSWatcher[] = []; let lastPositions: { [key: string]: number } = {}; diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts new file mode 100644 index 00000000..f0377916 --- /dev/null +++ b/web-server/src/constants/stream.ts @@ -0,0 +1,23 @@ +import { ServiceNames } from './service'; +export type LogFile = { + path: string; + serviceName: ServiceNames; +}; +export type ServiceStatus = { + [key in ServiceNames]: { isUp: boolean }; +}; + +export const UPDATE_INTERVAL = 5000; + +export const LOG_FILES: LogFile[] = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, + { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } +]; From 9a072fd09342300fa3b6c87240f19e058287bede Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 19:32:07 +0000 Subject: [PATCH 29/55] refactor --- web-server/app/api/stream/route.ts | 29 +++++++++++++++++++++++------ web-server/src/constants/stream.ts | 23 ----------------------- 2 files changed, 23 insertions(+), 29 deletions(-) delete mode 100644 web-server/src/constants/stream.ts diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index c9cb6cc9..05dcda2b 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -5,12 +5,29 @@ import { FSWatcher, createReadStream, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; -import { - ServiceStatus, - UPDATE_INTERVAL, - LOG_FILES, - LogFile -} from '@/constants/stream'; + +type LogFile = { + path: string; + serviceName: ServiceNames; +}; + +type ServiceStatus = { + [key in ServiceNames]: { isUp: boolean }; +}; + +const UPDATE_INTERVAL = 10000; +const LOG_FILES: LogFile[] = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, + { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } +]; let watchers: FSWatcher[] = []; let lastPositions: { [key: string]: number } = {}; diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts deleted file mode 100644 index f0377916..00000000 --- a/web-server/src/constants/stream.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ServiceNames } from './service'; -export type LogFile = { - path: string; - serviceName: ServiceNames; -}; -export type ServiceStatus = { - [key in ServiceNames]: { isUp: boolean }; -}; - -export const UPDATE_INTERVAL = 5000; - -export const LOG_FILES: LogFile[] = [ - { - path: '/var/log/apiserver/apiserver.log', - serviceName: ServiceNames.API_SERVER - }, - { - path: '/var/log/sync_server/sync_server.log', - serviceName: ServiceNames.SYNC_SERVER - }, - { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, - { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } -]; From 5ea08517dc4362240b263d11bacfc729eb3bb13c Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 20:32:28 +0000 Subject: [PATCH 30/55] fix duplication --- .../src/components/Service/SystemStatus.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index c91ca936..e3b6f310 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -22,18 +22,25 @@ export const SystemStatus: FC = () => { useEffect(() => { const eventSource = new EventSource(`/api/stream`); - + eventSource.onopen = (event)=>{ + console.log("OPEN") + } eventSource.onmessage = (event) => { const data = JSON.parse(event.data); - if (data.type.includes('status-update')) { + if (data.type === 'status-update') { const statuses = { statuses: data.statuses }; dispatch(serviceSlice.actions.setStatus(statuses)); loading.set(false); } - if (data.type.includes('log-update')) { + + if (data.type === 'log-update') { const { serviceName, content } = data; + if (serviceName === ServiceNames.REDIS) { + console.log(content); + } + const newLines = content.split('\n'); const trimmedLines = newLines.filter( (line: string) => line.trim() !== '' @@ -55,7 +62,7 @@ export const SystemStatus: FC = () => { return () => { eventSource.close(); }; - }, [dispatch, loading]); + }, [dispatch]); const { addPage } = useOverlayPage(); From 8defcfe647c07ca9e44ef989796c339a1f3ea5d7 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 20:34:35 +0000 Subject: [PATCH 31/55] remove log --- web-server/src/components/Service/SystemStatus.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index e3b6f310..52b83a4e 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -22,9 +22,6 @@ export const SystemStatus: FC = () => { useEffect(() => { const eventSource = new EventSource(`/api/stream`); - eventSource.onopen = (event)=>{ - console.log("OPEN") - } eventSource.onmessage = (event) => { const data = JSON.parse(event.data); @@ -36,11 +33,6 @@ export const SystemStatus: FC = () => { if (data.type === 'log-update') { const { serviceName, content } = data; - - if (serviceName === ServiceNames.REDIS) { - console.log(content); - } - const newLines = content.split('\n'); const trimmedLines = newLines.filter( (line: string) => line.trim() !== '' From 3e308d3c934a5e4f5ef1edb3119f1010deb4c94f Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 20:40:14 +0000 Subject: [PATCH 32/55] refactor --- web-server/app/api/stream/route.ts | 21 +-------------------- web-server/src/constants/stream.ts | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 web-server/src/constants/stream.ts diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 05dcda2b..2672e7b2 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -5,29 +5,10 @@ import { FSWatcher, createReadStream, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; +import { ServiceStatus, UPDATE_INTERVAL, LogFile, LOG_FILES } from '@/constants/stream'; -type LogFile = { - path: string; - serviceName: ServiceNames; -}; -type ServiceStatus = { - [key in ServiceNames]: { isUp: boolean }; -}; -const UPDATE_INTERVAL = 10000; -const LOG_FILES: LogFile[] = [ - { - path: '/var/log/apiserver/apiserver.log', - serviceName: ServiceNames.API_SERVER - }, - { - path: '/var/log/sync_server/sync_server.log', - serviceName: ServiceNames.SYNC_SERVER - }, - { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, - { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } -]; let watchers: FSWatcher[] = []; let lastPositions: { [key: string]: number } = {}; diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts new file mode 100644 index 00000000..781edbda --- /dev/null +++ b/web-server/src/constants/stream.ts @@ -0,0 +1,24 @@ +import { ServiceNames } from './service'; + +export type LogFile = { + path: string; + serviceName: ServiceNames; +}; + +export type ServiceStatus = { + [key in ServiceNames]: { isUp: boolean }; +}; + +export const UPDATE_INTERVAL = 10000; +export const LOG_FILES: LogFile[] = [ + { + path: '/var/log/apiserver/apiserver.log', + serviceName: ServiceNames.API_SERVER + }, + { + path: '/var/log/sync_server/sync_server.log', + serviceName: ServiceNames.SYNC_SERVER + }, + { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, + { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } +]; From 84eb20acc43c53319b98e8812b93e26cb06dd299 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Mon, 2 Sep 2024 20:41:16 +0000 Subject: [PATCH 33/55] refactor --- web-server/app/api/stream/route.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 2672e7b2..4d078e8c 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -5,10 +5,12 @@ import { FSWatcher, createReadStream, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; -import { ServiceStatus, UPDATE_INTERVAL, LogFile, LOG_FILES } from '@/constants/stream'; - - - +import { + ServiceStatus, + UPDATE_INTERVAL, + LogFile, + LOG_FILES +} from '@/constants/stream'; let watchers: FSWatcher[] = []; let lastPositions: { [key: string]: number } = {}; From b12ca02446231d56b9fd714d594fd572a811d864 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Tue, 3 Sep 2024 09:06:44 +0000 Subject: [PATCH 34/55] fix ui --- web-server/app/api/stream/route.ts | 117 ++++++++---------- .../src/components/Service/SystemStatus.tsx | 3 +- web-server/src/content/Service/SystemLogs.tsx | 40 ++---- 3 files changed, 61 insertions(+), 99 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 4d078e8c..bac42fe6 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,7 +1,7 @@ import { NextRequest } from 'next/server'; import { exec } from 'child_process'; -import { FSWatcher, createReadStream, watch } from 'fs'; +import { createReadStream, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; @@ -170,85 +170,68 @@ export async function GET(request: NextRequest): Promise { const responseStream = new TransformStream(); export async function GET(request: NextRequest): Promise { - const { writable, readable } = new TransformStream(); - const writer = writable.getWriter(); const encoder = new TextEncoder(); - - let timeoutId: NodeJS.Timeout | null = null; let streamClosed = false; - let watchers: FSWatcher[] = []; let lastPositions: { [key: string]: number } = {}; - const sendEvent = async (eventType: string, data: any) => { - if (streamClosed) return; - await writer.write( - encoder.encode( - `data: ${JSON.stringify({ type: eventType, ...data })}\n\n` - ) + const sendEvent = (eventType: string, data: any) => { + return encoder.encode( + `data: ${JSON.stringify({ type: eventType, ...data })}\n\n` ); }; - const sendStatuses = async () => { - if (streamClosed) return; - try { - const statuses = await getStatus(); - await sendEvent('status-update', { statuses }); - } catch (error) { - console.error('Error sending statuses:', error); - } - if (!streamClosed) { - timeoutId = setTimeout(sendStatuses, UPDATE_INTERVAL); - } - }; - - const sendFileContent = async ({ path, serviceName }: LogFile) => { - if (streamClosed) return; - return new Promise((resolve, reject) => { - const stream = createReadStream(path, { - start: lastPositions[path] || 0, - encoding: 'utf8' - }); - stream.on('data', async (chunk) => { + const stream = new ReadableStream({ + start(controller) { + const pushStatus = async () => { if (streamClosed) return; - await sendEvent('log-update', { serviceName, content: chunk }); - lastPositions[path] = - (lastPositions[path] || 0) + Buffer.byteLength(chunk); - }); - stream.on('end', resolve); - stream.on('error', reject); - }); - }; + try { + const statuses = await getStatus(); + controller.enqueue(sendEvent('status-update', { statuses })); + } catch (error) { + console.error('Error sending statuses:', error); + } + setTimeout(pushStatus, UPDATE_INTERVAL); + }; - const startWatchers = () => { - LOG_FILES.forEach(async (logFile) => { - await sendFileContent(logFile); - const watcher = watch(logFile.path, async (eventType) => { - if (eventType === 'change') { - await sendFileContent(logFile); + const pushFileContent = async ({ path, serviceName }: LogFile) => { + if (streamClosed) return; + const fileStream = createReadStream(path, { + start: lastPositions[path] || 0, + encoding: 'utf8' + }); + + for await (const chunk of fileStream) { + if (streamClosed) break; + controller.enqueue( + sendEvent('log-update', { serviceName, content: chunk }) + ); + lastPositions[path] = + (lastPositions[path] || 0) + Buffer.byteLength(chunk); } + }; + + const startWatchers = () => { + LOG_FILES.forEach((logFile) => { + watch(logFile.path, async (eventType) => { + if (eventType === 'change' && !streamClosed) { + await pushFileContent(logFile); + } + }); + }); + }; + + pushStatus(); + LOG_FILES.forEach(pushFileContent); + startWatchers(); + + request.signal.addEventListener('abort', () => { + streamClosed = true; + controller.close(); }); - watchers.push(watcher); - }); - }; - - const cleanup = () => { - if (!streamClosed) { - streamClosed = true; - writer - .close() - .catch((error) => console.error('Error closing writer:', error)); - if (timeoutId) clearTimeout(timeoutId); } - watchers.forEach((watcher) => watcher.close()); - watchers = []; - }; - - sendStatuses(); - startWatchers(); - - request.signal.onabort = cleanup; + }); - return new Response(readable, { + return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', Connection: 'keep-alive', diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 52b83a4e..d5ff7c94 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -23,12 +23,12 @@ export const SystemStatus: FC = () => { useEffect(() => { const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { + loading.set(false); const data = JSON.parse(event.data); if (data.type === 'status-update') { const statuses = { statuses: data.statuses }; dispatch(serviceSlice.actions.setStatus(statuses)); - loading.set(false); } if (data.type === 'log-update') { @@ -53,6 +53,7 @@ export const SystemStatus: FC = () => { return () => { eventSource.close(); + dispatch(serviceSlice.actions.resetState()); }; }, [dispatch]); diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 18ab4903..4c3fdff4 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -38,43 +38,21 @@ export const SystemLogs = () => { sx={{ padding: '16px 24px', borderRadius: 2, - color: 'white', overflowY: 'auto', - maxHeight: '750px' + maxHeight: '750px', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + lineHeight: 1.6, + marginTop: '8px' }} > {services && - logs.map((log, i) => ( + logs.map((log, index) => ( - - {i + 1} - - - {log} - + {log} ))} From c0d45fdb739dc37c9063e630fdc8d519b7aaaaea Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Tue, 3 Sep 2024 09:47:52 +0000 Subject: [PATCH 35/55] update stream --- web-server/app/api/stream/route.ts | 60 ++++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index bac42fe6..4b382e2f 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,7 +1,5 @@ -import { NextRequest } from 'next/server'; - import { exec } from 'child_process'; -import { createReadStream, watch } from 'fs'; +import { createReadStream, FSWatcher, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; @@ -173,11 +171,12 @@ export async function GET(request: NextRequest): Promise { const encoder = new TextEncoder(); let streamClosed = false; let lastPositions: { [key: string]: number } = {}; + let statusTimer: NodeJS.Timeout | null = null; + const watchers: FSWatcher[] = []; const sendEvent = (eventType: string, data: any) => { - return encoder.encode( - `data: ${JSON.stringify({ type: eventType, ...data })}\n\n` - ); + const eventData = JSON.stringify({ type: eventType, ...data }); + return encoder.encode(`data: ${eventData}\n\n`); }; const stream = new ReadableStream({ @@ -186,48 +185,61 @@ export async function GET(request: NextRequest): Promise { if (streamClosed) return; try { const statuses = await getStatus(); - controller.enqueue(sendEvent('status-update', { statuses })); + if (!streamClosed) { + controller.enqueue(sendEvent('status-update', { statuses })); + } } catch (error) { console.error('Error sending statuses:', error); } - setTimeout(pushStatus, UPDATE_INTERVAL); + if (!streamClosed) { + statusTimer = setTimeout(pushStatus, UPDATE_INTERVAL); + } }; const pushFileContent = async ({ path, serviceName }: LogFile) => { if (streamClosed) return; - const fileStream = createReadStream(path, { - start: lastPositions[path] || 0, - encoding: 'utf8' - }); + try { + const fileStream = createReadStream(path, { + start: lastPositions[path] || 0, + encoding: 'utf8' + }); - for await (const chunk of fileStream) { - if (streamClosed) break; - controller.enqueue( - sendEvent('log-update', { serviceName, content: chunk }) - ); - lastPositions[path] = - (lastPositions[path] || 0) + Buffer.byteLength(chunk); + for await (const chunk of fileStream) { + if (streamClosed) break; + controller.enqueue( + sendEvent('log-update', { serviceName, content: chunk }) + ); + lastPositions[path] = + (lastPositions[path] || 0) + Buffer.byteLength(chunk); + } + } catch (error) { + console.error(`Error reading log file for ${serviceName}:`, error); } }; const startWatchers = () => { LOG_FILES.forEach((logFile) => { - watch(logFile.path, async (eventType) => { + const watcher = watch(logFile.path, async (eventType) => { if (eventType === 'change' && !streamClosed) { await pushFileContent(logFile); } }); + watchers.push(watcher); }); }; pushStatus(); LOG_FILES.forEach(pushFileContent); startWatchers(); + }, + cancel() { + streamClosed = true; - request.signal.addEventListener('abort', () => { - streamClosed = true; - controller.close(); - }); + if (statusTimer) { + clearTimeout(statusTimer); + } + + watchers.forEach((watcher) => watcher.close()); } }); From b4e66930aa2335d6116e193651380c9db7c5ed03 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Tue, 3 Sep 2024 17:12:49 +0530 Subject: [PATCH 36/55] update header --- web-server/app/api/stream/route.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 4b382e2f..b87477c4 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -245,9 +245,11 @@ export async function GET(request: NextRequest): Promise { return new Response(stream, { headers: { - 'Content-Type': 'text/event-stream', + 'Content-Type': 'text/event-stream; charset=utf-8', Connection: 'keep-alive', - 'Cache-Control': 'no-cache, no-transform' + 'Cache-Control': 'no-cache, no-transform', + 'X-Accel-Buffering': 'no', + 'Content-Encoding': 'none' } }); } From fd3725fb568a455f8ae9e5fb199eba7c1ec24b72 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Tue, 3 Sep 2024 17:58:34 +0530 Subject: [PATCH 37/55] loading state --- web-server/src/components/Service/SystemStatus.tsx | 8 ++------ web-server/src/slices/service.ts | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index d5ff7c94..fdecd2db 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -3,7 +3,6 @@ import { FC, useEffect } from 'react'; import { ServiceNames } from '@/constants/service'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; -import { useBoolState } from '@/hooks/useEasyState'; import { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; @@ -13,8 +12,7 @@ import { Line } from '../Text'; export const SystemStatus: FC = () => { const dispatch = useDispatch(); - const loading = useBoolState(true); - + const loading = useSelector((state) => state.service.loading); const services = useSelector( (state: { service: { services: ServiceStatusState } }) => state.service.services @@ -23,7 +21,6 @@ export const SystemStatus: FC = () => { useEffect(() => { const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { - loading.set(false); const data = JSON.parse(event.data); if (data.type === 'status-update') { @@ -37,7 +34,6 @@ export const SystemStatus: FC = () => { const trimmedLines = newLines.filter( (line: string) => line.trim() !== '' ); - dispatch( serviceSlice.actions.setServiceLogs({ serviceName, @@ -72,7 +68,7 @@ export const SystemStatus: FC = () => { - {loading.value ? ( + {loading ? ( ) => { + state.loading = false; const { serviceName, serviceLog } = action.payload; state.services[serviceName].logs = [ ...state.services[serviceName].logs, From f047fb4c320708eefb7845c014225cf5a7b6cb8d Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Tue, 3 Sep 2024 18:01:26 +0530 Subject: [PATCH 38/55] type update --- web-server/src/slices/service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 521d9886..fdc7734c 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -6,13 +6,13 @@ type Status = { isUp: boolean; }; -type ServiceStatus = { +type Service = { isUp: boolean; logs: string[]; }; export type ServiceStatusState = { - [key in ServiceNames]: ServiceStatus; + [key in ServiceNames]: Service; }; type State = { From 67d3f56e43ca5dea2b3fafd4e221c2f7afd07f49 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Fri, 6 Sep 2024 18:38:18 +0530 Subject: [PATCH 39/55] refactor: add enum for event type and file change --- web-server/app/api/stream/route.ts | 15 ++++++--- .../src/components/Service/SystemStatus.tsx | 11 +++---- web-server/src/constants/stream.ts | 31 +++++++++++++++---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index b87477c4..c5c0fa1c 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -7,7 +7,9 @@ import { ServiceStatus, UPDATE_INTERVAL, LogFile, - LOG_FILES + LOG_FILES, + StreamEventType, + FileEvent } from '@/constants/stream'; let watchers: FSWatcher[] = []; @@ -186,7 +188,9 @@ export async function GET(request: NextRequest): Promise { try { const statuses = await getStatus(); if (!streamClosed) { - controller.enqueue(sendEvent('status-update', { statuses })); + controller.enqueue( + sendEvent(StreamEventType.StatusUpdate, { statuses }) + ); } } catch (error) { console.error('Error sending statuses:', error); @@ -207,7 +211,10 @@ export async function GET(request: NextRequest): Promise { for await (const chunk of fileStream) { if (streamClosed) break; controller.enqueue( - sendEvent('log-update', { serviceName, content: chunk }) + sendEvent(StreamEventType.LogUpdate, { + serviceName, + content: chunk + }) ); lastPositions[path] = (lastPositions[path] || 0) + Buffer.byteLength(chunk); @@ -220,7 +227,7 @@ export async function GET(request: NextRequest): Promise { const startWatchers = () => { LOG_FILES.forEach((logFile) => { const watcher = watch(logFile.path, async (eventType) => { - if (eventType === 'change' && !streamClosed) { + if (eventType === FileEvent.Change && !streamClosed) { await pushFileContent(logFile); } }); diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index fdecd2db..ea542a21 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -2,6 +2,7 @@ import { Box, CircularProgress, Divider } from '@mui/material'; import { FC, useEffect } from 'react'; import { ServiceNames } from '@/constants/service'; +import { StreamEventType } from '@/constants/stream'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; import { serviceSlice, ServiceStatusState } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; @@ -17,18 +18,17 @@ export const SystemStatus: FC = () => { (state: { service: { services: ServiceStatusState } }) => state.service.services ); - useEffect(() => { const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); - if (data.type === 'status-update') { + if (data.type === StreamEventType.StatusUpdate) { const statuses = { statuses: data.statuses }; dispatch(serviceSlice.actions.setStatus(statuses)); } - if (data.type === 'log-update') { + if (data.type === StreamEventType.LogUpdate) { const { serviceName, content } = data; const newLines = content.split('\n'); const trimmedLines = newLines.filter( @@ -81,14 +81,13 @@ export const SystemStatus: FC = () => { ) : ( {Object.keys(services).map((serviceName) => { - const ServiceName = serviceName as ServiceNames; - const { isUp } = services[ServiceName]; + const serviceKey = serviceName as ServiceNames; + const { isUp } = services[serviceKey]; return ( { dispatch(serviceSlice.actions.setActiveService(serviceName)); - dispatch(serviceSlice.actions.setLoading(true)); addPage({ page: { diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts index 781edbda..6505ff55 100644 --- a/web-server/src/constants/stream.ts +++ b/web-server/src/constants/stream.ts @@ -1,16 +1,17 @@ import { ServiceNames } from './service'; -export type LogFile = { +type LogFile = { path: string; serviceName: ServiceNames; }; -export type ServiceStatus = { +type ServiceStatus = { [key in ServiceNames]: { isUp: boolean }; }; -export const UPDATE_INTERVAL = 10000; -export const LOG_FILES: LogFile[] = [ +const UPDATE_INTERVAL = 10000; + +const LOG_FILES: LogFile[] = [ { path: '/var/log/apiserver/apiserver.log', serviceName: ServiceNames.API_SERVER @@ -19,6 +20,24 @@ export const LOG_FILES: LogFile[] = [ path: '/var/log/sync_server/sync_server.log', serviceName: ServiceNames.SYNC_SERVER }, - { path: '/var/log/redis/redis.log', serviceName: ServiceNames.REDIS }, - { path: '/var/log/postgres/postgres.log', serviceName: ServiceNames.POSTGRES } + { + path: '/var/log/redis/redis.log', + serviceName: ServiceNames.REDIS + }, + { + path: '/var/log/postgres/postgres.log', + serviceName: ServiceNames.POSTGRES + } ]; + +enum StreamEventType { + StatusUpdate = 'status-update', + LogUpdate = 'log-update' +} + +enum FileEvent { + Change = 'change' +} + +export type { LogFile, ServiceStatus }; +export { UPDATE_INTERVAL, LOG_FILES, StreamEventType, FileEvent }; From 8895f1283aa028bb3ceb61667f6d401d29b3bb0d Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 6 Sep 2024 13:26:49 +0000 Subject: [PATCH 40/55] fix:rerender --- web-server/src/components/Service/SystemStatus.tsx | 7 ++++--- web-server/src/content/Service/SystemLogs.tsx | 11 +++++------ web-server/src/slices/service.ts | 5 ----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index ea542a21..ab41d135 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -87,12 +87,13 @@ export const SystemStatus: FC = () => { { - dispatch(serviceSlice.actions.setActiveService(serviceName)); - addPage({ page: { ui: 'system_logs', - title: `${ServiceTitle[serviceName]} Logs` + title: `${ServiceTitle[serviceName]} Logs`, + props:{ + serviceName:serviceName + } } }); }} diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 4c3fdff4..9451d3d6 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -6,7 +6,7 @@ import { ServiceNames } from '@/constants/service'; import { ServiceStatusState } from '@/slices/service'; import { useSelector } from '@/store'; -export const SystemLogs = () => { +export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { const router = useRouter(); const containerRef = useRef(null); @@ -14,17 +14,16 @@ export const SystemLogs = () => { (state: { service: { services: ServiceStatusState } }) => state.service.services ); - const active = useSelector((s) => s.service.active) as ServiceNames; const logs = useMemo(() => { - return active ? services[active]?.logs || [] : []; - }, [active, services]); + return serviceName ? services[serviceName]?.logs || [] : []; + }, [serviceName, services]); useEffect(() => { - if (!active) { + if (!serviceName) { router.push('/system'); } - }, [active, router]); + }, [serviceName, router]); useEffect(() => { if (containerRef.current) { diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index fdc7734c..0437ed82 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -19,7 +19,6 @@ type State = { services: ServiceStatusState; loading: boolean; error?: string; - active: string | null; }; const initialState: State = { @@ -29,7 +28,6 @@ const initialState: State = { [ServiceNames.POSTGRES]: { isUp: false, logs: [] }, [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } }, - active: null, loading: true, error: undefined }; @@ -42,9 +40,6 @@ export const serviceSlice = createSlice({ name: 'services', initialState, reducers: { - setActiveService: (state, action: PayloadAction) => { - state.active = action.payload; - }, setLoading: (state, action: PayloadAction) => { state.loading = action.payload; }, From 7c00b64a687f0eaed67da89eb43f074f57d18876 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Fri, 6 Sep 2024 13:47:11 +0000 Subject: [PATCH 41/55] lint fix --- web-server/src/components/Service/SystemStatus.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index ab41d135..d0d47723 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -18,6 +18,7 @@ export const SystemStatus: FC = () => { (state: { service: { services: ServiceStatusState } }) => state.service.services ); + console.log('Status Page render'); useEffect(() => { const eventSource = new EventSource(`/api/stream`); eventSource.onmessage = (event) => { @@ -91,8 +92,8 @@ export const SystemStatus: FC = () => { page: { ui: 'system_logs', title: `${ServiceTitle[serviceName]} Logs`, - props:{ - serviceName:serviceName + props: { + serviceName: serviceName } } }); From 8a2c2bc4564d4156378cf9e835c60188dbc14d0f Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Fri, 6 Sep 2024 20:33:07 +0530 Subject: [PATCH 42/55] fix: type issues --- backend/analytics_server/app.py | 1 + backend/analytics_server/mhq/api/health.py | 10 ++++++ backend/analytics_server/sync_app.py | 1 + web-server/pages/system.tsx | 5 +-- .../src/components/Service/SystemStatus.tsx | 35 +++++++++++-------- web-server/src/constants/stream.ts | 4 +-- web-server/src/content/Service/SystemLogs.tsx | 6 +--- web-server/src/slices/service.ts | 21 +++++------ 8 files changed, 48 insertions(+), 35 deletions(-) create mode 100644 backend/analytics_server/mhq/api/health.py diff --git a/backend/analytics_server/app.py b/backend/analytics_server/app.py index c246a9c8..dd7749e1 100644 --- a/backend/analytics_server/app.py +++ b/backend/analytics_server/app.py @@ -8,6 +8,7 @@ from mhq.store import configure_db_with_app from mhq.api.hello import app as core_api +from mhq.api.health import app as core_api from mhq.api.settings import app as settings_api from mhq.api.pull_requests import app as pull_requests_api from mhq.api.incidents import app as incidents_api diff --git a/backend/analytics_server/mhq/api/health.py b/backend/analytics_server/mhq/api/health.py new file mode 100644 index 00000000..94210758 --- /dev/null +++ b/backend/analytics_server/mhq/api/health.py @@ -0,0 +1,10 @@ +from flask import Blueprint + +app = Blueprint("health", __name__) + +@app.route("/health", methods=["GET"]) +def health_check(): + return { + "message": "server is up and running", + "status": "healthy" + } \ No newline at end of file diff --git a/backend/analytics_server/sync_app.py b/backend/analytics_server/sync_app.py index c6711757..22c139b5 100644 --- a/backend/analytics_server/sync_app.py +++ b/backend/analytics_server/sync_app.py @@ -8,6 +8,7 @@ from mhq.store import configure_db_with_app from mhq.api.hello import app as core_api +from mhq.api.health import app as core_api from mhq.api.sync import app as sync_api SYNC_SERVER_PORT = getenv("SYNC_SERVER_PORT") diff --git a/web-server/pages/system.tsx b/web-server/pages/system.tsx index 78a4286f..86ac7601 100644 --- a/web-server/pages/system.tsx +++ b/web-server/pages/system.tsx @@ -1,4 +1,5 @@ import { Authenticated } from 'src/components/Authenticated'; + import { FlexBox } from '@/components/FlexBox'; import Loader from '@/components/Loader'; import { SystemStatus } from '@/components/Service/SystemStatus'; @@ -28,11 +29,11 @@ function Service() { - System + System logs } hideAllSelectors - pageTitle="System" + pageTitle="System logs" showEvenIfNoTeamSelected={true} isLoading={isLoading} > diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index d0d47723..d7182f50 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,10 +1,11 @@ -import { Box, CircularProgress, Divider } from '@mui/material'; +import { Box, CircularProgress, Divider, useTheme } from '@mui/material'; import { FC, useEffect } from 'react'; +import { alpha } from '@mui/material/styles'; import { ServiceNames } from '@/constants/service'; import { StreamEventType } from '@/constants/stream'; import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; -import { serviceSlice, ServiceStatusState } from '@/slices/service'; +import { serviceSlice } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; import { FlexBox } from '../FlexBox'; @@ -14,13 +15,13 @@ import { Line } from '../Text'; export const SystemStatus: FC = () => { const dispatch = useDispatch(); const loading = useSelector((state) => state.service.loading); - const services = useSelector( - (state: { service: { services: ServiceStatusState } }) => - state.service.services - ); + const services = useSelector((state) => state.service.services); + console.log('Status Page render'); + const theme = useTheme(); useEffect(() => { const eventSource = new EventSource(`/api/stream`); + eventSource.onmessage = (event) => { const data = JSON.parse(event.data); @@ -56,12 +57,13 @@ export const SystemStatus: FC = () => { const { addPage } = useOverlayPage(); - const ServiceTitle: { [key: string]: string } = { + const ServiceTitle: Record = { [ServiceNames.API_SERVER]: 'Backend Server', [ServiceNames.REDIS]: 'Redis Database', [ServiceNames.POSTGRES]: 'Postgres Database', [ServiceNames.SYNC_SERVER]: 'Sync Server' }; + return ( @@ -84,6 +86,7 @@ export const SystemStatus: FC = () => { {Object.keys(services).map((serviceName) => { const serviceKey = serviceName as ServiceNames; const { isUp } = services[serviceKey]; + return ( { addPage({ page: { ui: 'system_logs', - title: `${ServiceTitle[serviceName]} Logs`, - props: { - serviceName: serviceName - } + title: `${ServiceTitle[serviceKey]} Logs`, + props: { serviceName } } }); }} @@ -106,7 +107,9 @@ export const SystemStatus: FC = () => { backgroundColor: 'rgba(255, 255, 255, 0.05)', borderRadius: '12px', border: `1px solid ${ - isUp ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)' + isUp + ? alpha(theme.colors.success.main, 0.3) + : alpha(theme.colors.error.main, 0.3) }`, padding: '16px', cursor: 'pointer', @@ -125,7 +128,7 @@ export const SystemStatus: FC = () => { alignItems: 'center' }} > - {ServiceTitle[serviceName]} + {ServiceTitle[serviceKey]} { width: '10px', height: '10px', borderRadius: '50%', - backgroundColor: isUp ? '#28a745' : '#dc3545' + backgroundColor: isUp + ? theme.colors.success.main + : theme.colors.error.main }} - > + /> diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts index 6505ff55..25787b32 100644 --- a/web-server/src/constants/stream.ts +++ b/web-server/src/constants/stream.ts @@ -5,9 +5,7 @@ type LogFile = { serviceName: ServiceNames; }; -type ServiceStatus = { - [key in ServiceNames]: { isUp: boolean }; -}; +type ServiceStatus = Record; const UPDATE_INTERVAL = 10000; diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 9451d3d6..30be5d01 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -3,17 +3,13 @@ import { useRouter } from 'next/router'; import { useEffect, useRef, useMemo } from 'react'; import { ServiceNames } from '@/constants/service'; -import { ServiceStatusState } from '@/slices/service'; import { useSelector } from '@/store'; export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { const router = useRouter(); const containerRef = useRef(null); - const services = useSelector( - (state: { service: { services: ServiceStatusState } }) => - state.service.services - ); + const services = useSelector((state) => state.service.services); const logs = useMemo(() => { return serviceName ? services[serviceName]?.logs || [] : []; diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 0437ed82..5f9c96e5 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -11,14 +11,12 @@ type Service = { logs: string[]; }; -export type ServiceStatusState = { - [key in ServiceNames]: Service; -}; +export type ServiceStatusState = Record; type State = { services: ServiceStatusState; loading: boolean; - error?: string; + error: string | null; }; const initialState: State = { @@ -29,11 +27,11 @@ const initialState: State = { [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } }, loading: true, - error: undefined + error: null }; type SetStatusPayload = { - statuses: { [key in ServiceNames]: Status }; + statuses: Record; }; export const serviceSlice = createSlice({ @@ -47,11 +45,14 @@ export const serviceSlice = createSlice({ state.loading = false; const { statuses } = action.payload; - for (const [serviceName, { isUp }] of Object.entries(statuses)) { - if (state.services[serviceName as ServiceNames]) { - state.services[serviceName as ServiceNames].isUp = isUp; + Object.entries(statuses).forEach(([serviceNameKey, { isUp }]) => { + const serviceName = serviceNameKey as ServiceNames; + const service = state.services[serviceName]; + + if (service) { + service.isUp = isUp; } - } + }); }, setServiceLogs: ( state, From 4b24b22e9385d7608f20aa86eb92712023b0f855 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Fri, 6 Sep 2024 21:32:42 +0530 Subject: [PATCH 43/55] fix: fix type issues --- backend/analytics_server/mhq/api/hello.py | 1 - web-server/pages/system.tsx | 10 ++-------- web-server/src/components/Service/SystemStatus.tsx | 3 +-- web-server/src/constants/routes.ts | 2 +- .../Sidebar/SidebarMenu/items.tsx | 4 ++-- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/backend/analytics_server/mhq/api/hello.py b/backend/analytics_server/mhq/api/hello.py index 11504aee..5366cd08 100644 --- a/backend/analytics_server/mhq/api/hello.py +++ b/backend/analytics_server/mhq/api/hello.py @@ -2,7 +2,6 @@ app = Blueprint("hello", __name__) - @app.route("/", methods=["GET"]) def hello_world(): diff --git a/web-server/pages/system.tsx b/web-server/pages/system.tsx index 86ac7601..1c1426eb 100644 --- a/web-server/pages/system.tsx +++ b/web-server/pages/system.tsx @@ -7,7 +7,6 @@ import { useRedirectWithSession } from '@/constants/useRoute'; import { PageWrapper } from '@/content/PullRequests/PageWrapper'; import { useAuth } from '@/hooks/useAuth'; import ExtendedSidebarLayout from '@/layouts/ExtendedSidebarLayout'; -import { serviceSlice } from '@/slices/service'; import { useSelector } from '@/store'; import { PageLayout } from '@/types/resources'; @@ -18,12 +17,7 @@ function Service() { integrations: { github: isGithubIntegrated } } = useAuth(); - const initialState = serviceSlice.getInitialState(); - const currentState = useSelector( - (state: { service: { services: any } }) => state.service.services - ); - - const isLoading = currentState === initialState.services; + const loading = useSelector((state) => state.service.loading); return ( {isGithubIntegrated ? : } diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index d7182f50..1fdb0fbb 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -14,11 +14,10 @@ import { Line } from '../Text'; export const SystemStatus: FC = () => { const dispatch = useDispatch(); + const theme = useTheme(); const loading = useSelector((state) => state.service.loading); const services = useSelector((state) => state.service.services); - console.log('Status Page render'); - const theme = useTheme(); useEffect(() => { const eventSource = new EventSource(`/api/stream`); diff --git a/web-server/src/constants/routes.ts b/web-server/src/constants/routes.ts index dac0f165..ada95d7b 100644 --- a/web-server/src/constants/routes.ts +++ b/web-server/src/constants/routes.ts @@ -69,7 +69,7 @@ export const ROUTES = { }, INTEGRATIONS: new RoutePath('INTEGRATIONS'), SETTINGS: new RoutePath('SETTINGS'), - SYSTEM: new RoutePath('SYSTEM') + SYSTEM_LOGS: new RoutePath('SYSTEM_LOGS') }; export const DEFAULT_HOME_ROUTE = ROUTES.DORA_METRICS; diff --git a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx index 139ec6c7..8102d083 100644 --- a/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx +++ b/web-server/src/layouts/ExtendedSidebarLayout/Sidebar/SidebarMenu/items.tsx @@ -68,9 +68,9 @@ const menuItems = (): MenuItems[] => [ link: ROUTES.SETTINGS.PATH }, { - name: 'System', + name: 'System Logs', icon: Dns, - link: ROUTES.SYSTEM.PATH + link: ROUTES.SYSTEM_LOGS.PATH } ] } From 417694df6877674436609cba1a3a146fa24f30a5 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Fri, 6 Sep 2024 21:42:42 +0530 Subject: [PATCH 44/55] feat: add log color --- backend/analytics_server/mhq/api/health.py | 6 +-- backend/analytics_server/mhq/api/hello.py | 3 +- web-server/app/api/stream/route.ts | 2 +- web-server/src/content/Service/SystemLogs.tsx | 39 +++++++++++++++---- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/backend/analytics_server/mhq/api/health.py b/backend/analytics_server/mhq/api/health.py index 94210758..8a09741f 100644 --- a/backend/analytics_server/mhq/api/health.py +++ b/backend/analytics_server/mhq/api/health.py @@ -2,9 +2,7 @@ app = Blueprint("health", __name__) + @app.route("/health", methods=["GET"]) def health_check(): - return { - "message": "server is up and running", - "status": "healthy" - } \ No newline at end of file + return {"status": "healthy"} \ No newline at end of file diff --git a/backend/analytics_server/mhq/api/hello.py b/backend/analytics_server/mhq/api/hello.py index 5366cd08..7485a3df 100644 --- a/backend/analytics_server/mhq/api/hello.py +++ b/backend/analytics_server/mhq/api/hello.py @@ -2,7 +2,8 @@ app = Blueprint("hello", __name__) + @app.route("/", methods=["GET"]) def hello_world(): - return {"message": "hello world"} + return {"message": "hello world"} \ No newline at end of file diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index c5c0fa1c..d502b463 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -176,7 +176,7 @@ export async function GET(request: NextRequest): Promise { let statusTimer: NodeJS.Timeout | null = null; const watchers: FSWatcher[] = []; - const sendEvent = (eventType: string, data: any) => { + const sendEvent = (eventType: StreamEventType, data: any) => { const eventData = JSON.stringify({ type: eventType, ...data }); return encoder.encode(`data: ${eventData}\n\n`); }; diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 30be5d01..b3995983 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -5,6 +5,21 @@ import { useEffect, useRef, useMemo } from 'react'; import { ServiceNames } from '@/constants/service'; import { useSelector } from '@/store'; +const getColorTheme = (serviceName: ServiceNames): [string, string] => { + switch (serviceName) { + case ServiceNames.API_SERVER: + return ['api', '#06d6a0']; + case ServiceNames.SYNC_SERVER: + return ['snc', '#ab34eb']; + case ServiceNames.REDIS: + return ['rdi', '#ef476f']; + case ServiceNames.POSTGRES: + return ['pgs', '#ff70a6']; + default: + return ['', '#000000']; + } +}; + export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { const router = useRouter(); const containerRef = useRef(null); @@ -42,14 +57,22 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { }} > {services && - logs.map((log, index) => ( - - {log} - - ))} + logs.map((log, index) => { + const [code, color] = getColorTheme(serviceName); + return ( + + {log} + + ); + })} ); }; From 67c1e209c9386abec86ae4391ef3b290a649bba8 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Fri, 6 Sep 2024 21:51:53 +0530 Subject: [PATCH 45/55] fix: lint --- backend/analytics_server/mhq/api/health.py | 1 + web-server/src/content/Service/SystemLogs.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/analytics_server/mhq/api/health.py b/backend/analytics_server/mhq/api/health.py index 8a09741f..da83320a 100644 --- a/backend/analytics_server/mhq/api/health.py +++ b/backend/analytics_server/mhq/api/health.py @@ -5,4 +5,5 @@ @app.route("/health", methods=["GET"]) def health_check(): + return {"status": "healthy"} \ No newline at end of file diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index b3995983..0295ae83 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -5,18 +5,18 @@ import { useEffect, useRef, useMemo } from 'react'; import { ServiceNames } from '@/constants/service'; import { useSelector } from '@/store'; -const getColorTheme = (serviceName: ServiceNames): [string, string] => { +const getColorTheme = (serviceName: ServiceNames): string => { switch (serviceName) { case ServiceNames.API_SERVER: - return ['api', '#06d6a0']; + return '#06d6a0'; case ServiceNames.SYNC_SERVER: - return ['snc', '#ab34eb']; + return '#ab34eb'; case ServiceNames.REDIS: - return ['rdi', '#ef476f']; + return '#ef476f'; case ServiceNames.POSTGRES: - return ['pgs', '#ff70a6']; + return '#ff70a6'; default: - return ['', '#000000']; + return '#000000'; } }; @@ -58,7 +58,7 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { > {services && logs.map((log, index) => { - const [code, color] = getColorTheme(serviceName); + const color = getColorTheme(serviceName); return ( Date: Fri, 6 Sep 2024 22:09:04 +0530 Subject: [PATCH 46/55] fix: lint --- backend/analytics_server/mhq/api/health.py | 2 -- backend/analytics_server/mhq/api/hello.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/backend/analytics_server/mhq/api/health.py b/backend/analytics_server/mhq/api/health.py index da83320a..8260558e 100644 --- a/backend/analytics_server/mhq/api/health.py +++ b/backend/analytics_server/mhq/api/health.py @@ -2,8 +2,6 @@ app = Blueprint("health", __name__) - @app.route("/health", methods=["GET"]) def health_check(): - return {"status": "healthy"} \ No newline at end of file diff --git a/backend/analytics_server/mhq/api/hello.py b/backend/analytics_server/mhq/api/hello.py index 7485a3df..0d837f37 100644 --- a/backend/analytics_server/mhq/api/hello.py +++ b/backend/analytics_server/mhq/api/hello.py @@ -2,8 +2,6 @@ app = Blueprint("hello", __name__) - @app.route("/", methods=["GET"]) def hello_world(): - return {"message": "hello world"} \ No newline at end of file From 6ad0b0613e4709c337ec7212aea77620caee1fda Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 02:09:02 +0530 Subject: [PATCH 47/55] fix: check fix --- backend/analytics_server/app.py | 1 - backend/analytics_server/mhq/api/health.py | 7 ------- backend/analytics_server/mhq/api/hello.py | 3 ++- backend/analytics_server/sync_app.py | 1 - web-server/app/api/stream/route.ts | 2 +- web-server/src/components/Service/SystemStatus.tsx | 2 +- 6 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 backend/analytics_server/mhq/api/health.py diff --git a/backend/analytics_server/app.py b/backend/analytics_server/app.py index dd7749e1..c246a9c8 100644 --- a/backend/analytics_server/app.py +++ b/backend/analytics_server/app.py @@ -8,7 +8,6 @@ from mhq.store import configure_db_with_app from mhq.api.hello import app as core_api -from mhq.api.health import app as core_api from mhq.api.settings import app as settings_api from mhq.api.pull_requests import app as pull_requests_api from mhq.api.incidents import app as incidents_api diff --git a/backend/analytics_server/mhq/api/health.py b/backend/analytics_server/mhq/api/health.py deleted file mode 100644 index 8260558e..00000000 --- a/backend/analytics_server/mhq/api/health.py +++ /dev/null @@ -1,7 +0,0 @@ -from flask import Blueprint - -app = Blueprint("health", __name__) - -@app.route("/health", methods=["GET"]) -def health_check(): - return {"status": "healthy"} \ No newline at end of file diff --git a/backend/analytics_server/mhq/api/hello.py b/backend/analytics_server/mhq/api/hello.py index 0d837f37..f17a5d4a 100644 --- a/backend/analytics_server/mhq/api/hello.py +++ b/backend/analytics_server/mhq/api/hello.py @@ -2,6 +2,7 @@ app = Blueprint("hello", __name__) + @app.route("/", methods=["GET"]) def hello_world(): - return {"message": "hello world"} \ No newline at end of file + return {"message": "hello world"} diff --git a/backend/analytics_server/sync_app.py b/backend/analytics_server/sync_app.py index 22c139b5..c6711757 100644 --- a/backend/analytics_server/sync_app.py +++ b/backend/analytics_server/sync_app.py @@ -8,7 +8,6 @@ from mhq.store import configure_db_with_app from mhq.api.hello import app as core_api -from mhq.api.health import app as core_api from mhq.api.sync import app as sync_api SYNC_SERVER_PORT = getenv("SYNC_SERVER_PORT") diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index d502b463..8f3ae22d 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -26,7 +26,7 @@ import { }); stream.on('data', (chunk) => { - if (streamClosed) return; + if (streamClosed) return; const data = { type: 'log-update', serviceName, diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 1fdb0fbb..d9d63a37 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,6 +1,6 @@ import { Box, CircularProgress, Divider, useTheme } from '@mui/material'; -import { FC, useEffect } from 'react'; import { alpha } from '@mui/material/styles'; +import { FC, useEffect } from 'react'; import { ServiceNames } from '@/constants/service'; import { StreamEventType } from '@/constants/stream'; From d66e183c4035e5b69f012aec4f4687bcc05cbf41 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 03:32:10 +0530 Subject: [PATCH 48/55] feat:cli color --- web-server/app/api/stream/route.ts | 222 +----------------- .../pages/{system.tsx => system-logs.tsx} | 0 .../src/components/Service/SystemStatus.tsx | 19 +- web-server/src/content/Service/SystemLogs.tsx | 19 -- web-server/src/slices/service.ts | 7 +- 5 files changed, 21 insertions(+), 246 deletions(-) rename web-server/pages/{system.tsx => system-logs.tsx} (100%) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 8f3ae22d..e16ea636 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -4,7 +4,6 @@ import { createReadStream, FSWatcher, watch } from 'fs'; import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; import { - ServiceStatus, UPDATE_INTERVAL, LogFile, LOG_FILES, @@ -12,92 +11,9 @@ import { FileEvent } from '@/constants/stream'; - let watchers: FSWatcher[] = []; - let lastPositions: { [key: string]: number } = {}; - - const sendFileContent = async (filePath: string, serviceName: string) => { - if (streamClosed) return; // Prevent sending if the stream is closed - console.log(`Sending file content for ${serviceName}`); - - return new Promise((resolve, reject) => { - const stream = createReadStream(filePath, { - start: lastPositions[filePath] || 0, - encoding: 'utf8' - }); - - stream.on('data', (chunk) => { - if (streamClosed) return; - const data = { - type: 'log-update', - serviceName, - content: chunk - }; - - writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); - lastPositions[filePath] = - (lastPositions[filePath] || 0) + Buffer.byteLength(chunk); - }); - - stream.on('end', () => resolve()); - stream.on('error', (error) => reject(error)); - }); - }; - - const startWatchers = () => { - logFiles.forEach(async ({ path, serviceName }) => { - await sendFileContent(path, serviceName); - - const watcher = watch(path, async (eventType) => { - if (eventType === 'change') { - console.log(`File ${path} (${serviceName}) has been changed`); - await sendFileContent(path, serviceName); - } - }); - - watchers.push(watcher); - console.log(`Watcher created for ${path}`); - }); - }; - - const cleanupWatchers = () => { - watchers.forEach((watcher) => watcher.close()); - watchers = []; - }; - - sendStatuses(); - startWatchers(); - - const closeStream = () => { - console.log('Client Disconnected'); - if (!streamClosed) { - streamClosed = true; - writer - .close() - .catch((error) => console.error('Error closing writer:', error)); - if (timeoutId) { - clearTimeout(timeoutId); - } - } - cleanupWatchers(); - }; - - request.signal.onabort = closeStream; - - return new Response(responseStream.readable, { - headers: { - 'Content-Type': 'text/event-stream', - Connection: 'keep-alive', - 'Cache-Control': 'no-cache, no-transform' - } - }); -} - -process.setMaxListeners(20); - -// Utility function to execute shell commands as promises const execPromise = (command: string): Promise => { return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { + exec(command, (error, stdout) => { if (error) { return reject(error); } @@ -144,7 +60,7 @@ const checkServiceStatus = async (serviceName: string): Promise => { } }; -=const getStatus = async (): Promise<{ +const getStatus = async (): Promise<{ [key in ServiceNames]: { isUp: boolean }; }> => { const services = Object.values(ServiceNames); @@ -165,11 +81,7 @@ const checkServiceStatus = async (serviceName: string): Promise => { return statuses; }; -// Stream handling function -export async function GET(request: NextRequest): Promise { - const responseStream = new TransformStream(); - -export async function GET(request: NextRequest): Promise { +export async function GET(): Promise { const encoder = new TextEncoder(); let streamClosed = false; let lastPositions: { [key: string]: number } = {}; @@ -184,6 +96,7 @@ export async function GET(request: NextRequest): Promise { const stream = new ReadableStream({ start(controller) { const pushStatus = async () => { + console.log('push status'); if (streamClosed) return; try { const statuses = await getStatus(); @@ -201,6 +114,8 @@ export async function GET(request: NextRequest): Promise { }; const pushFileContent = async ({ path, serviceName }: LogFile) => { + console.log('push content'); + if (streamClosed) return; try { const fileStream = createReadStream(path, { @@ -240,6 +155,7 @@ export async function GET(request: NextRequest): Promise { startWatchers(); }, cancel() { + console.log('CLOSE '); streamClosed = true; if (statusTimer) { @@ -260,127 +176,3 @@ export async function GET(request: NextRequest): Promise { } }); } - -const execPromise = (command: string): Promise => { - return new Promise((resolve, reject) => { - exec(command, (error, stdout, _stderr) => { - if (error) { - return reject(error); - } - resolve(stdout); - }); - }); -}; - -const checkServiceStatus = async (serviceName: string): Promise => { - try { - switch (serviceName) { - case ServiceNames.API_SERVER: { - const response = await handleRequest(''); - return response.message.includes('hello world'); - } - - case ServiceNames.REDIS: { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - return response.trim().includes('PONG'); - } - - case ServiceNames.POSTGRES: { - const POSTGRES_PORT = process.env.DB_PORT; - const POSTGRES_HOST = process.env.DB_HOST; - const response = await execPromise( - `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` - ); - return response.includes('accepting connections'); - } - - case ServiceNames.SYNC_SERVER: { - const response = await handleSyncServerRequest(''); - return response.message.includes('hello world'); - } - - default: - console.warn(`Service ${serviceName} not recognized.`); - return false; - } - } catch (error) { - console.error(`${serviceName} service is down:`, error); - return false; - } -}; - -const getStatus = async (): Promise<{ - [key in ServiceNames]: { isUp: boolean }; -}> => { - const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; - - await Promise.all( - services.map(async (service) => { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp }; - }) - ); - - return statuses; -}; - -// Stream handling function -export async function GET(request: NextRequest): Promise { - const responseStream = new TransformStream(); - const writer = responseStream.writable.getWriter(); - const encoder = new TextEncoder(); - let timeoutId: NodeJS.Timeout | null = null; - let streamClosed = false; - - // Function to send statuses to the client - const sendStatuses = async () => { - if (streamClosed) return; // Prevent sending if the stream is closed - - try { - console.log('Fetching service statuses...'); - const statuses = await getStatus(); - const statusData = { type: 'status-update', statuses }; - await writer.write( - encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`) - ); - } catch (error) { - console.error('Error sending statuses:', error); - } - - // Schedule the next status update - timeoutId = setTimeout(sendStatuses, 15000); - }; - - // Start the initial status send - sendStatuses(); - - // Function to close the stream and clear timeout - const closeStream = () => { - console.log('CLIENT DISCONNECTED'); - if (!streamClosed) { - streamClosed = true; - writer - .close() - .catch((error) => console.error('Error closing writer:', error)); - if (timeoutId) { - clearTimeout(timeoutId); - } - } - }; - - // Return the response stream - return new Response(responseStream.readable, { - headers: { - 'Content-Type': 'text/event-stream', - Connection: 'keep-alive', - 'Cache-Control': 'no-cache, no-transform' - } - }); -} diff --git a/web-server/pages/system.tsx b/web-server/pages/system-logs.tsx similarity index 100% rename from web-server/pages/system.tsx rename to web-server/pages/system-logs.tsx diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index d9d63a37..ab16e25d 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -56,13 +56,20 @@ export const SystemStatus: FC = () => { const { addPage } = useOverlayPage(); - const ServiceTitle: Record = { + const serviceTitle: Record = { [ServiceNames.API_SERVER]: 'Backend Server', [ServiceNames.REDIS]: 'Redis Database', [ServiceNames.POSTGRES]: 'Postgres Database', [ServiceNames.SYNC_SERVER]: 'Sync Server' }; + const serviceColor: Record = { + [ServiceNames.API_SERVER]: '#06d6a0', + [ServiceNames.REDIS]: '#ef476f', + [ServiceNames.POSTGRES]: '#ff70a6', + [ServiceNames.SYNC_SERVER]: '#ab34eb' + }; + return ( @@ -85,7 +92,7 @@ export const SystemStatus: FC = () => { {Object.keys(services).map((serviceName) => { const serviceKey = serviceName as ServiceNames; const { isUp } = services[serviceKey]; - + const borderColor = serviceColor[serviceKey]; return ( { addPage({ page: { ui: 'system_logs', - title: `${ServiceTitle[serviceKey]} Logs`, + title: `${serviceTitle[serviceKey]} Logs`, props: { serviceName } } }); @@ -106,9 +113,7 @@ export const SystemStatus: FC = () => { backgroundColor: 'rgba(255, 255, 255, 0.05)', borderRadius: '12px', border: `1px solid ${ - isUp - ? alpha(theme.colors.success.main, 0.3) - : alpha(theme.colors.error.main, 0.3) + isUp ? borderColor : alpha(theme.colors.error.main, 0.3) }`, padding: '16px', cursor: 'pointer', @@ -127,7 +132,7 @@ export const SystemStatus: FC = () => { alignItems: 'center' }} > - {ServiceTitle[serviceKey]} + {serviceTitle[serviceKey]} { - switch (serviceName) { - case ServiceNames.API_SERVER: - return '#06d6a0'; - case ServiceNames.SYNC_SERVER: - return '#ab34eb'; - case ServiceNames.REDIS: - return '#ef476f'; - case ServiceNames.POSTGRES: - return '#ff70a6'; - default: - return '#000000'; - } -}; - export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { const router = useRouter(); const containerRef = useRef(null); - const services = useSelector((state) => state.service.services); - const logs = useMemo(() => { return serviceName ? services[serviceName]?.logs || [] : []; }, [serviceName, services]); @@ -58,14 +41,12 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { > {services && logs.map((log, index) => { - const color = getColorTheme(serviceName); return ( diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 5f9c96e5..4d349bbf 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -44,11 +44,8 @@ export const serviceSlice = createSlice({ setStatus: (state, action: PayloadAction) => { state.loading = false; const { statuses } = action.payload; - - Object.entries(statuses).forEach(([serviceNameKey, { isUp }]) => { - const serviceName = serviceNameKey as ServiceNames; - const service = state.services[serviceName]; - + Object.entries(statuses).forEach(([serviceName, { isUp }]) => { + const service = state.services[serviceName as ServiceNames]; if (service) { service.isUp = isUp; } From 7ddeb960ef33cc8282de112ec740fe0d799f3734 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 04:18:12 +0530 Subject: [PATCH 49/55] refactor code --- web-server/app/api/stream/route.ts | 95 +++++++------------ web-server/src/api-helpers/axios.ts | 38 ++++++++ .../src/components/Service/SystemStatus.tsx | 53 +++++------ web-server/src/slices/service.ts | 5 +- 4 files changed, 101 insertions(+), 90 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index e16ea636..5feacea1 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -1,7 +1,10 @@ import { exec } from 'child_process'; import { createReadStream, FSWatcher, watch } from 'fs'; -import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios'; +import { + getServerStatusCode, + getSyncServerStatusCode +} from '@/api-helpers/axios'; import { ServiceNames } from '@/constants/service'; import { UPDATE_INTERVAL, @@ -11,45 +14,33 @@ import { FileEvent } from '@/constants/stream'; -const execPromise = (command: string): Promise => { - return new Promise((resolve, reject) => { +const execPromise = (command: string): Promise => + new Promise((resolve, reject) => { exec(command, (error, stdout) => { - if (error) { - return reject(error); - } - resolve(stdout); + if (error) reject(error); + else resolve(stdout.trim()); }); }); -}; -const checkServiceStatus = async (serviceName: string): Promise => { +const checkServiceStatus = async ( + serviceName: ServiceNames +): Promise => { try { switch (serviceName) { - case ServiceNames.API_SERVER: { - const response = await handleRequest(''); - return response.message.includes('hello world'); - } - - case ServiceNames.REDIS: { - const REDIS_PORT = process.env.REDIS_PORT; - const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`); - return response.trim().includes('PONG'); - } - - case ServiceNames.POSTGRES: { - const POSTGRES_PORT = process.env.DB_PORT; - const POSTGRES_HOST = process.env.DB_HOST; - const response = await execPromise( - `pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}` - ); - return response.includes('accepting connections'); - } - - case ServiceNames.SYNC_SERVER: { - const response = await handleSyncServerRequest(''); - return response.message.includes('hello world'); - } - + case ServiceNames.API_SERVER: + return (await getServerStatusCode('')) === 200; + case ServiceNames.SYNC_SERVER: + return (await getSyncServerStatusCode('')) === 200; + case ServiceNames.REDIS: + return ( + await execPromise(`redis-cli -p ${process.env.REDIS_PORT} ping`) + ).includes('PONG'); + case ServiceNames.POSTGRES: + return ( + await execPromise( + `pg_isready -h ${process.env.DB_HOST} -p ${process.env.DB_PORT}` + ) + ).includes('accepting connections'); default: console.warn(`Service ${serviceName} not recognized.`); return false; @@ -60,21 +51,17 @@ const checkServiceStatus = async (serviceName: string): Promise => { } }; -const getStatus = async (): Promise<{ - [key in ServiceNames]: { isUp: boolean }; -}> => { +const getStatus = async (): Promise< + Record +> => { const services = Object.values(ServiceNames); - const statuses: { [key in ServiceNames]: { isUp: boolean } } = { - [ServiceNames.API_SERVER]: { isUp: false }, - [ServiceNames.REDIS]: { isUp: false }, - [ServiceNames.POSTGRES]: { isUp: false }, - [ServiceNames.SYNC_SERVER]: { isUp: false } - }; + const statuses = Object.fromEntries( + services.map((service) => [service, { isUp: false }]) + ) as Record; await Promise.all( services.map(async (service) => { - const isUp = await checkServiceStatus(service); - statuses[service] = { isUp }; + statuses[service].isUp = await checkServiceStatus(service); }) ); @@ -84,19 +71,16 @@ const getStatus = async (): Promise<{ export async function GET(): Promise { const encoder = new TextEncoder(); let streamClosed = false; - let lastPositions: { [key: string]: number } = {}; + const lastPositions: Record = {}; let statusTimer: NodeJS.Timeout | null = null; const watchers: FSWatcher[] = []; - const sendEvent = (eventType: StreamEventType, data: any) => { - const eventData = JSON.stringify({ type: eventType, ...data }); - return encoder.encode(`data: ${eventData}\n\n`); - }; + const sendEvent = (eventType: StreamEventType, data: unknown): Uint8Array => + encoder.encode(`data: ${JSON.stringify({ type: eventType, ...data })}\n\n`); const stream = new ReadableStream({ start(controller) { const pushStatus = async () => { - console.log('push status'); if (streamClosed) return; try { const statuses = await getStatus(); @@ -114,8 +98,6 @@ export async function GET(): Promise { }; const pushFileContent = async ({ path, serviceName }: LogFile) => { - console.log('push content'); - if (streamClosed) return; try { const fileStream = createReadStream(path, { @@ -155,13 +137,8 @@ export async function GET(): Promise { startWatchers(); }, cancel() { - console.log('CLOSE '); streamClosed = true; - - if (statusTimer) { - clearTimeout(statusTimer); - } - + if (statusTimer) clearTimeout(statusTimer); watchers.forEach((watcher) => watcher.close()); } }); diff --git a/web-server/src/api-helpers/axios.ts b/web-server/src/api-helpers/axios.ts index 52d5b12c..497cc46f 100644 --- a/web-server/src/api-helpers/axios.ts +++ b/web-server/src/api-helpers/axios.ts @@ -90,3 +90,41 @@ export const handleSyncServerRequest = ( }) .then(handleThen) .catch(handleCatch); + +export const getServerStatusCode = async ( + url: string, + params: AxiosRequestConfig = { method: 'get' } +): Promise => { + try { + const response = await internal({ + url, + ...params, + headers: { 'Content-Type': 'application/json' } + }); + return response.status; + } catch (error: any) { + if (error.response) { + return error.response.status; + } + throw error; + } +}; + +export const getSyncServerStatusCode = async ( + url: string, + params: AxiosRequestConfig = { method: 'get' } +): Promise => { + try { + const response = await internalSyncServer({ + url, + ...params, + headers: { 'Content-Type': 'application/json' } + }); + return response.status; + } catch (error: any) { + if (error.response) { + return error.response.status; + } + throw error; + } +}; diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index ab16e25d..b7ec8f41 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -12,6 +12,20 @@ import { FlexBox } from '../FlexBox'; import { useOverlayPage } from '../OverlayPageContext'; import { Line } from '../Text'; +const serviceTitle: Record = { + [ServiceNames.API_SERVER]: 'Backend Server', + [ServiceNames.REDIS]: 'Redis Database', + [ServiceNames.POSTGRES]: 'Postgres Database', + [ServiceNames.SYNC_SERVER]: 'Sync Server' +}; + +const serviceColor: Record = { + [ServiceNames.API_SERVER]: '#06d6a0', + [ServiceNames.REDIS]: '#ef476f', + [ServiceNames.POSTGRES]: '#ff70a6', + [ServiceNames.SYNC_SERVER]: '#ab34eb' +}; + export const SystemStatus: FC = () => { const dispatch = useDispatch(); const theme = useTheme(); @@ -25,10 +39,8 @@ export const SystemStatus: FC = () => { const data = JSON.parse(event.data); if (data.type === StreamEventType.StatusUpdate) { - const statuses = { statuses: data.statuses }; - dispatch(serviceSlice.actions.setStatus(statuses)); + dispatch(serviceSlice.actions.setStatus({ statuses: data.statuses })); } - if (data.type === StreamEventType.LogUpdate) { const { serviceName, content } = data; const newLines = content.split('\n'); @@ -44,10 +56,6 @@ export const SystemStatus: FC = () => { } }; - eventSource.onerror = () => { - eventSource.close(); - }; - return () => { eventSource.close(); dispatch(serviceSlice.actions.resetState()); @@ -56,18 +64,14 @@ export const SystemStatus: FC = () => { const { addPage } = useOverlayPage(); - const serviceTitle: Record = { - [ServiceNames.API_SERVER]: 'Backend Server', - [ServiceNames.REDIS]: 'Redis Database', - [ServiceNames.POSTGRES]: 'Postgres Database', - [ServiceNames.SYNC_SERVER]: 'Sync Server' - }; - - const serviceColor: Record = { - [ServiceNames.API_SERVER]: '#06d6a0', - [ServiceNames.REDIS]: '#ef476f', - [ServiceNames.POSTGRES]: '#ff70a6', - [ServiceNames.SYNC_SERVER]: '#ab34eb' + const handleCardClick = (serviceName: ServiceNames) => { + addPage({ + page: { + ui: 'system_logs', + title: `${serviceTitle[serviceName]} Logs`, + props: { serviceName } + } + }); }; return ( @@ -93,18 +97,11 @@ export const SystemStatus: FC = () => { const serviceKey = serviceName as ServiceNames; const { isUp } = services[serviceKey]; const borderColor = serviceColor[serviceKey]; + return ( { - addPage({ - page: { - ui: 'system_logs', - title: `${serviceTitle[serviceKey]} Logs`, - props: { serviceName } - } - }); - }} + onClick={() => handleCardClick(serviceKey)} sx={{ transition: 'box-shadow 0.2s ease', '&:hover': { diff --git a/web-server/src/slices/service.ts b/web-server/src/slices/service.ts index 4d349bbf..9e9cbbc1 100644 --- a/web-server/src/slices/service.ts +++ b/web-server/src/slices/service.ts @@ -16,7 +16,6 @@ export type ServiceStatusState = Record; type State = { services: ServiceStatusState; loading: boolean; - error: string | null; }; const initialState: State = { @@ -26,8 +25,7 @@ const initialState: State = { [ServiceNames.POSTGRES]: { isUp: false, logs: [] }, [ServiceNames.SYNC_SERVER]: { isUp: false, logs: [] } }, - loading: true, - error: null + loading: true }; type SetStatusPayload = { @@ -44,6 +42,7 @@ export const serviceSlice = createSlice({ setStatus: (state, action: PayloadAction) => { state.loading = false; const { statuses } = action.payload; + Object.entries(statuses).forEach(([serviceName, { isUp }]) => { const service = state.services[serviceName as ServiceNames]; if (service) { From fa30a2aac59eb767873198087a97dbc8bb768d11 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 08:20:56 +0530 Subject: [PATCH 50/55] refactor code --- backend/analytics_server/mhq/api/hello.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/analytics_server/mhq/api/hello.py b/backend/analytics_server/mhq/api/hello.py index f17a5d4a..11504aee 100644 --- a/backend/analytics_server/mhq/api/hello.py +++ b/backend/analytics_server/mhq/api/hello.py @@ -5,4 +5,5 @@ @app.route("/", methods=["GET"]) def hello_world(): + return {"message": "hello world"} From 9f58ebefa412446a34b496c725b0a3ea7105c4d8 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 08:59:19 +0530 Subject: [PATCH 51/55] add eventdata types --- web-server/app/api/stream/route.ts | 8 +++++-- web-server/src/api-helpers/axios.ts | 34 ++++++++++++----------------- web-server/src/constants/stream.ts | 13 ++++++++++- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 5feacea1..013711fb 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -11,7 +11,8 @@ import { LogFile, LOG_FILES, StreamEventType, - FileEvent + FileEvent, + SendEventData } from '@/constants/stream'; const execPromise = (command: string): Promise => @@ -75,7 +76,10 @@ export async function GET(): Promise { let statusTimer: NodeJS.Timeout | null = null; const watchers: FSWatcher[] = []; - const sendEvent = (eventType: StreamEventType, data: unknown): Uint8Array => + const sendEvent = ( + eventType: StreamEventType, + data: SendEventData + ): Uint8Array => encoder.encode(`data: ${JSON.stringify({ type: eventType, ...data })}\n\n`); const stream = new ReadableStream({ diff --git a/web-server/src/api-helpers/axios.ts b/web-server/src/api-helpers/axios.ts index 497cc46f..105d6fd8 100644 --- a/web-server/src/api-helpers/axios.ts +++ b/web-server/src/api-helpers/axios.ts @@ -91,12 +91,15 @@ export const handleSyncServerRequest = ( .then(handleThen) .catch(handleCatch); -export const getServerStatusCode = async ( +const getStatusCode = async ( url: string, - params: AxiosRequestConfig = { method: 'get' } + params: AxiosRequestConfig = { method: 'get' }, + server: 'internal' | 'sync' = 'internal' ): Promise => { + const instance = server === 'internal' ? internal : internalSyncServer; + try { - const response = await internal({ + const response = await instance({ url, ...params, headers: { 'Content-Type': 'application/json' } @@ -110,21 +113,12 @@ export const getServerStatusCode = async ( } }; -export const getSyncServerStatusCode = async ( +export const getServerStatusCode = ( url: string, - params: AxiosRequestConfig = { method: 'get' } -): Promise => { - try { - const response = await internalSyncServer({ - url, - ...params, - headers: { 'Content-Type': 'application/json' } - }); - return response.status; - } catch (error: any) { - if (error.response) { - return error.response.status; - } - throw error; - } -}; + params?: AxiosRequestConfig +) => getStatusCode(url, params, 'internal'); + +export const getSyncServerStatusCode = ( + url: string, + params?: AxiosRequestConfig +) => getStatusCode(url, params, 'sync'); diff --git a/web-server/src/constants/stream.ts b/web-server/src/constants/stream.ts index 25787b32..fb947f3e 100644 --- a/web-server/src/constants/stream.ts +++ b/web-server/src/constants/stream.ts @@ -7,6 +7,17 @@ type LogFile = { type ServiceStatus = Record; +type LogUpdateData = { + serviceName: ServiceNames; + content: string; +}; + +type StatusUpdateData = { + statuses: ServiceStatus; +}; + +type SendEventData = LogUpdateData | StatusUpdateData; + const UPDATE_INTERVAL = 10000; const LOG_FILES: LogFile[] = [ @@ -37,5 +48,5 @@ enum FileEvent { Change = 'change' } -export type { LogFile, ServiceStatus }; +export type { LogFile, ServiceStatus, SendEventData }; export { UPDATE_INTERVAL, LOG_FILES, StreamEventType, FileEvent }; From c737dcefedefa02b5c2b67690d7374f01f319e17 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 13:08:42 +0530 Subject: [PATCH 52/55] use custom components --- web-server/pages/system-logs.tsx | 8 +- .../src/components/Service/SystemStatus.tsx | 95 ++++++++----------- web-server/src/content/Service/SystemLogs.tsx | 65 +++++-------- 3 files changed, 68 insertions(+), 100 deletions(-) diff --git a/web-server/pages/system-logs.tsx b/web-server/pages/system-logs.tsx index 1c1426eb..7fa53333 100644 --- a/web-server/pages/system-logs.tsx +++ b/web-server/pages/system-logs.tsx @@ -1,11 +1,9 @@ import { Authenticated } from 'src/components/Authenticated'; import { FlexBox } from '@/components/FlexBox'; -import Loader from '@/components/Loader'; import { SystemStatus } from '@/components/Service/SystemStatus'; import { useRedirectWithSession } from '@/constants/useRoute'; import { PageWrapper } from '@/content/PullRequests/PageWrapper'; -import { useAuth } from '@/hooks/useAuth'; import ExtendedSidebarLayout from '@/layouts/ExtendedSidebarLayout'; import { useSelector } from '@/store'; import { PageLayout } from '@/types/resources'; @@ -13,10 +11,6 @@ import { PageLayout } from '@/types/resources'; function Service() { useRedirectWithSession(); - const { - integrations: { github: isGithubIntegrated } - } = useAuth(); - const loading = useSelector((state) => state.service.loading); return ( @@ -31,7 +25,7 @@ function Service() { showEvenIfNoTeamSelected={true} isLoading={loading} > - {isGithubIntegrated ? : } +
); } diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index b7ec8f41..9ec0059a 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -1,10 +1,8 @@ -import { Box, CircularProgress, Divider, useTheme } from '@mui/material'; -import { alpha } from '@mui/material/styles'; +import { Button, CircularProgress, Divider, useTheme } from '@mui/material'; import { FC, useEffect } from 'react'; import { ServiceNames } from '@/constants/service'; import { StreamEventType } from '@/constants/stream'; -import { CardRoot } from '@/content/DoraMetrics/DoraCards/sharedComponents'; import { serviceSlice } from '@/slices/service'; import { useDispatch, useSelector } from '@/store'; @@ -75,22 +73,16 @@ export const SystemStatus: FC = () => { }; return ( - - + + System Status - + {loading ? ( - - - + + + ) : ( {Object.keys(services).map((serviceName) => { @@ -99,23 +91,13 @@ export const SystemStatus: FC = () => { const borderColor = serviceColor[serviceKey]; return ( - handleCardClick(serviceKey)} sx={{ - transition: 'box-shadow 0.2s ease', - '&:hover': { - boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)' - }, - backgroundColor: 'rgba(255, 255, 255, 0.05)', - borderRadius: '12px', border: `1px solid ${ - isUp ? borderColor : alpha(theme.colors.error.main, 0.3) - }`, - padding: '16px', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden' + isUp ? borderColor : theme.colors.error.main + }` }} > @@ -123,48 +105,53 @@ export const SystemStatus: FC = () => { {serviceTitle[serviceKey]} - - + + Status: + + - {isUp ? 'Status: Healthy' : 'Status: Not Operational'} + {isUp ? 'Healthy' : 'Not Operational'}{' '} - + ); })} diff --git a/web-server/src/content/Service/SystemLogs.tsx b/web-server/src/content/Service/SystemLogs.tsx index 3fae5f88..ccbe4d38 100644 --- a/web-server/src/content/Service/SystemLogs.tsx +++ b/web-server/src/content/Service/SystemLogs.tsx @@ -1,23 +1,19 @@ -import { Box, Typography } from '@mui/material'; -import { useRouter } from 'next/router'; +import CircularProgress from '@mui/material/CircularProgress'; import { useEffect, useRef, useMemo } from 'react'; +import { FlexBox } from '@/components/FlexBox'; +import { Line } from '@/components/Text'; import { ServiceNames } from '@/constants/service'; import { useSelector } from '@/store'; export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { - const router = useRouter(); - const containerRef = useRef(null); const services = useSelector((state) => state.service.services); + const loading = useSelector((state) => state.service.loading); const logs = useMemo(() => { - return serviceName ? services[serviceName]?.logs || [] : []; + return services[serviceName].logs || []; }, [serviceName, services]); - useEffect(() => { - if (!serviceName) { - router.push('/system'); - } - }, [serviceName, router]); + const containerRef = useRef(null); useEffect(() => { if (containerRef.current) { @@ -26,34 +22,25 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => { }, [logs]); return ( - - {services && - logs.map((log, index) => { - return ( - - {log} - - ); - })} - + + {loading ? ( + + + Loading... + + ) : ( + services && + logs.map((log, index) => ( + + {log} + + )) + )} + ); }; From f7c3ab09619f3810cb4402f974a9655048147c6b Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 13:14:04 +0530 Subject: [PATCH 53/55] use span --- .../src/components/Service/SystemStatus.tsx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index 9ec0059a..a6560229 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -80,7 +80,7 @@ export const SystemStatus: FC = () => { {loading ? ( - + ) : ( @@ -133,20 +133,16 @@ export const SystemStatus: FC = () => { lineHeight={'1.4'} white > - Status: - - - {isUp ? 'Healthy' : 'Not Operational'}{' '} + Status:{' '} + + {isUp ? 'Healthy' : 'Not Operational'} + From 50d8550d084db3781c7207d0a587a8a7585a0838 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sat, 7 Sep 2024 23:40:31 +0530 Subject: [PATCH 54/55] refactor stream backend --- web-server/app/api/stream/route.ts | 204 ++++++++++++++++++----------- 1 file changed, 126 insertions(+), 78 deletions(-) diff --git a/web-server/app/api/stream/route.ts b/web-server/app/api/stream/route.ts index 013711fb..e372365d 100644 --- a/web-server/app/api/stream/route.ts +++ b/web-server/app/api/stream/route.ts @@ -15,135 +15,183 @@ import { SendEventData } from '@/constants/stream'; -const execPromise = (command: string): Promise => - new Promise((resolve, reject) => { +async function executeCommand(command: string): Promise { + return new Promise((resolve, reject) => { exec(command, (error, stdout) => { - if (error) reject(error); - else resolve(stdout.trim()); + if (error) { + reject(error); + } else { + resolve(stdout.trim()); + } }); }); +} -const checkServiceStatus = async ( - serviceName: ServiceNames -): Promise => { +async function isApiServerUp(): Promise { try { - switch (serviceName) { - case ServiceNames.API_SERVER: - return (await getServerStatusCode('')) === 200; - case ServiceNames.SYNC_SERVER: - return (await getSyncServerStatusCode('')) === 200; - case ServiceNames.REDIS: - return ( - await execPromise(`redis-cli -p ${process.env.REDIS_PORT} ping`) - ).includes('PONG'); - case ServiceNames.POSTGRES: - return ( - await execPromise( - `pg_isready -h ${process.env.DB_HOST} -p ${process.env.DB_PORT}` - ) - ).includes('accepting connections'); - default: - console.warn(`Service ${serviceName} not recognized.`); - return false; - } + const statusCode = await getServerStatusCode(''); + return statusCode === 200; + } catch { + return false; + } +} + +async function isSyncServerUp(): Promise { + try { + const statusCode = await getSyncServerStatusCode(''); + return statusCode === 200; + } catch { + return false; + } +} + +async function isRedisUp(): Promise { + try { + const response = await executeCommand( + `redis-cli -p ${process.env.REDIS_PORT} ping` + ); + return response.includes('PONG'); + } catch { + return false; + } +} + +async function isPostgresUp(): Promise { + try { + const response = await executeCommand( + `pg_isready -h ${process.env.DB_HOST} -p ${process.env.DB_PORT}` + ); + return response.includes('accepting connections'); + } catch { + return false; + } +} + +async function checkServiceStatus(serviceName: ServiceNames): Promise { + const statusCheckers = { + [ServiceNames.API_SERVER]: isApiServerUp, + [ServiceNames.SYNC_SERVER]: isSyncServerUp, + [ServiceNames.REDIS]: isRedisUp, + [ServiceNames.POSTGRES]: isPostgresUp + }; + + const checker = statusCheckers[serviceName]; + if (!checker) { + console.warn(`Service ${serviceName} not recognized.`); + return false; + } + + try { + return await checker(); } catch (error) { console.error(`${serviceName} service is down:`, error); return false; } -}; +} -const getStatus = async (): Promise< +async function getAllServicesStatus(): Promise< Record -> => { +> { const services = Object.values(ServiceNames); - const statuses = Object.fromEntries( - services.map((service) => [service, { isUp: false }]) - ) as Record; - - await Promise.all( - services.map(async (service) => { - statuses[service].isUp = await checkServiceStatus(service); - }) - ); + const statusPromises = services.map(async (service) => [ + service, + { isUp: await checkServiceStatus(service) } + ]); - return statuses; -}; + const statuses = Object.fromEntries(await Promise.all(statusPromises)); + return statuses as Record; +} -export async function GET(): Promise { +// Creates an event message for the stream. +function createEventMessage( + eventType: StreamEventType, + data: SendEventData +): Uint8Array { const encoder = new TextEncoder(); - let streamClosed = false; - const lastPositions: Record = {}; - let statusTimer: NodeJS.Timeout | null = null; - const watchers: FSWatcher[] = []; + return encoder.encode( + `data: ${JSON.stringify({ type: eventType, ...data })}\n\n` + ); +} - const sendEvent = ( - eventType: StreamEventType, - data: SendEventData - ): Uint8Array => - encoder.encode(`data: ${JSON.stringify({ type: eventType, ...data })}\n\n`); +export async function GET(): Promise { + let isStreamActive = true; + const filePositions: Record = {}; + let statusUpdateTimer: NodeJS.Timeout | null = null; + const fileWatchers: FSWatcher[] = []; const stream = new ReadableStream({ start(controller) { - const pushStatus = async () => { - if (streamClosed) return; + // Sends status updates periodically. + async function sendStatusUpdates() { + if (!isStreamActive) return; + try { - const statuses = await getStatus(); - if (!streamClosed) { + const statuses = await getAllServicesStatus(); + if (isStreamActive) { controller.enqueue( - sendEvent(StreamEventType.StatusUpdate, { statuses }) + createEventMessage(StreamEventType.StatusUpdate, { statuses }) ); } } catch (error) { console.error('Error sending statuses:', error); } - if (!streamClosed) { - statusTimer = setTimeout(pushStatus, UPDATE_INTERVAL); + + if (isStreamActive) { + statusUpdateTimer = setTimeout(sendStatusUpdates, UPDATE_INTERVAL); } - }; + } + + // Sends log file updates. + async function sendLogUpdates(logFile: LogFile) { + if (!isStreamActive) return; - const pushFileContent = async ({ path, serviceName }: LogFile) => { - if (streamClosed) return; try { + const { path, serviceName } = logFile; const fileStream = createReadStream(path, { - start: lastPositions[path] || 0, + start: filePositions[path] || 0, encoding: 'utf8' }); for await (const chunk of fileStream) { - if (streamClosed) break; + if (!isStreamActive) break; controller.enqueue( - sendEvent(StreamEventType.LogUpdate, { + createEventMessage(StreamEventType.LogUpdate, { serviceName, content: chunk }) ); - lastPositions[path] = - (lastPositions[path] || 0) + Buffer.byteLength(chunk); + filePositions[path] = + (filePositions[path] || 0) + Buffer.byteLength(chunk); } } catch (error) { - console.error(`Error reading log file for ${serviceName}:`, error); + console.error( + `Error reading log file for ${logFile.serviceName}:`, + error + ); } - }; + } - const startWatchers = () => { + // Sets up file watchers for log files. + function setupFileWatchers() { LOG_FILES.forEach((logFile) => { const watcher = watch(logFile.path, async (eventType) => { - if (eventType === FileEvent.Change && !streamClosed) { - await pushFileContent(logFile); + if (eventType === FileEvent.Change && isStreamActive) { + await sendLogUpdates(logFile); } }); - watchers.push(watcher); + fileWatchers.push(watcher); }); - }; + } - pushStatus(); - LOG_FILES.forEach(pushFileContent); - startWatchers(); + // Initialize the stream + sendStatusUpdates(); + LOG_FILES.forEach(sendLogUpdates); + setupFileWatchers(); }, cancel() { - streamClosed = true; - if (statusTimer) clearTimeout(statusTimer); - watchers.forEach((watcher) => watcher.close()); + isStreamActive = false; + if (statusUpdateTimer) clearTimeout(statusUpdateTimer); + fileWatchers.forEach((watcher) => watcher.close()); } }); From a0d1f2456c62c98053f7712a66749bba76cc6e15 Mon Sep 17 00:00:00 2001 From: VipinDevelops Date: Sun, 8 Sep 2024 10:29:14 +0530 Subject: [PATCH 55/55] fix style issues --- .../src/components/Service/SystemStatus.tsx | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/web-server/src/components/Service/SystemStatus.tsx b/web-server/src/components/Service/SystemStatus.tsx index a6560229..beff1cb1 100644 --- a/web-server/src/components/Service/SystemStatus.tsx +++ b/web-server/src/components/Service/SystemStatus.tsx @@ -74,13 +74,12 @@ export const SystemStatus: FC = () => { return ( - + System Status - {loading ? ( - + ) : ( @@ -102,13 +101,7 @@ export const SystemStatus: FC = () => { > - + {serviceTitle[serviceKey]} { - + Status:{' '}