diff --git a/frontend/src/component/application/ConnectedInstances/ConnectedInstances.tsx b/frontend/src/component/application/ConnectedInstances/ConnectedInstances.tsx index ad9723be64..226a2cb4fd 100644 --- a/frontend/src/component/application/ConnectedInstances/ConnectedInstances.tsx +++ b/frontend/src/component/application/ConnectedInstances/ConnectedInstances.tsx @@ -1,99 +1,38 @@ -import { useMemo } from 'react'; +import { FC, useEffect, useMemo, useState } from 'react'; import useApplication from 'hooks/api/getters/useApplication/useApplication'; -import { WarningAmber } from '@mui/icons-material'; import { formatDateYMDHMS } from 'utils/formatDate'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useConnectedInstancesTable } from './useConnectedInstancesTable'; import { ConnectedInstancesTable } from './ConnectedInstancesTable'; import { IApplication } from 'interfaces/application'; -import { useQueryParam } from 'use-query-params'; -import { styled } from '@mui/material'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Box, ToggleButton, ToggleButtonGroup } from '@mui/material'; +import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview'; +import { useConnectedInstances } from 'hooks/api/getters/useConnectedInstances/useConnectedInstances'; -const Container = styled('div')(({ theme }) => ({ - '* + *': { - marginBlockStart: theme.spacing(2), - }, -})); - -const EnvironmentSelectionContainer = styled('div')(({ theme }) => ({ - label: { - '--padding-horizontal': theme.spacing(3), - '--padding-vertical': theme.spacing(1), - color: theme.palette.primary.main, - background: theme.palette.background, - paddingInline: 'var(--padding-horizontal)', - paddingBlock: 'var(--padding-vertical)', - border: `1px solid ${theme.palette.background.alternative}`, - borderInlineStart: 'none', - fontWeight: 'bold', - position: 'relative', - - svg: { - color: theme.palette.warning.main, - position: 'absolute', - fontSize: theme.fontSizes.bodySize, - top: 'calc(var(--padding-horizontal) * .12)', - right: 'calc(var(--padding-horizontal) * .2)', - }, - }, - 'label:first-of-type': { - borderInlineStart: `1px solid ${theme.palette.background.alternative}`, - borderRadius: `${theme.shape.borderRadiusMedium}px 0 0 ${theme.shape.borderRadiusMedium}px`, - }, - 'label:last-of-type': { - borderRadius: `0 ${theme.shape.borderRadiusMedium}px ${theme.shape.borderRadiusMedium}px 0`, - }, - 'label:has(input:checked)': { - background: theme.palette.background.alternative, - color: theme.palette.primary.contrastText, - - svg: { - color: 'inherit', - }, - }, - 'label:focus-within': { - outline: `2px solid ${theme.palette.background.alternative}`, - outlineOffset: theme.spacing(0.5), - }, - - fieldset: { - border: 'none', - padding: 0, - margin: 0, - }, - legend: { - marginBlockEnd: theme.spacing(3), - }, - - '.visually-hidden': { - border: 0, - clip: 'rect(0 0 0 0)', - height: 'auto', - margin: 0, - overflow: 'hidden', - padding: 0, - position: 'absolute', - width: '1px', - whiteSpace: 'nowrap', - }, -})); - -export const ConnectedInstances = () => { +export const ConnectedInstances: FC = () => { const name = useRequiredPathParam('name'); const { application } = useApplication(name); - const [currentEnvironment, setCurrentEnvironment] = - useQueryParam('environment'); - const availableEnvironments = new Set( - application?.instances.map( - // @ts-expect-error: the type definition here is incomplete. It - // should be updated as part of this project. - (instance) => instance.environment, - ), + const { data: applicationOverview } = useApplicationOverview(name); + + const availableEnvironments = applicationOverview.environments.map( + (env) => env.name, ); - const allEnvironmentsSorted = Array.from(availableEnvironments) - .sort((a, b) => a.localeCompare(b)) - .map((env) => ({ name: env, problemsDetected: false })); + const allEnvironmentsSorted = Array.from(availableEnvironments).sort( + (a, b) => a.localeCompare(b), + ); + const [currentEnvironment, setCurrentEnvironment] = useState( + allEnvironmentsSorted[0], + ); + const { data: connectedInstances } = useConnectedInstances( + name, + currentEnvironment, + ); + + useEffect(() => { + if (!currentEnvironment && availableEnvironments.length > 0) { + setCurrentEnvironment(availableEnvironments[0]); + } + }, [JSON.stringify(availableEnvironments)]); const tableData = useMemo(() => { const map = ({ @@ -123,41 +62,28 @@ export const ConnectedInstances = () => { useConnectedInstancesTable(tableData); return ( - - -
- - Select which environment to display data for. Only - environments that have received traffic for this - application will be shown here. - + + + + Select which environment to display data for. Only + environments that have received traffic for this application + will be shown here. + + { + if (value !== null) { + setCurrentEnvironment(value); + } + }} + > {allEnvironmentsSorted.map((env) => { - return ( - - ); + return {env}; })} -
-
+ + { getTableProps={getTableProps} rows={rows} /> -
+ ); }; diff --git a/frontend/src/component/application/ConnectedInstances/useConnectedInstancesTable.tsx b/frontend/src/component/application/ConnectedInstances/useConnectedInstancesTable.tsx index 234b1d8aa1..751ac8215e 100644 --- a/frontend/src/component/application/ConnectedInstances/useConnectedInstancesTable.tsx +++ b/frontend/src/component/application/ConnectedInstances/useConnectedInstancesTable.tsx @@ -14,7 +14,7 @@ export const useConnectedInstancesTable = ( instanceData: ConnectedInstancesTableData[], ) => { const initialState = useMemo( - () => ({ sortBy: [{ id: 'instanceId' }] }), + () => ({ sortBy: [{ id: 'lastSeen', desc: true }] }), [], ); diff --git a/frontend/src/hooks/api/getters/useConnectedInstances/useConnectedInstances.ts b/frontend/src/hooks/api/getters/useConnectedInstances/useConnectedInstances.ts new file mode 100644 index 0000000000..912797b28a --- /dev/null +++ b/frontend/src/hooks/api/getters/useConnectedInstances/useConnectedInstances.ts @@ -0,0 +1,44 @@ +import { SWRConfiguration } from 'swr'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; + +type ConnectedInstancesSchema = { + instances: { + instanceId: string; + sdk: string; + clientIp: string; + lastSeen: string; + }[]; +}; + +export const useConnectedInstances = ( + application: string, + environment?: string, + options: SWRConfiguration = {}, +) => { + const path = formatApiPath( + `api/admin/metrics/instances/${application}/${environment}`, + ); + const { data, error } = useConditionalSWR( + Boolean(environment), + { instances: [] }, + path, + fetcher, + options, + ); + + return { + data: data || { instances: [] }, + error, + loading: !error && !data, + }; +}; + +const fetcher = async (path: string): Promise => { + const res = await fetch(path).then( + handleErrorResponses('Connected instances'), + ); + const data = await res.json(); + return data; +};