mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: connected instance ui api integration (#6343)
This commit is contained in:
parent
227abd8bba
commit
ae257d5957
@ -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 (
|
||||
<Container>
|
||||
<EnvironmentSelectionContainer>
|
||||
<fieldset>
|
||||
<legend>
|
||||
Select which environment to display data for. Only
|
||||
environments that have received traffic for this
|
||||
application will be shown here.
|
||||
</legend>
|
||||
<Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
Select which environment to display data for. Only
|
||||
environments that have received traffic for this application
|
||||
will be shown here.
|
||||
</Box>
|
||||
<ToggleButtonGroup
|
||||
color='primary'
|
||||
value={currentEnvironment}
|
||||
exclusive
|
||||
onChange={(event, value) => {
|
||||
if (value !== null) {
|
||||
setCurrentEnvironment(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{allEnvironmentsSorted.map((env) => {
|
||||
return (
|
||||
<label key={env.name}>
|
||||
{env.name}
|
||||
|
||||
<ConditionallyRender
|
||||
condition={env.problemsDetected}
|
||||
show={
|
||||
<WarningAmber titleAccess='Problems detected' />
|
||||
}
|
||||
/>
|
||||
<input
|
||||
defaultChecked={
|
||||
currentEnvironment === env.name
|
||||
}
|
||||
className='visually-hidden'
|
||||
type='radio'
|
||||
name='active-environment'
|
||||
onClick={() => {
|
||||
setCurrentEnvironment(env.name);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
return <ToggleButton value={env}>{env}</ToggleButton>;
|
||||
})}
|
||||
</fieldset>
|
||||
</EnvironmentSelectionContainer>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
<ConnectedInstancesTable
|
||||
loading={false}
|
||||
headerGroups={headerGroups}
|
||||
@ -166,6 +92,6 @@ export const ConnectedInstances = () => {
|
||||
getTableProps={getTableProps}
|
||||
rows={rows}
|
||||
/>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ export const useConnectedInstancesTable = (
|
||||
instanceData: ConnectedInstancesTableData[],
|
||||
) => {
|
||||
const initialState = useMemo(
|
||||
() => ({ sortBy: [{ id: 'instanceId' }] }),
|
||||
() => ({ sortBy: [{ id: 'lastSeen', desc: true }] }),
|
||||
[],
|
||||
);
|
||||
|
||||
|
@ -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<ConnectedInstancesSchema>(
|
||||
Boolean(environment),
|
||||
{ instances: [] },
|
||||
path,
|
||||
fetcher,
|
||||
options,
|
||||
);
|
||||
|
||||
return {
|
||||
data: data || { instances: [] },
|
||||
error,
|
||||
loading: !error && !data,
|
||||
};
|
||||
};
|
||||
|
||||
const fetcher = async (path: string): Promise<ConnectedInstancesSchema> => {
|
||||
const res = await fetch(path).then(
|
||||
handleErrorResponses('Connected instances'),
|
||||
);
|
||||
const data = await res.json();
|
||||
return data;
|
||||
};
|
Loading…
Reference in New Issue
Block a user