peridot/apollo/ui/src/components/ShowErrata.tsx

344 lines
12 KiB
TypeScript

/*
* Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
* Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
* Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
Heading,
HStack,
Link,
ListItem,
Spinner,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
UnorderedList,
useColorModeValue,
VStack,
} from '@chakra-ui/react';
import {
severityToBadge,
severityToText,
typeToText,
} from 'apollo/ui/src/enumToText';
import {
V1Advisory,
V1AdvisoryType,
} from 'bazel-bin/apollo/proto/v1/client_typescript';
import { reqap } from 'common/ui/reqap';
import React, { useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { Link as RouterLink } from 'react-router-dom';
import { api } from '../api';
import { COLOR_RESF_GREEN } from '../styles';
interface ShowErrataParams {
id: string;
}
export interface ShowErrataProps
extends RouteComponentProps<ShowErrataParams> {}
export const ShowErrata = (props: ShowErrataProps) => {
const id = props.match.params.id;
const cardBg = useColorModeValue('white', 'gray.800');
const sideBg = useColorModeValue('gray.100', 'gray.700');
const linkBlue = useColorModeValue('blue.600', 'blue.300');
const linkPurple = useColorModeValue('purple.600', 'purple.300');
const [errata, setErrata] = useState<V1Advisory>();
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
React.useEffect(() => {
const fetch = async () => {
setIsLoading(true);
const [err, res] = await reqap(() => api.getAdvisory({ id }));
setIsLoading(false);
if (err || !res) {
setIsError(true);
setErrata(undefined);
return;
}
setIsError(false);
setErrata(res.advisory);
};
fetch();
}, [id]);
return (
<Box
w="100%"
h="100%"
display="flex"
flexDirection="column"
p={4}
alignItems="stretch"
maxWidth="1300px"
m="auto"
>
<Breadcrumb mb={4}>
<BreadcrumbItem>
<BreadcrumbLink as={RouterLink} to="/">
Product Errata
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink isCurrentPage>{id}</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
{isLoading ? (
<Spinner
m="auto"
size="xl"
alignSelf="center"
color={COLOR_RESF_GREEN}
thickness="3px"
/>
) : isError ? (
<Alert
status="error"
m="auto"
flexDirection="column"
width="300px"
borderRadius="md"
>
<AlertIcon mr="0" />
<AlertTitle>Something has gone wrong</AlertTitle>
<AlertDescription>Failed to load errata</AlertDescription>
</Alert>
) : (
errata && (
<>
<HStack
alignItems="center"
backgroundColor={cardBg}
py="2"
px="4"
spacing="6"
mb={2}
>
{severityToBadge(errata.severity, errata.type, 40)}
<VStack alignItems="stretch" spacing="0" flexGrow={1}>
<HStack justifyContent="space-between">
<Text fontSize="lg" fontWeight="bold">
{errata.name}
</Text>
</HStack>
<Text fontSize="sm">{errata.synopsis}</Text>
</VStack>
</HStack>
<Tabs backgroundColor={cardBg} p="2">
<TabList>
<Tab>Erratum</Tab>
<Tab>Affected Packages</Tab>
</TabList>
<Box
display="flex"
flexDir="row"
alignItems="stretch"
flexWrap="wrap"
justifyContent="space-between"
>
<TabPanels maxWidth="850px" px="2">
<TabPanel>
<Heading as="h2" size="md">
Topic
</Heading>
{errata.topic?.split('\n').map((p, i) => (
<Text key={i} mt={2}>
{p}
</Text>
))}
<Heading as="h2" size="md" mt={4}>
Description
</Heading>
{errata.description?.split('\n').map((p, i) => (
<Text key={i} mt={2}>
{p}
</Text>
))}
</TabPanel>
<TabPanel>
<VStack alignItems="flex-start" spacing="6">
{Object.keys(errata.rpms || {}).map((product) => (
<div key={product}>
<Heading as="h2" size="lg" mb={4} fontWeight="300">
{product}
</Heading>
<Heading as="h3" size="md" mt={2}>
SRPMs
</Heading>
<UnorderedList pl="4">
{errata.rpms?.[product]?.nvras
?.filter((x) => x.indexOf('.src.rpm') !== -1)
.map((x) => (
<ListItem key={x}>{x}</ListItem>
))}
</UnorderedList>
<Heading as="h3" size="md" mt={2}>
RPMs
</Heading>
<UnorderedList pl="4">
{errata.rpms?.[product]?.nvras
?.filter((x) => x.indexOf('.src.rpm') === -1)
.map((x) => (
<ListItem key={x}>{x}</ListItem>
))}
</UnorderedList>
</div>
))}
</VStack>
</TabPanel>
</TabPanels>
<VStack
py="4"
px="8"
alignItems="flex-start"
minWidth="300px"
spacing="5"
flexShrink={0}
backgroundColor={sideBg}
>
<Text>
<b>Issued:</b> {errata.publishedAt?.toLocaleDateString()}
</Text>
<Text>
<b>Type:</b> {typeToText(errata.type)}
</Text>
{errata.type === V1AdvisoryType.Security && (
<Text>
<b>Severity:</b> {severityToText(errata.severity)}
</Text>
)}
<Box>
<Text fontWeight="bold">
Affected Product
{(errata.affectedProducts?.length || 0) > 1 ? 's' : ''}
</Text>
<UnorderedList>
{errata.affectedProducts?.map((x, idx) => (
<ListItem key={idx}>{x}</ListItem>
))}
</UnorderedList>
</Box>
<Box>
<Text fontWeight="bold">Fixes</Text>
<UnorderedList>
{errata.fixes?.map((x, idx) => (
<ListItem key={idx}>
<Link
href={x.sourceLink}
isExternal
color={linkBlue}
_visited={{
color: linkPurple,
}}
>
{x.sourceBy} - {x.ticket}
</Link>
</ListItem>
))}
</UnorderedList>
</Box>
<Box>
<Text fontWeight="bold">CVEs</Text>
<UnorderedList>
{!!errata.cves?.length ? (
errata.cves?.map((x, idx) => {
let text = `${x.name}${
x.sourceBy !== '' && ` (Source: ${x.sourceBy})`
}`;
return (
<ListItem key={idx}>
{x.sourceLink === '' ? (
<span>{text}</span>
) : (
<Link
href={x.sourceLink}
isExternal
color={linkBlue}
_visited={{
color: linkPurple,
}}
>
{text}
</Link>
)}
</ListItem>
);
})
) : (
<ListItem>No CVEs</ListItem>
)}
</UnorderedList>
</Box>
<Box>
<Text fontWeight="bold">References</Text>
<UnorderedList>
{!!errata.references?.length ? (
errata.references?.map((x, idx) => (
<ListItem key={idx}>{x}</ListItem>
))
) : (
<ListItem>No references</ListItem>
)}
</UnorderedList>
</Box>
</VStack>
</Box>
</Tabs>
</>
)
)}
</Box>
);
};