mirror of https://github.com/artifacthub/hub.git
Display security scanner disabled badge (#1337)
Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
parent
3452ca13d1
commit
e57e554d86
|
|
@ -3294,6 +3294,10 @@ components:
|
|||
name:
|
||||
type: string
|
||||
nullable: false
|
||||
whitelisted:
|
||||
type: boolean
|
||||
nullable: false
|
||||
example: false
|
||||
ts:
|
||||
type: integer
|
||||
nullable: false
|
||||
|
|
@ -3406,10 +3410,6 @@ components:
|
|||
official:
|
||||
type: boolean
|
||||
nullable: false
|
||||
scanner_disabled:
|
||||
type: boolean
|
||||
nullable: false
|
||||
example: false
|
||||
ts:
|
||||
type: integer
|
||||
nullable: false
|
||||
|
|
@ -3446,7 +3446,6 @@ components:
|
|||
- last_tracking_ts
|
||||
- last_scanning_ts
|
||||
- disabled
|
||||
- scanner_disabled
|
||||
properties:
|
||||
digest:
|
||||
type: string
|
||||
|
|
@ -3468,10 +3467,6 @@ components:
|
|||
disabled:
|
||||
type: boolean
|
||||
nullable: false
|
||||
scanner_disabled:
|
||||
type: boolean
|
||||
nullable: false
|
||||
example: false
|
||||
branch:
|
||||
type: string
|
||||
nullable: false
|
||||
|
|
@ -3566,6 +3561,7 @@ components:
|
|||
scanner_disabled:
|
||||
type: boolean
|
||||
nullable: false
|
||||
example: false
|
||||
user_alias:
|
||||
type: string
|
||||
nullable: false
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import OrganizationInfo from './OrganizationInfo';
|
|||
import styles from './PackageInfo.module.css';
|
||||
import RepositoryIconLabel from './RepositoryIconLabel';
|
||||
import RepositoryInfo from './RepositoryInfo';
|
||||
import ScannerDisabledRepositoryBadge from './ScannerDisabledRepositoryBadge';
|
||||
import SecurityRating from './SecutityRating';
|
||||
import SignedBadge from './SignedBadge';
|
||||
import StarBadge from './StarBadge';
|
||||
|
|
@ -225,6 +226,9 @@ const PackageInfo = (props: Props) => {
|
|||
onlyBadge={false}
|
||||
withLink={buildPackageURL(props.package.normalizedName, props.package.repository, props.package.version!)}
|
||||
/>
|
||||
{props.package.repository.scannerDisabled && (
|
||||
<ScannerDisabledRepositoryBadge scannerDisabled={props.package.repository.scannerDisabled} withTooltip />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
import { isUndefined } from 'lodash';
|
||||
import React from 'react';
|
||||
import { FaEyeSlash } from 'react-icons/fa';
|
||||
|
||||
import ElementWithTooltip from './ElementWithTooltip';
|
||||
import Label from './Label';
|
||||
|
||||
interface Props {
|
||||
scannerDisabled: boolean;
|
||||
className?: string;
|
||||
withTooltip?: boolean;
|
||||
}
|
||||
|
||||
const ScannerDisabledRepositoryBadge = (props: Props) => {
|
||||
if (!props.scannerDisabled) return null;
|
||||
return (
|
||||
<Label text="Security scanner disabled" labelStyle="warning" className={props.className} icon={<FaEyeSlash />} />
|
||||
<ElementWithTooltip
|
||||
active
|
||||
className={props.className}
|
||||
element={<Label text="Security scanner disabled" labelStyle="warning" icon={<FaEyeSlash />} />}
|
||||
tooltipMessage="Security scanning of this package has been disabled by the publisher."
|
||||
visibleTooltip={!isUndefined(props.withTooltip) && props.withTooltip}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,3 +45,20 @@
|
|||
font-size: 90%;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.badgesWrapper {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.badgeDecorator {
|
||||
top: -6px;
|
||||
height: 14px;
|
||||
width: 10px;
|
||||
border-left: 1px solid var(--color-black-25);
|
||||
border-bottom: 1px solid var(--color-black-25);
|
||||
}
|
||||
|
||||
.badge {
|
||||
border: 1px solid var(--orange);
|
||||
color: var(--orange);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { GoPackage } from 'react-icons/go';
|
|||
|
||||
import { ContainerImage } from '../../types';
|
||||
import ButtonCopyToClipboard from '../common/ButtonCopyToClipboard';
|
||||
import ElementWithTooltip from '../common/ElementWithTooltip';
|
||||
import SeeAllModal from '../common/SeeAllModal';
|
||||
import SmallTitle from '../common/SmallTitle';
|
||||
import styles from './ContainersImages.module.css';
|
||||
|
|
@ -20,6 +21,16 @@ interface ContainersList {
|
|||
}
|
||||
|
||||
const ContainersImages = (props: Props) => {
|
||||
const getBadge = (): JSX.Element => (
|
||||
<ElementWithTooltip
|
||||
className={styles.tooltipIcon}
|
||||
element={<span className={`badge badge-pill my-1 ${styles.badge}`}>Whitelisted</span>}
|
||||
tooltipMessage="This image has been whitelisted by the publisher and it won’t be scanned for security vulnerabilities."
|
||||
visibleTooltip
|
||||
active
|
||||
/>
|
||||
);
|
||||
|
||||
const getAllContainers = useCallback((): ContainersList | null => {
|
||||
if (isUndefined(props.containers) || isNull(props.containers) || props.containers.length === 0) return null;
|
||||
|
||||
|
|
@ -53,6 +64,14 @@ const ContainersImages = (props: Props) => {
|
|||
</div>
|
||||
{copyBtn}
|
||||
</div>
|
||||
{containerImage.whitelisted && (
|
||||
<div className={`d-flex flex-column mb-1 ${styles.badgesWrapper}`}>
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<div className={`${styles.badgeDecorator} position-relative mx-1`} />
|
||||
{getBadge()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -67,6 +86,7 @@ const ContainersImages = (props: Props) => {
|
|||
{containerImage.name || containerImage.image}
|
||||
</div>
|
||||
{copyBtn}
|
||||
{containerImage.whitelisted && <div className="ml-2 mr-1">{getBadge()}</div>}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ const Details = (props: Props) => {
|
|||
})()}
|
||||
|
||||
<SecurityReport
|
||||
disabledReport={props.package.repository.scannerDisabled}
|
||||
summary={props.package.securityReportSummary}
|
||||
packageId={props.package.packageId}
|
||||
version={props.package.version!}
|
||||
|
|
@ -211,6 +212,7 @@ const Details = (props: Props) => {
|
|||
visibleSecurityReport={props.visibleSecurityReport}
|
||||
searchUrlReferer={props.searchUrlReferer}
|
||||
fromStarredPage={props.fromStarredPage}
|
||||
containers={props.package.containersImages}
|
||||
/>
|
||||
|
||||
<CapabilityLevel capabilityLevel={props.package.capabilities} />
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface Props {
|
|||
visibleSecurityReport: boolean;
|
||||
searchUrlReferer?: SearchFiltersURL;
|
||||
fromStarredPage?: boolean;
|
||||
hasWhitelistedContainers: boolean;
|
||||
}
|
||||
|
||||
const SecurityModal = (props: Props) => {
|
||||
|
|
@ -133,7 +134,11 @@ const SecurityModal = (props: Props) => {
|
|||
>
|
||||
<div className="m-3">
|
||||
<div className="h5 mt-0 text-secondary text-uppercase font-weight-bold pb-2">Summary</div>
|
||||
{props.totalVulnerabilities > 0 && <SummaryTable report={report} />}
|
||||
{props.totalVulnerabilities > 0 && (
|
||||
<>
|
||||
<SummaryTable report={report} hasWhitelistedContainers={props.hasWhitelistedContainers} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<SecuritySummary summary={props.summary} totalVulnerabilities={props.totalVulnerabilities} />
|
||||
|
||||
|
|
|
|||
|
|
@ -26,3 +26,12 @@
|
|||
.badgeItems {
|
||||
background-color: var(--color-black-5);
|
||||
}
|
||||
|
||||
.legend {
|
||||
font-size: 75%;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.disabledBadgeWrapper {
|
||||
margin-top: calc(0.5rem + 4px);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.legend {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.table {
|
||||
font-size: 1rem;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import styles from './SummaryTable.module.css';
|
|||
|
||||
interface Props {
|
||||
report: SecurityReport;
|
||||
hasWhitelistedContainers: boolean;
|
||||
}
|
||||
|
||||
const SummaryTable = (props: Props) => {
|
||||
|
|
@ -65,6 +66,12 @@ const SummaryTable = (props: Props) => {
|
|||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{props.hasWhitelistedContainers && (
|
||||
<div className={`text-muted ${styles.legend}`}>
|
||||
* Some containers images used by this package have been whitelisted by the publisher, which may affect the
|
||||
security rating.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { isEmpty, isNull, isUndefined } from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SearchFiltersURL, SecurityReportSummary, VulnerabilitySeverity } from '../../../types';
|
||||
import { ContainerImage, SearchFiltersURL, SecurityReportSummary, VulnerabilitySeverity } from '../../../types';
|
||||
import { SEVERITY_ORDER, SEVERITY_RATING } from '../../../utils/data';
|
||||
import prettifyNumber from '../../../utils/prettifyNumber';
|
||||
import sumObjectValues from '../../../utils/sumObjectValues';
|
||||
import ScannerDisabledRepositoryBadge from '../../common/ScannerDisabledRepositoryBadge';
|
||||
import SecurityRating from '../../common/SecutityRating';
|
||||
import SmallTitle from '../../common/SmallTitle';
|
||||
import SecurityModal from './Modal';
|
||||
import styles from './SecurityReport.module.css';
|
||||
|
||||
interface Props {
|
||||
disabledReport?: boolean;
|
||||
containers?: ContainerImage[] | null;
|
||||
className?: string;
|
||||
summary?: SecurityReportSummary | null;
|
||||
packageId: string;
|
||||
|
|
@ -22,72 +25,113 @@ interface Props {
|
|||
}
|
||||
|
||||
const SecurityReport = (props: Props) => {
|
||||
if (isNull(props.summary) || isUndefined(props.summary) || isEmpty(props.summary)) return null;
|
||||
const checkIfWhitelistedContainers = useCallback((): boolean => {
|
||||
if (props.containers) {
|
||||
return props.containers.some((container: ContainerImage) => container.whitelisted);
|
||||
}
|
||||
return false;
|
||||
}, [props.containers]);
|
||||
|
||||
const total: number = sumObjectValues(props.summary);
|
||||
const [total, setTotal] = useState(props.summary ? sumObjectValues(props.summary) : 0);
|
||||
const [hasWhitelistedContainers, setHasWhitelistedContainers] = useState<boolean>(checkIfWhitelistedContainers());
|
||||
|
||||
useEffect(() => {
|
||||
setTotal(props.summary ? sumObjectValues(props.summary) : 0);
|
||||
}, [props.summary]);
|
||||
|
||||
useEffect(() => {
|
||||
setHasWhitelistedContainers(checkIfWhitelistedContainers());
|
||||
}, [checkIfWhitelistedContainers, props.containers]);
|
||||
|
||||
if (
|
||||
(isNull(props.summary) || isUndefined(props.summary) || isEmpty(props.summary)) &&
|
||||
isUndefined(props.disabledReport)
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<SmallTitle text="Security Report" />
|
||||
|
||||
<div className="mb-3">
|
||||
{total === 0 ? (
|
||||
<div className="d-flex flex-row align-items-center mb-2">
|
||||
<div>
|
||||
<small>No vulnerabilities found</small>
|
||||
{props.disabledReport ? (
|
||||
<div className={styles.disabledBadgeWrapper}>
|
||||
<ScannerDisabledRepositoryBadge scannerDisabled />
|
||||
<div className={`text-muted mt-2 ${styles.legend}`}>
|
||||
Security scanning of this package has been disabled by the publisher.
|
||||
</div>
|
||||
<SecurityRating summary={props.summary} className="position-relative ml-2" onlyBadge />
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex flex-row align-items-center mb-2">
|
||||
<div>
|
||||
<small>
|
||||
<span className="font-weight-bold mr-1">{prettifyNumber(total, 1)}</span>vulnerabilities found
|
||||
</small>
|
||||
</div>
|
||||
<SecurityRating summary={props.summary} className="position-relative ml-1" onlyBadge />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{total > 0 && (
|
||||
<>
|
||||
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => {
|
||||
if (!props.summary!.hasOwnProperty(severity) || props.summary![severity] === 0) return null;
|
||||
return (
|
||||
<div
|
||||
key={`summary_${severity}`}
|
||||
data-testid="summaryItem"
|
||||
className={`d-flex justify-content-between align-items-center pb-2 pb-md-0 pt-1 ${styles.summary}`}
|
||||
>
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<span
|
||||
data-testid="summaryBadge"
|
||||
className={`badge position-relative mr-2 ${styles.badge}`}
|
||||
style={{ backgroundColor: SEVERITY_RATING[severity]!.color }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
<span className={`text-uppercase ${styles.title}`}>{severity}</span>
|
||||
</div>
|
||||
<span className={`badge badge-pill ${styles.badgeItems}`}>{props.summary![severity]}</span>
|
||||
{total === 0 ? (
|
||||
<div className="d-flex flex-row align-items-center mb-2">
|
||||
<div>
|
||||
<small>No vulnerabilities found</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<SecurityRating summary={props.summary} className="position-relative ml-2" onlyBadge />
|
||||
{hasWhitelistedContainers && <span className="font-weight-bold ml-1">*</span>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex flex-row align-items-center mb-2">
|
||||
<div>
|
||||
<small>
|
||||
<span className="font-weight-bold mr-1">{prettifyNumber(total, 1)}</span>vulnerabilities found
|
||||
</small>
|
||||
</div>
|
||||
<SecurityRating summary={props.summary} className="position-relative ml-1" onlyBadge />
|
||||
{hasWhitelistedContainers && <span className="font-weight-bold ml-1">*</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasWhitelistedContainers && (
|
||||
<div className={`text-muted mb-3 ${styles.legend}`}>
|
||||
* Some containers images used by this package have been whitelisted by the publisher, which may affect
|
||||
the security rating.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{total > 0 && (
|
||||
<>
|
||||
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => {
|
||||
if (!props.summary!.hasOwnProperty(severity) || props.summary![severity] === 0) return null;
|
||||
return (
|
||||
<div
|
||||
key={`summary_${severity}`}
|
||||
data-testid="summaryItem"
|
||||
className={`d-flex justify-content-between align-items-center pb-2 pb-md-0 pt-1 ${styles.summary}`}
|
||||
>
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<span
|
||||
data-testid="summaryBadge"
|
||||
className={`badge position-relative mr-2 ${styles.badge}`}
|
||||
style={{ backgroundColor: SEVERITY_RATING[severity]!.color }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
<span className={`text-uppercase ${styles.title}`}>{severity}</span>
|
||||
</div>
|
||||
<span className={`badge badge-pill ${styles.badgeItems}`}>{props.summary![severity]}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="d-none d-md-block">
|
||||
<SecurityModal
|
||||
summary={props.summary!}
|
||||
totalVulnerabilities={total}
|
||||
packageId={props.packageId}
|
||||
version={props.version}
|
||||
createdAt={props.createdAt}
|
||||
visibleSecurityReport={props.visibleSecurityReport}
|
||||
searchUrlReferer={props.searchUrlReferer}
|
||||
fromStarredPage={props.fromStarredPage}
|
||||
hasWhitelistedContainers={hasWhitelistedContainers}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="d-none d-md-block">
|
||||
<SecurityModal
|
||||
summary={props.summary!}
|
||||
totalVulnerabilities={total}
|
||||
packageId={props.packageId}
|
||||
version={props.version}
|
||||
createdAt={props.createdAt}
|
||||
visibleSecurityReport={props.visibleSecurityReport}
|
||||
searchUrlReferer={props.searchUrlReferer}
|
||||
fromStarredPage={props.fromStarredPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export interface PackageStats {
|
|||
export interface ContainerImage {
|
||||
image: string;
|
||||
name?: string;
|
||||
whitelisted?: boolean;
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
|
|
|
|||
Loading…
Reference in New Issue