diff --git a/ui/rspack.config.ts b/ui/rspack.config.ts index 12d45721a7..844bec4210 100644 --- a/ui/rspack.config.ts +++ b/ui/rspack.config.ts @@ -123,6 +123,7 @@ const config: Configuration = { './platformLibrary': './src/services/platformlibrary/k8s.ts', './AlertsNavbarUpdater': './src/components/AlertNavbarUpdaterComponent.tsx', './Metalk8sLocalVolumeProvider': './src/services/k8s/Metalk8sLocalVolumeProvider.ts', + './PlatformGlobalHealthBarFederated': './src/components/PlatformGlobalHealthBarFederated.tsx', }, remotes: !isProduction ? { diff --git a/ui/src/components/DashboardGlobalHealth.tsx b/ui/src/components/DashboardGlobalHealth.tsx index 59793ee21e..117088f82a 100644 --- a/ui/src/components/DashboardGlobalHealth.tsx +++ b/ui/src/components/DashboardGlobalHealth.tsx @@ -1,59 +1,32 @@ import styled from 'styled-components'; -import DashboardAlerts from './DashboardAlerts'; -import { Box, useMetricsTimeSpan } from '@scality/core-ui/dist/next'; -import { - EmphaseText, - LargerText, - SmallerText, - StatusWrapper, - Loader, - AppContainer, - spacing, - Stack, - IconHelp, -} from '@scality/core-ui'; -import { Alert, GlobalHealthBar as GlobalHealthBarRecharts } from '@scality/core-ui/dist/next'; +import { AppContainer, LargerText, Stack, StatusWrapper } from '@scality/core-ui'; +import { Box } from '@scality/core-ui/dist/next'; import { highestAlertToStatus, useAlertLibrary, useHighestSeverityAlerts } from '../containers/AlertProvider'; import { useIntl } from 'react-intl'; -import { useStartingTimeStamp } from '../containers/StartTimeProvider'; -import CircleStatus from './CircleStatus'; +import DashboardAlerts from './DashboardAlerts'; +import PlatformGlobalHealthBar from './PlatformGlobalHealthBar'; import StatusIcon from './StatusIcon'; -import { getClusterAlertSegmentQuery } from '../services/platformlibrary/metrics'; - -import { useQuery } from 'react-query'; -const HealthBarContainer = styled.div` - flex-direction: column; - width: 90%; - margin: 0 auto; -`; const PlatformStatusIcon = styled.div` margin: 0 1rem; font-size: 2rem; `; -const StyledEmphaseText = styled(EmphaseText)` - letter-spacing: ${spacing.r2}; -`; - const DashboardGlobalHealth = () => { const intl = useIntl(); - const { startingTimeISO, currentTimeISO } = useStartingTimeStamp(); const alertsLibrary = useAlertLibrary(); - const { duration } = useMetricsTimeSpan(); - const { data: alerts, status: historyAlertStatus } = useQuery(getClusterAlertSegmentQuery(duration)); const platformHighestSeverityAlert = useHighestSeverityAlerts(alertsLibrary.getPlatformAlertSelectors()); const platformStatus = highestAlertToStatus(platformHighestSeverityAlert); + return ( - + - {intl.formatMessage({ id: 'platform', @@ -61,58 +34,7 @@ const DashboardGlobalHealth = () => { - - - Global Health - - - {intl - .formatMessage({ - id: 'global_health_explanation', - }) - .split('\n') - .map((line, key) => ( - {line} - ))} - - } - /> - - - - {historyAlertStatus === 'loading' ? ( - - - - ) : ( - - )} - + diff --git a/ui/src/components/PlatformGlobalHealthBar.tsx b/ui/src/components/PlatformGlobalHealthBar.tsx new file mode 100644 index 0000000000..c0896fa3d9 --- /dev/null +++ b/ui/src/components/PlatformGlobalHealthBar.tsx @@ -0,0 +1,83 @@ +import { IconHelp, Loader, SmallerText, Stack, Text } from '@scality/core-ui'; +import { Alert, Box, GlobalHealthBar as GlobalHealthBarRecharts, useMetricsTimeSpan } from '@scality/core-ui/dist/next'; +import { useIntl } from 'react-intl'; +import { useQuery } from 'react-query'; +import styled from 'styled-components'; +import { highestAlertToStatus, useAlertLibrary, useHighestSeverityAlerts } from '../containers/AlertProvider'; +import { useStartingTimeStamp } from '../containers/StartTimeProvider'; +import { getClusterAlertSegmentQuery } from '../services/platformlibrary/metrics'; +import CircleStatus from './CircleStatus'; + +const HealthBarContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const PlatformGlobalHealthBar = ({ title = 'Global Health' }: { title?: string }) => { + const intl = useIntl(); + const { startingTimeISO, currentTimeISO } = useStartingTimeStamp(); + const alertsLibrary = useAlertLibrary(); + const { duration } = useMetricsTimeSpan(); + const { data: alerts, status: historyAlertStatus } = useQuery({ + ...getClusterAlertSegmentQuery(duration), + keepPreviousData: true, + }); + const platformHighestSeverityAlert = useHighestSeverityAlerts(alertsLibrary.getPlatformAlertSelectors()); + const platformStatus = highestAlertToStatus(platformHighestSeverityAlert); + + return ( + + + + {title} + + {intl + .formatMessage({ + id: 'global_health_explanation', + }) + .split('\n') + .map((line, key) => ( + {line} + ))} + + } + /> + + + {historyAlertStatus === 'loading' ? ( + + + + ) : ( + + )} + + ); +}; + +export default PlatformGlobalHealthBar; diff --git a/ui/src/components/PlatformGlobalHealthBarFederated.tsx b/ui/src/components/PlatformGlobalHealthBarFederated.tsx new file mode 100644 index 0000000000..c49923d3c1 --- /dev/null +++ b/ui/src/components/PlatformGlobalHealthBarFederated.tsx @@ -0,0 +1,66 @@ +import { MetricsTimeSpanContext } from '@scality/core-ui/dist/components/charts/MetricsTimeSpanProvider'; +import { useShellHooks } from '@scality/module-federation'; +import { useLayoutEffect, useMemo } from 'react'; +import FederatedIntlProvider from '../containers/IntlProvider'; +import StartTimeProvider from '../containers/StartTimeProvider'; +import { initialize as initializePrometheus, setHeaders } from '../services/prometheus/api'; +import PlatformGlobalHealthBar from './PlatformGlobalHealthBar'; + +// 7-day defaults — same constants as core-ui's SAMPLE_DURATION/FREQUENCY_LAST_SEVEN_DAYS +const DEFAULT_DURATION_SECONDS = 7 * 24 * 60 * 60; +const DEFAULT_FREQUENCY_SECONDS = 60 * 60; + +type Props = { + prometheusUrl: string; + title?: string; + durationSeconds?: number; + frequencySeconds?: number; +}; + +export default function PlatformGlobalHealthBarFederated({ + prometheusUrl, + title, + durationSeconds = DEFAULT_DURATION_SECONDS, + frequencySeconds = DEFAULT_FREQUENCY_SECONDS, +}: Props) { + const { useAuth } = useShellHooks(); + const { userData } = useAuth(); + const token = userData?.token; + + + /** + * Initialize the Prometheus client and set the authorization header if the token is available + * The initialization of Prometheus client is needed here as it is shared with Module Federation to another ui + * The prometheus client could not be initialized in the parent component rendering it. + */ + useLayoutEffect(() => { + if (token) { + initializePrometheus(prometheusUrl); + setHeaders({ Authorization: `Bearer ${token}` }); + } + }, [prometheusUrl, token]); + + const timeSpan = useMemo( + () => ({ + query: '', + label: '', + duration: durationSeconds, + interval: frequencySeconds, + //TODO: remove this field when QueryTimeSpan type is updated + frequency: frequencySeconds, // required by QueryTimeSpan type (deprecated but not optional) + }), + [durationSeconds, frequencySeconds], + ); + + if (!token) return null; + + return ( + + + + + + + + ); +} diff --git a/ui/src/containers/StartTimeProvider.tsx b/ui/src/containers/StartTimeProvider.tsx index b2489a20b2..d04472a49a 100644 --- a/ui/src/containers/StartTimeProvider.tsx +++ b/ui/src/containers/StartTimeProvider.tsx @@ -1,8 +1,8 @@ -import React, { createContext, useContext, useCallback, useState } from 'react'; import { useMetricsTimeSpan } from '@scality/core-ui/dist/next'; +import type React from 'react'; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { REFRESH_METRICS_GRAPH } from '../constants'; -import { useEffect } from 'react'; -import { useMemo } from 'react'; + type StartTimeContextType = { startingTimeISO: string; currentTimeISO: string; @@ -22,23 +22,23 @@ export const useStartingTimeStamp = (): { }; const StartTimeProvider = ({ children }: { children: React.ReactNode }) => { - const { duration, frequency } = useMetricsTimeSpan(); + const { duration, interval } = useMetricsTimeSpan(); const [currentTime, setCurrentTime] = useState(() => { - const newCurrentDate = new Date().getTime(); - return newCurrentDate - (newCurrentDate % (frequency * 1000)); + const newCurrentDate = Date.now(); + return newCurrentDate - (newCurrentDate % (interval * 1000)); }); const [startingTimeISO, setStartingTimeISO] = useState( new Date((currentTime / 1000 - duration) * 1000).toISOString(), ); const updateCurrentTime = useCallback(() => { - const newCurrentDate = new Date().getTime(); + const newCurrentDate = Date.now(); //In order to always display the same data on the charts over refresh and new entroes comming //we round start and end time to frequency factors. Hence for example for a 30 seconds fequency //we will rount current time and start time to the previous 30 seconds factor (00, or 30 seconds for each minute) - const newCurrentTime = newCurrentDate - (newCurrentDate % (frequency * 1000)); + const newCurrentTime = newCurrentDate - (newCurrentDate % (interval * 1000)); setCurrentTime(newCurrentTime); setStartingTimeISO(new Date((newCurrentTime / 1000 - duration) * 1000).toISOString()); - }, [duration, frequency]); + }, [duration, interval]); useMemo(() => { updateCurrentTime(); }, [updateCurrentTime]);