Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
354 lines
13 KiB
Text
354 lines
13 KiB
Text
<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
|
|
<Layouts.page>
|
|
<.breadcrumbs id="profile-breadcrumb">
|
|
<:crumb navigate={~p"/"}>registry</:crumb>
|
|
<:crumb>@{@account.handle}</:crumb>
|
|
</.breadcrumbs>
|
|
|
|
<header id="profile-header" class="border-b-2 border-strong pb-6">
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<.notch_badge
|
|
:if={role_label(@account.platform_role)}
|
|
id="profile-role"
|
|
class="text-signal"
|
|
>
|
|
{role_label(@account.platform_role)}
|
|
</.notch_badge>
|
|
<.notch_badge id="profile-tier" class="text-ink-muted">
|
|
{tier_label(@account.trust_tier)}
|
|
</.notch_badge>
|
|
<span :if={@badges != []} id="profile-badges" class="contents">
|
|
<.notch_badge
|
|
:for={badge <- @badges}
|
|
id={"profile-badge-#{badge}"}
|
|
class="text-quote"
|
|
>
|
|
{badge_label(badge)}
|
|
</.notch_badge>
|
|
</span>
|
|
</div>
|
|
|
|
<h1
|
|
id="profile-handle"
|
|
class="mt-3 break-words font-display text-3xl font-medium uppercase leading-tight tracking-[0.02em] text-ink sm:text-4xl"
|
|
>
|
|
<span class="text-ink-faint">@</span>{@account.handle}
|
|
</h1>
|
|
<p :if={@account.display_name} class="mt-1 text-sm text-ink-muted">
|
|
{@account.display_name}
|
|
</p>
|
|
|
|
<div class="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 font-mono text-xs text-ink-faint">
|
|
<span
|
|
class="inline-flex items-center gap-1.5"
|
|
title={"#{@reputation.verification} from verified work · #{@reputation.votes} from votes"}
|
|
>
|
|
<.icon name="hero-bolt" class="size-3.5" />
|
|
<span id="profile-reputation" class="tabular-nums text-ink-muted">
|
|
{@reputation.total}
|
|
</span>
|
|
reputation
|
|
<span class="text-ink-faint">
|
|
({@reputation.verification} verified · {@reputation.votes} votes<span :if={
|
|
@reputation.at_risk > 0
|
|
}> · {@reputation.at_risk} at stake in {@stake_summary.at_risk} {if(
|
|
@stake_summary.at_risk == 1,
|
|
do: "report",
|
|
else: "reports"
|
|
)}</span><span
|
|
:if={@reputation.slashed > 0}
|
|
class="text-signal"
|
|
> · {@reputation.slashed} slashed across {@stake_summary.slashed} {if(
|
|
@stake_summary.slashed == 1,
|
|
do: "report",
|
|
else: "reports"
|
|
)}</span>)
|
|
</span>
|
|
</span>
|
|
<span class="inline-flex items-center gap-1.5">
|
|
<.icon name="hero-clock" class="size-3.5" /> joined {joined_on(@account.inserted_at)}
|
|
</span>
|
|
<a
|
|
:for={identity <- @account.identities}
|
|
:if={identity.profile_url}
|
|
id={"profile-identity-#{identity.provider}"}
|
|
href={identity.profile_url}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
class="inline-flex items-center gap-1.5 transition hover:text-signal"
|
|
>
|
|
<.icon name="hero-arrow-up-right" class="size-3.5" />
|
|
{provider_label(identity.provider)}<span :if={identity.provider_login}>/{identity.provider_login}</span>
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<section
|
|
id="profile-stats"
|
|
class="mt-8 grid grid-cols-2 border-2 border-strong lg:grid-cols-5"
|
|
>
|
|
<div class="border-b border-r border-rule px-6 py-7 lg:border-b-0">
|
|
<p class="text-xs font-medium text-ink-faint">Reports</p>
|
|
<p
|
|
id="profile-review-count"
|
|
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
|
|
>
|
|
{@stats.reviews}
|
|
</p>
|
|
</div>
|
|
<div class="border-b border-rule px-6 py-7 lg:border-b-0 lg:border-r">
|
|
<p class="text-xs font-medium text-ink-faint">Findings</p>
|
|
<p
|
|
id="profile-finding-count"
|
|
class="mt-3 font-display text-4xl font-medium tabular-nums text-signal"
|
|
>
|
|
{@stats.findings}
|
|
</p>
|
|
</div>
|
|
<div class="border-b border-r border-rule px-6 py-7 lg:border-b-0">
|
|
<p class="text-xs font-medium text-ink-faint">Checks</p>
|
|
<p
|
|
id="profile-verdict-count"
|
|
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
|
|
>
|
|
{@stats.verdicts}
|
|
</p>
|
|
</div>
|
|
<div class="border-b border-rule px-6 py-7 lg:border-b-0 lg:border-r">
|
|
<p class="text-xs font-medium text-ink-faint">
|
|
Repositories
|
|
</p>
|
|
<p
|
|
id="profile-repo-count"
|
|
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
|
|
>
|
|
{@stats.repositories}
|
|
</p>
|
|
</div>
|
|
<div class="px-6 py-7">
|
|
<p class="text-xs font-medium text-ink-faint">Credits</p>
|
|
<p
|
|
id="profile-credit-balance"
|
|
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
|
|
>
|
|
{@credit_balance}
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="mt-10 grid gap-10 lg:grid-cols-2">
|
|
<section id="profile-repositories">
|
|
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
|
|
Repositories added
|
|
</h2>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-faint">
|
|
Publicly listed repositories this contributor registered.
|
|
</p>
|
|
|
|
<p
|
|
:if={@repositories == []}
|
|
id="profile-no-repositories"
|
|
class="mt-4 font-mono text-xs text-ink-faint"
|
|
>
|
|
No listed repositories yet.
|
|
</p>
|
|
|
|
<ul
|
|
:if={@repositories != []}
|
|
class="mt-4 divide-y divide-rule border-t border-rule"
|
|
>
|
|
<li :for={repository <- @repositories} id={"profile-repo-#{repository.id}"}>
|
|
<.link
|
|
navigate={TarakanWeb.RepositoryPaths.repository_path(repository)}
|
|
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
|
|
>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">
|
|
{activity_time(repository.inserted_at)}
|
|
</span>
|
|
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
|
|
<span class="text-ink-faint">{repository.host}/</span>{repository.owner}/{repository.name}
|
|
</span>
|
|
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
|
|
<span :if={repository.primary_language}>{repository.primary_language}</span>
|
|
</span>
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="profile-reviews">
|
|
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
|
|
Reports submitted
|
|
</h2>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-faint">
|
|
Security reviews pinned to exact commits.
|
|
</p>
|
|
|
|
<p
|
|
:if={@reviews == []}
|
|
id="profile-no-reviews"
|
|
class="mt-4 font-mono text-xs text-ink-faint"
|
|
>
|
|
No public reports yet.
|
|
</p>
|
|
|
|
<ul :if={@reviews != []} class="mt-4 divide-y divide-rule border-t border-rule">
|
|
<li :for={entry <- @reviews} id={"profile-review-#{entry.id}"}>
|
|
<.link
|
|
navigate={TarakanWeb.RepositoryPaths.repository_security_path(entry.repository)}
|
|
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
|
|
>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
|
|
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
|
|
<span class="text-ink-faint">reviewed</span>
|
|
{entry.repository.owner}/{entry.repository.name}
|
|
<span :if={entry.commit_sha} class="text-ink-faint">
|
|
· {short_sha(entry.commit_sha)}
|
|
</span>
|
|
</span>
|
|
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
|
|
<span :if={entry.verified} class="text-ink-muted">verified · </span>
|
|
<span :if={entry.findings_count > 0} class="text-signal">
|
|
{entry.findings_count} findings
|
|
</span>
|
|
<span :if={entry.findings_count == 0}>no findings</span>
|
|
</span>
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="profile-findings">
|
|
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
|
|
Findings filed
|
|
</h2>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-faint">
|
|
Individual findings from public reports.
|
|
</p>
|
|
|
|
<p
|
|
:if={@findings == []}
|
|
id="profile-no-findings"
|
|
class="mt-4 font-mono text-xs text-ink-faint"
|
|
>
|
|
No public findings yet.
|
|
</p>
|
|
|
|
<ul :if={@findings != []} class="mt-4 divide-y divide-rule border-t border-rule">
|
|
<li :for={entry <- @findings} id={"profile-finding-#{entry.public_id}"}>
|
|
<.link
|
|
navigate={~p"/findings/#{entry.public_id}"}
|
|
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
|
|
>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
|
|
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
|
|
<span class={[
|
|
"mr-1.5 inline-block border px-1 text-[10px] uppercase tracking-[0.12em]",
|
|
entry.severity in ~w(critical high) && "border-signal text-signal",
|
|
entry.severity not in ~w(critical high) && "border-rule text-ink-faint"
|
|
]}>
|
|
{entry.severity}
|
|
</span>
|
|
{entry.title}
|
|
</span>
|
|
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
|
|
{entry.repository.owner}/{entry.repository.name}
|
|
</span>
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="profile-checks">
|
|
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
|
|
Checks cast
|
|
</h2>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-faint">
|
|
Independent confirmations, disputes, and fix notes.
|
|
</p>
|
|
|
|
<p
|
|
:if={@checks == []}
|
|
id="profile-no-checks"
|
|
class="mt-4 font-mono text-xs text-ink-faint"
|
|
>
|
|
No public checks yet.
|
|
</p>
|
|
|
|
<ul :if={@checks != []} class="mt-4 divide-y divide-rule border-t border-rule">
|
|
<li :for={entry <- @checks} id={"profile-check-#{entry.id}"}>
|
|
<.link
|
|
:if={entry.public_id || entry.repository}
|
|
navigate={check_path(entry)}
|
|
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
|
|
>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
|
|
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
|
|
<span class={[
|
|
entry.verdict == "confirmed" && "text-quote",
|
|
entry.verdict == "disputed" && "text-signal",
|
|
entry.verdict == "fixed" && "text-ink"
|
|
]}>
|
|
{entry.verdict}
|
|
</span>
|
|
<span :if={entry.title} class="text-ink-faint"> · </span>
|
|
<span :if={entry.title}>{entry.title}</span>
|
|
<span
|
|
:if={is_nil(entry.title) and entry.repository}
|
|
class="text-ink-faint"
|
|
>
|
|
on {entry.repository.owner}/{entry.repository.name}
|
|
</span>
|
|
</span>
|
|
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
|
|
{provenance_label(entry.provenance)}
|
|
</span>
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
|
|
<section id="profile-credits" class="mt-10">
|
|
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
|
|
Credits
|
|
</h2>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-faint">
|
|
Earned on verification events only - never purchasable, never transferable.
|
|
</p>
|
|
|
|
<p
|
|
:if={@credit_entries == []}
|
|
id="profile-no-credits"
|
|
class="mt-4 font-mono text-xs text-ink-faint"
|
|
>
|
|
No credit activity yet.
|
|
</p>
|
|
|
|
<ul :if={@credit_entries != []} class="mt-4 divide-y divide-rule border-t border-rule">
|
|
<li
|
|
:for={entry <- @credit_entries}
|
|
id={"profile-credit-#{entry.id}"}
|
|
class="flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs"
|
|
>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">
|
|
{activity_time(entry.inserted_at)}
|
|
</span>
|
|
<span class="min-w-0 truncate text-ink-muted">
|
|
{credit_kind_label(entry.kind)}
|
|
</span>
|
|
<span class={[
|
|
"ml-auto shrink-0 tabular-nums",
|
|
entry.amount > 0 && "text-quote",
|
|
entry.amount < 0 && "text-signal"
|
|
]}>
|
|
{credit_amount(entry.amount)}
|
|
</span>
|
|
<span class="shrink-0 tabular-nums text-ink-faint">
|
|
balance {entry.balance_after}
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</Layouts.page>
|
|
</Layouts.app>
|