Skip to content

Commit 7a9b4f8

Browse files
committed
fix: option to update existing vuln analysis entry with new analysis
1 parent a91db65 commit 7a9b4f8

File tree

4 files changed

+237
-26
lines changed

4 files changed

+237
-26
lines changed

ui/src/components/CreateVulnAnalysisModal.vue

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import { ref, computed, watch } from 'vue'
104104
import { NModal, NForm, NFormItem, NInput, NSelect, NButton, NSpace, NTag, NDynamicInput, useNotification, FormInst, FormRules } from 'naive-ui'
105105
import gql from 'graphql-tag'
106106
import graphqlClient from '@/utils/graphql'
107+
import { ANALYSIS_STATE_OPTIONS, ANALYSIS_JUSTIFICATION_OPTIONS } from '@/constants/vulnAnalysis'
107108
108109
interface Props {
109110
show: boolean
@@ -154,11 +155,6 @@ const formData = ref({
154155
details: ''
155156
})
156157
157-
const locationTypeOptions = [
158-
{ label: 'PURL', value: 'PURL' },
159-
{ label: 'Code Point', value: 'CODE_POINT' }
160-
]
161-
162158
const scopeOptions = computed(() => {
163159
// If availableScopesOnly is provided, filter to only those scopes
164160
if (props.availableScopesOnly && props.availableScopesOnly.length > 0) {
@@ -190,24 +186,8 @@ const scopeOptions = computed(() => {
190186
return options
191187
})
192188
193-
const stateOptions = [
194-
{ label: 'Exploitable', value: 'EXPLOITABLE' },
195-
{ label: 'In Triage', value: 'IN_TRIAGE' },
196-
{ label: 'False Positive', value: 'FALSE_POSITIVE' },
197-
{ label: 'Not Affected', value: 'NOT_AFFECTED' }
198-
]
199-
200-
const justificationOptions = [
201-
{ label: 'Code Not Present', value: 'CODE_NOT_PRESENT' },
202-
{ label: 'Code Not Reachable', value: 'CODE_NOT_REACHABLE' },
203-
{ label: 'Requires Configuration', value: 'REQUIRES_CONFIGURATION' },
204-
{ label: 'Requires Dependency', value: 'REQUIRES_DEPENDENCY' },
205-
{ label: 'Requires Environment', value: 'REQUIRES_ENVIRONMENT' },
206-
{ label: 'Protected by Compiler', value: 'PROTECTED_BY_COMPILER' },
207-
{ label: 'Protected at Runtime', value: 'PROTECTED_AT_RUNTIME' },
208-
{ label: 'Protected at Perimeter', value: 'PROTECTED_AT_PERIMETER' },
209-
{ label: 'Protected by Mitigating Control', value: 'PROTECTED_BY_MITIGATING_CONTROL' }
210-
]
189+
const stateOptions = ANALYSIS_STATE_OPTIONS
190+
const justificationOptions = ANALYSIS_JUSTIFICATION_OPTIONS
211191
212192
const rules: FormRules = {
213193
findingId: [{ required: true, message: 'Finding ID is required', trigger: 'blur' }],
@@ -292,7 +272,7 @@ const handleSubmit = async () => {
292272
await formRef.value.validate()
293273
submitting.value = true
294274
295-
const input = {
275+
const input: any = {
296276
org: props.orgUuid,
297277
location: formData.value.location,
298278
locationType: formData.value.locationType,
@@ -302,9 +282,12 @@ const handleSubmit = async () => {
302282
scope: formData.value.scope,
303283
scopeUuid: formData.value.scopeUuid,
304284
state: formData.value.state,
305-
justification: formData.value.justification,
306285
details: formData.value.details || null
307286
}
287+
288+
if (formData.value.justification) {
289+
input.justification = formData.value.justification
290+
}
308291
309292
const response = await graphqlClient.mutate({
310293
mutation: gql`
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<template>
2+
<n-modal
3+
v-model:show="isVisible"
4+
title="Update Vulnerability Analysis"
5+
preset="dialog"
6+
:show-icon="false"
7+
style="width: 60%;"
8+
>
9+
<n-form ref="formRef" :model="formData" :rules="rules">
10+
<n-form-item label="Current State" path="state">
11+
<n-select
12+
v-model:value="formData.state"
13+
:options="stateOptions"
14+
placeholder="Select analysis state"
15+
/>
16+
</n-form-item>
17+
18+
<n-form-item label="Justification" path="justification">
19+
<n-select
20+
v-model:value="formData.justification"
21+
:options="justificationOptions"
22+
placeholder="Select justification (optional)"
23+
clearable
24+
/>
25+
</n-form-item>
26+
27+
<n-form-item label="Details" path="details">
28+
<n-input
29+
v-model:value="formData.details"
30+
type="textarea"
31+
placeholder="Additional details (optional)"
32+
:rows="3"
33+
/>
34+
</n-form-item>
35+
</n-form>
36+
37+
<template #action>
38+
<n-space>
39+
<n-button @click="handleCancel">Cancel</n-button>
40+
<n-button type="primary" @click="handleSubmit" :loading="submitting">
41+
Update Analysis
42+
</n-button>
43+
</n-space>
44+
</template>
45+
</n-modal>
46+
</template>
47+
48+
<script lang="ts">
49+
export default {
50+
name: 'UpdateVulnAnalysisModal'
51+
}
52+
</script>
53+
54+
<script lang="ts" setup>
55+
import { ref, computed, watch } from 'vue'
56+
import { NModal, NForm, NFormItem, NSelect, NInput, NButton, NSpace, useNotification, FormInst, FormRules } from 'naive-ui'
57+
import gql from 'graphql-tag'
58+
import graphqlClient from '@/utils/graphql'
59+
import { ANALYSIS_STATE_OPTIONS, ANALYSIS_JUSTIFICATION_OPTIONS } from '@/constants/vulnAnalysis'
60+
61+
interface Props {
62+
show: boolean
63+
analysisRecord: any
64+
}
65+
66+
const props = defineProps<Props>()
67+
68+
const emit = defineEmits<{
69+
'update:show': [value: boolean]
70+
'updated': [analysis: any]
71+
}>()
72+
73+
const notification = useNotification()
74+
const formRef = ref<FormInst | null>(null)
75+
const submitting = ref(false)
76+
77+
const isVisible = computed({
78+
get: () => props.show,
79+
set: (value: boolean) => emit('update:show', value)
80+
})
81+
82+
const formData = ref({
83+
state: 'IN_TRIAGE',
84+
justification: null as string | null,
85+
details: ''
86+
})
87+
88+
const stateOptions = ANALYSIS_STATE_OPTIONS
89+
const justificationOptions = ANALYSIS_JUSTIFICATION_OPTIONS
90+
91+
const rules: FormRules = {
92+
state: [{ required: true, message: 'Analysis state is required', trigger: 'change' }]
93+
}
94+
95+
// Watch for changes in the analysis record and populate form
96+
watch(() => props.analysisRecord, (newRecord) => {
97+
if (newRecord) {
98+
formData.value.state = newRecord.analysisState || 'IN_TRIAGE'
99+
formData.value.justification = newRecord.analysisJustification || null
100+
formData.value.details = ''
101+
}
102+
}, { immediate: true })
103+
104+
const handleCancel = () => {
105+
isVisible.value = false
106+
}
107+
108+
const handleSubmit = async () => {
109+
if (!formRef.value) return
110+
111+
try {
112+
await formRef.value.validate()
113+
submitting.value = true
114+
115+
const input: any = {
116+
analysisUuid: props.analysisRecord.uuid,
117+
state: formData.value.state,
118+
details: formData.value.details || null
119+
}
120+
121+
if (formData.value.justification) {
122+
input.justification = formData.value.justification
123+
}
124+
125+
const response = await graphqlClient.mutate({
126+
mutation: gql`
127+
mutation updateVulnAnalysis($analysis: UpdateVulnAnalysisInput!) {
128+
updateVulnAnalysis(analysis: $analysis) {
129+
uuid
130+
org
131+
location
132+
locationType
133+
findingId
134+
findingAliases
135+
findingType
136+
scope
137+
scopeUuid
138+
analysisState
139+
analysisJustification
140+
analysisHistory {
141+
state
142+
justification
143+
details
144+
createdDate
145+
}
146+
}
147+
}
148+
`,
149+
variables: { analysis: input }
150+
})
151+
152+
notification.success({
153+
content: 'Vulnerability analysis updated successfully',
154+
duration: 3000
155+
})
156+
157+
emit('updated', response.data.updateVulnAnalysis)
158+
isVisible.value = false
159+
} catch (error: any) {
160+
console.error('Error updating vulnerability analysis:', error)
161+
notification.error({
162+
content: 'Failed to update vulnerability analysis',
163+
meta: error.message || 'Unknown error',
164+
duration: 5000
165+
})
166+
} finally {
167+
submitting.value = false
168+
}
169+
}
170+
</script>

ui/src/components/ViewVulnAnalysisModal.vue

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
</n-spin>
2222
</n-modal>
2323

24+
<update-vuln-analysis-modal
25+
v-model:show="showUpdateAnalysisModal"
26+
:analysis-record="selectedAnalysisRecord"
27+
@updated="onAnalysisUpdated"
28+
/>
29+
2430
<create-vuln-analysis-modal
2531
v-model:show="showCreateAnalysisModal"
2632
:finding-row="findingRowData"
@@ -42,10 +48,12 @@ export default {
4248

4349
<script lang="ts" setup>
4450
import { ref, computed, watch, h } from 'vue'
45-
import { NModal, NSpin, NDataTable, NTag, NSpace, NButton, useNotification, DataTableColumns } from 'naive-ui'
51+
import { NModal, NSpin, NDataTable, NTag, NSpace, NButton, NIcon, useNotification, DataTableColumns } from 'naive-ui'
4652
import gql from 'graphql-tag'
4753
import graphqlClient from '@/utils/graphql'
4854
import CreateVulnAnalysisModal from './CreateVulnAnalysisModal.vue'
55+
import UpdateVulnAnalysisModal from './UpdateVulnAnalysisModal.vue'
56+
import { Edit } from '@vicons/tabler'
4957
5058
interface Props {
5159
show: boolean
@@ -76,6 +84,8 @@ const notification = useNotification()
7684
const loading = ref(false)
7785
const analysisRecords = ref<any[]>([])
7886
const showCreateAnalysisModal = ref(false)
87+
const showUpdateAnalysisModal = ref(false)
88+
const selectedAnalysisRecord = ref<any>(null)
7989
8090
const isVisible = computed({
8191
get: () => props.show,
@@ -139,6 +149,11 @@ const handleAddScope = () => {
139149
showCreateAnalysisModal.value = true
140150
}
141151
152+
const handleEditAnalysis = (record: any) => {
153+
selectedAnalysisRecord.value = record
154+
showUpdateAnalysisModal.value = true
155+
}
156+
142157
const onAnalysisCreated = async () => {
143158
notification.success({
144159
content: 'Vulnerability analysis created successfully',
@@ -148,6 +163,15 @@ const onAnalysisCreated = async () => {
148163
await fetchAnalysisRecords()
149164
}
150165
166+
const onAnalysisUpdated = async () => {
167+
notification.success({
168+
content: 'Vulnerability analysis updated successfully',
169+
duration: 3000
170+
})
171+
// Refresh the analysis records
172+
await fetchAnalysisRecords()
173+
}
174+
151175
const columns: DataTableColumns<any> = [
152176
{
153177
title: 'Scope',
@@ -233,6 +257,21 @@ const columns: DataTableColumns<any> = [
233257
})
234258
})
235259
}
260+
},
261+
{
262+
title: 'Actions',
263+
key: 'actions',
264+
width: 80,
265+
render: (row: any) => {
266+
const editIcon = h(NIcon, {
267+
title: 'Edit Analysis',
268+
class: 'icons clickable',
269+
size: 25,
270+
onClick: () => handleEditAnalysis(row)
271+
}, { default: () => h(Edit) })
272+
273+
return h('div', {}, [editIcon])
274+
}
236275
}
237276
]
238277

ui/src/constants/vulnAnalysis.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const ANALYSIS_STATE_OPTIONS = [
2+
{ label: 'Exploitable', value: 'EXPLOITABLE' },
3+
{ label: 'In Triage', value: 'IN_TRIAGE' },
4+
{ label: 'False Positive', value: 'FALSE_POSITIVE' },
5+
{ label: 'Not Affected', value: 'NOT_AFFECTED' }
6+
]
7+
8+
export const ANALYSIS_JUSTIFICATION_OPTIONS = [
9+
{ label: 'N/A', value: '' },
10+
{ label: 'Code Not Present', value: 'CODE_NOT_PRESENT' },
11+
{ label: 'Code Not Reachable', value: 'CODE_NOT_REACHABLE' },
12+
{ label: 'Requires Configuration', value: 'REQUIRES_CONFIGURATION' },
13+
{ label: 'Requires Dependency', value: 'REQUIRES_DEPENDENCY' },
14+
{ label: 'Requires Environment', value: 'REQUIRES_ENVIRONMENT' },
15+
{ label: 'Protected by Compiler', value: 'PROTECTED_BY_COMPILER' },
16+
{ label: 'Protected at Runtime', value: 'PROTECTED_AT_RUNTIME' },
17+
{ label: 'Protected at Perimeter', value: 'PROTECTED_AT_PERIMETER' },
18+
{ label: 'Protected by Mitigating Control', value: 'PROTECTED_BY_MITIGATING_CONTROL' }
19+
]

0 commit comments

Comments
 (0)