defmodule GitGudWeb.SecretsLive.Repo do
use GitGudWeb, :live_view
alias GitGud.Repositories
alias GitGud.Repositories.Storage
alias GitGud.Secrets
@impl true
def mount(%{"owner" => owner, "name" => name}, _session, socket) do
repo =
Repositories.get_repository_by_path!(owner, name)
|> GitGud.Repo.preload([:owner, :organization])
{:ok,
socket
|> assign(:repo, repo)
|> assign(:handle, Storage.repo_handle(repo))
|> GitGudWeb.RepoLive.Header.assign_chrome(repo)
|> load()
|> assign(:page_title, "Secrets — #{owner}/#{name}")}
end
defp load(socket) do
socket
|> assign(:secret_names, Secrets.list_repo_secret_names(socket.assigns.repo))
|> assign(:variables, Secrets.list_repo_variables(socket.assigns.repo))
end
@impl true
def handle_event("put_secret", %{"name" => name, "value" => value}, socket)
when value != "" do
case Secrets.put_repo_secret(socket.assigns.repo, name, value) do
{:ok, _} -> {:noreply, socket |> load() |> put_flash(:info, "Secret saved.")}
{:error, cs} -> {:noreply, put_flash(socket, :error, error_message(cs))}
end
end
def handle_event("put_secret", _, socket),
do: {:noreply, put_flash(socket, :error, "Name and value required.")}
def handle_event("delete_secret", %{"name" => name}, socket) do
:ok = Secrets.delete_repo_secret(socket.assigns.repo, name)
{:noreply, socket |> load() |> put_flash(:info, "Removed.")}
end
def handle_event("put_var", %{"name" => name, "value" => value}, socket) do
case Secrets.put_repo_variable(socket.assigns.repo, name, value) do
{:ok, _} -> {:noreply, socket |> load() |> put_flash(:info, "Variable saved.")}
{:error, cs} -> {:noreply, put_flash(socket, :error, error_message(cs))}
end
end
def handle_event("delete_var", %{"name" => name}, socket) do
:ok = Secrets.delete_repo_variable(socket.assigns.repo, name)
{:noreply, socket |> load()}
end
defp error_message(%Ecto.Changeset{} = cs) do
cs
|> Ecto.Changeset.traverse_errors(fn {m, _} -> m end)
|> inspect()
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-8">
<GitGudWeb.RepoLive.Header.header
repo={@repo}
handle={@handle}
current_scope={@current_scope}
has_packages?={@has_packages?}
can_admin?={@can_admin?}
latest_run={@latest_run}
/>
<.repo_settings_header handle={@handle} repo={@repo} current={:secrets} />
<section class="max-w-2xl">
<h2 class="font-semibold mb-2">Secrets</h2>
<p class="text-xs opacity-70 mb-3">
Encrypted at rest. Values cannot be read back once saved —
only re-set. Available to runners as <code phx-no-curly-interpolation>${{ secrets.NAME }}</code>.
</p>
<form phx-submit="put_secret" class="flex gap-2 mb-3 items-end">
<input
name="name"
placeholder="MY_SECRET"
class="input input-sm input-bordered"
required
autocomplete="off"
/>
<input
name="value"
type="password"
placeholder="value"
class="input input-sm input-bordered flex-1"
required
autocomplete="off"
/>
<button type="submit" class="btn btn-sm btn-primary">Save</button>
</form>
<ul class="divide-y divide-base-300">
<li :for={n <- @secret_names} class="flex items-center gap-2 py-2 font-mono text-sm">
<span class="flex-1">{n}</span>
<button
phx-click="delete_secret"
phx-value-name={n}
data-confirm="Delete this secret?"
class="btn btn-xs btn-ghost"
>
<.icon name="hero-trash" class="size-3" />
</button>
</li>
<li :if={@secret_names == []} class="text-sm opacity-60 py-3">
No secrets set.
</li>
</ul>
</section>
<section class="max-w-2xl">
<h2 class="font-semibold mb-2">Variables</h2>
<p class="text-xs opacity-70 mb-3">
Plaintext, available to runners as <code phx-no-curly-interpolation>${{ vars.NAME }}</code>.
</p>
<form phx-submit="put_var" class="flex gap-2 mb-3 items-end">
<input
name="name"
placeholder="MY_VAR"
class="input input-sm input-bordered"
required
autocomplete="off"
/>
<input name="value" placeholder="value" class="input input-sm input-bordered flex-1" />
<button type="submit" class="btn btn-sm btn-primary">Save</button>
</form>
<ul class="divide-y divide-base-300">
<li :for={v <- @variables} class="flex items-center gap-2 py-2 font-mono text-sm">
<span class="flex-1">{v.name}</span>
<span class="opacity-60 truncate max-w-xs">{v.value}</span>
<button
phx-click="delete_var"
phx-value-name={v.name}
data-confirm="Delete this variable?"
class="btn btn-xs btn-ghost"
>
<.icon name="hero-trash" class="size-3" />
</button>
</li>
<li :if={@variables == []} class="text-sm opacity-60 py-3">
No variables set.
</li>
</ul>
</section>
</div>
</Layouts.app>
"""
end
end
5.6 KiB · text
5af55d9