6868 <textarea id =" config-target-details" v-model =" configForm.target_details_json" rows =" 5" placeholder =' e.g., {\n "type": "git_repo",\n "url": "https://github.com/user/repo.git",\n "branch": "main",\n "include_paths": ["src/"],\n "exclude_paths": ["tests/"]\n}' ></textarea >
6969 <small >Specify targets if 'has_predefined_targets' is checked. Otherwise, manual input will be required during scan run.</small >
7070 </div >
71- <div class =" form-group" >
72- <label for =" config-tool-settings" >Tool Configurations (JSON):</label >
73- <textarea id =" config-tool-settings" v-model =" configForm.tool_configurations_json" rows =" 5" placeholder =' e.g., {\n "bandit": { "enabled": true, "severity_level": "MEDIUM" },\n "semgrep": { "enabled": true, "rulesets": ["p/default"] }\n}' ></textarea >
74- <small >Define tool-specific settings. If empty, default tool behavior will apply.</small >
75- </div >
76- <div v-if =" formErrorMessage" class =" error-message" >{{ formErrorMessage }}</div >
71+
72+ <!-- New Tool Configuration Section -->
73+ <fieldset class =" tool-config-fieldset" >
74+ <legend >Tool Configurations</legend >
75+
76+ <!-- Semgrep -->
77+ <div class =" tool-config-group" >
78+ <label class =" tool-enable-label" >
79+ <input type =" checkbox" v-model =" configForm.tools.semgrep.enabled" >
80+ Enable Semgrep
81+ </label >
82+ <div v-if =" configForm.tools.semgrep.enabled" class =" tool-options" >
83+ <label for =" semgrep-rulesets" >Semgrep Rulesets (kommagetrennt):</label >
84+ <input type =" text" id =" semgrep-rulesets" v-model =" configForm.tools.semgrep.rulesets" placeholder =" z.B. p/ci,r/generic" >
85+ <small >Standard: community-recommended (oft 'p/ci' oder leer lassen für Standard)</small >
86+ </div >
87+ </div >
88+
89+ <!-- Trivy -->
90+ <div class =" tool-config-group" >
91+ <label class =" tool-enable-label" >
92+ <input type =" checkbox" v-model =" configForm.tools.trivy.enabled" >
93+ Enable Trivy
94+ </label >
95+ <div v-if =" configForm.tools.trivy.enabled" class =" tool-options" >
96+ <label for =" trivy-scan-type" >Trivy Scan Type:</label >
97+ <select id =" trivy-scan-type" v-model =" configForm.tools.trivy.scanType" >
98+ <option value =" fs" >Filesystem</option >
99+ <option value =" image" >Image</option >
100+ <option value =" repo" >Repository</option >
101+ <option value =" vuln" >Vulnerability Database</option > <!-- Selten direkt hier konfiguriert -->
102+ </select >
103+ <label >Severity Levels (kommagetrennt):</label >
104+ <input type =" text" v-model =" configForm.tools.trivy.severity" placeholder =" UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" >
105+ <small >Standard: HIGH,CRITICAL</small >
106+ <label >
107+ <input type =" checkbox" v-model =" configForm.tools.trivy.ignoreUnfixed" >
108+ Ignore Unfixed Vulnerabilities
109+ </label >
110+ </div >
111+ </div >
112+
113+ <!-- ZAP (Placeholder for now) -->
114+ <div class =" tool-config-group" >
115+ <label class =" tool-enable-label" >
116+ <input type =" checkbox" v-model =" configForm.tools.zap.enabled" disabled >
117+ Enable ZAP (Konfiguration folgt)
118+ </label >
119+ </div >
120+
121+ </fieldset >
122+ <!-- End of New Tool Configuration Section -->
123+
124+ <div v-if =" formErrorMessage" class =" error-message" >{{ formErrorMessage }}</div >
77125 <div class =" form-actions" >
78126 <button type =" submit" :disabled =" isSaving" class =" action-button save-button" >
79127 {{ isSaving ? 'Saving...' : (editingConfiguration ? 'Update Configuration' : 'Create Configuration') }}
@@ -98,7 +146,22 @@ const initialConfigFormState = () => ({
98146 project: null , // Will be set from selectedProjectId prop
99147 has_predefined_targets: false ,
100148 target_details_json: ' ' ,
101- tool_configurations_json: ' '
149+ tools: {
150+ semgrep: {
151+ enabled: false ,
152+ rulesets: ' p/ci' , // Standard-Regelsatz als Beispiel
153+ },
154+ trivy: {
155+ enabled: false ,
156+ scanType: ' fs' , // Standard Scan-Typ
157+ severity: ' HIGH,CRITICAL' , // Standard Schweregrade
158+ ignoreUnfixed: false ,
159+ },
160+ zap: {
161+ enabled: false ,
162+ // Weitere ZAP spezifische einfache Felder hier...
163+ }
164+ }
102165});
103166
104167export default {
@@ -197,17 +260,48 @@ export default {
197260 },
198261 prepareEditForm (config ) {
199262 this .editingConfiguration = config; // Store the original config object
200- this .configForm = { // Populate form with its data
263+
264+ let parsedTools = initialConfigFormState ().tools ; // Start with defaults
265+ if (config .tool_configurations_json ) {
266+ try {
267+ const savedToolSettings = JSON .parse (config .tool_configurations_json );
268+ if (savedToolSettings .semgrep ) {
269+ parsedTools .semgrep .enabled = savedToolSettings .semgrep .enabled !== undefined ? savedToolSettings .semgrep .enabled : parsedTools .semgrep .enabled ;
270+ if (Array .isArray (savedToolSettings .semgrep .rulesets )) {
271+ parsedTools .semgrep .rulesets = savedToolSettings .semgrep .rulesets .join (' ,' );
272+ } else if (typeof savedToolSettings .semgrep .rulesets === ' string' ) {
273+ parsedTools .semgrep .rulesets = savedToolSettings .semgrep .rulesets ;
274+ }
275+ }
276+ if (savedToolSettings .trivy ) {
277+ parsedTools .trivy .enabled = savedToolSettings .trivy .enabled !== undefined ? savedToolSettings .trivy .enabled : parsedTools .trivy .enabled ;
278+ parsedTools .trivy .scanType = savedToolSettings .trivy .scan_type || parsedTools .trivy .scanType ;
279+ if (Array .isArray (savedToolSettings .trivy .severity )) {
280+ parsedTools .trivy .severity = savedToolSettings .trivy .severity .join (' ,' );
281+ } else if (typeof savedToolSettings .trivy .severity === ' string' ) {
282+ parsedTools .trivy .severity = savedToolSettings .trivy .severity ;
283+ }
284+ parsedTools .trivy .ignoreUnfixed = savedToolSettings .trivy .ignore_unfixed !== undefined ? savedToolSettings .trivy .ignore_unfixed : parsedTools .trivy .ignoreUnfixed ;
285+ }
286+ // TODO: ZAP parsing if/when ZAP is added
287+ } catch (e) {
288+ console .error (' Error parsing tool_configurations_json for editing:' , e);
289+ this .formErrorMessage = ' Could not parse existing tool configurations. Please check and re-save.' ;
290+ // Beibehaltung der Standardwerte für Tools, wenn Parsen fehlschlägt
291+ }
292+ }
293+
294+ this .configForm = {
201295 id: config .id ,
202296 name: config .name ,
203297 description: config .description || ' ' ,
204298 project: config .project ,
205299 has_predefined_targets: config .has_predefined_targets || false ,
206300 target_details_json: config .target_details_json || ' ' ,
207- tool_configurations_json : config . tool_configurations_json || ' '
301+ tools : parsedTools
208302 };
209- this .showCreateForm = false ; // Ensure create mode is off if edit is clicked
210- this .formErrorMessage = null ;
303+ this .showCreateForm = false ;
304+ this .formErrorMessage = this . formErrorMessage || null ; // Behalte den Parse-Fehler, falls vorhanden
211305 },
212306 async deleteConfiguration (configId ) {
213307 if (! confirm (` Are you sure you want to delete configuration ID ${ configId} ? This cannot be undone.` )) {
@@ -236,7 +330,7 @@ export default {
236330 async saveConfiguration() {
237331 this.isSaving = true;
238332 this.formErrorMessage = null;
239- // Validate JSON fields before sending if they are not empty
333+
240334 let targetDetailsPayload = null;
241335 if (this.configForm.has_predefined_targets && this.configForm.target_details_json.trim()) {
242336 try {
@@ -252,42 +346,65 @@ export default {
252346 return;
253347 }
254348
255- let toolConfigsPayload = null;
256- if (this.configForm.tool_configurations_json.trim()) {
257- try {
258- toolConfigsPayload = JSON.parse(this.configForm.tool_configurations_json);
259- } catch (e) {
260- this.formErrorMessage = "Tool Configurations JSON is invalid.";
261- this.isSaving = false;
262- return;
349+ // Build tool_configurations_json from form data
350+ const toolConfigurations = {};
351+ if (this.configForm.tools.semgrep.enabled) {
352+ toolConfigurations.semgrep = {
353+ enabled: true,
354+ rulesets: this.configForm.tools.semgrep.rulesets.split(',').map(s => s.trim()).filter(s => s)
355+ };
356+ if (toolConfigurations.semgrep.rulesets.length === 0) {
357+ // Backend erwartet vielleicht einen Default oder spezifischen Wert wenn enabled
358+ // Für jetzt, wenn leer, senden wir leeres Array, Backend muss damit umgehen oder wir definieren Default hier
359+ }
360+ }
361+
362+ if (this.configForm.tools.trivy.enabled) {
363+ toolConfigurations.trivy = {
364+ enabled: true,
365+ scan_type: this.configForm.tools.trivy.scanType,
366+ severity: this.configForm.tools.trivy.severity.split(',').map(s => s.trim()).filter(s => s),
367+ ignore_unfixed: this.configForm.tools.trivy.ignoreUnfixed
368+ };
369+ if (toolConfigurations.trivy.severity.length === 0) {
370+ // Ähnlich wie bei Semgrep, Backend-Verhalten oder Default hier definieren
263371 }
264372 }
265373
374+ // TODO: ZAP configuration building
375+
266376 const payload = {
267- ...this.configForm,
268- target_details_json: targetDetailsPayload, // Send parsed JSON or null
269- tool_configurations_json: toolConfigsPayload, // Send parsed JSON or null
270- project: this.configForm.project || this.selectedProjectId // Ensure project is set
377+ name: this.configForm.name,
378+ description: this.configForm.description,
379+ project: this.configForm.project,
380+ has_predefined_targets: this.configForm.has_predefined_targets,
381+ target_info_json: targetDetailsPayload, // Ensure this key matches backend serializer expectations
382+ tool_configurations_json: Object.keys(toolConfigurations).length > 0 ? JSON.stringify(toolConfigurations) : null
271383 };
272384
273- console.log('ScanConfigurationManager.vue: Attempting to save configuration.');
274- console.log('ScanConfigurationManager.vue: API URL:', API_CONFIGURATIONS_URL);
275- console.log('ScanConfigurationManager.vue: Payload:', JSON.parse(JSON.stringify(payload)));
276-
277385 try {
278- if (this.editingConfiguration) { // Update (PUT)
279- console.log('ScanConfigurationManager.vue: Sending PUT request to:', ` ${API_CONFIGURATIONS_URL }${this .editingConfiguration .id }/ ` );
280- await axios.put(` ${API_CONFIGURATIONS_URL }${this .editingConfiguration .id }/ ` , payload);
281- } else { // Create (POST)
282- console.log('ScanConfigurationManager.vue: Sending POST request to:', API_CONFIGURATIONS_URL);
283- await axios.post(API_CONFIGURATIONS_URL, payload);
386+ let response;
387+ if (this.editingConfiguration && this.editingConfiguration.id) {
388+ response = await axios.put(` ${API_CONFIGURATIONS_URL }${this .editingConfiguration .id }/ ` , payload);
389+ } else {
390+ response = await axios.post(API_CONFIGURATIONS_URL, payload);
284391 }
285- await this.fetchConfigurations(this.selectedProjectId); // Refresh list
286- this.cancelEditOrCreate(); // Close form and reset
392+ // Successfully saved, update local list or re-fetch for the project
393+ this.fetchConfigurations(this.configForm.project);
394+ this.cancelEditOrCreate();
287395 } catch (error) {
288- console.error('Error saving configuration:', error);
289- this.formErrorMessage = ` Failed to save configuration . $ {error .response ? .data ? .detail || JSON .stringify (error .response ? .data ) || error .message }` ;
290- if (error.response && error.response.status === 401) {
396+ console.error('Error saving configuration:', error.response || error.message || error);
397+ if (error.response && error.response.data) {
398+ let messages = [];
399+ for (const key in error.response.data) {
400+ const fieldErrors = Array.isArray(error.response.data[key]) ? error.response.data[key].join(', ') : error.response.data[key];
401+ messages.push(` ${key .charAt (0 ).toUpperCase () + key .slice (1 )}: ${fieldErrors}` );
402+ }
403+ this.formErrorMessage = messages.join('; ');
404+ } else {
405+ this.formErrorMessage = 'An unknown error occurred while saving the configuration.';
406+ }
407+ if (error.response && error.response.status === 401) {
291408 this.$emit('session-expired');
292409 }
293410 } finally {
@@ -298,6 +415,10 @@ export default {
298415 this.showCreateForm = false;
299416 this.editingConfiguration = null;
300417 this.configForm = initialConfigFormState();
418+ // Wenn selectedProjectId beim Cancel immer noch gesetzt ist, soll es im Formular bleiben für den nächsten "Create New"
419+ if(this.selectedProjectId) {
420+ this.configForm.project = this.selectedProjectId;
421+ }
301422 this.formErrorMessage = null;
302423 }
303424 },
@@ -425,7 +546,8 @@ export default {
425546 font-weight: bold;
426547}
427548.form-group input[type="text"],
428- .form-group textarea {
549+ .form-group textarea,
550+ .form-group select {
429551 width: 100%;
430552 padding: 10px;
431553 border: 1px solid #ced4da;
@@ -446,4 +568,58 @@ export default {
446568 margin-top: 20px;
447569 text-align: right;
448570}
571+
572+ .tool-config-fieldset {
573+ border: 1px solid #ddd;
574+ padding: 15px;
575+ margin-bottom: 15px;
576+ border-radius: 4px;
577+ }
578+
579+ .tool-config-fieldset legend {
580+ font-weight: bold;
581+ padding: 0 10px;
582+ width: auto; /* Behaves more like a natural legend */
583+ font-size: 1.1em;
584+ }
585+
586+ .tool-config-group {
587+ padding: 10px;
588+ margin-bottom: 10px;
589+ border: 1px dashed #eee;
590+ border-radius: 3px;
591+ }
592+
593+ .tool-enable-label {
594+ font-weight: normal;
595+ display: flex;
596+ align-items: center;
597+ }
598+
599+ .tool-enable-label input[type="checkbox"] {
600+ margin-right: 8px;
601+ width: auto; /* Override global input width for checkbox */
602+ }
603+
604+ .tool-options {
605+ margin-top: 10px;
606+ padding-left: 25px; /* Indent options */
607+ }
608+
609+ .tool-options label {
610+ font-weight: normal;
611+ font-size: 0.95em;
612+ }
613+
614+ .tool-options input[type="text"],
615+ .tool-options select {
616+ margin-bottom: 8px;
617+ }
618+
619+ .tool-options small {
620+ display: block;
621+ font-size: 0.85em;
622+ color: #666;
623+ margin-bottom: 5px;
624+ }
449625</style>
0 commit comments