Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
309
lib/tarakan_web/components/repository_components.ex
Normal file
309
lib/tarakan_web/components/repository_components.ex
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
defmodule TarakanWeb.RepositoryComponents do
|
||||
@moduledoc """
|
||||
Shared repository page chrome.
|
||||
|
||||
Every repository-scoped page renders the same header at the same scale:
|
||||
status badge row, display-face title, canonical source link, host metadata,
|
||||
and the Code / Commits / Security tab rail. Page bodies differ; the frame does not.
|
||||
"""
|
||||
|
||||
use Phoenix.Component
|
||||
|
||||
import TarakanWeb.CoreComponents,
|
||||
only: [icon: 1, breadcrumbs: 1, handle_link: 1, notch_badge: 1]
|
||||
|
||||
alias TarakanWeb.RepositoryPaths
|
||||
|
||||
@doc ~S'Formats a star count compactly (`1200` -> `"1.2k"`); `nil` -> `"0"`.'
|
||||
def compact_stars(count) when is_integer(count) and count >= 1000,
|
||||
do: "#{Float.round(count / 1000, 1)}k"
|
||||
|
||||
def compact_stars(count) when is_integer(count), do: Integer.to_string(count)
|
||||
def compact_stars(_count), do: "0"
|
||||
|
||||
attr :repository, Tarakan.Repositories.Repository, required: true
|
||||
attr :active_tab, :atom, required: true, values: [:code, :commits, :security]
|
||||
|
||||
def repository_header(assigns) do
|
||||
~H"""
|
||||
<header>
|
||||
<.breadcrumbs id="repository-breadcrumb">
|
||||
<:crumb navigate="/">registry</:crumb>
|
||||
<:crumb navigate={RepositoryPaths.repository_path(@repository)}>
|
||||
{@repository.owner}
|
||||
</:crumb>
|
||||
<:crumb navigate={RepositoryPaths.repository_path(@repository)}>
|
||||
{@repository.name}
|
||||
</:crumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<.notch_badge
|
||||
id="repository-status"
|
||||
class={status_badge_class(@repository)}
|
||||
title={repository_status_title(@repository)}
|
||||
>
|
||||
{repository_status_label(@repository)}
|
||||
</.notch_badge>
|
||||
<.notch_badge :if={@repository.archived} class="text-signal">
|
||||
archived
|
||||
</.notch_badge>
|
||||
<.notch_badge
|
||||
:if={@repository.managed_disclosure}
|
||||
id="repository-managed-disclosure"
|
||||
class="text-quote"
|
||||
title="Disclosure to the vendor is handled by Tarakan staff. Handling only - the public record is unchanged."
|
||||
>
|
||||
Managed disclosure
|
||||
</.notch_badge>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex flex-col justify-between gap-4 lg:flex-row lg:items-end">
|
||||
<div class="min-w-0">
|
||||
<h1
|
||||
id="repository-name"
|
||||
class="break-words font-display text-2xl font-medium uppercase leading-tight tracking-[0.02em] text-ink sm:text-3xl"
|
||||
>
|
||||
<span class="text-ink-faint">{@repository.owner}/</span>{@repository.name}
|
||||
</h1>
|
||||
<p :if={@repository.description} class="mt-2 max-w-3xl text-sm leading-6 text-ink-muted">
|
||||
{@repository.description}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
:if={not Tarakan.Repositories.Repository.hosted?(@repository)}
|
||||
id="repository-source-link"
|
||||
href={@repository.canonical_url}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
class="inline-flex max-w-full min-w-0 items-center gap-2 font-mono text-xs text-ink-muted transition hover:text-signal"
|
||||
>
|
||||
<span class="min-w-0 break-all">{@repository.canonical_url}</span>
|
||||
<.icon name="hero-arrow-up-right" class="size-4 shrink-0" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="github-metadata"
|
||||
class="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 font-mono text-[11px] text-ink-faint sm:gap-x-5 sm:text-xs"
|
||||
>
|
||||
<span :if={@repository.primary_language} class="inline-flex items-center gap-2">
|
||||
<span class="size-2 bg-signal"></span>
|
||||
{@repository.primary_language}
|
||||
</span>
|
||||
<span
|
||||
:if={not Tarakan.Repositories.Repository.hosted?(@repository)}
|
||||
class="inline-flex items-center gap-1.5"
|
||||
>
|
||||
<.icon name="hero-star" class="size-3.5" /> {@repository.stars_count} stars
|
||||
</span>
|
||||
<span
|
||||
:if={not Tarakan.Repositories.Repository.hosted?(@repository)}
|
||||
class="inline-flex items-center gap-1.5"
|
||||
>
|
||||
<.icon name="hero-code-bracket" class="size-3.5" /> {@repository.forks_count} forks
|
||||
</span>
|
||||
<span :if={Tarakan.Repositories.Repository.hosted?(@repository)}>hosted on tarakan</span>
|
||||
<span :if={@repository.default_branch}>default: {@repository.default_branch}</span>
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<.icon name="hero-clock" class="size-3.5" />
|
||||
registered {Calendar.strftime(
|
||||
@repository.inserted_at,
|
||||
"%Y-%m-%d"
|
||||
)}
|
||||
</span>
|
||||
<span :if={@repository.submitted_by} id="repository-submitter">
|
||||
by <.handle_link handle={@repository.submitted_by.handle} class="text-ink-muted" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav
|
||||
id="repository-navigation"
|
||||
class="mt-5 flex items-end gap-0 overflow-x-auto overscroll-x-contain border-b border-rule text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
aria-label="Repository"
|
||||
>
|
||||
<.repository_tab
|
||||
id="repository-code-tab"
|
||||
active={@active_tab == :code}
|
||||
navigate={RepositoryPaths.repository_path(@repository)}
|
||||
icon="hero-code-bracket"
|
||||
label="Code"
|
||||
/>
|
||||
<.repository_tab
|
||||
id="repository-commits-tab"
|
||||
active={@active_tab == :commits}
|
||||
navigate={RepositoryPaths.repository_commits_path(@repository)}
|
||||
icon="hero-clock"
|
||||
label="Commits"
|
||||
/>
|
||||
<.repository_tab
|
||||
id="repository-overview-tab"
|
||||
active={@active_tab == :security}
|
||||
navigate={RepositoryPaths.repository_security_path(@repository)}
|
||||
icon="hero-shield-check"
|
||||
label="Security"
|
||||
>
|
||||
<span
|
||||
:if={security_tab_count(@repository)}
|
||||
class={[
|
||||
"rounded-full px-1.5 py-0.5 font-mono text-[10px] leading-none",
|
||||
security_tab_count_class(@repository)
|
||||
]}
|
||||
>
|
||||
{security_tab_count(@repository)}
|
||||
</span>
|
||||
</.repository_tab>
|
||||
</nav>
|
||||
</header>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :id, :string, required: true
|
||||
attr :active, :boolean, required: true
|
||||
attr :navigate, :string, required: true
|
||||
attr :icon, :string, required: true
|
||||
attr :label, :string, required: true
|
||||
slot :inner_block
|
||||
|
||||
# The active tab stays a link. It used to render as a span, which made the
|
||||
# most natural navigation on the page impossible: from a commit detail page
|
||||
# the Commits tab points at the commit list, and from a nested file the Code
|
||||
# tab points at the repository root, and neither could be clicked. A span is
|
||||
# also not focusable, so keyboard users tabbed straight past the section they
|
||||
# were in. aria-current carries the "you are here" meaning instead.
|
||||
defp repository_tab(assigns) do
|
||||
~H"""
|
||||
<.link
|
||||
id={@id}
|
||||
navigate={@navigate}
|
||||
aria-current={@active && "page"}
|
||||
class={[
|
||||
"-mb-px inline-flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 transition sm:px-4",
|
||||
@active && "border-signal font-semibold text-ink",
|
||||
!@active && "border-transparent text-ink-muted hover:border-rule hover:text-ink"
|
||||
]}
|
||||
>
|
||||
<.icon name={@icon} class="size-4" /> {@label} {render_slot(@inner_block)}
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
The scan's submitted Tarakan Scan Format document, pretty-printed for the
|
||||
raw-report block. Returns the stored text unchanged when it isn't JSON.
|
||||
"""
|
||||
def raw_report(%{raw_document: nil}), do: nil
|
||||
|
||||
def raw_report(%{raw_document: raw_document}) do
|
||||
Jason.Formatter.pretty_print(raw_document)
|
||||
rescue
|
||||
_not_json -> raw_document
|
||||
end
|
||||
|
||||
@doc """
|
||||
Short label for who may do a job, or how a submission was made.
|
||||
|
||||
Hybrid is agent draft with a person in the loop - not a description of
|
||||
Tarakan overall (the product is already human-owned).
|
||||
"""
|
||||
def provenance_label("agent"), do: "Agent"
|
||||
def provenance_label("human"), do: "Human"
|
||||
def provenance_label("hybrid"), do: "Agent + human"
|
||||
def provenance_label(other), do: other
|
||||
|
||||
@doc "Human-readable label for a review or task kind."
|
||||
def review_kind_label("code_review"), do: "Code review"
|
||||
def review_kind_label("threat_model"), do: "Threat model"
|
||||
def review_kind_label("privacy_review"), do: "Privacy review"
|
||||
def review_kind_label("business_logic"), do: "Business logic"
|
||||
def review_kind_label("verify_findings"), do: "Verify findings"
|
||||
def review_kind_label("write_fix"), do: "Write a fix"
|
||||
def review_kind_label("diff_review"), do: "Diff review"
|
||||
def review_kind_label("refute_finding"), do: "Refute a finding"
|
||||
def review_kind_label("reproduce_finding"), do: "Reproduce a finding"
|
||||
def review_kind_label("calibrate_severity"), do: "Re-score severity"
|
||||
def review_kind_label("synthesize_rule"), do: "Write a detector"
|
||||
def review_kind_label(other), do: other
|
||||
|
||||
@doc """
|
||||
Short status for repo headers and meta.
|
||||
|
||||
Avoids the useless lone label \"findings\" once every repo has some.
|
||||
Prefers open/verified counts and clear/unreviewed states.
|
||||
"""
|
||||
def repository_status_label(%{status: "unscanned"}), do: "No report"
|
||||
|
||||
def repository_status_label(%{open_findings_count: open, verified_findings_count: verified})
|
||||
when is_integer(open) and open > 0 do
|
||||
open_label = if open == 1, do: "1 open", else: "#{open} open"
|
||||
|
||||
if is_integer(verified) and verified > 0 do
|
||||
verified_label = if verified == 1, do: "1 verified", else: "#{verified} verified"
|
||||
"#{open_label} · #{verified_label}"
|
||||
else
|
||||
open_label
|
||||
end
|
||||
end
|
||||
|
||||
def repository_status_label(%{status: status}) when status in ["reviewed", "clear"],
|
||||
do: "Clear"
|
||||
|
||||
def repository_status_label(%{scan_count: n}) when is_integer(n) and n > 0, do: "Clear"
|
||||
def repository_status_label(_repository), do: "No report"
|
||||
|
||||
defp repository_status_title(%{open_findings_count: open, verified_findings_count: verified})
|
||||
when is_integer(open) and open > 0 do
|
||||
open_part =
|
||||
if open == 1,
|
||||
do: "1 unique finding still open",
|
||||
else: "#{open} unique findings still open"
|
||||
|
||||
if is_integer(verified) and verified > 0 do
|
||||
verified_part =
|
||||
if verified == 1,
|
||||
do: "1 independently verified",
|
||||
else: "#{verified} independently verified"
|
||||
|
||||
"#{open_part}; #{verified_part}"
|
||||
else
|
||||
open_part
|
||||
end
|
||||
end
|
||||
|
||||
defp repository_status_title(%{status: "unscanned"}),
|
||||
do: "No public review on the record yet"
|
||||
|
||||
defp repository_status_title(%{status: status}) when status in ["reviewed", "clear"],
|
||||
do: "Reported with no open findings"
|
||||
|
||||
defp repository_status_title(%{scan_count: n}) when is_integer(n) and n > 0,
|
||||
do: "Reported with no open findings"
|
||||
|
||||
defp repository_status_title(_repository), do: "No public review on the record yet"
|
||||
|
||||
defp status_badge_class(%{open_findings_count: open}) when is_integer(open) and open > 0,
|
||||
do: "text-signal"
|
||||
|
||||
defp status_badge_class(%{status: "unscanned"}), do: "text-ink-faint"
|
||||
|
||||
defp status_badge_class(%{status: status}) when status in ["reviewed", "clear"],
|
||||
do: "text-quote"
|
||||
|
||||
defp status_badge_class(_repository), do: "text-ink-muted"
|
||||
|
||||
# Tab pill: open count when > 0, else verified count, else nothing for unscanned.
|
||||
defp security_tab_count(%{open_findings_count: open}) when is_integer(open) and open > 0,
|
||||
do: open
|
||||
|
||||
defp security_tab_count(%{verified_findings_count: verified})
|
||||
when is_integer(verified) and verified > 0,
|
||||
do: verified
|
||||
|
||||
defp security_tab_count(%{scan_count: scans}) when is_integer(scans) and scans > 0, do: "ok"
|
||||
defp security_tab_count(_repository), do: nil
|
||||
|
||||
defp security_tab_count_class(%{open_findings_count: open}) when is_integer(open) and open > 0,
|
||||
do: "bg-signal text-ground"
|
||||
|
||||
defp security_tab_count_class(_repository), do: "bg-panel text-ink-muted border border-rule"
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue