Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
119 lines
3.9 KiB
Elixir
119 lines
3.9 KiB
Elixir
defmodule TarakanWeb.InfestationLive.Index do
|
|
@moduledoc "Cross-repo infestation list: patterns spanning multiple listed repositories."
|
|
use TarakanWeb, :live_view
|
|
|
|
alias Tarakan.Infestations
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, "Infestations")
|
|
|> assign(
|
|
:meta_description,
|
|
"Cross-repo infestations: the same finding title in multiple listed repositories."
|
|
)
|
|
|> assign(:canonical_path, ~p"/infestations")
|
|
|> assign(:days, 30)
|
|
|> assign(:min_repos, 2)
|
|
|> load_infestations()}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("filter", params, socket) do
|
|
days = parse_int(params["days"], socket.assigns.days, 1, 365)
|
|
min_repos = parse_int(params["min_repos"], socket.assigns.min_repos, 2, 20)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:days, days)
|
|
|> assign(:min_repos, min_repos)
|
|
|> load_infestations()}
|
|
end
|
|
|
|
defp load_infestations(socket) do
|
|
infestations =
|
|
Infestations.list_infestations(
|
|
days: socket.assigns.days,
|
|
min_repos: socket.assigns.min_repos,
|
|
limit: 50
|
|
)
|
|
|
|
assign(socket, :infestations, infestations)
|
|
end
|
|
|
|
defp parse_int(value, default, min, max) do
|
|
case Integer.parse(to_string(value || "")) do
|
|
{n, _} -> n |> max(min) |> min(max)
|
|
:error -> default
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def render(assigns) do
|
|
~H"""
|
|
<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
|
|
<Layouts.page width={:wide}>
|
|
<div class="flex flex-col gap-4 border-b-2 border-strong pb-6 sm:flex-row sm:items-end sm:justify-between">
|
|
<div class="max-w-2xl">
|
|
<h1 class="font-display text-3xl font-medium uppercase tracking-[0.02em] text-ink sm:text-5xl">
|
|
Infestations
|
|
</h1>
|
|
<p class="mt-3 text-sm leading-6 text-ink-muted">
|
|
Same finding title in {@min_repos}+ listed repos within the window.
|
|
Ranked by affected repo count.
|
|
</p>
|
|
</div>
|
|
|
|
<form
|
|
id="infestations-filter"
|
|
phx-change="filter"
|
|
class="flex flex-wrap items-end gap-3 font-mono text-xs"
|
|
>
|
|
<label class="flex flex-col gap-1 text-ink-faint">
|
|
Min repos
|
|
<select
|
|
name="min_repos"
|
|
class="border border-strong bg-panel px-2 py-1.5 text-ink"
|
|
>
|
|
<option :for={n <- [2, 3, 5, 10]} value={n} selected={@min_repos == n}>{n}</option>
|
|
</select>
|
|
</label>
|
|
<label class="flex flex-col gap-1 text-ink-faint">
|
|
Window
|
|
<select name="days" class="border border-strong bg-panel px-2 py-1.5 text-ink">
|
|
<option :for={n <- [7, 30, 90, 365]} value={n} selected={@days == n}>
|
|
{n}d
|
|
</option>
|
|
</select>
|
|
</label>
|
|
</form>
|
|
</div>
|
|
|
|
<div
|
|
:if={@infestations == []}
|
|
id="infestations-empty"
|
|
class="mt-10 bg-panel px-6 py-16 text-center"
|
|
>
|
|
<p class="text-sm font-medium text-ink">No infestations in this window</p>
|
|
<p class="mt-2 text-xs text-ink-muted">
|
|
Infestations appear when the same finding title hits {@min_repos}+ listed repos.
|
|
</p>
|
|
</div>
|
|
|
|
<div :if={@infestations != []} class="mt-8">
|
|
<.constellation id="infestations-constellation" infestations={@infestations} />
|
|
|
|
<ul id="infestations-list" class="sr-only">
|
|
<li :for={infestation <- @infestations} id={"infestation-#{infestation.pattern_key}"}>
|
|
<.link navigate={~p"/infestations/#{infestation.pattern_key}"}>
|
|
{infestation.title} · {infestation.repo_count} repos
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</Layouts.page>
|
|
</Layouts.app>
|
|
"""
|
|
end
|
|
end
|