diff --git a/lib/towerops_web/components/core_components.ex b/lib/towerops_web/components/core_components.ex
index c66324f6..db24e584 100644
--- a/lib/towerops_web/components/core_components.ex
+++ b/lib/towerops_web/components/core_components.ex
@@ -456,6 +456,10 @@ defmodule ToweropsWeb.CoreComponents do
attr :row_id, :any, default: nil, doc: "the function for generating the row id"
attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
+ attr :row_link, :any,
+ default: nil,
+ doc: "the function for generating a navigate path for each row, renders real tags"
+
attr :row_item, :any,
default: &Function.identity/1,
doc: "the function for mapping each row before calling the :col and :action slots"
@@ -498,13 +502,23 @@ defmodule ToweropsWeb.CoreComponents do
:for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && @row_click.(row)}
class={[
- "py-4 pr-3 pl-4 text-sm whitespace-nowrap sm:pl-6",
i == 0 && "font-medium text-gray-900 dark:text-white",
i > 0 && "text-gray-500 dark:text-gray-400",
+ @row_link && "p-0 text-sm whitespace-nowrap",
+ !@row_link && "py-4 pr-3 pl-4 text-sm whitespace-nowrap sm:pl-6",
@row_click && "cursor-pointer"
]}
>
- {render_slot(col, @row_item.(row))}
+ <%= if @row_link do %>
+ <.link
+ navigate={@row_link.(row)}
+ class="block py-4 pr-3 pl-4 sm:pl-6 text-inherit no-underline"
+ >
+ {render_slot(col, @row_item.(row))}
+
+ <% else %>
+ {render_slot(col, @row_item.(row))}
+ <% end %>
<.icon name="hero-cloud" class="w-5 h-5 inline mr-1" /> Cloud Pollers
- <.table id="cloud-pollers-table" rows={@streams.cloud_pollers}>
+ <.table
+ id="cloud-pollers-table"
+ rows={@streams.cloud_pollers}
+ row_link={fn {_id, agent} -> ~p"/agents/#{agent.id}" end}
+ >
<:col :let={{_id, agent}} label="Name">
- <.link
- navigate={~p"/agents/#{agent.id}"}
- class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- {agent.name}
-
+ {agent.name}
<%= if agent.allow_remote_debug do %>
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
@@ -83,16 +82,15 @@
<.icon name="hero-server" class="w-5 h-5 inline mr-1" /> Organization Agents
- <.table id="org-agents-table" rows={@streams.org_agents}>
+ <.table
+ id="org-agents-table"
+ rows={@streams.org_agents}
+ row_link={fn {_id, agent} -> ~p"/agents/#{agent.id}" end}
+ >
<:col :let={{_id, agent}} label="Name">
- <.link
- navigate={~p"/agents/#{agent.id}"}
- class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- {agent.name}
-
+ {agent.name}
<%= if agent.allow_remote_debug do %>
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
diff --git a/lib/towerops_web/live/agent_live/index.html.heex b/lib/towerops_web/live/agent_live/index.html.heex
index 64335f31..bc7dab1a 100644
--- a/lib/towerops_web/live/agent_live/index.html.heex
+++ b/lib/towerops_web/live/agent_live/index.html.heex
@@ -67,15 +67,14 @@
<% else %>
- <.table id="agents-table" rows={@streams.agent_tokens}>
+ <.table
+ id="agents-table"
+ rows={@streams.agent_tokens}
+ row_link={fn {_id, agent} -> ~p"/agents/#{agent.id}" end}
+ >
<:col :let={{_id, agent}} label="Name">
- <.link
- navigate={~p"/agents/#{agent.id}"}
- class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- {agent.name}
-
+ {agent.name}
<%= if agent.is_cloud_poller do %>
<.icon name="hero-cloud" class="h-3 w-3" /> Cloud
@@ -178,15 +177,14 @@
Application-wide agents that can poll devices for any organization. Only visible and manageable by superadmins.
- <.table id="cloud-pollers-table" rows={@streams.cloud_pollers}>
+ <.table
+ id="cloud-pollers-table"
+ rows={@streams.cloud_pollers}
+ row_link={fn {_id, agent} -> ~p"/agents/#{agent.id}" end}
+ >
<:col :let={{_id, agent}} label="Name">
- <.link
- navigate={~p"/agents/#{agent.id}"}
- class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- {agent.name}
-
+ {agent.name}
<%= if agent.id == @global_default_cloud_poller_id do %>
<.icon name="hero-check-circle" class="h-3 w-3" /> Default
diff --git a/lib/towerops_web/live/device_live/index.html.heex b/lib/towerops_web/live/device_live/index.html.heex
index 9e831b4a..bd3d23f1 100644
--- a/lib/towerops_web/live/device_live/index.html.heex
+++ b/lib/towerops_web/live/device_live/index.html.heex
@@ -319,43 +319,54 @@
<.icon name="hero-bars-3" class="h-5 w-5" />
|
-
- {row.device.name}
+ |
+ <.link
+ navigate={~p"/devices/#{row.device.id}"}
+ class={[
+ "block py-4 pr-3 text-inherit no-underline",
+ @reorder_mode && "pl-2",
+ !@reorder_mode && "pl-4 sm:pl-3"
+ ]}
+ >
+ {row.device.name}
+
|
-
- {row.device.ip_address}
+ |
+ <.link
+ navigate={~p"/devices/#{row.device.id}"}
+ class="block px-3 py-4 text-inherit no-underline"
+ >
+ {row.device.ip_address}
+
|
-
-
- {row.device.status |> to_string() |> String.upcase()}
-
+ |
+ <.link
+ navigate={~p"/devices/#{row.device.id}"}
+ class="block px-3 py-4 text-inherit no-underline"
+ >
+
+ {row.device.status |> to_string() |> String.upcase()}
+
+
|
-
- <.timestamp datetime={row.device.last_checked_at} timezone={@timezone} />
+ |
+ <.link
+ navigate={~p"/devices/#{row.device.id}"}
+ class="block px-3 py-4 text-inherit no-underline"
+ >
+ <.timestamp
+ datetime={row.device.last_checked_at}
+ timezone={@timezone}
+ />
+
|
<% end %>
diff --git a/test/towerops_web/components/core_components_test.exs b/test/towerops_web/components/core_components_test.exs
new file mode 100644
index 00000000..6c962634
--- /dev/null
+++ b/test/towerops_web/components/core_components_test.exs
@@ -0,0 +1,107 @@
+defmodule ToweropsWeb.CoreComponentsTest do
+ use ExUnit.Case, async: true
+
+ import Phoenix.Component
+ import Phoenix.LiveViewTest
+
+ alias ToweropsWeb.CoreComponents
+
+ describe "table/1" do
+ test "renders basic table without row_link" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+
+ <:col :let={row} label="Name">{row.name}
+
+ """)
+
+ assert html =~ "Alice"
+ assert html =~ "Bob"
+ refute html =~ " "/users/#{row.id}" end}
+ >
+ <:col :let={row} label="Name">{row.name}
+
+ """)
+
+ assert html =~ ~s|href="/users/1"|
+ assert html =~ ~s|href="/users/2"|
+ assert html =~ "Alice"
+ assert html =~ "Bob"
+ end
+
+ test "row_link renders a link in each column cell" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ "/users/#{row.id}" end}
+ >
+ <:col :let={row} label="Name">{row.name}
+ <:col :let={row} label="Email">{row.email}
+
+ """)
+
+ # Each column cell should have a link (2 columns = 2 links)
+ link_count = length(Regex.scan(~r/href="\/users\/1"/, html))
+ assert link_count == 2
+ end
+
+ test "row_click still works without row_link" do
+ assigns = %{}
+
+ html =
+ rendered_to_string(~H"""
+ Phoenix.LiveView.JS.navigate("/users/#{row.id}") end}
+ >
+ <:col :let={row} label="Name">{row.name}
+
+ """)
+
+ assert html =~ "phx-click"
+ assert html =~ "cursor-pointer"
+ refute html =~ ~s| "/users/#{row.id}" end}
+ >
+ <:col :let={row} label="Name">{row.name}
+
+ """)
+
+ # td should have p-0 when row_link is used
+ assert html =~ "p-0"
+ # link should have the padding
+ assert html =~ "py-4"
+ end
+ end
+end