Add teams features to Vuln details page (#16761)
## Addresses #16758 - Add Teams functionality to the Vulnerability details page - Premium only - All links from page include team_id query param - To hosts page - To OS details pages - To SW version details pages - API call includes `team_id` query param - Teams header - If user only has access to one team, just shows the teamname (no dropdown) - Otherwise, Dropdown with team options - Includes "All teams," but not "No teams" - Add empty state when ~both vulnerable OS versions and SW versions are empty~ affected hosts === 0 <img width="1657" alt="Screenshot 2024-02-12 at 4 47 36 PM" src="https://github.com/fleetdm/fleet/assets/61553566/4f0bb856-57c4-4905-b82b-b00dac6a8306"> <img width="1552" alt="Screenshot 2024-02-12 at 4 45 42 PM" src="https://github.com/fleetdm/fleet/assets/61553566/45d6bbb5-0b31-46df-a715-7fa176029a1d"> <img width="1552" alt="Screenshot 2024-02-12 at 4 45 47 PM" src="https://github.com/fleetdm/fleet/assets/61553566/de6c68a6-aaa4-4637-998c-569b4d189433"> --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
committed by
mostlikelee
co-authored by
Jacob Shandling
parent
4f32ba3235
commit
f0c4dd854b
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
|
||||
import { ITeamSummary } from "interfaces/team";
|
||||
|
||||
import TeamsDropdown from "components/TeamsDropdown";
|
||||
|
||||
interface ITeamsHeader {
|
||||
isOnGlobalTeam?: boolean;
|
||||
currentTeamId?: number;
|
||||
userTeams?: ITeamSummary[];
|
||||
isSandboxMode?: boolean;
|
||||
onTeamChange: (teamId: number) => void;
|
||||
}
|
||||
|
||||
const TeamsHeader = ({
|
||||
isOnGlobalTeam,
|
||||
currentTeamId,
|
||||
userTeams = [],
|
||||
isSandboxMode = false,
|
||||
onTeamChange,
|
||||
}: ITeamsHeader) => {
|
||||
if (userTeams) {
|
||||
if (userTeams.length > 1 || isOnGlobalTeam) {
|
||||
return (
|
||||
<TeamsDropdown
|
||||
currentUserTeams={userTeams}
|
||||
selectedTeamId={currentTeamId}
|
||||
onChange={onTeamChange}
|
||||
isSandboxMode={isSandboxMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (userTeams.length === 1 && !isOnGlobalTeam) {
|
||||
return <h1>{userTeams[0].name}</h1>;
|
||||
}
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default TeamsHeader;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./TeamsHeader";
|
||||
@@ -17,6 +17,8 @@ interface IHostLinkProps {
|
||||
customText?: string;
|
||||
/** Table links shows on row hover only */
|
||||
rowHover?: boolean;
|
||||
// don't actually create a link, useful when click is handled by an ancestor
|
||||
noLink?: boolean;
|
||||
}
|
||||
|
||||
const baseClass = "view-all-hosts-link";
|
||||
@@ -29,6 +31,7 @@ const ViewAllHostsLink = ({
|
||||
responsive = false,
|
||||
customText,
|
||||
rowHover = false,
|
||||
noLink = false,
|
||||
}: IHostLinkProps): JSX.Element => {
|
||||
const viewAllHostsLinkClass = classnames(baseClass, className, {
|
||||
"row-hover-link": rowHover,
|
||||
@@ -43,7 +46,11 @@ const ViewAllHostsLink = ({
|
||||
: endpoint;
|
||||
|
||||
return (
|
||||
<Link className={viewAllHostsLinkClass} to={path} title="host-link">
|
||||
<Link
|
||||
className={viewAllHostsLinkClass}
|
||||
to={noLink ? "" : path}
|
||||
title="host-link"
|
||||
>
|
||||
{!condensed && (
|
||||
<span
|
||||
className={`${baseClass}__text${responsive ? "--responsive" : ""}`}
|
||||
|
||||
@@ -327,6 +327,7 @@ export const useTeamIdParam = ({
|
||||
}, [contextTeam?.id, currentTeam, isRouteOk, setContextTeam]);
|
||||
|
||||
return {
|
||||
// essentially `currentTeamIdForAppContext`, where -1: all teams, 0: no team, and positive integers: team ids
|
||||
currentTeamId: currentTeam?.id,
|
||||
currentTeamName: currentTeam?.name,
|
||||
currentTeamSummary: currentTeam,
|
||||
@@ -347,7 +348,7 @@ export const useTeamIdParam = ({
|
||||
!!currentTeam?.id &&
|
||||
!!currentUser &&
|
||||
permissions.isObserverPlus(currentUser, currentTeam.id),
|
||||
teamIdForApi: getTeamIdForApi({ currentTeam, includeNoTeam }),
|
||||
teamIdForApi: getTeamIdForApi({ currentTeam, includeNoTeam }), // for everywhere except AppContext
|
||||
userTeams,
|
||||
handleTeamChange,
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ import { buildQueryStringFromParams } from "utilities/url";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import MainContent from "components/MainContent";
|
||||
import TeamsDropdown from "components/TeamsDropdown";
|
||||
import TeamsHeader from "components/TeamsHeader";
|
||||
import TabsWrapper from "components/TabsWrapper";
|
||||
|
||||
import ManageAutomationsModal from "./components/ManageSoftwareAutomationsModal";
|
||||
@@ -280,20 +280,15 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
|
||||
return (
|
||||
<>
|
||||
{isFreeTier && <h1>Software</h1>}
|
||||
{isPremiumTier &&
|
||||
userTeams &&
|
||||
(userTeams.length > 1 || isOnGlobalTeam) && (
|
||||
<TeamsDropdown
|
||||
currentUserTeams={userTeams || []}
|
||||
selectedTeamId={currentTeamId}
|
||||
onChange={onTeamChange}
|
||||
isSandboxMode={isSandboxMode}
|
||||
/>
|
||||
)}
|
||||
{isPremiumTier &&
|
||||
!isOnGlobalTeam &&
|
||||
userTeams &&
|
||||
userTeams.length === 1 && <h1>{userTeams[0].name}</h1>}
|
||||
{isPremiumTier && (
|
||||
<TeamsHeader
|
||||
isOnGlobalTeam={isOnGlobalTeam}
|
||||
currentTeamId={currentTeamId}
|
||||
userTeams={userTeams}
|
||||
onTeamChange={onTeamChange}
|
||||
isSandboxMode={isSandboxMode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-5
@@ -18,16 +18,19 @@ interface ISoftwareVulnOSVersions {
|
||||
osVersions: IVulnerability["os_versions"];
|
||||
isPremiumTier: boolean;
|
||||
router: InjectedRouter;
|
||||
teamIdForApi?: number;
|
||||
}
|
||||
|
||||
const SoftwareVulnOSVersions = ({
|
||||
osVersions,
|
||||
isPremiumTier,
|
||||
router,
|
||||
teamIdForApi,
|
||||
}: ISoftwareVulnOSVersions) => {
|
||||
const columnConfigs = useMemo(() => generateColumnConfigs(isPremiumTier), [
|
||||
isPremiumTier,
|
||||
]);
|
||||
const columnConfigs = useMemo(
|
||||
() => generateColumnConfigs(isPremiumTier, router, teamIdForApi),
|
||||
[isPremiumTier, router, teamIdForApi]
|
||||
);
|
||||
|
||||
const onSelectSingleRow = useCallback(
|
||||
({ original: { os_version_id } }) => {
|
||||
@@ -36,10 +39,13 @@ const SoftwareVulnOSVersions = ({
|
||||
}
|
||||
|
||||
router.push(
|
||||
`${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({ os_version_id })}`
|
||||
`${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
os_version_id,
|
||||
team_id: teamIdForApi,
|
||||
})}`
|
||||
);
|
||||
},
|
||||
[router]
|
||||
[teamIdForApi, router]
|
||||
);
|
||||
|
||||
const renderVulnerableOSTable = () => {
|
||||
|
||||
+20
-2
@@ -10,6 +10,7 @@ import PATHS from "router/paths";
|
||||
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
interface ICellProps {
|
||||
row: {
|
||||
@@ -23,7 +24,11 @@ interface INumberCellProps extends ICellProps {
|
||||
};
|
||||
}
|
||||
|
||||
const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
const generateColumnConfigs = (
|
||||
isPremiumTier: boolean,
|
||||
router: InjectedRouter,
|
||||
teamIdForApi?: number
|
||||
): Column[] => {
|
||||
const configs = [
|
||||
{
|
||||
Header: "Name",
|
||||
@@ -31,9 +36,21 @@ const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
accessor: "name_only",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
const { name, os_version_id, platform } = row.original;
|
||||
const endpoint = PATHS.SOFTWARE_OS_DETAILS(os_version_id);
|
||||
// since No Teams not supported on this page, falsiness of 0 is okay
|
||||
const path = teamIdForApi
|
||||
? `${endpoint}?team_id=${teamIdForApi}`
|
||||
: endpoint;
|
||||
const onClickVulnOs = (e: React.MouseEvent) => {
|
||||
// Allows for button to be clickable in a clickable row
|
||||
e.stopPropagation();
|
||||
|
||||
router?.push(path);
|
||||
};
|
||||
return (
|
||||
<LinkCell
|
||||
path={PATHS.SOFTWARE_OS_DETAILS(os_version_id)}
|
||||
path={path}
|
||||
customOnClick={onClickVulnOs}
|
||||
value={
|
||||
<>
|
||||
<SoftwareIcon name={platform} />
|
||||
@@ -73,6 +90,7 @@ const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
queryParams={{ os_version_id }}
|
||||
responsive
|
||||
rowHover
|
||||
noLink
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+8
-4
@@ -20,16 +20,19 @@ interface ISoftwareVulnSoftwareVersions {
|
||||
vulnSoftware: IVulnerability["software"];
|
||||
isPremiumTier: boolean;
|
||||
router: InjectedRouter;
|
||||
teamIdForApi?: number;
|
||||
}
|
||||
|
||||
const SoftwareVulnSoftwareVersions = ({
|
||||
vulnSoftware,
|
||||
isPremiumTier,
|
||||
router,
|
||||
teamIdForApi,
|
||||
}: ISoftwareVulnSoftwareVersions) => {
|
||||
const columnConfigs = useMemo(() => generateColumnConfigs(isPremiumTier), [
|
||||
isPremiumTier,
|
||||
]);
|
||||
const columnConfigs = useMemo(
|
||||
() => generateColumnConfigs(isPremiumTier, router, teamIdForApi),
|
||||
[isPremiumTier, router, teamIdForApi]
|
||||
);
|
||||
|
||||
const onSelectSingleRow = useCallback(
|
||||
({ original: { id: software_title_id } }) => {
|
||||
@@ -40,10 +43,11 @@ const SoftwareVulnSoftwareVersions = ({
|
||||
router.push(
|
||||
`${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
software_title_id,
|
||||
team_id: teamIdForApi,
|
||||
})}`
|
||||
);
|
||||
},
|
||||
[router]
|
||||
[teamIdForApi, router]
|
||||
);
|
||||
const renderVulnerableSoftwareTable = () => {
|
||||
return (
|
||||
|
||||
+20
-2
@@ -9,6 +9,7 @@ import LinkCell from "components/TableContainer/DataTable/LinkCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
@@ -25,7 +26,11 @@ interface IStringCellProps extends ICellProps {
|
||||
};
|
||||
}
|
||||
|
||||
const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
const generateColumnConfigs = (
|
||||
isPremiumTier: boolean,
|
||||
router: InjectedRouter,
|
||||
teamIdForApi?: number
|
||||
): Column[] => {
|
||||
const configs = [
|
||||
{
|
||||
Header: "Name",
|
||||
@@ -33,9 +38,21 @@ const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
accessor: "name",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
const { name, id } = row.original;
|
||||
const endpoint = PATHS.SOFTWARE_VERSION_DETAILS(id.toString());
|
||||
// since No Teams not supported on this page, falsiness of 0 is okay
|
||||
const path = teamIdForApi
|
||||
? `${endpoint}?team_id=${teamIdForApi}`
|
||||
: endpoint;
|
||||
const onClickVulnSwVersion = (e: React.MouseEvent) => {
|
||||
// Allows for button to be clickable in a clickable row
|
||||
e.stopPropagation();
|
||||
|
||||
router?.push(path);
|
||||
};
|
||||
return (
|
||||
<LinkCell
|
||||
path={PATHS.SOFTWARE_VERSION_DETAILS(id.toString())}
|
||||
path={path}
|
||||
customOnClick={onClickVulnSwVersion}
|
||||
value={
|
||||
<>
|
||||
<SoftwareIcon name={name} />
|
||||
@@ -75,6 +92,7 @@ const generateColumnConfigs = (isPremiumTier: boolean): Column[] => {
|
||||
queryParams={{ software_title_id: id }}
|
||||
responsive
|
||||
rowHover
|
||||
noLink
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+3
-1
@@ -17,11 +17,13 @@ const baseClass = "software-vuln-summary";
|
||||
interface ISoftwareVulnSummaryProps {
|
||||
vuln: IVulnerability;
|
||||
isPremiumTier: boolean;
|
||||
teamIdForApi?: number;
|
||||
}
|
||||
|
||||
const SoftwareVulnSummary = ({
|
||||
vuln,
|
||||
isPremiumTier,
|
||||
teamIdForApi,
|
||||
}: ISoftwareVulnSummaryProps) => {
|
||||
const {
|
||||
cve,
|
||||
@@ -43,7 +45,7 @@ const SoftwareVulnSummary = ({
|
||||
<CustomLink url={details_link} text="Visit NVD page" newTab />
|
||||
<ViewAllHostsLink
|
||||
customText="View affected hosts"
|
||||
queryParams={{ cve }}
|
||||
queryParams={{ cve, team_id: teamIdForApi }}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+91
-23
@@ -1,16 +1,17 @@
|
||||
/** software/vulnerabilities/:cve */
|
||||
/* software/vulnerabilities/:cve */
|
||||
import React, { useCallback, useContext } from "react";
|
||||
|
||||
import React, { useContext } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import useTeamIdParam from "hooks/useTeamIdParam";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
|
||||
import { IVulnerability } from "interfaces/vulnerability";
|
||||
|
||||
import softwareVulnAPI, {
|
||||
IGetVulnerabilityQueryKey,
|
||||
IVulnerabilityResponse,
|
||||
} from "services/entities/vulnerabilities";
|
||||
|
||||
@@ -18,6 +19,8 @@ import Spinner from "components/Spinner";
|
||||
import DataError from "components/DataError";
|
||||
import Fleet404 from "pages/errors/Fleet404";
|
||||
import MainContent from "components/MainContent";
|
||||
import TeamsHeader from "components/TeamsHeader";
|
||||
import Card from "components/Card";
|
||||
|
||||
import SoftwareVulnSummary from "./SoftwareVulnSummary/SoftwareVulnSummary";
|
||||
import SoftwareVulnOSVersions from "./SoftwareVulnOSVersions";
|
||||
@@ -27,6 +30,7 @@ const baseClass = "software-vulnerability-details-page";
|
||||
|
||||
interface ISoftwareVulnerabilityDetailsRouteParams {
|
||||
cve: string;
|
||||
team_id?: string;
|
||||
}
|
||||
|
||||
type ISoftwareVulnerabilityDetailsPageProps = RouteComponentProps<
|
||||
@@ -37,24 +41,92 @@ type ISoftwareVulnerabilityDetailsPageProps = RouteComponentProps<
|
||||
const SoftwareVulnerabilityDetailsPage = ({
|
||||
router,
|
||||
routeParams,
|
||||
location,
|
||||
}: ISoftwareVulnerabilityDetailsPageProps) => {
|
||||
const { isPremiumTier } = useContext(AppContext);
|
||||
const { isPremiumTier, isOnGlobalTeam } = useContext(AppContext);
|
||||
|
||||
const cve = routeParams.cve;
|
||||
const {
|
||||
currentTeamId,
|
||||
teamIdForApi,
|
||||
userTeams,
|
||||
handleTeamChange,
|
||||
} = useTeamIdParam({
|
||||
location,
|
||||
router,
|
||||
includeAllTeams: true,
|
||||
includeNoTeam: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: vuln,
|
||||
isLoading: isVulnLoading,
|
||||
isError: isVulnError,
|
||||
error: vulnError,
|
||||
} = useQuery<IVulnerabilityResponse, AxiosError, IVulnerability>(
|
||||
["softwareVulnByCVE", cve],
|
||||
() => softwareVulnAPI.getVulnerability(cve),
|
||||
} = useQuery<
|
||||
IVulnerabilityResponse,
|
||||
AxiosError,
|
||||
IVulnerability,
|
||||
IGetVulnerabilityQueryKey[]
|
||||
>(
|
||||
[
|
||||
{
|
||||
scope: "softwareVulnByCVE",
|
||||
cve: routeParams.cve,
|
||||
teamId: teamIdForApi,
|
||||
},
|
||||
],
|
||||
({ queryKey }) => {
|
||||
return softwareVulnAPI.getVulnerability(queryKey[0]);
|
||||
},
|
||||
{
|
||||
select: (data) => data.vulnerability,
|
||||
}
|
||||
);
|
||||
|
||||
const onTeamChange = useCallback(
|
||||
(teamId: number) => {
|
||||
handleTeamChange(teamId);
|
||||
},
|
||||
[handleTeamChange]
|
||||
);
|
||||
|
||||
const renderVulnTables = () => {
|
||||
// always the case, just for typing
|
||||
if (vuln) {
|
||||
if (vuln.hosts_count === 0) {
|
||||
return (
|
||||
<Card borderRadiusSize="large" includeShadow className="not-detected">
|
||||
<h2>Vulnerability not detected</h2>
|
||||
<p>
|
||||
No host {!!teamIdForApi && "on this team "}is affected by{" "}
|
||||
{vuln.cve}.
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!!vuln.os_versions && vuln.os_versions.length > 0 && (
|
||||
<SoftwareVulnOSVersions
|
||||
osVersions={vuln.os_versions}
|
||||
isPremiumTier={isPremiumTier ?? false}
|
||||
router={router}
|
||||
teamIdForApi={teamIdForApi}
|
||||
/>
|
||||
)}
|
||||
{!!vuln.software && vuln.software.length > 0 && (
|
||||
<SoftwareVulnSoftwareVersions
|
||||
vulnSoftware={vuln.software}
|
||||
isPremiumTier={isPremiumTier ?? false}
|
||||
router={router}
|
||||
teamIdForApi={teamIdForApi}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (isVulnLoading || !vuln) {
|
||||
return <Spinner />;
|
||||
@@ -68,24 +140,20 @@ const SoftwareVulnerabilityDetailsPage = ({
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{isPremiumTier && (
|
||||
<TeamsHeader
|
||||
isOnGlobalTeam={isOnGlobalTeam}
|
||||
currentTeamId={currentTeamId}
|
||||
userTeams={userTeams}
|
||||
onTeamChange={onTeamChange}
|
||||
/>
|
||||
)}
|
||||
<SoftwareVulnSummary
|
||||
vuln={vuln}
|
||||
isPremiumTier={isPremiumTier ?? false}
|
||||
teamIdForApi={teamIdForApi}
|
||||
/>
|
||||
{!!vuln.os_versions && vuln.os_versions.length > 0 && (
|
||||
<SoftwareVulnOSVersions
|
||||
osVersions={vuln.os_versions}
|
||||
isPremiumTier={isPremiumTier ?? false}
|
||||
router={router}
|
||||
/>
|
||||
)}
|
||||
{!!vuln.software && vuln.software.length > 0 && (
|
||||
<SoftwareVulnSoftwareVersions
|
||||
vulnSoftware={vuln.software}
|
||||
isPremiumTier={isPremiumTier ?? false}
|
||||
router={router}
|
||||
/>
|
||||
)}
|
||||
{renderVulnTables()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
padding: $pad-xxlarge;
|
||||
}
|
||||
|
||||
h1,
|
||||
* > h1,
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-weight: $bold;
|
||||
@@ -22,6 +22,16 @@
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.not-detected {
|
||||
gap: 0;
|
||||
text-align: center;
|
||||
* {
|
||||
font-size: $x-small;
|
||||
line-height: 21px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.resolved-suffix {
|
||||
display: inline;
|
||||
@media (max-width: $break-md) {
|
||||
|
||||
@@ -23,6 +23,15 @@ export interface IGetVulnerabilitiesQueryKey
|
||||
scope: string;
|
||||
}
|
||||
|
||||
interface IGetVulnerabilityOptions {
|
||||
cve: string;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
export interface IGetVulnerabilityQueryKey extends IGetVulnerabilityOptions {
|
||||
scope: "softwareVulnByCVE";
|
||||
}
|
||||
|
||||
export interface IVulnerabilitiesResponse {
|
||||
count: number;
|
||||
counts_updated_at: string;
|
||||
@@ -67,10 +76,14 @@ export const getVulnerabilities = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getVulnerability = (cve: string): Promise<IVulnerabilityResponse> => {
|
||||
const { VULNERABILITY } = endpoints;
|
||||
const getVulnerability = ({
|
||||
cve,
|
||||
teamId,
|
||||
}: IGetVulnerabilityOptions): Promise<IVulnerabilityResponse> => {
|
||||
const endpoint = endpoints.VULNERABILITY(cve);
|
||||
const path = teamId ? `${endpoint}?team_id=${teamId}` : endpoint;
|
||||
|
||||
// return sendRequest("GET", VULNERABILITY(cve)); // TODO: API INTEGRATION: uncomment when API is ready
|
||||
// return sendRequest("GET", path); // TODO: API INTEGRATION: uncomment when API is ready
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(createMockVulnerabilityResponse());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user