From 9fd2e1085e99d4166092a88005d39a0b8ebb4b92 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 12:14:12 +0200 Subject: [PATCH 01/13] fix table component --- assets/design-system/src/components/table/Table.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/design-system/src/components/table/Table.tsx b/assets/design-system/src/components/table/Table.tsx index 86ff05c8c1..1115873ab5 100644 --- a/assets/design-system/src/components/table/Table.tsx +++ b/assets/design-system/src/components/table/Table.tsx @@ -129,6 +129,8 @@ function Table({ string | null >(null) + useEffect(() => setFixedGridTemplateColumns(null), [columns.length]) + const { rows: tableRows } = table.getRowModel() const getItemKey = useCallback< Parameters[0]['getItemKey'] From a1d29ed42b527219a7bdf4ca183da02b23df544a Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 12:15:42 +0200 Subject: [PATCH 02/13] add select column --- .../VulnReportDetailsTableCols.tsx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/assets/src/components/security/vulnerabilities/VulnReportDetailsTableCols.tsx b/assets/src/components/security/vulnerabilities/VulnReportDetailsTableCols.tsx index 73dcac7837..c1effeccca 100644 --- a/assets/src/components/security/vulnerabilities/VulnReportDetailsTableCols.tsx +++ b/assets/src/components/security/vulnerabilities/VulnReportDetailsTableCols.tsx @@ -1,4 +1,10 @@ -import { Chip, ChipProps, Tooltip, WrapWithIf } from '@pluralsh/design-system' +import { + Checkbox, + Chip, + ChipProps, + Tooltip, + WrapWithIf, +} from '@pluralsh/design-system' import { createColumnHelper } from '@tanstack/react-table' import { VulnerabilityFragment, VulnSeverity } from 'generated/graphql' import { truncate } from 'lodash' @@ -6,6 +12,30 @@ import { useTheme } from 'styled-components' const columnHelper = createColumnHelper() +export const ColVulnSelect = columnHelper.display({ + id: 'select', + header: ({ table }) => ( +
e.stopPropagation()}> + +
+ ), + cell: ({ row }) => ( +
e.stopPropagation()}> + +
+ ), + meta: { gridTemplate: '48px' }, +}) + export const ColID = columnHelper.accessor((report) => report, { id: 'id', header: 'ID', From 88863327ab09d82fbafb696a69d63172b0e4921d Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 12:19:03 +0200 Subject: [PATCH 03/13] update path --- .../security/vulnerabilities/VulnReportsTableCols.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/src/components/security/vulnerabilities/VulnReportsTableCols.tsx b/assets/src/components/security/vulnerabilities/VulnReportsTableCols.tsx index 562ea9f5af..ecbbdead98 100644 --- a/assets/src/components/security/vulnerabilities/VulnReportsTableCols.tsx +++ b/assets/src/components/security/vulnerabilities/VulnReportsTableCols.tsx @@ -158,13 +158,17 @@ export const ColActions = columnHelper.display({ gridTemplate: 'minmax(auto, 80px)', }, cell: function Cell({ row: { original } }) { - const { clusterId = '' } = useParams() + const { clusterId = '', flowIdOrName } = useParams<{ + clusterId?: string + flowIdOrName?: string + }>() const vulnerabilityReportId = original?.id ?? '' return ( Date: Wed, 8 Jul 2026 13:09:43 +0200 Subject: [PATCH 04/13] refactor --- .../ai/agent-runs/AgentRunFixButton.tsx | 222 ------------------ .../cd/clusters/ClusterUpgradeAgentButton.tsx | 25 +- .../vulnerabilities/VulnDetailExpanded.tsx | 11 +- .../vulnerabilities/vulnerability-prompt.ejs | 3 +- 4 files changed, 29 insertions(+), 232 deletions(-) delete mode 100644 assets/src/components/ai/agent-runs/AgentRunFixButton.tsx diff --git a/assets/src/components/ai/agent-runs/AgentRunFixButton.tsx b/assets/src/components/ai/agent-runs/AgentRunFixButton.tsx deleted file mode 100644 index c356c8c128..0000000000 --- a/assets/src/components/ai/agent-runs/AgentRunFixButton.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { - AiSparkleFilledIcon, - ArrowTopRightIcon, - Button, - ButtonProps, - Card, - CloseIcon, - DiscoverIcon, - Flex, - FormField, - IconFrame, - Markdown, - SelectButton, -} from '@pluralsh/design-system' -import { AgentRunInfoCard } from 'components/ai/agent-runs/AgentRunInfoDisplays' -import { useOutsideClick } from 'components/hooks/useOutsideClick' -import { SimplePopupMenu } from 'components/layout/HeaderPopupMenu' -import { GqlError } from 'components/utils/Alert' -import { EditableDiv } from 'components/utils/EditableDiv' -import { FillLevelDiv } from 'components/utils/FillLevelDiv' -import { StretchedFlex } from 'components/utils/StretchedFlex' -import { StackedText } from 'components/utils/table/StackedText' -import { InlineLink } from 'components/utils/typography/InlineLink' -import { - AgentRunFragment, - AgentRunMode, - useCreateAgentRunMutation, -} from 'generated/graphql' -import { useRef, useState } from 'react' -import { Link } from 'react-router-dom' -import { getAgentRunAbsPath } from 'routes/aiRoutesConsts' -import { AI_SETTINGS_AGENT_RUNTIMES_ABS_PATH } from 'routes/settingsRoutesConst' -import styled, { useTheme } from 'styled-components' -import { AIAgentRuntimesSelector } from './AIAgentRuntimesSelector' -import { AgentRunRepoSelector } from './AgentRunRepoSelector' - -export function AgentRunFixButton({ - headerTitle, - initialRepo, - initialPrompt, - ...props -}: { - headerTitle: string - initialPrompt: string - initialRepo?: Nullable -} & ButtonProps) { - const { colors } = useTheme() - const [dropdownOpen, setDropdownOpen] = useState(false) - const [prompt, setPrompt] = useState(initialPrompt) - - const menuBtnRef = useRef(null) - useOutsideClick(menuBtnRef, () => setDropdownOpen(false)) - - const [agentRun, setAgentRun] = useState(null) - - return ( -
- - - ) : ( - - )} - -
- ) -} - -function AgentRunForm({ - initialRepo, - prompt, - setPrompt, - setAgentRun, -}: { - initialRepo?: Nullable - prompt: string - setPrompt: (prompt: string) => void - setAgentRun: (agentRun: AgentRunFragment) => void -}) { - const [runtimeId, setRuntimeId] = useState('') - const [repository, setRepository] = useState(initialRepo ?? '') - - const [mutation, { loading, error }] = useCreateAgentRunMutation({ - variables: { - runtimeId, - attributes: { prompt, mode: AgentRunMode.Write, repository }, - }, - onCompleted: ({ createAgentRun }) => - createAgentRun && setAgentRun(createAgentRun), - }) - - const canSubmit = !!runtimeId && !!repository && !!prompt && !loading - - return ( - <> - {error && } - - Runtime settings - - } - > - - - runtimeId && setRuntimeId(runtimeId) - } - outerStyles={{ width: '100%' }} - /> - - - - - repository && setRepository(repository) - } - selectedRuntimeId={runtimeId} - triggerButton={ - {repository || 'Select repository'} - } - width={500} - leftContent={undefined} - /> - - - - - - - ) -} - -export const AgentRunFormPopupSC = styled(SimplePopupMenu)(({ theme }) => ({ - width: 578, - padding: theme.spacing.medium, - gap: theme.spacing.large, - transform: 'translateY(20px)', - marginBottom: theme.spacing.xxlarge, - boxShadow: theme.boxShadows.moderate, - zIndex: theme.zIndexes.toast, -})) - -export const PromptInputBoxSC = styled(Card)(({ theme }) => ({ - padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, - '&:focus-within': { - border: theme.borders['outline-focused'], - }, -})) diff --git a/assets/src/components/cd/clusters/ClusterUpgradeAgentButton.tsx b/assets/src/components/cd/clusters/ClusterUpgradeAgentButton.tsx index 00d7e6cd6f..04699bdc1d 100644 --- a/assets/src/components/cd/clusters/ClusterUpgradeAgentButton.tsx +++ b/assets/src/components/cd/clusters/ClusterUpgradeAgentButton.tsx @@ -3,6 +3,7 @@ import { AccordionItem, AiSparkleFilledIcon, Button, + Card, Chip, CloseIcon, DiscoverIcon, @@ -14,12 +15,9 @@ import { ReloadIcon, SpinnerAlt, } from '@pluralsh/design-system' -import { - AgentRunFormPopupSC, - PromptInputBoxSC, -} from 'components/ai/agent-runs/AgentRunFixButton' import { AIAgentRuntimesSelector } from 'components/ai/agent-runs/AIAgentRuntimesSelector' import { useOutsideClick } from 'components/hooks/useOutsideClick' +import { SimplePopupMenu } from 'components/layout/HeaderPopupMenu' import { GqlError } from 'components/utils/Alert' import { EditableDiv } from 'components/utils/EditableDiv' import { FillLevelDiv } from 'components/utils/FillLevelDiv' @@ -35,7 +33,7 @@ import { import { useRef, useState } from 'react' import { Link } from 'react-router-dom' import { AI_SETTINGS_AGENT_RUNTIMES_ABS_PATH } from 'routes/settingsRoutesConst' -import { useTheme } from 'styled-components' +import styled, { useTheme } from 'styled-components' type CurUpgradeStatus = 'none' | 'running' | 'completed' | 'failed' @@ -226,3 +224,20 @@ const getCurUpgradeStatus = ( return 'failed' } } + +const AgentRunFormPopupSC = styled(SimplePopupMenu)(({ theme }) => ({ + width: 578, + padding: theme.spacing.medium, + gap: theme.spacing.large, + transform: 'translateY(20px)', + marginBottom: theme.spacing.xxlarge, + boxShadow: theme.boxShadows.moderate, + zIndex: theme.zIndexes.toast, +})) + +const PromptInputBoxSC = styled(Card)(({ theme }) => ({ + padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, + '&:focus-within': { + border: theme.borders['outline-focused'], + }, +})) diff --git a/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx b/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx index b0fcb28639..f35d4694cc 100644 --- a/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx +++ b/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx @@ -12,7 +12,8 @@ import { VulnerabilityFragment, VulnerabilityReportFragment, } from 'generated/graphql' -import { AgentRunFixButton } from 'components/ai/agent-runs/AgentRunFixButton' +import { useCurrentFlow } from 'components/flows/hooks/useCurrentFlow' +import { VulnFixButton } from './VulnFixButton' import { useMemo } from 'react' import ejs from 'ejs' import vulnPromptTemplate from './vulnerability-prompt.ejs?raw' @@ -26,6 +27,8 @@ export function VulnDetailExpanded({ }) { const { original: v } = row + const { flowData } = useCurrentFlow() + const initialPrompt = useMemo( () => ejs.render(vulnPromptTemplate, { vuln: v, report: parentReport }), [v, parentReport] @@ -45,13 +48,13 @@ export function VulnDetailExpanded({ secondPartialType="body2" css={{ maxWidth: 900 }} /> - Fix vulnerability - + + <%= vuln.description %> It is found in the following docker image: <%= report.artifactUrl %> @@ -11,4 +12,4 @@ Fix Version: <%= vuln.fixedVersion %> Current Version: <%= vuln.installedVersion %> Package: <%= vuln.resource %> -Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by this vulnerability, ignore them for now. \ No newline at end of file +Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by this vulnerability, ignore them for now. From 33e8f1576d2f0953d2d257c156e582786820d194 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 13:38:16 +0200 Subject: [PATCH 05/13] add bulk selection --- .../vulnerabilities/VulnFixButton.tsx | 290 ++++++++++++++++++ .../vulnerabilities/VulnReportDetails.tsx | 219 +++++++++---- .../vulnerability-batch-prompt.ejs | 17 + 3 files changed, 474 insertions(+), 52 deletions(-) create mode 100644 assets/src/components/security/vulnerabilities/VulnFixButton.tsx create mode 100644 assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs diff --git a/assets/src/components/security/vulnerabilities/VulnFixButton.tsx b/assets/src/components/security/vulnerabilities/VulnFixButton.tsx new file mode 100644 index 0000000000..ee7d0ed8a3 --- /dev/null +++ b/assets/src/components/security/vulnerabilities/VulnFixButton.tsx @@ -0,0 +1,290 @@ +import { + AiSparkleFilledIcon, + Button, + ButtonProps, + Card, + CloseIcon, + DiscoverIcon, + Flex, + FormField, + IconFrame, + ListBoxItem, + Modal, + Select, + SelectButton, +} from '@pluralsh/design-system' +import { runtimeToIcon } from 'components/settings/ai/agent-runtimes/AIAgentRuntimeIcon' +import { WorkbenchStartedJobPanel } from 'components/workbenches/common/WorkbenchStartedJobPanel' +import { GqlError } from 'components/utils/Alert' +import { EditableDiv } from 'components/utils/EditableDiv' +import { FillLevelDiv } from 'components/utils/FillLevelDiv' +import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' +import { StretchedFlex } from 'components/utils/StretchedFlex' +import { StackedText } from 'components/utils/table/StackedText' +import { + AgentRuntimeType, + useCreateWorkbenchJobMutation, + useFlowWorkbenchesQuery, + useWorkbenchesQuery, + WorkbenchJobFragment, + WorkbenchTinyFragment, +} from 'generated/graphql' +import { useEffect, useMemo, useState } from 'react' + +import styled, { useTheme } from 'styled-components' +import { mapExistingNodes } from 'utils/graphql' +import { isNonNullable } from 'utils/isNonNullable' + +export function VulnFixButton({ + headerTitle, + initialPrompt, + flowId, + ...props +}: { + headerTitle: string + initialPrompt: string + flowId?: Nullable +} & ButtonProps) { + const { colors } = useTheme() + const [modalOpen, setModalOpen] = useState(false) + const [prompt, setPrompt] = useState(initialPrompt) + const [workbenchJob, setWorkbenchJob] = useState( + null + ) + + return ( + <> + + + ) +} + +function useWorkbenchOptions(flowId?: Nullable) { + const { data: flowData, loading: flowLoading } = useFlowWorkbenchesQuery({ + variables: { id: flowId ?? '' }, + skip: !flowId, + }) + const { data: allWorkbenchesData, loading: allWorkbenchesLoading } = + useWorkbenchesQuery({ + skip: !!flowId, + }) + + const workbenches = useMemo(() => { + if (flowId) return (flowData?.flow?.workbenches ?? []).filter(isNonNullable) + + return mapExistingNodes(allWorkbenchesData?.workbenches) + }, [allWorkbenchesData?.workbenches, flowData?.flow?.workbenches, flowId]) + + return { + workbenches, + loading: flowId ? flowLoading && !flowData : allWorkbenchesLoading, + } +} + +function WorkbenchSelector({ + workbenchId, + setWorkbenchId, + workbenches, + loading, +}: { + workbenchId: Nullable + setWorkbenchId: (id: Nullable) => void + workbenches: WorkbenchTinyFragment[] + loading: boolean +}) { + const [isOpen, setIsOpen] = useState(false) + const selectedWorkbench = workbenches.find( + (workbench) => workbench.id === workbenchId + ) + const loaded = !loading + const SelectedIcon = selectedWorkbench + ? runtimeToIcon[ + selectedWorkbench.agentRuntime?.type ?? AgentRuntimeType.Custom + ] + : null + + return ( + + ) +} + +const PromptInputBoxSC = styled(Card)(({ theme }) => ({ + padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, + '&:focus-within': { + border: theme.borders['outline-focused'], + }, +})) diff --git a/assets/src/components/security/vulnerabilities/VulnReportDetails.tsx b/assets/src/components/security/vulnerabilities/VulnReportDetails.tsx index 77747bde67..6853e69259 100644 --- a/assets/src/components/security/vulnerabilities/VulnReportDetails.tsx +++ b/assets/src/components/security/vulnerabilities/VulnReportDetails.tsx @@ -3,11 +3,14 @@ import { ReturnIcon, Table, useSetBreadcrumbs, + Flex, + Button, + CopyIcon, } from '@pluralsh/design-system' import { ColExpander } from 'components/cd/cluster/pod/PodContainers' import { GqlError } from 'components/utils/Alert' import { StackedText } from 'components/utils/table/StackedText' - +import pluralize from 'pluralize' import { useClusterQuery, useVulnerabilityReportQuery } from 'generated/graphql' import { useCurrentFlow } from 'components/flows/hooks/useCurrentFlow' import { Link, useParams } from 'react-router-dom' @@ -23,11 +26,15 @@ import { ColInstalledVersion, ColPackage, ColSeverity, + ColVulnSelect, } from './VulnReportDetailsTableCols' import { getFlowBreadcrumbs } from 'components/flows/flow/Flow' import { securityVulnReportsCrumbs } from './VulnReports' -import { useMemo } from 'react' -import { AgentRunFormPopupSC } from 'components/ai/agent-runs/AgentRunFixButton' +import { useMemo, useState } from 'react' +import { VulnFixButton } from './VulnFixButton' +import { RowSelectionState } from '@tanstack/react-table' +import ejs from 'ejs' +import vulnBatchPromptTemplate from './vulnerability-batch-prompt.ejs?raw' export function VulnerabilityReportDetails() { const { vulnerabilityReportId, clusterId } = useParams() @@ -52,6 +59,49 @@ export function VulnerabilityReportDetails() { const loading = clusterLoading || flowLoading || reportLoading + const [bulkSelectMode, setBulkSelectMode] = useState(false) + const [rowSelection, setRowSelection] = useState({}) + + const vulnerabilities = useMemo( + () => + (data?.vulnerabilityReport?.vulnerabilities ?? []).filter( + (v): v is NonNullable => !!v + ), + [data?.vulnerabilityReport?.vulnerabilities] + ) + const selectedVulns = useMemo( + () => vulnerabilities.filter((v) => rowSelection[v.id]), + [vulnerabilities, rowSelection] + ) + const batchPrompt = useMemo( + () => + selectedVulns.length > 0 + ? ejs.render(vulnBatchPromptTemplate, { + vulns: selectedVulns, + report: data?.vulnerabilityReport, + }) + : '', + [selectedVulns, data?.vulnerabilityReport] + ) + + const columns = useMemo( + () => [ + ...(bulkSelectMode ? [ColVulnSelect] : []), + ColExpander, + ColID, + ColPackage, + ColInstalledVersion, + ColFixedVersion, + ColSeverity, + ], + [bulkSelectMode] + ) + + const exitBulkSelectMode = () => { + setBulkSelectMode(false) + setRowSelection({}) + } + useSetBreadcrumbs( useMemo( () => @@ -69,60 +119,125 @@ export function VulnerabilityReportDetails() { return ( - } - style={{ flexShrink: 0 }} - /> - } - /> - true} - renderExpanded={({ row }) => ( - + + } + style={{ flexShrink: 0 }} + /> + } + css={{ minWidth: 0, flex: 1 }} + /> + + + +
true} + renderExpanded={({ row }) => ( + + )} + onRowClick={(_, row) => row.getToggleExpandedHandler()()} + emptyStateProps={{ message: 'No vulnerabilities found.' }} + expandedBgColor="fill-zero" + expandedRowType="custom" + reactTableOptions={{ + enableRowSelection: bulkSelectMode, + enableMultiRowSelection: true, + onRowSelectionChange: setRowSelection, + state: { rowSelection }, + }} + /> + {bulkSelectMode && ( + + {selectedVulns.length} selected + v.id).join(',')} + small + headerTitle={`Fix ${pluralize('vulnerability', selectedVulns.length)}`} + initialPrompt={batchPrompt} + flowId={flowData?.flow?.id} + disabled={selectedVulns.length === 0} + > + Fix {pluralize('vulnerability', selectedVulns.length)} + + )} - onRowClick={(_, row) => row.getToggleExpandedHandler()()} - emptyStateProps={{ message: 'No vulnerabilities found.' }} - expandedBgColor="fill-zero" - expandedRowType="custom" - // should probably find a better DS solution for this - {...{ [`td:has(${AgentRunFormPopupSC})`]: { overflow: 'visible' } }} - /> + ) } -const columns = [ - ColExpander, - ColID, - ColPackage, - ColInstalledVersion, - ColFixedVersion, - ColSeverity, -] +const HeaderSC = styled(Flex)(({ theme }) => ({ + alignItems: 'center', + justifyContent: 'space-between', + gap: theme.spacing.medium, +})) + +const TableWrapperSC = styled.div(() => ({ + position: 'relative', + flex: 1, + overflow: 'hidden', +})) + +const BulkSelectionBarSC = styled.div(({ theme }) => ({ + position: 'absolute', + bottom: theme.spacing.large, + left: '50%', + transform: 'translateX(-50%)', + display: 'flex', + alignItems: 'center', + gap: theme.spacing.large, + padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, + backgroundColor: theme.colors['fill-three'], + border: theme.borders['fill-three'], + borderRadius: 12, + boxShadow: theme.boxShadows.moderate, + zIndex: theme.zIndexes.toast, + whiteSpace: 'nowrap', +})) + +const SelectionCountSC = styled.span(({ theme }) => ({ + ...theme.partials.text.body2, + color: theme.colors.text, + paddingRight: theme.spacing.medium, + borderRight: theme.borders['fill-three'], + alignSelf: 'stretch', + display: 'flex', + alignItems: 'center', +})) const WrapperSC = styled.div(({ theme }) => ({ display: 'flex', diff --git a/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs b/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs new file mode 100644 index 0000000000..f4bfe5098e --- /dev/null +++ b/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs @@ -0,0 +1,17 @@ +Create a PR to fix these vulnerabilities: +<% vulns.forEach(function(vuln) { %> +@<%= vuln.title %> +<% }); %> + +They are found in the following docker image: <%= report.artifactUrl %> + +<% vulns.forEach(function(vuln) { %> +<%= vuln.description %> + +Fix Version: <%= vuln.fixedVersion %> +Current Version: <%= vuln.installedVersion %> +Package: <%= vuln.resource %> + +<% }); %> + +Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by these vulnerabilities, ignore them for now. From 8dd3b90c0d5f70896eb567b20141dfb3c3b45fcb Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 14:47:35 +0200 Subject: [PATCH 06/13] update queries --- assets/src/generated/graphql.ts | 19 ++++++++++++------- .../generated/persisted-queries/client.json | 12 ++++++------ assets/src/graph/vulnerabilities.graphql | 5 +++++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/assets/src/generated/graphql.ts b/assets/src/generated/graphql.ts index def9461201..a4d10c8316 100644 --- a/assets/src/generated/graphql.ts +++ b/assets/src/generated/graphql.ts @@ -19363,7 +19363,7 @@ export type FlowVulnerabilityReportsQueryVariables = Exact<{ }>; -export type FlowVulnerabilityReportsQuery = { __typename?: 'RootQueryType', flow?: { __typename?: 'Flow', id: string, vulnerabilityReports?: { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null } | null } | null }; +export type FlowVulnerabilityReportsQuery = { __typename?: 'RootQueryType', flow?: { __typename?: 'Flow', id: string, vulnerabilityReports?: { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null } | null } | null }; export type UpsertFlowMutationVariables = Exact<{ attributes: FlowAttributes; @@ -20702,11 +20702,11 @@ export type CreateInviteMutationVariables = Exact<{ export type CreateInviteMutation = { __typename?: 'RootMutationType', createInvite?: { __typename?: 'Invite', secureId: string } | null }; -export type VulnerabilityReportTinyFragment = { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null }; +export type VulnerabilityReportTinyFragment = { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null }; -export type VulnerabilityReportFragment = { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, vulnerabilities?: Array<{ __typename?: 'Vulnerability', id: string, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null } | null> | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null }; +export type VulnerabilityReportFragment = { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, vulnerabilities?: Array<{ __typename?: 'Vulnerability', id: string, vulnId?: string | null, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null } | null> | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null }; -export type VulnerabilityFragment = { __typename?: 'Vulnerability', id: string, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null }; +export type VulnerabilityFragment = { __typename?: 'Vulnerability', id: string, vulnId?: string | null, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null }; export type VulnerabilityStatisticFragment = { __typename?: 'VulnerabilityStatistic', count: number, grade: VulnReportGrade }; @@ -20714,7 +20714,7 @@ export type ClusterVulnAggregateFragment = { __typename?: 'ClusterVulnAggregate' export type CvssBundleFragment = { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null }; -export type VulnerabilityReportConnectionFragment = { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null }; +export type VulnerabilityReportConnectionFragment = { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null }; export type VulnerabilityReportsQueryVariables = Exact<{ clusters?: InputMaybe> | InputMaybe>; @@ -20726,14 +20726,14 @@ export type VulnerabilityReportsQueryVariables = Exact<{ }>; -export type VulnerabilityReportsQuery = { __typename?: 'RootQueryType', vulnerabilityReports?: { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null } | null }; +export type VulnerabilityReportsQuery = { __typename?: 'RootQueryType', vulnerabilityReports?: { __typename?: 'VulnerabilityReportConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'VulnerabilityReportEdge', node?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null } | null> | null } | null }; export type VulnerabilityReportQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type VulnerabilityReportQuery = { __typename?: 'RootQueryType', vulnerabilityReport?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, vulnerabilities?: Array<{ __typename?: 'Vulnerability', id: string, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null } | null> | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', name: string } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null }; +export type VulnerabilityReportQuery = { __typename?: 'RootQueryType', vulnerabilityReport?: { __typename?: 'VulnerabilityReport', id: string, artifactUrl?: string | null, artifactRepoUrl?: string | null, vulnerabilities?: Array<{ __typename?: 'Vulnerability', id: string, vulnId?: string | null, title?: string | null, description?: string | null, severity?: VulnSeverity | null, score?: number | null, primaryLink?: string | null, links?: Array | null, target?: string | null, class?: string | null, packageType?: string | null, pkgPath?: string | null, publishedDate?: string | null, installedVersion?: string | null, fixedVersion?: string | null, lastModifiedDate?: string | null, cvssSource?: string | null, resource?: string | null, insertedAt?: string | null, updatedAt?: string | null, cvss?: { __typename?: 'CvssBundle', attackComplexity?: VulnSeverity | null, attackVector?: VulnAttackVector | null, availability?: VulnSeverity | null, confidentiality?: VulnSeverity | null, integrity?: VulnSeverity | null, privilegesRequired?: VulnSeverity | null, userInteraction?: VulnUserInteraction | null, nvidia?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null, redhat?: { __typename?: 'Cvss', v2Score?: number | null, v2Vector?: string | null, v3Score?: number | null, v3Vector?: string | null, v40Score?: number | null, v40Vector?: string | null } | null } | null } | null> | null, services?: Array<{ __typename?: 'ServiceVuln', service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string } | null } | null } | null> | null, namespaces?: Array<{ __typename?: 'NamespaceVuln', namespace: string } | null> | null, summary?: { __typename?: 'VulnSummary', criticalCount?: number | null, highCount?: number | null, mediumCount?: number | null, lowCount?: number | null, unknownCount?: number | null, noneCount?: number | null } | null } | null }; export type VulnerabilityStatisticsQueryVariables = Exact<{ clusters?: InputMaybe> | InputMaybe>; @@ -26175,7 +26175,11 @@ export const VulnerabilityReportTinyFragmentDoc = gql` artifactUrl services { service { + id name + cluster { + id + } } } namespaces { @@ -26222,6 +26226,7 @@ export const CvssBundleFragmentDoc = gql` export const VulnerabilityFragmentDoc = gql` fragment Vulnerability on Vulnerability { id + vulnId title description severity diff --git a/assets/src/generated/persisted-queries/client.json b/assets/src/generated/persisted-queries/client.json index 1c3799e786..e59f0acf2d 100644 --- a/assets/src/generated/persisted-queries/client.json +++ b/assets/src/generated/persisted-queries/client.json @@ -1122,10 +1122,10 @@ "name": "FlowPreviewEnvironmentTemplates", "body": "query FlowPreviewEnvironmentTemplates($id: ID!, $first: Int = 100, $after: String) {\n flow(id: $id) {\n id\n previewEnvironmentTemplates(first: $first, after: $after) {\n ...PreviewEnvironmentTemplateConnection\n __typename\n }\n __typename\n }\n}\n\nfragment PreviewEnvironmentTemplateConnection on PreviewEnvironmentTemplateConnection {\n pageInfo {\n ...PageInfo\n __typename\n }\n edges {\n node {\n ...PreviewEnvironmentTemplate\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n __typename\n}\n\nfragment PreviewEnvironmentTemplate on PreviewEnvironmentTemplate {\n id\n name\n commentTemplate\n referenceService {\n id\n name\n cluster {\n id\n __typename\n }\n __typename\n }\n template {\n ...ServiceTemplateWithoutConfiguration\n __typename\n }\n __typename\n}\n\nfragment ServiceTemplateWithoutConfiguration on ServiceTemplate {\n contexts\n dependencies {\n id\n name\n status\n __typename\n }\n git {\n folder\n ref\n __typename\n }\n helm {\n chart\n git {\n folder\n ref\n __typename\n }\n ignoreCrds\n ignoreHooks\n release\n repository {\n name\n namespace\n __typename\n }\n set {\n name\n value\n __typename\n }\n url\n values\n valuesFiles\n version\n __typename\n }\n kustomize {\n path\n enableHelm\n __typename\n }\n name\n namespace\n repository {\n ...GitRepository\n __typename\n }\n repositoryId\n syncConfig {\n createNamespace\n enforceNamespace\n namespaceMetadata {\n annotations\n labels\n __typename\n }\n __typename\n }\n templated\n __typename\n}\n\nfragment GitRepository on GitRepository {\n id\n url\n health\n authMethod\n editable\n error\n insertedAt\n pulledAt\n updatedAt\n urlFormat\n httpsPath\n recurseSubmodules\n __typename\n}" }, - "sha256:516cf69d023ebb1f1444e0a9f007979163fa5853fc7b602b4629781fa678ced3": { + "sha256:3ba0a1fc72dee4d9979e76692346e9416b79688bf40f6957520b6b0c92c94238": { "type": "query", "name": "FlowVulnerabilityReports", - "body": "query FlowVulnerabilityReports($id: ID!, $first: Int = 100, $after: String) {\n flow(id: $id) {\n id\n vulnerabilityReports(first: $first, after: $after) {\n ...VulnerabilityReportConnection\n __typename\n }\n __typename\n }\n}\n\nfragment VulnerabilityReportConnection on VulnerabilityReportConnection {\n pageInfo {\n ...PageInfo\n __typename\n }\n edges {\n node {\n ...VulnerabilityReportTiny\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n name\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}" + "body": "query FlowVulnerabilityReports($id: ID!, $first: Int = 100, $after: String) {\n flow(id: $id) {\n id\n vulnerabilityReports(first: $first, after: $after) {\n ...VulnerabilityReportConnection\n __typename\n }\n __typename\n }\n}\n\nfragment VulnerabilityReportConnection on VulnerabilityReportConnection {\n pageInfo {\n ...PageInfo\n __typename\n }\n edges {\n node {\n ...VulnerabilityReportTiny\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n id\n name\n cluster {\n id\n __typename\n }\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}" }, "sha256:98abe49796f5c9a4b65a17c6ae91b679f2cb93e9ec62eb2c69a2d00c443dbea0": { "type": "mutation", @@ -1812,15 +1812,15 @@ "name": "CreateInvite", "body": "mutation CreateInvite($attributes: InviteAttributes!) {\n createInvite(attributes: $attributes) {\n ...Invite\n __typename\n }\n}\n\nfragment Invite on Invite {\n secureId\n __typename\n}" }, - "sha256:b726ea3e1cb7e68df12f44a5a4eca0286277df51933cb519ea7404f6dc5a9b6f": { + "sha256:f37ba8e83f953710e180c2075fa2d84e3ce319bb45e097c516f24922a5d4c439": { "type": "query", "name": "VulnerabilityReports", - "body": "query VulnerabilityReports($clusters: [ID], $namespaces: [String], $q: String, $grade: VulnReportGrade, $first: Int, $after: String) {\n vulnerabilityReports(\n clusters: $clusters\n namespaces: $namespaces\n q: $q\n grade: $grade\n first: $first\n after: $after\n ) {\n ...VulnerabilityReportConnection\n __typename\n }\n}\n\nfragment VulnerabilityReportConnection on VulnerabilityReportConnection {\n pageInfo {\n ...PageInfo\n __typename\n }\n edges {\n node {\n ...VulnerabilityReportTiny\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n name\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}" + "body": "query VulnerabilityReports($clusters: [ID], $namespaces: [String], $q: String, $grade: VulnReportGrade, $first: Int, $after: String) {\n vulnerabilityReports(\n clusters: $clusters\n namespaces: $namespaces\n q: $q\n grade: $grade\n first: $first\n after: $after\n ) {\n ...VulnerabilityReportConnection\n __typename\n }\n}\n\nfragment VulnerabilityReportConnection on VulnerabilityReportConnection {\n pageInfo {\n ...PageInfo\n __typename\n }\n edges {\n node {\n ...VulnerabilityReportTiny\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n id\n name\n cluster {\n id\n __typename\n }\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}" }, - "sha256:db3c341b13fc8f2f81886bb7f69779ed27974f14c95b9a9e2f2c4139bd0708d1": { + "sha256:db27bda6b3282a0e18dc6a1ff85ba3524e502adee14d43a0acde8f21bdae6f8e": { "type": "query", "name": "VulnerabilityReport", - "body": "query VulnerabilityReport($id: ID!) {\n vulnerabilityReport(id: $id) {\n ...VulnerabilityReport\n __typename\n }\n}\n\nfragment VulnerabilityReport on VulnerabilityReport {\n ...VulnerabilityReportTiny\n vulnerabilities {\n ...Vulnerability\n __typename\n }\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n name\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}\n\nfragment Vulnerability on Vulnerability {\n id\n title\n description\n severity\n score\n primaryLink\n links\n target\n class\n packageType\n pkgPath\n publishedDate\n installedVersion\n fixedVersion\n lastModifiedDate\n cvss {\n ...CvssBundle\n __typename\n }\n cvssSource\n resource\n insertedAt\n updatedAt\n __typename\n}\n\nfragment CvssBundle on CvssBundle {\n attackComplexity\n attackVector\n availability\n confidentiality\n integrity\n privilegesRequired\n userInteraction\n nvidia {\n v2Score\n v2Vector\n v3Score\n v3Vector\n v40Score\n v40Vector\n __typename\n }\n redhat {\n v2Score\n v2Vector\n v3Score\n v3Vector\n v40Score\n v40Vector\n __typename\n }\n __typename\n}" + "body": "query VulnerabilityReport($id: ID!) {\n vulnerabilityReport(id: $id) {\n ...VulnerabilityReport\n __typename\n }\n}\n\nfragment VulnerabilityReport on VulnerabilityReport {\n ...VulnerabilityReportTiny\n vulnerabilities {\n ...Vulnerability\n __typename\n }\n __typename\n}\n\nfragment VulnerabilityReportTiny on VulnerabilityReport {\n id\n artifactUrl\n services {\n service {\n id\n name\n cluster {\n id\n __typename\n }\n __typename\n }\n __typename\n }\n namespaces {\n namespace\n __typename\n }\n summary {\n criticalCount\n highCount\n mediumCount\n lowCount\n unknownCount\n noneCount\n __typename\n }\n artifactRepoUrl\n __typename\n}\n\nfragment Vulnerability on Vulnerability {\n id\n vulnId\n title\n description\n severity\n score\n primaryLink\n links\n target\n class\n packageType\n pkgPath\n publishedDate\n installedVersion\n fixedVersion\n lastModifiedDate\n cvss {\n ...CvssBundle\n __typename\n }\n cvssSource\n resource\n insertedAt\n updatedAt\n __typename\n}\n\nfragment CvssBundle on CvssBundle {\n attackComplexity\n attackVector\n availability\n confidentiality\n integrity\n privilegesRequired\n userInteraction\n nvidia {\n v2Score\n v2Vector\n v3Score\n v3Vector\n v40Score\n v40Vector\n __typename\n }\n redhat {\n v2Score\n v2Vector\n v3Score\n v3Vector\n v40Score\n v40Vector\n __typename\n }\n __typename\n}" }, "sha256:31df5a43f4f42d497b4f3f5b57530027765d148c3005099ff59df1993ec7b42b": { "type": "query", diff --git a/assets/src/graph/vulnerabilities.graphql b/assets/src/graph/vulnerabilities.graphql index 78b255d751..d655cfee73 100644 --- a/assets/src/graph/vulnerabilities.graphql +++ b/assets/src/graph/vulnerabilities.graphql @@ -3,7 +3,11 @@ fragment VulnerabilityReportTiny on VulnerabilityReport { artifactUrl services { service { + id name + cluster { + id + } } } namespaces { @@ -29,6 +33,7 @@ fragment VulnerabilityReport on VulnerabilityReport { fragment Vulnerability on Vulnerability { id + vulnId title description severity From a84ffe793331e032faaa2f6aefd38f12b26d2c25 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 14:50:43 +0200 Subject: [PATCH 07/13] start adding mentions --- .../security/vulnerabilities/VulnFixButton.tsx | 1 + .../vulnerabilities/VulnReportDetails.tsx | 10 ++-------- .../vulnerability-batch-prompt.ejs | 17 ----------------- 3 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs diff --git a/assets/src/components/security/vulnerabilities/VulnFixButton.tsx b/assets/src/components/security/vulnerabilities/VulnFixButton.tsx index ee7d0ed8a3..b4d8138432 100644 --- a/assets/src/components/security/vulnerabilities/VulnFixButton.tsx +++ b/assets/src/components/security/vulnerabilities/VulnFixButton.tsx @@ -157,6 +157,7 @@ function VulnFixForm({ - selectedVulns.length > 0 - ? ejs.render(vulnBatchPromptTemplate, { - vulns: selectedVulns, - report: data?.vulnerabilityReport, - }) - : '', + buildVulnerabilityBatchPrompt(selectedVulns, data?.vulnerabilityReport), [selectedVulns, data?.vulnerabilityReport] ) diff --git a/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs b/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs deleted file mode 100644 index f4bfe5098e..0000000000 --- a/assets/src/components/security/vulnerabilities/vulnerability-batch-prompt.ejs +++ /dev/null @@ -1,17 +0,0 @@ -Create a PR to fix these vulnerabilities: -<% vulns.forEach(function(vuln) { %> -@<%= vuln.title %> -<% }); %> - -They are found in the following docker image: <%= report.artifactUrl %> - -<% vulns.forEach(function(vuln) { %> -<%= vuln.description %> - -Fix Version: <%= vuln.fixedVersion %> -Current Version: <%= vuln.installedVersion %> -Package: <%= vuln.resource %> - -<% }); %> - -Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by these vulnerabilities, ignore them for now. From d7d7228180d622967687bf1fabde0d2f53872ff8 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 14:57:36 +0200 Subject: [PATCH 08/13] add missing assignment in the controller --- .../internal/controller/vulnerabilityreports_controller.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go/deployment-operator/internal/controller/vulnerabilityreports_controller.go b/go/deployment-operator/internal/controller/vulnerabilityreports_controller.go index 360e194a6b..e46da6a454 100644 --- a/go/deployment-operator/internal/controller/vulnerabilityreports_controller.go +++ b/go/deployment-operator/internal/controller/vulnerabilityreports_controller.go @@ -179,6 +179,7 @@ func createVulnAttributes(vulnerabilityReport trivy.VulnerabilityReport, service Class: lo.ToPtr(v.Class), PackageType: lo.ToPtr(v.PackageType), PkgPath: lo.ToPtr(v.PkgPath), + VulnID: lo.ToPtr(v.VulnerabilityID), RepositoryURL: repositoryInfo.URL, AgentRuntime: repositoryInfo.AgentRuntime, } From 9615579a2665163f82884f85c60981e21ce97dff Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 15:02:25 +0200 Subject: [PATCH 09/13] add mentions --- .../input/autocomplete/MentionResults.tsx | 4 + .../autocomplete/PlrlChipMdRenderers.tsx | 35 ++++++++ .../input/autocomplete/mentionTypes.ts | 38 ++++++++ .../vulnerabilities/vulnerabilityMention.ts | 87 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 assets/src/components/security/vulnerabilities/vulnerabilityMention.ts diff --git a/assets/src/components/ai/chatbot/input/autocomplete/MentionResults.tsx b/assets/src/components/ai/chatbot/input/autocomplete/MentionResults.tsx index 9e6650bc42..ff6e111a39 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/MentionResults.tsx +++ b/assets/src/components/ai/chatbot/input/autocomplete/MentionResults.tsx @@ -2,6 +2,7 @@ import { ClusterIcon, GitPullIcon, StackIcon, + WarningShieldIcon, WorkbenchIcon, } from '@pluralsh/design-system' import { ReactNode, useEffect, useRef } from 'react' @@ -17,6 +18,7 @@ const itemToIcon: Record = { [MentionKind.Service]: , [MentionKind.Stack]: , [MentionKind.Skill]: , + [MentionKind.Vulnerability]: , } function subtitleForItem(item: ChipAttrs) { @@ -29,6 +31,8 @@ function subtitleForItem(item: ChipAttrs) { return item.type ? stackTypeLabel(item.type as StackType) : undefined case MentionKind.Skill: return item.description + case MentionKind.Vulnerability: + return item.resource ?? item['vuln-id'] ?? undefined } } diff --git a/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx b/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx index 09d62ace10..039b3d29e9 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx +++ b/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx @@ -2,6 +2,7 @@ import { GitPullIcon, StackIcon, Tooltip, + WarningShieldIcon, WorkbenchIcon, WrapWithIf, } from '@pluralsh/design-system' @@ -139,11 +140,40 @@ function PlrlSkillChip(props: RenderedChipProps) { ) } +function PlrlVulnerabilityChip( + props: RenderedChipProps +) { + const label = + chipDisplayText(MentionKind.Vulnerability, { + 'item-name': props['item-name'], + severity: props.severity, + }) || + props['item-name'] || + props['item-id'] + const link = props['primary-link'] + + return ( + + } + > + }>{label} + + ) +} + export const plrlChipComponents = { [MentionKind.Cluster]: PlrlClusterChip, [MentionKind.Service]: PlrlServiceChip, [MentionKind.Stack]: PlrlStackChip, [MentionKind.Skill]: PlrlSkillChip, + [MentionKind.Vulnerability]: PlrlVulnerabilityChip, } satisfies { [K in MentionKind]: ComponentType> } @@ -169,3 +199,8 @@ const ChipLinkSC = styled(Link)(({ theme }) => ({ textDecoration: 'none', '&:hover > *': { background: theme.colors['fill-two-hover'] }, })) + +const ChipExternalLinkSC = styled.a(({ theme }) => ({ + textDecoration: 'none', + '&:hover > *': { background: theme.colors['fill-two-hover'] }, +})) diff --git a/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts b/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts index 8310491ef3..95c19798d5 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts +++ b/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts @@ -5,6 +5,7 @@ export enum MentionKind { Service = 'plrl-service', Stack = 'plrl-stack', Skill = 'plrl-skill', + Vulnerability = 'plrl-vulnerability', } export const PLRL_CHIP_TAG_NAMES: readonly MentionKind[] = @@ -15,6 +16,7 @@ export const KIND_LABELS: Record = { [MentionKind.Service]: 'service', [MentionKind.Stack]: 'stack', [MentionKind.Skill]: 'skill', + [MentionKind.Vulnerability]: 'vulnerability', } // --- Triggers --- @@ -57,11 +59,27 @@ export type SkillChipAttrs = BaseChipAttrs & { subagents?: string } +export type VulnerabilityChipAttrs = + BaseChipAttrs & { + severity?: Nullable + 'vuln-id'?: Nullable + title?: Nullable + 'report-id'?: string + 'service-ids'?: string + 'service-names'?: string + 'cluster-ids'?: string + resource?: Nullable + 'installed-version'?: Nullable + 'fixed-version'?: Nullable + 'primary-link'?: Nullable + } + export type ChipAttrsByKind = { [MentionKind.Cluster]: ClusterChipAttrs [MentionKind.Service]: ServiceChipAttrs [MentionKind.Stack]: StackChipAttrs [MentionKind.Skill]: SkillChipAttrs + [MentionKind.Vulnerability]: VulnerabilityChipAttrs } export type ChipAttrs = ChipAttrsByKind[MentionKind] @@ -88,6 +106,21 @@ export const CHIP_ATTRIBUTE_SCHEMA: { ], [MentionKind.Stack]: ['item-id', 'item-name', 'type'], [MentionKind.Skill]: ['item-id', 'item-name', 'description', 'subagents'], + [MentionKind.Vulnerability]: [ + 'item-id', + 'item-name', + 'severity', + 'vuln-id', + 'title', + 'report-id', + 'service-ids', + 'service-names', + 'cluster-ids', + 'resource', + 'installed-version', + 'fixed-version', + 'primary-link', + ], } type ChipAttrRecord = Record @@ -111,6 +144,11 @@ export function chipDisplayText( if (ch && name) return `${name} (${ch})` return name } + case MentionKind.Vulnerability: { + const severity = attrs.severity + if (severity && name) return `${name} (${severity})` + return name + } case MentionKind.Stack: default: return name diff --git a/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts new file mode 100644 index 0000000000..23dc8b7660 --- /dev/null +++ b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts @@ -0,0 +1,87 @@ +import { MentionKind } from 'components/ai/chatbot/input/autocomplete/mentionTypes' +import { + VulnerabilityFragment, + VulnerabilityReportFragment, +} from 'generated/graphql' +import escape from 'lodash/escape' +import { isNonNullable } from 'utils/isNonNullable' + +export function vulnerabilityIdentifier(vuln: VulnerabilityFragment): string { + if (vuln.vulnId?.trim()) return vuln.vulnId.trim() + return vuln.resource ?? vuln.id +} + +function xmlAttr(name: string, value: Nullable): string { + if (value == null || value === '') return '' + return `${name}="${escape(value)}"` +} + +export function serializeVulnerabilityMention( + vuln: VulnerabilityFragment, + report: VulnerabilityReportFragment +): string { + const identifier = vulnerabilityIdentifier(vuln) + const services = (report.services ?? []).filter(isNonNullable) + const serviceIds = services + .map((entry) => entry.service?.id) + .filter(isNonNullable) + .join(',') + const serviceNames = services + .map((entry) => entry.service?.name) + .filter(isNonNullable) + .join(',') + const clusterIds = services + .map((entry) => entry.service?.cluster?.id) + .filter(isNonNullable) + .join(',') + + const attrs = [ + xmlAttr('item-id', vuln.id), + xmlAttr('item-name', identifier), + xmlAttr('severity', vuln.severity ?? undefined), + xmlAttr('vuln-id', vuln.vulnId ?? undefined), + xmlAttr('title', vuln.title ?? undefined), + xmlAttr('report-id', report.id), + xmlAttr('service-ids', serviceIds || undefined), + xmlAttr('service-names', serviceNames || undefined), + xmlAttr('cluster-ids', clusterIds || undefined), + xmlAttr('resource', vuln.resource ?? undefined), + xmlAttr('installed-version', vuln.installedVersion ?? undefined), + xmlAttr('fixed-version', vuln.fixedVersion ?? undefined), + xmlAttr('primary-link', vuln.primaryLink ?? undefined), + ].filter(Boolean) + + return `<${MentionKind.Vulnerability}${attrs.length ? ' ' + attrs.join(' ') : ''}>` +} + +export function buildVulnerabilityBatchPrompt( + vulns: VulnerabilityFragment[], + report: Nullable +): string { + if (!vulns.length || !report) return '' + + const mentions = vulns + .map((vuln) => serializeVulnerabilityMention(vuln, report)) + .join('\n') + + const details = vulns + .map((vuln) => + [ + vuln.description, + '', + `Fix Version: ${vuln.fixedVersion ?? ''}`, + `Current Version: ${vuln.installedVersion ?? ''}`, + `Package: ${vuln.resource ?? ''}`, + ].join('\n') + ) + .join('\n\n') + + return `Create a PR to fix these vulnerabilities: +${mentions} + +They are found in the following docker image: ${report.artifactUrl ?? ''} + +${details} + +Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by these vulnerabilities, ignore them for now.` +} From a859cea08a1fc4149eea85b941f545142ff49523 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 15:03:18 +0200 Subject: [PATCH 10/13] add regex fallback for identifier --- .../vulnerabilities/vulnerabilityMention.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts index 23dc8b7660..67332ce706 100644 --- a/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts +++ b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts @@ -6,8 +6,21 @@ import { import escape from 'lodash/escape' import { isNonNullable } from 'utils/isNonNullable' +const VULN_IDENTIFIER_PATTERN = /(?:CVE|GHSA)-[\w-]+/i + export function vulnerabilityIdentifier(vuln: VulnerabilityFragment): string { if (vuln.vulnId?.trim()) return vuln.vulnId.trim() + + for (const candidate of [ + vuln.primaryLink, + ...(vuln.links ?? []), + vuln.title, + ]) { + if (!candidate) continue + const match = candidate.match(VULN_IDENTIFIER_PATTERN)?.[0] + if (match) return match.toUpperCase() + } + return vuln.resource ?? vuln.id } @@ -39,7 +52,7 @@ export function serializeVulnerabilityMention( xmlAttr('item-id', vuln.id), xmlAttr('item-name', identifier), xmlAttr('severity', vuln.severity ?? undefined), - xmlAttr('vuln-id', vuln.vulnId ?? undefined), + xmlAttr('vuln-id', vuln.vulnId ?? identifier), xmlAttr('title', vuln.title ?? undefined), xmlAttr('report-id', report.id), xmlAttr('service-ids', serviceIds || undefined), From ddd1f886a81232fc2d96f677ec01365d86deb1a8 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 15:24:00 +0200 Subject: [PATCH 11/13] refactor --- .../input/autocomplete/mentionTypes.ts | 2 + .../vulnerabilities/VulnDetailExpanded.tsx | 37 +++--- .../{VulnFixButton.tsx => VulnFixModal.tsx} | 122 ++++++++---------- .../vulnerabilities/VulnReportDetails.tsx | 48 +++++-- .../vulnerabilities/vulnerability-prompt.ejs | 15 --- .../vulnerabilities/vulnerabilityMention.ts | 24 +--- 6 files changed, 115 insertions(+), 133 deletions(-) rename assets/src/components/security/vulnerabilities/{VulnFixButton.tsx => VulnFixModal.tsx} (77%) delete mode 100644 assets/src/components/security/vulnerabilities/vulnerability-prompt.ejs diff --git a/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts b/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts index 95c19798d5..9a40467699 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts +++ b/assets/src/components/ai/chatbot/input/autocomplete/mentionTypes.ts @@ -72,6 +72,7 @@ export type VulnerabilityChipAttrs = 'installed-version'?: Nullable 'fixed-version'?: Nullable 'primary-link'?: Nullable + description?: Nullable } export type ChipAttrsByKind = { @@ -120,6 +121,7 @@ export const CHIP_ATTRIBUTE_SCHEMA: { 'installed-version', 'fixed-version', 'primary-link', + 'description', ], } diff --git a/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx b/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx index f35d4694cc..a03ad7430a 100644 --- a/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx +++ b/assets/src/components/security/vulnerabilities/VulnDetailExpanded.tsx @@ -1,4 +1,10 @@ -import { Chip, ChipProps, Flex } from '@pluralsh/design-system' +import { + AiSparkleFilledIcon, + Button, + Chip, + ChipProps, + Flex, +} from '@pluralsh/design-system' import styled, { useTheme } from 'styled-components' import { Row } from '@tanstack/react-table' @@ -10,30 +16,17 @@ import { CvssBundle, VulnAttackVector, VulnerabilityFragment, - VulnerabilityReportFragment, } from 'generated/graphql' -import { useCurrentFlow } from 'components/flows/hooks/useCurrentFlow' -import { VulnFixButton } from './VulnFixButton' -import { useMemo } from 'react' -import ejs from 'ejs' -import vulnPromptTemplate from './vulnerability-prompt.ejs?raw' export function VulnDetailExpanded({ row, - parentReport, + onFixVulnerability, }: { row: Row - parentReport: Nullable + onFixVulnerability: (vuln: VulnerabilityFragment) => void }) { const { original: v } = row - const { flowData } = useCurrentFlow() - - const initialPrompt = useMemo( - () => ejs.render(vulnPromptTemplate, { vuln: v, report: parentReport }), - [v, parentReport] - ) - if (!v.title && !v.description && !v.cvssSource && !v.score && !v.cvss) return No details available. @@ -48,13 +41,15 @@ export function VulnDetailExpanded({ secondPartialType="body2" css={{ maxWidth: 900 }} /> - } + onClick={(e) => { + e.stopPropagation() + onFixVulnerability(v) + }} > Fix vulnerability - + void + vulnCount: number initialPrompt: string flowId?: Nullable -} & ButtonProps) { +}) { const { colors } = useTheme() - const [modalOpen, setModalOpen] = useState(false) + const headerTitle = `Fix ${pluralize('vulnerability', vulnCount, vulnCount !== 1)}` const [prompt, setPrompt] = useState(initialPrompt) const [workbenchJob, setWorkbenchJob] = useState( null ) return ( - <> - )} + {fixVulns && ( + setFixVulns(null)} + vulnCount={fixVulns.length} + initialPrompt={fixPrompt} + flowId={flowData?.flow?.id} + /> + )} ) } diff --git a/assets/src/components/security/vulnerabilities/vulnerability-prompt.ejs b/assets/src/components/security/vulnerabilities/vulnerability-prompt.ejs deleted file mode 100644 index 0c4fe24583..0000000000 --- a/assets/src/components/security/vulnerabilities/vulnerability-prompt.ejs +++ /dev/null @@ -1,15 +0,0 @@ -Security scanners have found the following vulnerability in our cluster: - -<%= vuln.title %> - -<%= vuln.description %> - -It is found in the following docker image: <%= report.artifactUrl %> - -Here are the recommendations for potential upgrades: - -Fix Version: <%= vuln.fixedVersion %> -Current Version: <%= vuln.installedVersion %> -Package: <%= vuln.resource %> - -Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by this vulnerability, ignore them for now. diff --git a/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts index 67332ce706..0f3c88f73c 100644 --- a/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts +++ b/assets/src/components/security/vulnerabilities/vulnerabilityMention.ts @@ -5,6 +5,7 @@ import { } from 'generated/graphql' import escape from 'lodash/escape' import { isNonNullable } from 'utils/isNonNullable' +import pluralize from 'pluralize' const VULN_IDENTIFIER_PATTERN = /(?:CVE|GHSA)-[\w-]+/i @@ -62,39 +63,26 @@ export function serializeVulnerabilityMention( xmlAttr('installed-version', vuln.installedVersion ?? undefined), xmlAttr('fixed-version', vuln.fixedVersion ?? undefined), xmlAttr('primary-link', vuln.primaryLink ?? undefined), + xmlAttr('description', vuln.description?.trim()), ].filter(Boolean) return `<${MentionKind.Vulnerability}${attrs.length ? ' ' + attrs.join(' ') : ''}>` } -export function buildVulnerabilityBatchPrompt( +export function buildVulnerabilityFixPrompt( vulns: VulnerabilityFragment[], report: Nullable ): string { if (!vulns.length || !report) return '' + const multiple = vulns.length > 1 const mentions = vulns .map((vuln) => serializeVulnerabilityMention(vuln, report)) .join('\n') - const details = vulns - .map((vuln) => - [ - vuln.description, - '', - `Fix Version: ${vuln.fixedVersion ?? ''}`, - `Current Version: ${vuln.installedVersion ?? ''}`, - `Package: ${vuln.resource ?? ''}`, - ].join('\n') - ) - .join('\n\n') + return `Security scanners found ${pluralize('vulnerability', vulns.length, vulns.length !== 1)} in the ${report.artifactUrl ?? ''} Docker image: - return `Create a PR to fix these vulnerabilities: ${mentions} -They are found in the following docker image: ${report.artifactUrl ?? ''} - -${details} - -Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other docker images affected by these vulnerabilities, ignore them for now.` +Create a pull request to fix ${multiple ? 'them' : 'it'}. Do your best to upgrade all necessary dependencies and confirm that the code still compiles (if there are compilation instructions available) and introduces no regressions in the process. If there are other Docker images affected by ${multiple ? 'these vulnerabilities' : 'this vulnerability'}, ignore them for now.` } From 58cad96a91e9ef55408c6d8071766be562ac007e Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 8 Jul 2026 15:29:00 +0200 Subject: [PATCH 12/13] add tooltips --- .../autocomplete/EditableSkillChipTooltip.tsx | 81 +++++++++++++++---- .../autocomplete/PlrlChipMdRenderers.tsx | 40 +++++++-- .../VulnerabilityChipTooltipLabel.tsx | 68 ++++++++++++++++ .../security/vulnerabilities/VulnFixModal.tsx | 6 +- 4 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 assets/src/components/ai/chatbot/input/autocomplete/VulnerabilityChipTooltipLabel.tsx diff --git a/assets/src/components/ai/chatbot/input/autocomplete/EditableSkillChipTooltip.tsx b/assets/src/components/ai/chatbot/input/autocomplete/EditableSkillChipTooltip.tsx index 2fcd3bd103..48722f872b 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/EditableSkillChipTooltip.tsx +++ b/assets/src/components/ai/chatbot/input/autocomplete/EditableSkillChipTooltip.tsx @@ -6,16 +6,25 @@ import { } from 'components/utils/contentEditableChips' import { forwardRef, + ReactNode, RefObject, useEffect, useLayoutEffect, useState, } from 'react' import { MentionKind } from './mentionTypes' +import { + hasVulnerabilityChipTooltip, + VulnerabilityChipTooltipLabel, + vulnerabilityChipTooltipText, +} from './VulnerabilityChipTooltipLabel' -const SKILL_SELECTOR = `[${CHIP_DATA_ATTR}="true"][${CHIP_TAG_ATTR}="${MentionKind.Skill}"]` +const PLRL_CHIP_SELECTOR = [MentionKind.Skill, MentionKind.Vulnerability] + .map((kind) => `[${CHIP_DATA_ATTR}="true"][${CHIP_TAG_ATTR}="${kind}"]`) + .join(',') const DESC_ATTR = `${CHIP_ATTR_PREFIX}description` const NAME_ATTR = `${CHIP_ATTR_PREFIX}item-name` +const TITLE_ATTR = `${CHIP_ATTR_PREFIX}title` const TIP_ON_INPUT_STYLE = { maxWidth: 500, @@ -23,18 +32,45 @@ const TIP_ON_INPUT_STYLE = { zIndex: 11_000, } -/** Skill chip + tooltip label, resolved from pointer event target. */ -function skillHintFromTarget( +function chipHintFromTarget( eventTarget: EventTarget | null -): { chip: HTMLElement; label: string } | null { +): { chip: HTMLElement; label: ReactNode; hintKey: string } | null { if (!(eventTarget instanceof Element)) return null - const chip = eventTarget.closest(SKILL_SELECTOR) + const chip = eventTarget.closest(PLRL_CHIP_SELECTOR) if (!(chip instanceof HTMLElement)) return null - const label = - chip.getAttribute(DESC_ATTR)?.trim() || - chip.getAttribute(NAME_ATTR)?.trim() || - null - return label ? { chip, label } : null + + const tag = chip.getAttribute(CHIP_TAG_ATTR) + const itemId = chip.getAttribute(`${CHIP_ATTR_PREFIX}item-id`) ?? '' + const description = chip.getAttribute(DESC_ATTR)?.trim() + const name = chip.getAttribute(NAME_ATTR)?.trim() + const title = chip.getAttribute(TITLE_ATTR)?.trim() + + switch (tag) { + case MentionKind.Skill: { + const label = description || name + if (!label) return null + return { + chip, + label, + hintKey: `${tag}|${itemId}|${description ?? ''}|${name ?? ''}`, + } + } + case MentionKind.Vulnerability: { + if (!hasVulnerabilityChipTooltip({ title, description })) return null + return { + chip, + label: ( + + ), + hintKey: `${tag}|${itemId}|${title ?? ''}|${description ?? ''}`, + } + } + default: + return null + } } const ChipAnchorStub = forwardRef( @@ -81,19 +117,21 @@ export function EditableSkillChipTooltip({ }: { containerRef: RefObject }) { - const [hint, setHint] = useState<{ chip: HTMLElement; label: string } | null>( - null - ) + const [hint, setHint] = useState<{ + chip: HTMLElement + label: ReactNode + hintKey: string + } | null>(null) useEffect(() => { const editorRoot = containerRef.current if (!editorRoot) return const onPointerMove = (event: PointerEvent) => { - const next = skillHintFromTarget(event.target) + const next = chipHintFromTarget(event.target) setHint((current) => { if (!next) return null - return current?.chip === next.chip && current.label === next.label + return current?.chip === next.chip && current.hintKey === next.hintKey ? current : next }) @@ -111,13 +149,24 @@ export function EditableSkillChipTooltip({ if (!hint) return null + const textValue = + hint.chip.getAttribute(CHIP_TAG_ATTR) === MentionKind.Vulnerability + ? vulnerabilityChipTooltipText({ + title: hint.chip.getAttribute(TITLE_ATTR) ?? undefined, + description: hint.chip.getAttribute(DESC_ATTR) ?? undefined, + }) + : typeof hint.label === 'string' + ? hint.label + : undefined + return ( diff --git a/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx b/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx index 039b3d29e9..e29a12f550 100644 --- a/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx +++ b/assets/src/components/ai/chatbot/input/autocomplete/PlrlChipMdRenderers.tsx @@ -23,6 +23,11 @@ import { import { getStacksAbsPath } from 'routes/stacksRoutesConsts' import styled from 'styled-components' import { chipDisplayText, ChipAttrsByKind, MentionKind } from './mentionTypes' +import { + hasVulnerabilityChipTooltip, + VulnerabilityChipTooltipLabel, + vulnerabilityChipTooltipText, +} from './VulnerabilityChipTooltipLabel' // span-only so chips can safely render inside markdown

(as opposed to our DS chip) type ChipBodyProps = { @@ -151,19 +156,42 @@ function PlrlVulnerabilityChip( props['item-name'] || props['item-id'] const link = props['primary-link'] + const title = props.title + const description = props.description + const hasTooltip = hasVulnerabilityChipTooltip({ title, description }) + const chip = ( + }>{label} + ) return ( + } + textValue={vulnerabilityChipTooltipText({ title, description })} + placement="top" + style={{ maxWidth: 500, overflowWrap: 'break-word' }} /> } > - }>{label} + + } + > + {chip} + ) } diff --git a/assets/src/components/ai/chatbot/input/autocomplete/VulnerabilityChipTooltipLabel.tsx b/assets/src/components/ai/chatbot/input/autocomplete/VulnerabilityChipTooltipLabel.tsx new file mode 100644 index 0000000000..da71f13805 --- /dev/null +++ b/assets/src/components/ai/chatbot/input/autocomplete/VulnerabilityChipTooltipLabel.tsx @@ -0,0 +1,68 @@ +import styled from 'styled-components' + +export function hasVulnerabilityChipTooltip({ + title, + description, +}: { + title?: Nullable + description?: Nullable +}) { + return !!(title?.trim() || description?.trim()) +} + +export function vulnerabilityChipTooltipText({ + title, + description, +}: { + title?: Nullable + description?: Nullable +}) { + return [title?.trim(), description?.trim()].filter(Boolean).join('\n\n') +} + +export function VulnerabilityChipTooltipLabel({ + title, + description, +}: { + title?: Nullable + description?: Nullable +}) { + const titleText = title?.trim() + const descriptionText = description?.trim() + if (!titleText && !descriptionText) return null + + return ( + + {titleText && {titleText}} + {descriptionText && {descriptionText}} + + ) +} + +const lineClamp = (lines: number) => ({ + display: '-webkit-box', + WebkitBoxOrient: 'vertical' as const, + WebkitLineClamp: lines, + lineClamp: lines, + overflow: 'hidden', + wordBreak: 'break-word' as const, +}) + +const WrapSC = styled.div(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing.xxsmall, + minWidth: 0, +})) + +const TitleSC = styled.div(({ theme }) => ({ + ...theme.partials.text.body2Bold, + color: theme.colors.text, + ...lineClamp(1), +})) + +const DescriptionSC = styled.div(({ theme }) => ({ + ...theme.partials.text.body2, + color: theme.colors.text, + ...lineClamp(4), +})) diff --git a/assets/src/components/security/vulnerabilities/VulnFixModal.tsx b/assets/src/components/security/vulnerabilities/VulnFixModal.tsx index 53c5f37daf..fc19331c56 100644 --- a/assets/src/components/security/vulnerabilities/VulnFixModal.tsx +++ b/assets/src/components/security/vulnerabilities/VulnFixModal.tsx @@ -13,6 +13,7 @@ import { } from '@pluralsh/design-system' import { runtimeToIcon } from 'components/settings/ai/agent-runtimes/AIAgentRuntimeIcon' import { WorkbenchStartedJobPanel } from 'components/workbenches/common/WorkbenchStartedJobPanel' +import { EditableSkillChipTooltip } from 'components/ai/chatbot/input/autocomplete/EditableSkillChipTooltip' import { GqlError } from 'components/utils/Alert' import { EditableDiv } from 'components/utils/EditableDiv' import { FillLevelDiv } from 'components/utils/FillLevelDiv' @@ -28,7 +29,7 @@ import { WorkbenchJobFragment, WorkbenchTinyFragment, } from 'generated/graphql' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import styled, { useTheme } from 'styled-components' import { mapExistingNodes } from 'utils/graphql' import { isNonNullable } from 'utils/isNonNullable' @@ -131,6 +132,7 @@ function VulnFixForm({ const canSubmit = !!workbenchId && !!prompt.trim() && !mutationLoading && !loading + const promptInputRef = useRef(null) return ( <> @@ -147,6 +149,7 @@ function VulnFixForm({ +