Fix credentials UI regression from #4

The test accounts section was mounted after the scrollable field area
of the New task sheet, so it sat pinned under the scrollbar instead of
scrolling with the other fields, and its Add account button floated in
a header row instead of sitting in the field content like Add row.

Move it inside the scroll container as a proper Field with the add
button below the rows, matching the intercept rules editor pattern.
Also place the credentials TabsContent after assets in the task detail
page to match the TABS array order and use the local id variable.
This commit is contained in:
BaaRai
2026-10-09 14:45:52 +02:00
parent 6c7169565b
commit d3e6f4167e
2 changed files with 56 additions and 56 deletions
@@ -434,15 +434,15 @@ function TaskDetailInner() {
<TabsContent value="findings" className="mt-0">
<FindingsTab taskId={id} />
</TabsContent>
<TabsContent value="credentials" className="mt-0">
<CredentialsTab taskId={task.id} />
</TabsContent>
<TabsContent value="retests" className="mt-0">
<RetestsTab key={id} taskId={id} />
</TabsContent>
<TabsContent value="assets" className="mt-0">
<AssetsTab taskId={id} />
</TabsContent>
<TabsContent value="credentials" className="mt-0">
<CredentialsTab taskId={id} />
</TabsContent>
<TabsContent value="coverage" className="mt-0">
<CoverageGraphTab taskId={id} coverageEnabled={task?.coverage_enabled !== false} />
</TabsContent>
+53 -53
View File
@@ -3335,6 +3335,59 @@ function CreateTaskSheet({
/>
<FieldDescription>Failover follows the list order; the first entry is the current config, and the next one is used only when the quota is clearly insufficient.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="task-credentials">Test accounts (optional)</FieldLabel>
<div className="grid gap-2">
{credentials.map((c, i) => (
<div key={i} className="grid gap-2 sm:grid-cols-[1fr_1fr_1fr_auto]">
<Input
placeholder="domain (e.g. target.example.com)"
value={c.domain}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, domain: e.target.value } : x)))}
/>
<Input
placeholder="username"
value={c.username}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, username: e.target.value } : x)))}
/>
<Input
placeholder="password (empty = agent generates)"
value={c.password}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, password: e.target.value } : x)))}
/>
<Button
type="button"
variant="ghost"
size="icon"
aria-label="Remove account"
onClick={() => setCredentials((prev) => prev.filter((_, j) => j !== i))}
>
<TrashIcon />
</Button>
<Input
className="sm:col-span-4"
placeholder="note (optional, e.g. program alias account)"
value={c.note ?? ""}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, note: e.target.value } : x)))}
/>
</div>
))}
<Button
type="button"
size="sm"
variant="outline"
className="w-fit"
onClick={() => setCredentials((prev) => [...prev, { domain: "", username: "", password: "", note: "" }])}
>
<PlusIcon className="size-4" /> Add account
</Button>
</div>
<FieldDescription>
Test accounts reused by the agent for authenticated testing. Leave the password empty to have the agent register that
account itself and record the password it chose. Use dedicated test accounts only — anything declared here is sent to
the configured LLM provider during the task.
</FieldDescription>
</Field>
{/* Advanced parameters are collapsed by default: timeout/heartbeat/first intent; they take space only when expanded, keeping the common path clean. */}
<Collapsible>
@@ -3408,59 +3461,6 @@ function CreateTaskSheet({
</div>
</div>
<div className="grid gap-2">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">Test accounts (optional)</label>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setCredentials((prev) => [...prev, { domain: "", username: "", password: "", note: "" }])}
>
<PlusIcon /> Add account
</Button>
</div>
<p className="text-muted-foreground text-xs">
Test accounts reused by the agent for authenticated testing. Leave the password empty to have the agent register that
account itself and record the password it chose. Use dedicated test accounts only — anything declared here is sent to
the configured LLM provider during the task.
</p>
{credentials.map((c, i) => (
<div key={i} className="grid gap-2 sm:grid-cols-[1fr_1fr_1fr_auto]">
<Input
placeholder="domain (e.g. target.example.com)"
value={c.domain}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, domain: e.target.value } : x)))}
/>
<Input
placeholder="username"
value={c.username}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, username: e.target.value } : x)))}
/>
<Input
placeholder="password (empty = agent generates)"
value={c.password}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, password: e.target.value } : x)))}
/>
<Button
type="button"
variant="ghost"
size="icon"
aria-label="Remove account"
onClick={() => setCredentials((prev) => prev.filter((_, j) => j !== i))}
>
<TrashIcon />
</Button>
<Input
className="sm:col-span-4"
placeholder="note (optional, e.g. program alias account)"
value={c.note ?? ""}
onChange={(e) => setCredentials((prev) => prev.map((x, j) => (j === i ? { ...x, note: e.target.value } : x)))}
/>
</div>
))}
</div>
<SheetFooter className="flex-row justify-end gap-2 border-t p-4">
<SheetClose asChild>
<Button variant="outline">Cancel</Button>