|
99 | 99 | Install |
100 | 100 | </button> |
101 | 101 | <button |
102 | | - v-if="scanner.status === 'installed' && scanner.required_env?.length" |
| 102 | + v-if="scanner.status === 'installed' || scanner.status === 'configured'" |
103 | 103 | @click="openConfigDialog(scanner)" |
104 | 104 | class="btn btn-sm btn-outline" |
105 | 105 | > |
|
215 | 215 |
|
216 | 216 | <!-- Configure Scanner Dialog --> |
217 | 217 | <dialog ref="configDialog" class="modal"> |
218 | | - <div class="modal-box"> |
| 218 | + <div class="modal-box max-w-lg"> |
219 | 219 | <h3 class="font-bold text-lg">Configure {{ configScanner?.name }}</h3> |
| 220 | + <p class="text-sm text-base-content/60 mt-1">Set API keys and environment variables. Secrets are stored in your OS keychain.</p> |
220 | 221 | <div class="py-4 space-y-4" v-if="configScanner"> |
221 | | - <div v-for="env in configScanner.required_env" :key="env.key" class="form-control"> |
222 | | - <label class="label"><span class="label-text">{{ env.label }}</span></label> |
| 222 | + <!-- Required env vars --> |
| 223 | + <div v-for="env in (configScanner.required_env || [])" :key="env.key" class="form-control"> |
| 224 | + <label class="label"> |
| 225 | + <span class="label-text font-medium">{{ env.label }}</span> |
| 226 | + <span class="badge badge-sm badge-error">Required</span> |
| 227 | + </label> |
223 | 228 | <input |
224 | 229 | v-model="configValues[env.key]" |
225 | 230 | :type="env.secret ? 'password' : 'text'" |
226 | | - :placeholder="env.key" |
| 231 | + :placeholder="configuredPlaceholder(env.key)" |
227 | 232 | class="input input-bordered" |
228 | 233 | /> |
229 | 234 | </div> |
| 235 | + <!-- Optional env vars --> |
230 | 236 | <div v-for="env in (configScanner.optional_env || [])" :key="env.key" class="form-control"> |
231 | 237 | <label class="label"> |
232 | 238 | <span class="label-text">{{ env.label }}</span> |
233 | | - <span class="label-text-alt">Optional</span> |
| 239 | + <span class="badge badge-sm badge-ghost">Optional</span> |
234 | 240 | </label> |
235 | 241 | <input |
236 | 242 | v-model="configValues[env.key]" |
237 | 243 | :type="env.secret ? 'password' : 'text'" |
238 | | - :placeholder="env.key" |
| 244 | + :placeholder="configuredPlaceholder(env.key)" |
239 | 245 | class="input input-bordered" |
240 | 246 | /> |
241 | 247 | </div> |
| 248 | + <!-- Custom env var --> |
| 249 | + <div class="divider text-xs">Add Custom Variable</div> |
| 250 | + <div class="flex gap-2"> |
| 251 | + <input v-model="customEnvKey" type="text" placeholder="OPENAI_API_KEY" class="input input-bordered input-sm flex-1" /> |
| 252 | + <input v-model="customEnvValue" type="password" placeholder="Value" class="input input-bordered input-sm flex-1" /> |
| 253 | + <button @click="addCustomEnv" :disabled="!customEnvKey || !customEnvValue" class="btn btn-sm btn-outline">Add</button> |
| 254 | + </div> |
| 255 | + <!-- Show existing configured vars --> |
| 256 | + <div v-if="Object.keys(configValues).length > 0" class="mt-2"> |
| 257 | + <div class="text-xs text-base-content/50 mb-1">Configured variables:</div> |
| 258 | + <div v-for="(val, key) in configValues" :key="key" class="flex items-center gap-2 text-sm py-1"> |
| 259 | + <code class="font-mono text-xs bg-base-200 px-2 py-0.5 rounded">{{ key }}</code> |
| 260 | + <span class="text-base-content/50">{{ val.startsWith('${keyring:') ? 'stored in keyring' : 'set' }}</span> |
| 261 | + <button @click="delete configValues[key]" class="btn btn-ghost btn-xs text-error">x</button> |
| 262 | + </div> |
| 263 | + </div> |
242 | 264 | </div> |
243 | 265 | <div class="modal-action"> |
244 | 266 | <button @click="closeConfigDialog" class="btn">Cancel</button> |
@@ -267,6 +289,8 @@ const scanResult = ref<any>(null) |
267 | 289 | const configDialog = ref<HTMLDialogElement>() |
268 | 290 | const configScanner = ref<any>(null) |
269 | 291 | const configValues = ref<Record<string, string>>({}) |
| 292 | +const customEnvKey = ref('') |
| 293 | +const customEnvValue = ref('') |
270 | 294 |
|
271 | 295 | const totalFindings = computed(() => overview.value?.findings_by_severity?.total || 0) |
272 | 296 |
|
@@ -334,21 +358,47 @@ async function removeScanner(id: string) { |
334 | 358 |
|
335 | 359 | function openConfigDialog(scanner: any) { |
336 | 360 | configScanner.value = scanner |
337 | | - configValues.value = {} |
| 361 | + // Pre-populate with existing configured values |
| 362 | + const existing = scanner.configured_env || {} |
| 363 | + configValues.value = { ...existing } |
| 364 | + customEnvKey.value = '' |
| 365 | + customEnvValue.value = '' |
338 | 366 | configDialog.value?.showModal() |
339 | 367 | } |
340 | 368 |
|
341 | 369 | function closeConfigDialog() { |
342 | 370 | configDialog.value?.close() |
343 | 371 | } |
344 | 372 |
|
| 373 | +function configuredPlaceholder(key: string): string { |
| 374 | + const existing = configScanner.value?.configured_env?.[key] |
| 375 | + if (existing) { |
| 376 | + if (existing.startsWith('${keyring:')) return '(stored in keyring)' |
| 377 | + return '(configured)' |
| 378 | + } |
| 379 | + return key |
| 380 | +} |
| 381 | +
|
| 382 | +function addCustomEnv() { |
| 383 | + if (customEnvKey.value && customEnvValue.value) { |
| 384 | + configValues.value[customEnvKey.value] = customEnvValue.value |
| 385 | + customEnvKey.value = '' |
| 386 | + customEnvValue.value = '' |
| 387 | + } |
| 388 | +} |
| 389 | +
|
345 | 390 | async function saveConfig() { |
346 | 391 | if (!configScanner.value) return |
347 | | - const nonEmpty: Record<string, string> = {} |
| 392 | + // Only send non-empty values that aren't keyring references (new values) |
| 393 | + const toSend: Record<string, string> = {} |
348 | 394 | for (const [k, v] of Object.entries(configValues.value)) { |
349 | | - if (v) nonEmpty[k] = v |
| 395 | + if (v && !v.startsWith('${keyring:')) { |
| 396 | + toSend[k] = v |
| 397 | + } |
| 398 | + } |
| 399 | + if (Object.keys(toSend).length > 0) { |
| 400 | + await api.configureScanner(configScanner.value.id, toSend) |
350 | 401 | } |
351 | | - await api.configureScanner(configScanner.value.id, nonEmpty) |
352 | 402 | closeConfigDialog() |
353 | 403 | await refresh() |
354 | 404 | } |
|
0 commit comments