Skip to content

Commit 4df9133

Browse files
feat(site): display xray scan result in the agent (#11955)
1 parent ac64155 commit 4df9133

File tree

7 files changed

+186
-2
lines changed

7 files changed

+186
-2
lines changed

site/src/api/api.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios from "axios";
1+
import axios, { isAxiosError } from "axios";
22
import dayjs from "dayjs";
33
import * as TypesGen from "./typesGenerated";
44
// This needs to include the `../`, otherwise it breaks when importing into
@@ -1703,3 +1703,27 @@ export const putFavoriteWorkspace = async (workspaceID: string) => {
17031703
export const deleteFavoriteWorkspace = async (workspaceID: string) => {
17041704
await axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
17051705
};
1706+
1707+
export type GetJFrogXRayScanParams = {
1708+
workspaceId: string;
1709+
agentId: string;
1710+
};
1711+
1712+
export const getJFrogXRayScan = async (options: GetJFrogXRayScanParams) => {
1713+
const searchParams = new URLSearchParams({
1714+
workspace_id: options.workspaceId,
1715+
agent_id: options.agentId,
1716+
});
1717+
1718+
try {
1719+
const res = await axios.get<TypesGen.JFrogXrayScan>(
1720+
`/api/v2/integrations/jfrog/xray-scan?${searchParams}`,
1721+
);
1722+
return res.data;
1723+
} catch (error) {
1724+
if (isAxiosError(error) && error.response?.status === 404) {
1725+
// react-query library does not allow undefined to be returned as a query result
1726+
return null;
1727+
}
1728+
}
1729+
};

site/src/api/queries/integrations.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { GetJFrogXRayScanParams } from "api/api";
2+
import * as API from "api/api";
3+
4+
export const xrayScan = (params: GetJFrogXRayScanParams) => {
5+
return {
6+
queryKey: ["xray", params],
7+
queryFn: () => API.getJFrogXRayScan(params),
8+
};
9+
};

site/src/modules/resources/AgentRow.stories.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,24 @@ export const Deprecated: Story = {
311311
serverAPIVersion: "2.0",
312312
},
313313
};
314+
315+
export const WithXRayScan: Story = {
316+
parameters: {
317+
queries: [
318+
{
319+
key: [
320+
"xray",
321+
{ agentId: MockWorkspaceAgent.id, workspaceId: MockWorkspace.id },
322+
],
323+
data: {
324+
workspace_id: MockWorkspace.id,
325+
agent_id: MockWorkspaceAgent.id,
326+
critical: 10,
327+
high: 3,
328+
medium: 5,
329+
results_url: "http://localhost:8080",
330+
},
331+
},
332+
],
333+
},
334+
};

site/src/modules/resources/AgentRow.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import { PortForwardButton } from "./PortForwardButton";
3737
import { SSHButton } from "./SSHButton/SSHButton";
3838
import { TerminalLink } from "./TerminalLink/TerminalLink";
3939
import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton";
40+
import { useQuery } from "react-query";
41+
import { xrayScan } from "api/queries/integrations";
42+
import { XRayScanAlert } from "./XRayScanAlert";
4043

4144
// Logs are stored as the Line interface to make rendering
4245
// much more efficient. Instead of mapping objects each time, we're
@@ -74,6 +77,12 @@ export const AgentRow: FC<AgentRowProps> = ({
7477
sshPrefix,
7578
storybookLogs,
7679
}) => {
80+
// XRay integration
81+
const xrayScanQuery = useQuery(
82+
xrayScan({ workspaceId: workspace.id, agentId: agent.id }),
83+
);
84+
85+
// Apps visibility
7786
const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0;
7887
const shouldDisplayApps =
7988
showApps &&
@@ -84,6 +93,7 @@ export const AgentRow: FC<AgentRowProps> = ({
8493
agent.display_apps.includes("vscode_insiders");
8594
const showVSCode = hasVSCodeApp && !hideVSCodeDesktopButton;
8695

96+
// Agent runtime logs
8797
const logSourceByID = useMemo(() => {
8898
const sources: { [id: string]: WorkspaceAgentLogSource } = {};
8999
for (const source of agent.log_sources) {
@@ -216,6 +226,8 @@ export const AgentRow: FC<AgentRowProps> = ({
216226
)}
217227
</header>
218228

229+
{xrayScanQuery.data && <XRayScanAlert scan={xrayScanQuery.data} />}
230+
219231
<div css={styles.content}>
220232
{agent.status === "connected" && (
221233
<section css={styles.apps}>
@@ -276,7 +288,9 @@ export const AgentRow: FC<AgentRowProps> = ({
276288

277289
{hasStartupFeatures && (
278290
<section
279-
css={(theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })}
291+
css={(theme) => ({
292+
borderTop: `1px solid ${theme.palette.divider}`,
293+
})}
280294
>
281295
<Collapse in={showLogs}>
282296
<AutoSizer disableHeight>
@@ -571,6 +585,10 @@ const styles = {
571585
flexWrap: "wrap",
572586
lineHeight: "1.5",
573587

588+
"&:has(+ [role='alert'])": {
589+
paddingBottom: 16,
590+
},
591+
574592
[theme.breakpoints.down("md")]: {
575593
gap: 16,
576594
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { Interpolation, Theme } from "@emotion/react";
2+
import Button from "@mui/material/Button";
3+
import { JFrogXrayScan } from "api/typesGenerated";
4+
import { ExternalImage } from "components/ExternalImage/ExternalImage";
5+
import { FC } from "react";
6+
7+
export const XRayScanAlert: FC<{ scan: JFrogXrayScan }> = ({ scan }) => {
8+
return (
9+
<div role="alert" css={styles.root}>
10+
<ExternalImage
11+
alt="JFrog logo"
12+
src="/icon/jfrog.svg"
13+
css={{ width: 40, height: 40 }}
14+
/>
15+
<div>
16+
<span css={styles.title}>
17+
JFrog Xray detected new vulnerabilities for this agent
18+
</span>
19+
20+
<ul css={styles.issues}>
21+
{scan.critical > 0 && (
22+
<li css={[styles.critical, styles.issueItem]}>
23+
{scan.critical} critical
24+
</li>
25+
)}
26+
{scan.high > 0 && (
27+
<li css={[styles.high, styles.issueItem]}>{scan.high} high</li>
28+
)}
29+
{scan.medium > 0 && (
30+
<li css={[styles.medium, styles.issueItem]}>
31+
{scan.medium} medium
32+
</li>
33+
)}
34+
</ul>
35+
</div>
36+
<div css={styles.link}>
37+
<Button
38+
component="a"
39+
size="small"
40+
variant="text"
41+
href={scan.results_url}
42+
target="_blank"
43+
rel="noreferrer"
44+
>
45+
Review results
46+
</Button>
47+
</div>
48+
</div>
49+
);
50+
};
51+
52+
const styles = {
53+
root: (theme) => ({
54+
backgroundColor: theme.palette.background.paper,
55+
border: `1px solid ${theme.palette.divider}`,
56+
borderLeft: 0,
57+
borderRight: 0,
58+
fontSize: 14,
59+
padding: "24px 16px 24px 32px",
60+
lineHeight: "1.5",
61+
display: "flex",
62+
alignItems: "center",
63+
gap: 24,
64+
}),
65+
title: {
66+
display: "block",
67+
fontWeight: 500,
68+
},
69+
issues: {
70+
listStyle: "none",
71+
margin: 0,
72+
padding: 0,
73+
fontSize: 13,
74+
display: "flex",
75+
alignItems: "center",
76+
gap: 16,
77+
marginTop: 4,
78+
},
79+
issueItem: {
80+
display: "flex",
81+
alignItems: "center",
82+
gap: 8,
83+
84+
"&:before": {
85+
content: '""',
86+
display: "block",
87+
width: 6,
88+
height: 6,
89+
borderRadius: "50%",
90+
backgroundColor: "currentColor",
91+
},
92+
},
93+
critical: (theme) => ({
94+
color: theme.experimental.roles.error.fill.solid,
95+
}),
96+
high: (theme) => ({
97+
color: theme.experimental.roles.warning.fill.solid,
98+
}),
99+
medium: (theme) => ({
100+
color: theme.experimental.roles.notice.fill.solid,
101+
}),
102+
link: {
103+
marginLeft: "auto",
104+
alignSelf: "flex-start",
105+
},
106+
} satisfies Record<string, Interpolation<Theme>>;

site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.stories.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import type { Meta, StoryObj } from "@storybook/react";
1313
const meta: Meta<typeof VersionsTable> = {
1414
title: "pages/TemplatePage/VersionsTable",
1515
component: VersionsTable,
16+
args: {
17+
onPromoteClick: () => {},
18+
onArchiveClick: () => {},
19+
},
1620
};
1721

1822
export default meta;

site/src/pages/UsersPage/ResetPasswordDialog.stories.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const Example: Story = {
1616
open: true,
1717
user: MockUser,
1818
newPassword: "somerandomstringhere",
19+
onConfirm: () => {},
20+
onClose: () => {},
1921
},
2022
};
2123

0 commit comments

Comments
 (0)