feat(site): display xray scan result in the agent (#11955)

This commit is contained in:
Bruno Quaresma
2024-01-31 19:16:01 -03:00
committed by GitHub
parent ac64155282
commit 4df913372f
7 changed files with 186 additions and 2 deletions
+25 -1
View File
@@ -1,4 +1,4 @@
import axios from "axios";
import axios, { isAxiosError } from "axios";
import dayjs from "dayjs";
import * as TypesGen from "./typesGenerated";
// This needs to include the `../`, otherwise it breaks when importing into
@@ -1703,3 +1703,27 @@ export const putFavoriteWorkspace = async (workspaceID: string) => {
export const deleteFavoriteWorkspace = async (workspaceID: string) => {
await axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
};
export type GetJFrogXRayScanParams = {
workspaceId: string;
agentId: string;
};
export const getJFrogXRayScan = async (options: GetJFrogXRayScanParams) => {
const searchParams = new URLSearchParams({
workspace_id: options.workspaceId,
agent_id: options.agentId,
});
try {
const res = await axios.get<TypesGen.JFrogXrayScan>(
`/api/v2/integrations/jfrog/xray-scan?${searchParams}`,
);
return res.data;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 404) {
// react-query library does not allow undefined to be returned as a query result
return null;
}
}
};
+9
View File
@@ -0,0 +1,9 @@
import { GetJFrogXRayScanParams } from "api/api";
import * as API from "api/api";
export const xrayScan = (params: GetJFrogXRayScanParams) => {
return {
queryKey: ["xray", params],
queryFn: () => API.getJFrogXRayScan(params),
};
};
@@ -311,3 +311,24 @@ export const Deprecated: Story = {
serverAPIVersion: "2.0",
},
};
export const WithXRayScan: Story = {
parameters: {
queries: [
{
key: [
"xray",
{ agentId: MockWorkspaceAgent.id, workspaceId: MockWorkspace.id },
],
data: {
workspace_id: MockWorkspace.id,
agent_id: MockWorkspaceAgent.id,
critical: 10,
high: 3,
medium: 5,
results_url: "http://localhost:8080",
},
},
],
},
};
+19 -1
View File
@@ -37,6 +37,9 @@ import { PortForwardButton } from "./PortForwardButton";
import { SSHButton } from "./SSHButton/SSHButton";
import { TerminalLink } from "./TerminalLink/TerminalLink";
import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton";
import { useQuery } from "react-query";
import { xrayScan } from "api/queries/integrations";
import { XRayScanAlert } from "./XRayScanAlert";
// Logs are stored as the Line interface to make rendering
// much more efficient. Instead of mapping objects each time, we're
@@ -74,6 +77,12 @@ export const AgentRow: FC<AgentRowProps> = ({
sshPrefix,
storybookLogs,
}) => {
// XRay integration
const xrayScanQuery = useQuery(
xrayScan({ workspaceId: workspace.id, agentId: agent.id }),
);
// Apps visibility
const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0;
const shouldDisplayApps =
showApps &&
@@ -84,6 +93,7 @@ export const AgentRow: FC<AgentRowProps> = ({
agent.display_apps.includes("vscode_insiders");
const showVSCode = hasVSCodeApp && !hideVSCodeDesktopButton;
// Agent runtime logs
const logSourceByID = useMemo(() => {
const sources: { [id: string]: WorkspaceAgentLogSource } = {};
for (const source of agent.log_sources) {
@@ -216,6 +226,8 @@ export const AgentRow: FC<AgentRowProps> = ({
)}
</header>
{xrayScanQuery.data && <XRayScanAlert scan={xrayScanQuery.data} />}
<div css={styles.content}>
{agent.status === "connected" && (
<section css={styles.apps}>
@@ -276,7 +288,9 @@ export const AgentRow: FC<AgentRowProps> = ({
{hasStartupFeatures && (
<section
css={(theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })}
css={(theme) => ({
borderTop: `1px solid ${theme.palette.divider}`,
})}
>
<Collapse in={showLogs}>
<AutoSizer disableHeight>
@@ -571,6 +585,10 @@ const styles = {
flexWrap: "wrap",
lineHeight: "1.5",
"&:has(+ [role='alert'])": {
paddingBottom: 16,
},
[theme.breakpoints.down("md")]: {
gap: 16,
},
@@ -0,0 +1,106 @@
import { Interpolation, Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import { JFrogXrayScan } from "api/typesGenerated";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { FC } from "react";
export const XRayScanAlert: FC<{ scan: JFrogXrayScan }> = ({ scan }) => {
return (
<div role="alert" css={styles.root}>
<ExternalImage
alt="JFrog logo"
src="/icon/jfrog.svg"
css={{ width: 40, height: 40 }}
/>
<div>
<span css={styles.title}>
JFrog Xray detected new vulnerabilities for this agent
</span>
<ul css={styles.issues}>
{scan.critical > 0 && (
<li css={[styles.critical, styles.issueItem]}>
{scan.critical} critical
</li>
)}
{scan.high > 0 && (
<li css={[styles.high, styles.issueItem]}>{scan.high} high</li>
)}
{scan.medium > 0 && (
<li css={[styles.medium, styles.issueItem]}>
{scan.medium} medium
</li>
)}
</ul>
</div>
<div css={styles.link}>
<Button
component="a"
size="small"
variant="text"
href={scan.results_url}
target="_blank"
rel="noreferrer"
>
Review results
</Button>
</div>
</div>
);
};
const styles = {
root: (theme) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderLeft: 0,
borderRight: 0,
fontSize: 14,
padding: "24px 16px 24px 32px",
lineHeight: "1.5",
display: "flex",
alignItems: "center",
gap: 24,
}),
title: {
display: "block",
fontWeight: 500,
},
issues: {
listStyle: "none",
margin: 0,
padding: 0,
fontSize: 13,
display: "flex",
alignItems: "center",
gap: 16,
marginTop: 4,
},
issueItem: {
display: "flex",
alignItems: "center",
gap: 8,
"&:before": {
content: '""',
display: "block",
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: "currentColor",
},
},
critical: (theme) => ({
color: theme.experimental.roles.error.fill.solid,
}),
high: (theme) => ({
color: theme.experimental.roles.warning.fill.solid,
}),
medium: (theme) => ({
color: theme.experimental.roles.notice.fill.solid,
}),
link: {
marginLeft: "auto",
alignSelf: "flex-start",
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -13,6 +13,10 @@ import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof VersionsTable> = {
title: "pages/TemplatePage/VersionsTable",
component: VersionsTable,
args: {
onPromoteClick: () => {},
onArchiveClick: () => {},
},
};
export default meta;
@@ -16,6 +16,8 @@ const Example: Story = {
open: true,
user: MockUser,
newPassword: "somerandomstringhere",
onConfirm: () => {},
onClose: () => {},
},
};