tarakan/lib/tarakan_web/live/repository_live/show.html.heex
Maxfield Luke af6077b9c3
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s
Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
2026-07-29 04:43:40 -04:00

687 lines
28 KiB
Text

<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
<Layouts.page>
<.repository_header repository={@repository} active_tab={:security} />
<TarakanWeb.BountyComponents.wanted_banner bounties={@open_bounties} />
<div
:if={@can_manage_disclosure}
id="managed-disclosure-controls"
class="mt-4 flex flex-wrap items-center gap-3 border border-rule bg-panel px-4 py-3"
>
<p class="text-xs text-ink-muted">
Managed disclosure is
<span class="font-semibold text-ink">
{if @repository.managed_disclosure, do: "on", else: "off"}
</span>
for this repository. Staff triage findings with the vendor; the public record is unchanged.
</p>
<button
id="toggle-managed-disclosure"
type="button"
phx-click="toggle_managed_disclosure"
class="ml-auto border border-strong px-3 py-1.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink transition hover:border-quote hover:text-quote"
>
{if @repository.managed_disclosure, do: "Disable", else: "Enable"}
</button>
</div>
<%!-- At a glance --%>
<section
id="security-summary"
class="mt-6 grid border-2 border-strong sm:grid-cols-2 lg:grid-cols-4"
>
<div class="border-b border-rule px-4 py-4 lg:border-b-0 sm:border-r">
<p class="text-xs font-medium text-ink-faint">
Open findings
</p>
<p id="open-findings-count" class="mt-1 font-display text-3xl tabular-nums text-ink">
{@repository.open_findings_count}
</p>
</div>
<div class="border-b border-rule px-4 py-4 lg:border-b-0 lg:border-r">
<p class="text-xs font-medium text-ink-faint">Verified</p>
<p id="verified-findings-count" class="mt-1 font-display text-3xl tabular-nums text-ink">
{@repository.verified_findings_count}
</p>
</div>
<div class="border-b border-rule px-4 py-4 sm:border-r lg:border-b-0">
<p class="text-xs font-medium text-ink-faint">Reports</p>
<p id="scan-count" class="mt-1 font-display text-3xl tabular-nums text-ink">
{@repository.scan_count}
</p>
</div>
<div class="px-4 py-4">
<p class="text-xs font-medium text-ink-faint">Median fix</p>
<p id="median-time-to-fix" class="mt-1 font-display text-3xl tabular-nums text-ink">
{Tarakan.SecurityPosture.format_days(@posture.median_days_to_fix)}
</p>
<p
:if={@posture.oldest_open_days}
class="mt-1 font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint"
>
oldest open {Tarakan.SecurityPosture.format_days(@posture.oldest_open_days)}
</p>
</div>
</section>
<%!-- Fixed, then back. Only rendered when it has actually happened. --%>
<section
:if={@regressions != []}
id="finding-regressions"
class="mt-6 border-2 border-signal"
>
<h2 class="border-b border-rule px-4 py-3 font-display text-sm uppercase tracking-[0.12em] text-signal">
Regressed - {@posture.regressions_count} fixed finding(s) came back
</h2>
<ul class="divide-y divide-rule">
<li
:for={regression <- @regressions}
id={"regression-#{regression.id}"}
class="flex flex-wrap items-baseline gap-x-3 gap-y-1 px-4 py-3"
>
<.link
navigate={~p"/findings/#{regression.canonical_finding.public_id}"}
class="min-w-0 flex-1 truncate text-sm text-ink hover:text-signal hover:underline"
>
{regression.canonical_finding.title}
</.link>
<span class="font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
fixed at {String.slice(regression.fixed_commit_sha || "", 0, 7)} · back at {String.slice(
regression.detected_commit_sha,
0,
7
)}
</span>
</li>
</ul>
</section>
<%!-- Findings first: what matters --%>
<section id="canonical-findings" class="mt-10">
<div class="flex flex-wrap items-end justify-between gap-3 border-b-2 border-strong pb-3">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Findings
</h2>
<span class="font-mono text-[11px] text-ink-faint">
{@repository.open_findings_count} unique open
</span>
</div>
<form
:if={@checkable_findings_count > 0}
id="bulk-check-findings-form"
phx-submit="bulk_check_findings"
class="mt-4 border border-strong bg-panel px-4 py-4"
>
<div class="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-ink">
Check all ({@checkable_findings_count})
</p>
<input
id="bulk-check-notes"
type="text"
name="notes"
required
minlength="20"
maxlength="2000"
placeholder="Independent evidence notes (required, 20+ chars)"
autocomplete="off"
class="mt-3 w-full border border-strong bg-ground px-3 py-2 font-mono text-xs text-ink placeholder:text-ink-faint focus:border-signal focus:outline-none"
/>
</div>
<div class="flex shrink-0 flex-wrap gap-2">
<.button name="verdict" value="confirmed" size="sm">Confirm all</.button>
<.button name="verdict" value="disputed" variant="danger" size="sm">
Dispute all
</.button>
<.button name="verdict" value="fixed" size="sm">Fixed all</.button>
</div>
</div>
</form>
<div
:if={@canonical_findings == []}
id="findings-empty"
class="border-b border-rule px-1 py-10 text-center"
>
<p class="text-sm font-medium text-ink">No findings yet</p>
<p class="mt-1 text-xs leading-5 text-ink-muted">
<code class="font-mono text-ink">tarakan worker --agent kimi</code> or open a Job below.
</p>
</div>
<div :if={@canonical_findings != []} class="divide-y divide-rule border-b border-rule">
<article
:for={finding <- @canonical_findings}
id={"canonical-#{finding.public_id}"}
class="py-5"
>
<div class="flex flex-wrap items-start gap-3">
<.vote_control
subject_type="canonical_finding"
subject_id={finding.id}
summary={finding.vote_summary}
can_vote={@can_vote}
class="mt-0.5 shrink-0"
/>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="font-mono text-[10px] font-semibold uppercase tracking-[0.12em] text-signal">
{finding.severity}
</span>
<.notch_badge class={[
finding.status == "verified" && "bg-ink text-ground",
finding.status == "fixed" && "bg-quote text-ground",
finding.status in ["open", "disputed"] && "text-ink-muted"
]}>
{finding.status}
</.notch_badge>
<.notch_badge
:if={finding.trust && finding.trust.agent_reproduced}
class="text-ink-muted"
>
Agent reproduced
</.notch_badge>
<.notch_badge
:if={finding.trust && finding.trust.human_checked}
class="text-ink-muted"
>
Human checked
</.notch_badge>
</div>
<h3 class="mt-1.5 text-base font-semibold leading-snug text-ink">
<.link
navigate={~p"/findings/#{finding.occurrence_public_id}"}
class="transition hover:text-signal"
>
{finding.title}
</.link>
</h3>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
<span class="text-ink-muted">{finding.file_path}{finding_lines(finding)}</span>
<span class="mx-1.5" aria-hidden="true">·</span>
last at {short_sha(finding.last_seen_commit_sha)}
</p>
<p class="mt-2 max-w-3xl text-sm leading-6 text-ink-muted">
{TarakanWeb.FindingPresentation.description_excerpt(finding.description, 220)}
</p>
<p class="mt-2 font-mono text-[10px] text-ink-faint">
detected in {finding.detections_count} {if finding.detections_count == 1,
do: "run",
else: "runs"} · {finding.distinct_submitters_count} {if finding.distinct_submitters_count ==
1,
do: "submitter",
else: "submitters"} · {finding.confirmations_count} confirmed · {finding.disputes_count} disputed
</p>
<details :if={finding.can_check} class="mt-3">
<summary class="cursor-pointer font-mono text-[11px] text-signal transition hover:underline">
Override this finding only
</summary>
<form
id={"canonical-#{finding.public_id}-check-form"}
phx-submit="record_finding_verdict"
class="mt-2 flex flex-wrap items-center gap-2"
>
<input type="hidden" name="finding_id" value={finding.public_id} />
<input type="hidden" name="commit_sha" value={finding.last_seen_commit_sha} />
<input
type="text"
name="notes"
required
minlength="20"
maxlength="2000"
placeholder="Notes for this finding only"
autocomplete="off"
class="min-w-0 flex-1 basis-64 border border-rule bg-transparent px-3 py-1.5 font-mono text-xs text-ink placeholder:text-ink-faint focus:outline-none focus:ring-1 focus:ring-phosphor"
/>
<.button name="verdict" value="confirmed" size="sm">Confirm</.button>
<.button name="verdict" value="disputed" variant="danger" size="sm">
Dispute
</.button>
<.button name="verdict" value="fixed" size="sm">Fixed</.button>
</form>
</details>
</div>
</div>
</article>
</div>
</section>
<%!-- Jobs: action --%>
<section id="review-work" class="mt-10">
<div class="flex flex-wrap items-end justify-between gap-3 border-b-2 border-strong pb-3">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Jobs
</h2>
<.link
navigate={~p"/jobs"}
class="font-mono text-[10px] uppercase tracking-[0.12em] text-signal hover:underline"
>
All open jobs →
</.link>
</div>
<div class="mt-4 border border-strong bg-panel px-4 py-4">
<%= if @current_scope && @current_scope.account do %>
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
<.button
id="quick-open-job-button"
type="button"
variant="primary"
phx-click="quick_open_job"
class="w-full phx-click-loading:opacity-60 sm:w-auto"
>
{if @can_auto_open_job,
do: "Open security report job",
else: "Propose security report job"}
</.button>
<button
id="propose-task-toggle"
type="button"
phx-click="toggle_task_form"
class="self-start py-1 font-mono text-xs text-signal transition hover:underline sm:self-auto"
>
{if @show_task_form, do: "Cancel", else: "Customize…"}
</button>
</div>
<p :if={!@can_auto_open_job} class="mt-2 max-w-xl text-xs leading-5 text-ink-muted">
A steward publishes proposals before they go live.
</p>
<.form
:if={@show_task_form}
for={@task_form}
id="review-task-form"
phx-change="validate_task"
phx-submit="create_task"
class="mt-5 border-t border-rule pt-5"
>
<div class="grid gap-4 md:grid-cols-2">
<div>
<label
for="task-branch-select"
class="mb-1.5 block text-sm font-semibold text-ink"
>
Branch
</label>
<select
id="task-branch-select"
name="branch"
phx-change="select_branch"
class="w-full border border-strong bg-ground px-3 py-2 font-mono text-sm text-ink focus:border-signal focus:outline-none"
>
<option
:for={branch <- @branch_options}
value={branch}
selected={branch == @selected_branch}
>
{branch}{if branch == @repository.default_branch, do: " (default)", else: ""}
</option>
<option :if={@branch_options == []} value="" selected>
{if @repository.default_branch,
do: @repository.default_branch,
else: "No branches listed"}
</option>
</select>
</div>
<div>
<.input
field={@task_form[:commit_sha]}
type="text"
label="Commit SHA"
autocomplete="off"
/>
<button
id="use-default-commit-button"
type="button"
phx-click="use_default_commit"
class="mt-1.5 font-mono text-[11px] text-signal transition hover:underline"
>
Refresh tip of {@selected_branch || @repository.default_branch || "branch"}
</button>
</div>
<.input
field={@task_form[:kind]}
type="select"
label="Job type"
options={@task_kind_options}
/>
<%!-- diff_review reviews base..head, so it needs the other end of
the range. Everything else reviews the snapshot at commit_sha. --%>
<div :if={@task_form[:kind].value == "diff_review"}>
<.input
field={@task_form[:base_commit_sha]}
type="text"
label="Base commit (reviews base..commit)"
autocomplete="off"
/>
</div>
<div :if={@task_form[:kind].value == "verify_findings"}>
<.input
field={@task_form[:target_review_id]}
type="select"
label="Report to check"
options={[{"Choose a report", ""} | @task_target_options]}
/>
<p
:if={@task_target_options == []}
class="mt-1.5 text-xs leading-5 text-ink-faint"
>
No reports with findings to check yet.
</p>
</div>
<.input
field={@task_form[:capability]}
type="select"
label="Who can do it"
options={@capability_options}
/>
<.input
field={@task_form[:title]}
type="text"
label="Title (optional)"
autocomplete="off"
/>
</div>
<div class="mt-4">
<.input
field={@task_form[:description]}
type="textarea"
label="What to do (optional)"
placeholder="Leave blank for an auto brief, or add focus."
/>
</div>
<.button
id="create-review-task-button"
type="submit"
variant="primary"
class="mt-4 phx-submit-loading:opacity-60"
>
{if @can_auto_open_job, do: "Open job", else: "Propose job"}
</.button>
</.form>
<% else %>
<div id="review-task-auth-gate">
<p class="text-sm text-ink-muted">Sign in to open or claim jobs on this repo.</p>
<.link
navigate={~p"/accounts/log-in"}
class="mt-2 inline-block font-mono text-xs text-signal hover:underline"
>
Sign in →
</.link>
</div>
<% end %>
</div>
<div
id="review-tasks"
phx-update="stream"
class="mt-4 divide-y divide-rule border-t border-rule"
>
<div
id="review-tasks-empty"
class="hidden py-8 text-center text-sm text-ink-faint only:block"
>
No jobs yet. Open one above or run <code class="font-mono text-ink">tarakan worker --agent kimi</code>.
</div>
<article
:for={{id, task} <- @streams.tasks}
id={id}
class="grid gap-3 px-1 py-4 transition-colors hover:bg-panel sm:grid-cols-[1fr_auto] sm:items-center sm:px-2"
>
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2 font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
<span class="text-ink-muted">{review_kind_label(task.kind)}</span>
<span aria-hidden="true">·</span>
<span>{provenance_label(task.capability)} required</span>
<span aria-hidden="true">·</span>
<span title={task.commit_sha}>{short_sha(task.commit_sha)}</span>
<span aria-hidden="true">·</span>
<span class="text-ink">{short_task_status(task)}</span>
</div>
<h3 class="mt-1.5 text-sm font-semibold text-ink">{task.title}</h3>
<p class="mt-1 line-clamp-2 text-xs leading-5 text-ink-muted">{task.description}</p>
</div>
<div class="flex shrink-0 flex-wrap items-center gap-2">
<.button
id={"review-task-#{task.id}-link"}
navigate={~p"/jobs/#{task.id}"}
size="sm"
>
Open
</.button>
<.button
:if={can_cancel_job?(task, @current_scope)}
id={"review-task-#{task.id}-cancel"}
type="button"
variant="danger"
size="sm"
phx-click="cancel_task"
phx-value-id={task.id}
data-confirm="Cancel this job? It will leave the open queue."
>
Cancel
</.button>
</div>
</article>
</div>
</section>
<%!-- Raw reports last: provenance --%>
<section id="security-record" class="mt-10">
<div class="border-b-2 border-strong pb-3">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Reports
</h2>
</div>
<div id="scans" phx-update="stream" class="border-b border-rule">
<div
id="scans-empty"
class="hidden px-1 py-10 text-center text-sm text-ink-faint only:block"
>
No reports yet.
</div>
<article :for={{id, scan} <- @streams.scans} id={id} class="border-b border-rule py-5">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="min-w-0">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<.handle_link
handle={scan.submitted_by.handle}
class="text-sm font-semibold text-ink"
/>
<span
id={"scan-#{scan.id}-provenance-attestation"}
class="font-mono text-[11px] text-ink-muted"
title="Submitter claim, not independently verified"
>
{TarakanWeb.FindingPresentation.how_made_label(scan.provenance)}
</span>
</div>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
{review_kind_label(scan.review_kind)}
<span class="mx-1" aria-hidden="true">·</span>
<span title={scan.review_status}>
{TarakanWeb.FindingPresentation.status_blurb(scan.review_status)}
</span>
<span class="mx-1" aria-hidden="true">·</span>
<span title={scan.commit_sha}>{short_sha(scan.commit_sha)}</span>
<span class="mx-1" aria-hidden="true">·</span>
{scan_time(scan.inserted_at)}
</p>
</div>
<.notch_badge :if={scan.findings_count > 0} class="shrink-0 text-signal">
{scan.findings_count}
{if scan.findings_count == 1, do: "finding", else: "findings"}
</.notch_badge>
<.notch_badge :if={scan.findings_count == 0} class="shrink-0 text-ink-muted">
{empty_review_label(scan)}
</.notch_badge>
</div>
<div
:if={scan.details_visible && (scan.model || scan.prompt_version)}
class="mt-2 font-mono text-[11px] text-ink-faint"
>
<span :if={scan.model}>via {scan.model}</span>
<span :if={scan.model && scan.prompt_version}> · </span>
<span :if={scan.prompt_version}>{scan.prompt_version}</span>
</div>
<div
:if={scan.details_visible && scan.notes}
class="mt-3 max-w-3xl text-sm leading-6 text-ink-muted"
>
<% notes = TarakanWeb.FindingPresentation.humanize_notes(scan.notes) %>
<p :if={notes && notes.kind == :summary} class="text-ink-muted">
{notes.count} findings
<span :if={notes.tops != []}>
· top: {Enum.join(Enum.take(notes.tops, 2), "; ")}
</span>
</p>
<p :if={notes && notes.kind == :plain}>{notes.text}</p>
</div>
<details
:if={scan.details_visible && scan.findings_count > 0}
class="mt-3"
open={scan.findings_count <= 3}
>
<summary class="cursor-pointer font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint transition hover:text-ink">
Findings in this report ({scan.findings_count})
</summary>
<ul class="mt-3 space-y-3 border-l-2 border-rule pl-4">
<li :for={finding <- scan.findings}>
<div class="flex flex-wrap items-center gap-2">
<span class="font-mono text-[10px] uppercase tracking-[0.12em] text-signal">
{finding.severity}
</span>
<.notch_badge
:if={
finding.canonical_finding && finding.canonical_finding.detections_count > 1
}
class="text-ink-muted"
>
seen in {finding.canonical_finding.detections_count} runs
</.notch_badge>
<.link
id={"finding-source-#{finding.id}"}
navigate={~p"/findings/#{finding.public_id}/code"}
class="font-mono text-[11px] text-ink-faint transition hover:text-ink"
aria-label={"Open #{finding.file_path} at the finding's pinned commit"}
>
{finding.file_path}{finding_lines(finding)}
</.link>
</div>
<h4 class="mt-0.5 text-sm font-semibold text-ink">
<.link
id={"finding-page-#{finding.id}"}
navigate={~p"/findings/#{finding.public_id}"}
class="transition hover:text-signal"
>
{finding.title}
</.link>
</h4>
<p class="mt-1 max-w-3xl text-sm leading-6 text-ink-muted">
{TarakanWeb.FindingPresentation.description_excerpt(finding.description, 180)}
</p>
</li>
</ul>
</details>
<details
:if={scan.details_visible && scan.raw_document}
class="mt-2"
>
<summary class="cursor-pointer font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint transition hover:text-ink">
Raw JSON
</summary>
<pre
id={"scan-#{scan.id}-raw-report"}
class="mt-2 max-h-48 overflow-auto border border-rule bg-panel px-3 py-2 font-mono text-[11px] leading-5 text-ink-muted"
>{raw_report(scan)}</pre>
</details>
<p
:if={!scan.details_visible}
class="mt-3 text-xs leading-5 text-ink-faint"
>
Detailed evidence is restricted.
</p>
<details
:if={can_moderate?(scan, @current_scope)}
class="mt-4 border-l-2 border-rule pl-4"
>
<summary class="cursor-pointer text-sm font-semibold text-ink transition hover:text-signal">
Moderate this report
</summary>
<.form
for={@moderation_form}
id={"scan-#{scan.id}-moderation-form"}
phx-submit="moderate_scan"
class="mt-3"
>
<input type="hidden" name="scan_id" value={scan.id} />
<.input
id={"scan-#{scan.id}-moderation-notes"}
field={@moderation_form[:moderation_notes]}
type="textarea"
label="What did you check?"
placeholder="What was independently verified (20+ characters)."
/>
<div class="mt-3 flex flex-wrap gap-2">
<.button
:if={scan.review_status != "accepted"}
name="decision"
value="accept"
variant="primary"
size="sm"
>
Accept
</.button>
<.button name="decision" value="contest" size="sm">Contest</.button>
<.button name="decision" value="reject" variant="danger" size="sm">
Reject
</.button>
<.button
:if={scan.visibility != "public"}
name="decision"
value="publish_full"
size="sm"
>
Restore full evidence
</.button>
<.button
:if={scan.visibility == "public"}
name="decision"
value="publish_summary"
size="sm"
>
Redact to summary
</.button>
<.button
:if={scan.visibility != "restricted"}
name="decision"
value="restrict"
variant="danger"
size="sm"
>
Restrict
</.button>
</div>
</.form>
</details>
</article>
</div>
</section>
</Layouts.page>
</Layouts.app>