/* * 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 { AddIcon, ArrowLeftIcon, ArrowRightIcon, MinusIcon, SearchIcon, } from '@chakra-ui/icons'; import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, ButtonGroup, FormControl, FormLabel, HStack, IconButton, Input, InputGroup, InputLeftElement, Link, Select, Spinner, Stack, Table, TableColumnHeaderProps, TableContainer, Tbody, Td, Text, Th, Thead, Tr, useColorModeValue, } from '@chakra-ui/react'; import { severityToBadge, severityToText, typeToText, } from 'apollo/ui/src/enumToText'; import { ListAdvisoriesFiltersSeverityEnum, ListAdvisoriesFiltersTypeEnum, } from 'bazel-bin/apollo/proto/v1/client_typescript'; import { V1Advisory, V1AdvisoryType, } from 'bazel-bin/apollo/proto/v1/client_typescript/models'; import { reqap } from 'common/ui/reqap'; import React, { useEffect, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { api } from '../api'; import { COLOR_RESF_GREEN } from '../styles'; export const Overview = () => { const inputBackground = useColorModeValue('white', undefined); const tableBg = useColorModeValue('white', 'gray.800'); const pagerButtonScheme = useColorModeValue('blackAlpha', 'gray'); const linkBlue = useColorModeValue('blue.600', 'blue.300'); const linkPurple = useColorModeValue('purple.600', 'purple.300'); const [advisories, setAdvisories] = useState(); const [lastUpdated, setLastUpdated] = useState(); const [total, setTotal] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); // Request State const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(25); const [filtersKeyword, setFiltersKeyword] = useState(); const [filterBefore, setFilterBefore] = useState(); const [filterAfter, setFilterAfter] = useState(); const [filterProduct, setFilterProduct] = useState(''); const [filtersType, setFiltersType] = useState(); const [filtersSeverity, setFiltersSeverity] = useState(); useEffect(() => { const fetch = async () => { setIsLoading(true); const [err, res] = await reqap(() => api.listAdvisories({ page, limit: pageSize, filtersKeyword, filtersFetchRelated: false, filtersBefore: filterBefore, filtersAfter: filterAfter, filtersProduct: filterProduct, filtersSeverity: filtersSeverity ? ListAdvisoriesFiltersSeverityEnum[filtersSeverity] : undefined, filtersType: filtersType ? ListAdvisoriesFiltersTypeEnum[filtersType] : undefined, }) ); setIsLoading(false); if (err || !res) { setIsError(true); setAdvisories(undefined); return; } setIsError(false); if (res) { setAdvisories(res.advisories); setLastUpdated(res.lastUpdated); setTotal(parseInt(res.total || '0')); } }; const timer = setTimeout(() => fetch(), 500); return () => clearTimeout(timer); }, [ pageSize, page, filtersKeyword, filterBefore, filterAfter, filtersSeverity, filterProduct, filtersType, ]); // TODO: Figure out why sticky isn't sticking const stickyProps: TableColumnHeaderProps = { position: 'sticky', top: '0px', zIndex: '10', scope: 'col', }; const lastPage = total < pageSize ? 0 : Math.ceil(total / pageSize) - 1; return ( setFiltersKeyword(e.target.value)} /> Type {filtersType === 'Security' && ( Severity )} Product From { const newVal = e.currentTarget.value; console.log(newVal); if (!newVal) { setFilterAfter(undefined); } const asDate = new Date(newVal); if (!(asDate instanceof Date) || isNaN(asDate.getTime())) { // Check value parses as a date return; } const [year, month, date] = newVal.split('-').map(Number); setFilterAfter(new Date(year, month - 1, date)); }} /> To { const newVal = e.currentTarget.value; if (!newVal) { setFilterBefore(undefined); } const asDate = new Date(newVal); if (!(asDate instanceof Date) || isNaN(asDate.getTime())) { // Check value parses as a date return; } const [year, month, date] = newVal.split('-').map(Number); setFilterBefore( new Date(year, month - 1, date, 23, 59, 59, 59) // Set to 1ms prior to midnight to be inclusive of selected date ); }} /> Last updated {lastUpdated?.toLocaleString() || 'never'} Displaying {(page * pageSize + 1).toLocaleString()}- {Math.min(total, page * pageSize + pageSize).toLocaleString()} of{' '} {total.toLocaleString()} } disabled={page <= 0} onClick={() => setPage(0)} /> } disabled={page <= 0} onClick={() => setPage((old) => old - 1)} /> {(page + 1).toLocaleString()} / {(lastPage + 1).toLocaleString()} } disabled={page >= lastPage} onClick={() => setPage((old) => old + 1)} /> } disabled={page >= lastPage} onClick={() => setPage(lastPage)} /> {isLoading ? ( ) : isError ? ( Something has gone wrong Failed to load errata ) : ( {!advisories?.length && ( )} {advisories?.map((a, idx) => ( ))}
Advisory Synopsis Type / Severity Products Issue Date
No rows found
{severityToBadge(a.severity, a.type)} {a.name} {a.synopsis?.replace( /^(Critical|Important|Moderate|Low): /, '' )} {typeToText(a.type)} {a.type === V1AdvisoryType.Security ? ` / ${severityToText(a.severity)}` : ''} {a.affectedProducts?.join(', ')} {Intl.DateTimeFormat(undefined, { day: '2-digit', month: 'short', year: 'numeric', }).format(a.publishedAt)}
)} Rows per page:
); };