Current section
Files
Jump to
Current section
Files
lib/gust_web/live/run_live/index.html.heex
<Layouts.app flash={@flash}>
<div
:if={@error != {}}
class="alert alert-error shadow-lg mb-4"
role="alert"
id="compilation-error"
>
<% {_lines, error, description} = @error %>
{"#{error} #{description}"}
</div>
<.live_component
module={GustWeb.BreadcrumbsComponent}
dag_def={@dag}
run={@selected_run}
task={@selected_task}
id="breadcrumbs"
/>
<.live_component
module={GustWeb.DagSummaryComponent}
dag={@dag}
dag_def={@dag_def}
id={@dag.id}
/>
<div class="p-3 border-b bg-gray-50">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center border-b border-gray-200 pb-4 mb-4">
<h2 class="font-semibold text-gray-800 text-lg mb-2 sm:mb-0">Run History</h2>
<div class="flex justify-end space-x-2">
<%= if @selected_task && (@selected_task.status in [:failed, :succeeded]) do %>
<button
id="restart-task"
phx-click="restart_task"
phx-value-id={@selected_task.id}
class="btn btn-warning"
>
Restart task: {@selected_task.name}
</button>
<% end %>
<%= if @selected_run && (@selected_run.status in [:failed, :succeeded]) do %>
<button
id="restart-run"
phx-click="restart_run"
phx-value-id={@selected_run.id}
class="btn btn-warning"
>
Restart run: {@selected_run.id}
</button>
<% end %>
<%= if @selected_task && (@selected_task.status in [:running, :retrying]) do %>
<button
id="cancel-task"
phx-click="cancel_task"
phx-value-id={@selected_task.id}
class="px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white text-sm font-medium rounded shadow transition duration-150"
>
Cancel
</button>
<% end %>
</div>
</div>
</div>
<div class="bg-white rounded shadow-sm overflow-hidden mb-4">
<div class="p-3 overflow-x-auto">
<div class="min-w-max">
<!-- Task Grid -->
<div class="flex">
<!-- Task names column -->
<div class="pr-2 border-r border-gray-200 pt-2">
<div class="flex items-center h-6 mb-2 border-b-2">
<span class="text-xs font-medium text-gray-800 ">Run</span>
</div>
<div :for={name <- @dag_def.task_list} class="flex items-center h-6 mb-2">
<span class="text-xs font-medium text-gray-800">{name}</span>
</div>
</div>
<!-- Run columns -->
<div class="flex" phx-update="stream" id="run_history">
<%= for {id, %Run{status: status, id: run_id, tasks: tasks} = run} <- @streams.runs do %>
<div class={"px-1 rounded #{selected_run_class(run_id, @selected_run)}"} id={id}>
<.link navigate={~p"/dags/#{@dag_def.name}/runs?run_id=#{run_id}&page=#{@page}"}>
<div class="flex justify-center mb-2 pt-2">
<.task_cell id={"run-status-cell-#{run_id}"} status={status} />
</div>
</.link>
<div :for={name <- @dag_def.task_list} class="flex justify-center mb-2">
<.interactive_task_cell
name={name}
ran_tasks={tasks}
run_id={run_id}
selected_task={@selected_task}
navigate={
~p"/dags/#{@dag_def.name}/runs?run_id=#{run_id}&task_name=#{name}&page=#{@page}"
}
data-testid={"#{name}-at-run-#{run_id}-link"}
/>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="flex gap-2 items-center">
<.link
id="next-page"
class="p-3"
navigate={~p"/dags/#{@dag_def.name}/runs?page=#{handle_page(@page, :next)}"}
>
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
</.link>
</div>
<span class="text-xs p-3">page: {@page}</span>
<div class="flex gap-2 items-center">
<.link
id="prev-page"
class="p-3"
navigate={~p"/dags/#{@dag_def.name}/runs?page=#{handle_page(@page, :prev)}"}
>
<.icon name="hero-arrow-right-solid" class="h-3 w-3" />
</.link>
</div>
</div>
</div>
<% current = @selected_task || @selected_run %>
<div
:if={@selected_run}
id="selected-run"
class="bg-white rounded shadow-sm overflow-hidden mb-4"
>
<div class="p-3 border-b flex justify-between items-center">
<.status_badge status={current.status} data-testid="status-badge" />
</div>
<div :if={current} class="grid grid-cols-2 md:grid-cols-3 text-xs">
<div class="p-3 border-r border-b">
<div class="text-gray-500 font-medium">Started</div>
<div class="mt-1 text-gray-800" id="inserted-at">
{current.inserted_at}
</div>
</div>
<div class="p-3 border-r border-b">
<div class="text-gray-500 font-medium">Updated</div>
<div class="mt-1 text-gray-800" id="updated-at">
{current.updated_at}
</div>
</div>
</div>
<div :if={@selected_task && map_size(@selected_task.error) > 0} class="p-3">
<h4 class=" text-sm font-semibold text-red-500">Error</h4>
<div class="alert overflow-auto">
<pre
id="task-error"
class="mt-1 whitespace-pre-wrap text-sm "
>
<%= pretty_json!(@selected_task.error) %>
</pre>
</div>
</div>
<div :if={@selected_task && map_size(@selected_task.result) > 0} class="p-3">
<h4 class=" text-sm font-semibold text-gray-500">Result</h4>
<div class="overflow-x-auto overflow-y-auto max-h-96">
<pre
id="task-result"
data-testid="task-result"
class="language-json rounded-md overflow-x-auto"
>
<%= pretty_json!(@selected_task.result) %>
</pre>
</div>
</div>
<div
:if={@selected_task}
class="bg-white rounded shadow-sm overflow-hidden mb-4"
>
<div class="p-3 border-b"></div>
<div class="overflow-x-auto overflow-y-auto max-h-96">
<h4 class="p-3 text-sm font-semibold text-gray-500">Logs</h4>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32"
>
Timestamp
</th>
<th
scope="col"
class="px-3 py-2 text-left text-xs font-medium text-gray-5stream_insert00 uppercase tracking-wider w-20"
>
Attempt
</th>
<th
scope="col"
class="px-3 py-2 text-left text-xs font-medium text-gray-5stream_insert00 uppercase tracking-wider w-20"
>
Level
</th>
<th
scope="col"
class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Content
</th>
</tr>
</thead>
<tbody
id="log-list"
class="bg-white divide-y divide-gray-200"
phx-update="stream"
>
<tr
:for={
{id,
%Flows.Log{
attempt: attempt,
inserted_at: inserted_at,
level: level,
content: content
}} <-
@streams.logs
}
id={"log-#{id}"}
>
<td class="px-3 py-2 whitespace-nowrap text-xs text-gray-800 font-mono">
{inserted_at}
</td>
<td class="px-3 py-2 whitespace-nowrap text-center align-middle">
<div class=" text-xs badge">{attempt}</div>
</td>
<td class="px-3 py-2 whitespace-nowrap">
<.log_badge level={level} />
</td>
<td class="px-3 py-2 text-xs text-gray-800 bg-slate-100">
{content}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="bg-white rounded shadow-sm overflow-hidden mb-4">
<div class="p-3 border-b">
<h3 class="text-sm font-semibold text-gray-800">Graph</h3>
</div>
<div class="p-3">
<div class="bg-gray-50 p-3 rounded border border-gray-200 overflow-auto">
<pre class="mermaid" phx-hook="Mermaid" id="mermaid-chart">
{"""
---
config:
theme: 'base'
themeVariables:
primaryColor: '#e5e6ea'
primaryTextColor: '#18181b'
primaryBorderColor: '#2563eb'
lineColor: '#676876'
secondaryColor: '#6a65ab'
tertiaryColor: '#fbfbfc'
---
#{mermaid_chart(@dag_def.tasks)}
"""}
</pre>
</div>
</div>
</div>
<div class="bg-white rounded shadow-sm overflow-hidden mb-4">
<div class="p-3 border-b flex justify-between items-center">
<h3 class="text-sm font-semibold text-gray-800">Code</h3>
<span id="reload-time" class="badge">{reload_time(@reload_dag_file)}</span>
</div>
<div class="p-3">
<pre class="language-elixir rounded-md overflow-x-auto">
<code id="code-highlight" class={"language-elixir code-block p-3 text-xs leading-relaxed"} phx-hook="CodeHighlight">
{read_code(@reload_dag_file)}
</code>
</pre>
</div>
</div>
</Layouts.app>