Skip to content

Commit

Permalink
New UI: Support Archives (#1797)
Browse files Browse the repository at this point in the history
Feature parity with the current/old UI
  • Loading branch information
joshk authored Jan 21, 2025
1 parent be4a54f commit 512ccb9
Show file tree
Hide file tree
Showing 4 changed files with 459 additions and 3 deletions.
21 changes: 21 additions & 0 deletions lib/nerves_hub/archives.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ defmodule NervesHub.Archives do
alias NervesHub.Repo
alias NervesHub.Workers.DeleteArchive

@spec filter(Product.t(), map()) ::
{:ok, {[Product.t()], Flop.Meta.t()}} | {:error, Flop.Meta.t()}
def filter(product_id, opts \\ %{}) do
opts = Map.reject(opts, fn {_key, val} -> is_nil(val) end)

sort = Map.get(opts, :sort, "inserted_at")
sort_direction = Map.get(opts, :sort_direction, "desc")

sort_opts = {String.to_existing_atom(sort_direction), String.to_atom(sort)}

flop = %Flop{
page: String.to_integer(Map.get(opts, :page, "1")),
page_size: String.to_integer(Map.get(opts, :page_size, "25"))
}

Archive
|> where([f], f.product_id == ^product_id)
|> order_by(^sort_opts)
|> Flop.run(flop)
end

@spec all_by_product(Product.t()) :: [Archive.t()]
def all_by_product(product) do
Archive
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<div class="h-[56px] flex justify-end bg-base-900 border-b border-base-700">
<div class="h-full border-l flex items-center justify-center border-base-700 bg-base-900">
<a :if={Application.get_env(:nerves_hub, :new_ui)} href={"/ui/switch?return_to=#{@current_path}"} class="">
<svg class="box-content px-5 h-5 w-5 stroke-zinc-500 hover:stroke-indigo-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21 14V19C21 20.1046 20.1046 21 19 21H16M3 16V19C3 20.1046 3.89543 21 5 21H16M3 16V5C3 3.89543 3.89543 3 5 3H11M3 16C4.40293 15.7662 6.63687 15.7073 8.94504 16.2427M16 21C14.2965 18.2317 11.5726 16.8522 8.94504 16.2427M8.94504 16.2427C9.87157 15.1698 11.1851 14.1585 13 13.3925M8.5 7C8 7 7 7.3 7 8.5C7 9.7 8 10 8.5 10C9 10 10 9.7 10 8.5C10 7.3 9 7 8.5 7ZM17.5 9.46262L14.7188 11L15.25 7.74377L13 5.43769L16.1094 4.96262L17.5 2L18.8906 4.96262L22 5.43769L19.75 7.74377L20.2812 11L17.5 9.46262Z"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
</div>
</div>

<div class="h-0 flex-1 overflow-y-auto" phx-drop-target={@uploads.archive.ref}>
<div class="flex items-center h-[90px] gap-4 px-6 py-7 border-b border-[#3F3F46] text-sm font-medium">
<h1 class="text-xl leading-[30px] font-semibold text-neutral-50">All Archives</h1>
<div class="rounded-sm bg-zinc-800 text-xs text-zinc-300 px-1.5 py-0.5 mr-auto">
{@pager_meta.total_count}
</div>

<div :for={entry <- @uploads.archive.entries} class="w-2/5 pl-4 pr-2 flex flex-col items-center">
<div class="flex justify-between mb-1">
<span class="text-sm font-base text-indigo-500 dark:text-white">Uploading archive... </span>
<span class="text-sm font-base text-indigo-500 dark:text-white">{entry.progress}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div class="bg-indigo-500 h-2.5 rounded-full animate-pulse" style={"width: #{entry.progress}%"}></div>
</div>
</div>

<form phx-change="validate-firmware" class={[Enum.any?(@uploads.archive.entries) && "hidden"]}>
<div class="flex px-3 py-1.5 gap-2 rounded bg-zinc-800 border border-zinc-600 hover:cursor-pointer">
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path d="M4.1665 10.0001H9.99984M15.8332 10.0001H9.99984M9.99984 10.0001V4.16675M9.99984 10.0001V15.8334" stroke="#A1A1AA" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<label for={@uploads.archive.ref} class="text-sm font-medium text-zinc-300 hover:cursor-pointer">Upload Archive</label>
<.live_file_input upload={@uploads.archive} class="hidden" />
</div>
</form>
</div>
<%= if Enum.any?(@archives) do %>
<div class="listing">
<table>
<thead>
<tr>
<th>UUID</th>
<th phx-click="sort" phx-value-sort="version" class="cursor-pointer">
<Sorting.sort_icon text="Version" field="version" selected_field={@current_sort} selected_direction={@sort_direction} />
</th>
<th>Platform</th>
<th>Architecture</th>
<th>Signing Key</th>
<th phx-click="sort" phx-value-sort="inserted_at" class="cursor-pointer">
<Sorting.sort_icon text="Uploaded on" field="inserted_at" selected_field={@current_sort} selected_direction={@sort_direction} />
</th>
</tr>
</thead>
<tbody>
<tr :for={archive <- @archives} class="border-b border-zinc-800">
<td>
<div class="flex gap-[8px] items-center">
<.link navigate={~p"/org/#{@org.name}/#{@product.name}/archives/#{archive.uuid}"}>
{archive.uuid}
</.link>
</div>
</td>

<td>
<div class="flex gap-[8px] items-center">
{archive.version}
</div>
</td>

<td>
{archive.platform}
</td>

<td>
{archive.architecture}
</td>

<td>
<span class="inline-flex gap-1 pl-2.5 pr-3 py-0.5 border border-zinc-700 rounded-full bg-zinc-800">
<code class="text-xs text-zinc-300">{format_signed(archive, @org_keys)}</code>
</span>
</td>

<td>
<%= if is_nil(archive.inserted_at) do %>
Never
<% else %>
{Calendar.strftime(archive.inserted_at, "%Y-%m-%d at %I:%M %p UTC")}
<% end %>
</td>

<%!-- <td class="actions">
<div class="">
<a class="" data-target="#" id={"actions-#{device.id}"} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" phx-click={show_menu("actions-menu-#{device.id}")}>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M9.1665 10C9.1665 10.2211 9.2543 10.433 9.41058 10.5893C9.56686 10.7456 9.77882 10.8334 9.99984 10.8334C10.2209 10.8334 10.4328 10.7456 10.5891 10.5893C10.7454 10.433 10.8332 10.2211 10.8332 10C10.8332 9.77903 10.7454 9.56707 10.5891 9.41079C10.4328 9.2545 10.2209 9.16671 9.99984 9.16671C9.77882 9.16671 9.56686 9.2545 9.41058 9.41079C9.2543 9.56707 9.1665 9.77903 9.1665 10ZM9.1665 15.8334C9.1665 16.0544 9.2543 16.2663 9.41058 16.4226C9.56686 16.5789 9.77882 16.6667 9.99984 16.6667C10.2209 16.6667 10.4328 16.5789 10.5891 16.4226C10.7454 16.2663 10.8332 16.0544 10.8332 15.8334C10.8332 15.6124 10.7454 15.4004 10.5891 15.2441C10.4328 15.0878 10.2209 15 9.99984 15C9.77882 15 9.56686 15.0878 9.41058 15.2441C9.2543 15.4004 9.1665 15.6124 9.1665 15.8334ZM9.1665 4.16671C9.1665 4.38772 9.2543 4.59968 9.41058 4.75596C9.56686 4.91224 9.77882 5.00004 9.99984 5.00004C10.2209 5.00004 10.4328 4.91224 10.5891 4.75596C10.7454 4.59968 10.8332 4.38772 10.8332 4.16671C10.8332 3.94569 10.7454 3.73373 10.5891 3.57745C10.4328 3.42117 10.2209 3.33337 9.99984 3.33337C9.77882 3.33337 9.56686 3.42117 9.41058 3.57745C9.2543 3.73373 9.1665 3.94569 9.1665 4.16671Z"
stroke="#A1A1AA"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<div class="hidden absolute right-[24px] menu-box" id={"actions-menu-#{device.id}"} phx-click-away={hide_menu("actions-menu-#{device.id}")} phx-key="escape" )}>
<.link phx-click="reboot-device" phx-value-device_identifier={device.identifier} class="dropdown-item">
Reboot
</.link>
<div class="dropdown-divider"></div>
<.link navigate={~p"/org/#{@org.name}/#{@product.name}/devices/#{device.identifier}/console"} class="dropdown-item">
Console
</.link>
<div class="dropdown-divider"></div>
<div class="dropdown-divider"></div>
<.link phx-click="toggle-device-updates" phx-value-device_identifier={device.identifier} class="dropdown-item">
<span>
<%= if device.updates_enabled, do: "Disable Updates", else: "Enable Updates" %>
</span>
</.link>

<div class="dropdown-divider"></div>

<%= link to: Routes.device_path(@socket, :export_audit_logs, @org.name, @product.name, device.identifier), class: "dropdown-item", aria: [label: "Download Audit Logs"] do %>
<div class="button-icon download"></div>
<span class="action-text">Download Audit Logs</span>
<% end %>
</div>
</div>
</td> --%>
</tr>
</tbody>
</table>
</div>
<% else %>
<div class="h-full pb-16 flex items-center justify-center">
<span class="text-xl font-medium text-neutral-50">{@product.name} doesn’t have any available archives.</span>
</div>
<% end %>
</div>

<Pager.render_with_page_sizes pager={@pager_meta} page_sizes={[25, 50, 100]} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<div class="h-[56px] skrink-0 flex justify-between bg-base-900 border-b border-base-700 pl-6 items-center">
<div class="flex gap-2.5">
<.link navigate={~p"/org/#{@org.name}/#{@product.name}/archives"} class="back-link flex gap-2.5 items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M4.16671 10L9.16671 5M4.16671 10L9.16671 15M4.16671 10H15.8334" stroke="#A1A1AA" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span class="text-base-400">All Archives</span>
</.link>
<span class="text-base-400">/</span>
<span class="text-zinc-50 font-semibold">{@archive.uuid}</span>
</div>

<div class="h-full border-l flex items-center justify-center border-base-700 bg-base-900">
<a :if={Application.get_env(:nerves_hub, :new_ui)} href={"/ui/switch?return_to=#{@current_path}"} class="">
<svg class="box-content px-5 h-5 w-5 stroke-zinc-500 hover:stroke-indigo-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21 14V19C21 20.1046 20.1046 21 19 21H16M3 16V19C3 20.1046 3.89543 21 5 21H16M3 16V5C3 3.89543 3.89543 3 5 3H11M3 16C4.40293 15.7662 6.63687 15.7073 8.94504 16.2427M16 21C14.2965 18.2317 11.5726 16.8522 8.94504 16.2427M8.94504 16.2427C9.87157 15.1698 11.1851 14.1585 13 13.3925M8.5 7C8 7 7 7.3 7 8.5C7 9.7 8 10 8.5 10C9 10 10 9.7 10 8.5C10 7.3 9 7 8.5 7ZM17.5 9.46262L14.7188 11L15.25 7.74377L13 5.43769L16.1094 4.96262L17.5 2L18.8906 4.96262L22 5.43769L19.75 7.74377L20.2812 11L17.5 9.46262Z"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
</div>
</div>

<div class="h-0 flex-1 overflow-y-auto flex flex-col">
<div class="h-[90px] shrink-0 flex justify-between p-6 border-b border-zinc-700">
<div class="flex gap-3 items-center">
<h1 class="text-xl font-semibold leading-[30px] text-zinc-50">
{@archive.uuid}
</h1>
</div>

<div class="flex items-center gap-2">
<.link class="flex px-3 py-1.5 gap-2 rounded border border-base-600 bg-zinc-800" href={~p"/org/#{@org.name}/#{@product.name}/archives/#{@archive.uuid}/download"} download>
<svg class="size-5 stroke-zinc-400" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18 21L15 18M18 21L21 18M18 21V15M20 9V8.82843C20 8.29799 19.7893 7.78929 19.4142 7.41421L15.5858 3.58579C15.2107 3.21071 14.702 3 14.1716 3H14M20 9H16C14.8954 9 14 8.10457 14 7V3M20 9V11M14 3H6C4.89543 3 4 3.89543 4 5V19C4 20.1046 4.89543 21 6 21H12"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="text-sm font-medium text-zinc-300">Download</span>
</.link>

<button
class="box-content h-5 py-1.5 px-3 flex gap-2 justify-center items-center rounded border border-red-500 bg-zinc-800 text-sm font-medium text-zinc-300 disabled:text-zinc-500"
aria-label="Delete archive"
type="button"
phx-click="delete-archive"
>
<svg class="size-5 stroke-red-500" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4999 5.83337H7.49992M12.4999 5.83337H14.9999M12.4999 5.83337C12.4999 4.45266 11.3806 3.33337 9.99992 3.33337C8.61921 3.33337 7.49992 4.45266 7.49992 5.83337M7.49992 5.83337H4.99992M3.33325 5.83337H4.99992M4.99992 5.83337V15C4.99992 15.9205 5.74611 16.6667 6.66659 16.6667H13.3333C14.2537 16.6667 14.9999 15.9205 14.9999 15V5.83337M14.9999 5.83337H16.6666M8.33325 9.16671V13.3334M11.6666 13.3334V9.16671"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="text-sm font-medium text-red-500">Delete</span>
</button>
</div>
</div>

<div class="h-full flex items-start justify-between gap-4 p-6">
<div class="w-1/2 flex flex-col gap-4">
<div class="flex flex-col pb-4 rounded border border-zinc-700 bg-zinc-900 shadow-device-details-content">
<div class="h-14 pl-4 pr-3 flex items-center text-neutral-50 font-medium leading-6">
General info
</div>
<div class="flex flex-col gap-3">
<div :if={@archive.author} class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Author:</span>
<span class="text-sm text-zinc-300">{@archive.author}</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Description:</span>
<span :if={@archive.description} class="text-sm text-zinc-300">{@archive.description}</span>
<span :if={!@archive.description} class="text-sm text-zinc-400">No description</span>
</div>

<div :if={@archive.misc} class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Misc:</span>
<span class="text-sm text-zinc-300">{@archive.misc}</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Signed with:</span>
<span class="flex items-center gap-1 pl-2.5 pr-3 py-0.5 border border-zinc-700 rounded-full bg-zinc-800">
<code class="text-xs text-zinc-300">{format_signed(@archive, @org_keys)}</code>
</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Uploaded on:</span>
<span class="text-sm text-zinc-300">{Calendar.strftime(@archive.inserted_at, "%Y-%m-%d at %I:%M %p UTC")}</span>
</div>
</div>
</div>
</div>

<div class="w-1/2 flex flex-col gap-4">
<div class="flex flex-col pb-4 rounded border border-zinc-700 bg-zinc-900 shadow-device-details-content">
<div class="h-14 pl-4 pr-3 flex items-center text-neutral-50 font-medium leading-6">
Metadata
</div>
<div class="flex flex-col gap-3">
<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Version:</span>
<span class="text-sm text-zinc-300">{@archive.version}</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Architecture:</span>
<span class="text-sm text-zinc-300">{@archive.architecture}</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Platform:</span>
<span class="text-sm text-zinc-300">{@archive.platform}</span>
</div>

<div :if={@archive.vcs_identifier} class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">VCS ID:</span>
<span class="text-sm text-zinc-300">{@archive.vcs_identifier}</span>
</div>

<div class="min-h-7 px-4 flex gap-4 items-center">
<span class="text-sm text-nerves-gray-500">Size:</span>
<span class="text-sm text-zinc-300">{format_file_size(@archive.size)}</span>
</div>
</div>
</div>
</div>
</div>
</div>
Loading

0 comments on commit 512ccb9

Please sign in to comment.