chore(deps): bump vendored oban_web from 2.12.3 to 2.12.5
Some checks failed
Build and Push / Build and Push Docker Image (push) Failing after 5m5s
Some checks failed
Build and Push / Build and Push Docker Image (push) Failing after 5m5s
Fixes two vulnerabilities: - CVE-2026-48592: Missing authorization on save-job event handler - CVE-2026-48593: Unbounded cron range expansion causing DoS
This commit is contained in:
parent
079346a1b9
commit
ed8bb33acb
11 changed files with 186 additions and 49 deletions
|
|
@ -212,6 +212,9 @@ defmodule Oban.Web.Components.Core do
|
||||||
defp badge_icon(%{name: "lock_closed"} = assigns),
|
defp badge_icon(%{name: "lock_closed"} = assigns),
|
||||||
do: ~H[<Icons.icon name="icon-lock-closed" class="h-4 w-4 shrink-0" />]
|
do: ~H[<Icons.icon name="icon-lock-closed" class="h-4 w-4 shrink-0" />]
|
||||||
|
|
||||||
|
defp badge_icon(%{name: "signal"} = assigns),
|
||||||
|
do: ~H[<Icons.icon name="icon-signal" class="h-4 w-4 shrink-0" />]
|
||||||
|
|
||||||
defp badge_icon(%{name: "sparkles"} = assigns),
|
defp badge_icon(%{name: "sparkles"} = assigns),
|
||||||
do: ~H[<Icons.icon name="icon-sparkles" class="h-4 w-4 shrink-0" />]
|
do: ~H[<Icons.icon name="icon-sparkles" class="h-4 w-4 shrink-0" />]
|
||||||
|
|
||||||
|
|
|
||||||
39
vendor/oban_web/lib/oban/web/cron_expr.ex
vendored
39
vendor/oban_web/lib/oban/web/cron_expr.ex
vendored
|
|
@ -45,10 +45,10 @@ defmodule Oban.Web.CronExpr do
|
||||||
|
|
||||||
defp describe_parsed(expression) do
|
defp describe_parsed(expression) do
|
||||||
with [min, hrs, dom, "*", dow] <- String.split(expression, " ", parts: 5),
|
with [min, hrs, dom, "*", dow] <- String.split(expression, " ", parts: 5),
|
||||||
{:ok, parsed_min} <- parse_field(min),
|
{:ok, parsed_min} <- parse_field(min, 0..59),
|
||||||
{:ok, parsed_hrs} <- parse_field(hrs),
|
{:ok, parsed_hrs} <- parse_field(hrs, 0..23),
|
||||||
{:ok, parsed_dom} <- parse_field(dom),
|
{:ok, parsed_dom} <- parse_field(dom, 1..31),
|
||||||
{:ok, parsed_dow} <- parse_field(dow, @days_of_week_translations) do
|
{:ok, parsed_dow} <- parse_field(dow, 0..7, @days_of_week_translations) do
|
||||||
combine_description(parsed_min, parsed_hrs, parsed_dom, parsed_dow)
|
combine_description(parsed_min, parsed_hrs, parsed_dom, parsed_dow)
|
||||||
else
|
else
|
||||||
_ -> nil
|
_ -> nil
|
||||||
|
|
@ -57,11 +57,11 @@ defmodule Oban.Web.CronExpr do
|
||||||
|
|
||||||
# Field Parsing
|
# Field Parsing
|
||||||
|
|
||||||
defp parse_field(field, translations \\ %{}) do
|
defp parse_field(field, bounds, translations \\ %{}) do
|
||||||
parts =
|
parts =
|
||||||
field
|
field
|
||||||
|> String.split(",")
|
|> String.split(",")
|
||||||
|> Enum.map(&parse_part(&1, translations))
|
|> Enum.map(&parse_part(&1, bounds, translations))
|
||||||
|
|
||||||
if Enum.any?(parts, &(&1 == :error)) do
|
if Enum.any?(parts, &(&1 == :error)) do
|
||||||
:error
|
:error
|
||||||
|
|
@ -70,37 +70,40 @@ defmodule Oban.Web.CronExpr do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_part("*", _translations), do: :wildcard
|
defp parse_part("*", _bounds, _translations), do: :wildcard
|
||||||
|
|
||||||
defp parse_part("*/" <> step, _translations) do
|
defp parse_part("*/" <> step, bounds, _translations) do
|
||||||
case Integer.parse(step) do
|
case Integer.parse(step) do
|
||||||
{num, ""} when num > 0 -> {:step, num}
|
{num, ""} when num > bounds.first and num <= bounds.last -> {:step, num}
|
||||||
_ -> :error
|
_ -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_part(part, translations) do
|
defp parse_part(part, bounds, translations) do
|
||||||
if String.contains?(part, "-") do
|
if String.contains?(part, "-") do
|
||||||
parse_range(part, translations)
|
parse_range(part, bounds, translations)
|
||||||
else
|
else
|
||||||
parse_value(part, translations)
|
parse_value(part, bounds, translations)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_range(part, translations) do
|
defp parse_range(part, bounds, translations) do
|
||||||
with [start_str, end_str] <- String.split(part, "-", parts: 2),
|
with [start_str, end_str] <- String.split(part, "-", parts: 2),
|
||||||
{:ok, start_val} <- translate_or_parse(start_str, translations),
|
{:ok, start_val} <- translate_or_parse(start_str, translations),
|
||||||
{:ok, end_val} <- translate_or_parse(end_str, translations) do
|
{:ok, end_val} <- translate_or_parse(end_str, translations),
|
||||||
|
true <- start_val in bounds and end_val in bounds and end_val >= start_val do
|
||||||
{:range, start_val, end_val}
|
{:range, start_val, end_val}
|
||||||
else
|
else
|
||||||
_ -> :error
|
_ -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_value(part, translations) do
|
defp parse_value(part, bounds, translations) do
|
||||||
case translate_or_parse(part, translations) do
|
with {:ok, val} <- translate_or_parse(part, translations),
|
||||||
{:ok, val} -> {:value, val}
|
true <- val in bounds do
|
||||||
:error -> :error
|
{:value, val}
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
6
vendor/oban_web/lib/oban/web/helpers.ex
vendored
6
vendor/oban_web/lib/oban/web/helpers.ex
vendored
|
|
@ -28,7 +28,7 @@ defmodule Oban.Web.Helpers do
|
||||||
|
|
||||||
def oban_path(route, params) when is_list(route) do
|
def oban_path(route, params) when is_list(route) do
|
||||||
route
|
route
|
||||||
|> Enum.join("/")
|
|> Enum.map_join("/", &encode_segment/1)
|
||||||
|> oban_path(params)
|
|> oban_path(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -50,6 +50,10 @@ defmodule Oban.Web.Helpers do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp encode_segment(segment) do
|
||||||
|
segment |> to_string() |> URI.encode(&URI.char_unreserved?/1)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Toggle filterable params into a patch path.
|
Toggle filterable params into a patch path.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,14 @@ defmodule Oban.Web.Crons.Helpers do
|
||||||
@doc """
|
@doc """
|
||||||
Converts a NaiveDateTime to unix milliseconds, returning empty string for nil.
|
Converts a NaiveDateTime to unix milliseconds, returning empty string for nil.
|
||||||
"""
|
"""
|
||||||
def maybe_to_unix(nil), do: ""
|
def maybe_to_unix(timestamp) when is_struct(timestamp) do
|
||||||
|
|
||||||
def maybe_to_unix(timestamp) do
|
|
||||||
timestamp
|
timestamp
|
||||||
|> DateTime.from_naive!("Etc/UTC")
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|> DateTime.to_unix(:millisecond)
|
|> DateTime.to_unix(:millisecond)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_to_unix(_timestamp), do: ""
|
||||||
|
|
||||||
def show_name?(%{dynamic?: true, name: name, worker: worker}), do: name != worker
|
def show_name?(%{dynamic?: true, name: name, worker: worker}), do: name != worker
|
||||||
def show_name?(_cron), do: false
|
def show_name?(_cron), do: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
<Core.status_badge :if={@job.meta["chunk"]} icon="user_group" label="Chunk" />
|
<Core.status_badge :if={@job.meta["chunk"]} icon="user_group" label="Chunk" />
|
||||||
<Core.status_badge :if={@job.meta["chain"]} icon="link" label="Chain" />
|
<Core.status_badge :if={@job.meta["chain"]} icon="link" label="Chain" />
|
||||||
<Core.status_badge :if={@job.meta["recorded"]} icon="camera" label="Recorded" />
|
<Core.status_badge :if={@job.meta["recorded"]} icon="camera" label="Recorded" />
|
||||||
|
<Core.status_badge :if={signal_status(@job) != :none} icon="signal" label="Signal" />
|
||||||
<Core.status_badge :if={@job.meta["encrypted"]} icon="lock_closed" label="Encrypted" />
|
<Core.status_badge :if={@job.meta["encrypted"]} icon="lock_closed" label="Encrypted" />
|
||||||
<Core.status_badge :if={@job.meta["structured"]} icon="table_cells" label="Structured" />
|
<Core.status_badge :if={@job.meta["structured"]} icon="table_cells" label="Structured" />
|
||||||
<Core.status_badge :if={@job.meta["decorated"]} icon="sparkles" label="Decorated" />
|
<Core.status_badge :if={@job.meta["decorated"]} icon="sparkles" label="Decorated" />
|
||||||
|
|
@ -502,7 +503,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div id="edit-content" class={["mt-3", if(executing?(@job), do: "hidden")]}>
|
<div id="edit-content" class={["mt-3", if(executing?(@job), do: "hidden")]}>
|
||||||
<fieldset disabled={executing?(@job)}>
|
<fieldset disabled={executing?(@job) or not can?(:update_jobs, @access)}>
|
||||||
<form
|
<form
|
||||||
id="job-edit-form"
|
id="job-edit-form"
|
||||||
class="grid grid-cols-4 gap-4 bg-gray-50 dark:bg-gray-800 rounded-md p-4"
|
class="grid grid-cols-4 gap-4 bg-gray-50 dark:bg-gray-800 rounded-md p-4"
|
||||||
|
|
@ -570,7 +571,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={not @edit_changed?}
|
disabled={not @edit_changed? or not can?(:update_jobs, @access)}
|
||||||
class="px-6 py-2 bg-blue-500 text-white text-sm font-medium rounded-md hover:bg-blue-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
class="px-6 py-2 bg-blue-500 text-white text-sm font-medium rounded-md hover:bg-blue-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
|
|
@ -701,6 +702,33 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
<pre class="font-mono text-sm text-gray-600 dark:text-gray-400 whitespace-pre-wrap break-all">{format_recorded(@job, @resolver)}</pre>
|
<pre class="font-mono text-sm text-gray-600 dark:text-gray-400 whitespace-pre-wrap break-all">{format_recorded(@job, @resolver)}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :if={signal_status(@job) != :none} class="mt-4">
|
||||||
|
<div class="relative bg-gray-50 dark:bg-gray-800 rounded-md p-4">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<h4 class="font-medium text-xs uppercase text-gray-500 dark:text-gray-400">
|
||||||
|
{signal_heading(@job)}
|
||||||
|
</h4>
|
||||||
|
<.pro_badge id="signal-pro-badge" tooltip="Awaitable signal from Oban.Pro.Worker" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="copy-signal"
|
||||||
|
class={[
|
||||||
|
"w-9 h-9 -mr-2 -mt-2 flex items-center justify-center rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-white dark:hover:bg-gray-700 cursor-pointer",
|
||||||
|
signal_status(@job) != :received && "invisible"
|
||||||
|
]}
|
||||||
|
data-title="Copy to clipboard"
|
||||||
|
phx-hook="Tippy"
|
||||||
|
phx-click={copy_to_clipboard(format_signal(@job, @resolver))}
|
||||||
|
>
|
||||||
|
<Icons.icon name="icon-clipboard" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre class="font-mono text-sm text-gray-600 dark:text-gray-400 whitespace-pre-wrap break-all">{format_signal(@job, @resolver)}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
@ -785,14 +813,16 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
def handle_event("save-job", params, socket) do
|
def handle_event("save-job", params, socket) do
|
||||||
job = socket.assigns.job
|
job = socket.assigns.job
|
||||||
|
|
||||||
changes =
|
if can?(:update_jobs, socket.assigns.access) do
|
||||||
params
|
changes =
|
||||||
|> parse_edit_params(job)
|
params
|
||||||
|> Enum.reject(fn {_key, val} -> is_nil(val) end)
|
|> parse_edit_params(job)
|
||||||
|> Map.new()
|
|> Enum.reject(fn {_key, val} -> is_nil(val) end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
if map_size(changes) > 0 do
|
if map_size(changes) > 0 do
|
||||||
send(self(), {:update_job, job, changes})
|
send(self(), {:update_job, job, changes})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, assign(socket, edit_changed?: false)}
|
{:noreply, assign(socket, edit_changed?: false)}
|
||||||
|
|
@ -805,12 +835,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_meta(%{meta: meta} = job, resolver) do
|
defp format_meta(%{meta: meta} = job, resolver) do
|
||||||
job =
|
job = %{job | meta: Map.drop(meta, ["return", "signal"])}
|
||||||
if meta["recorded"] do
|
|
||||||
%{job | meta: Map.delete(meta, "return")}
|
|
||||||
else
|
|
||||||
job
|
|
||||||
end
|
|
||||||
|
|
||||||
Resolver.call_with_fallback(resolver, :format_job_meta, [job])
|
Resolver.call_with_fallback(resolver, :format_job_meta, [job])
|
||||||
end
|
end
|
||||||
|
|
@ -828,6 +853,41 @@ defmodule Oban.Web.Jobs.DetailComponent do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp format_signal(%{meta: %{"signal" => value}} = job, resolver) do
|
||||||
|
Resolver.call_with_fallback(resolver, :format_signal, [value, job])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_signal(%{meta: %{"wait_until" => wait_until}}, _resolver) do
|
||||||
|
case wait_until_to_datetime(wait_until) do
|
||||||
|
{:ok, datetime} -> "Deadline #{Timing.datetime_to_words(datetime)}"
|
||||||
|
:infinity -> "No deadline"
|
||||||
|
:error -> ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp signal_status(%{meta: %{"signal" => _}}), do: :received
|
||||||
|
defp signal_status(%{meta: %{"wait_until" => _}}), do: :awaiting
|
||||||
|
defp signal_status(_), do: :none
|
||||||
|
|
||||||
|
defp signal_heading(job) do
|
||||||
|
case signal_status(job) do
|
||||||
|
:received -> "Received Signal"
|
||||||
|
:awaiting -> "Awaiting Signal"
|
||||||
|
:none -> "Signal"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp wait_until_to_datetime("infinity"), do: :infinity
|
||||||
|
|
||||||
|
defp wait_until_to_datetime(ms) when is_integer(ms) do
|
||||||
|
case DateTime.from_unix(ms, :millisecond) do
|
||||||
|
{:ok, datetime} -> {:ok, datetime |> DateTime.to_naive() |> NaiveDateTime.truncate(:second)}
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp wait_until_to_datetime(_), do: :error
|
||||||
|
|
||||||
defp error_entry(assigns) do
|
defp error_entry(assigns) do
|
||||||
error =
|
error =
|
||||||
assigns.errors
|
assigns.errors
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ defmodule Oban.Web.Jobs.NewComponent do
|
||||||
|
|
||||||
defp parse_scheduled_at(str) when is_binary(str) do
|
defp parse_scheduled_at(str) when is_binary(str) do
|
||||||
case DateTime.from_iso8601(str <> ":00Z") do
|
case DateTime.from_iso8601(str <> ":00Z") do
|
||||||
{:ok, datetime, _offset} -> datetime
|
{:ok, datetime} -> datetime
|
||||||
_ -> nil
|
_ -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ defmodule Oban.Web.WorkflowsPage do
|
||||||
)
|
)
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
workflows = WorkflowQuery.all_workflows(params, conf)
|
workflows = WorkflowQuery.all_workflows(conf, params)
|
||||||
limit = params.limit
|
limit = params.limit
|
||||||
|
|
||||||
assign(socket,
|
assign(socket,
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,11 @@ defmodule Oban.Web.CronQuery do
|
||||||
state: j.state,
|
state: j.state,
|
||||||
scheduled_at: j.scheduled_at,
|
scheduled_at: j.scheduled_at,
|
||||||
attempted_at: j.attempted_at,
|
attempted_at: j.attempted_at,
|
||||||
finished_at: fragment("COALESCE(?, ?, ?)", j.completed_at, j.cancelled_at, j.discarded_at)
|
finished_at:
|
||||||
|
type(
|
||||||
|
fragment("COALESCE(?, ?, ?)", j.completed_at, j.cancelled_at, j.discarded_at),
|
||||||
|
:utc_datetime_usec
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
conf
|
conf
|
||||||
|
|
@ -280,7 +284,10 @@ defmodule Oban.Web.CronQuery do
|
||||||
attempted_at: o.attempted_at,
|
attempted_at: o.attempted_at,
|
||||||
scheduled_at: o.scheduled_at,
|
scheduled_at: o.scheduled_at,
|
||||||
finished_at:
|
finished_at:
|
||||||
fragment("COALESCE(?, ?, ?)", o.completed_at, o.cancelled_at, o.discarded_at)
|
type(
|
||||||
|
fragment("COALESCE(?, ?, ?)", o.completed_at, o.cancelled_at, o.discarded_at),
|
||||||
|
:utc_datetime_usec
|
||||||
|
)
|
||||||
},
|
},
|
||||||
select_merge: %{
|
select_merge: %{
|
||||||
rn: over(row_number(), partition_by: o.meta["cron_name"], order_by: [desc: o.id])
|
rn: over(row_number(), partition_by: o.meta["cron_name"], order_by: [desc: o.id])
|
||||||
|
|
|
||||||
|
|
@ -126,30 +126,32 @@ defmodule Oban.Web.WorkflowQuery do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacrop sub_workflow_parent_dep(sub_workflow_id, sup_workflow_id) do
|
defmacrop sub_workflow_parent_dep(prefix, sub_workflow_id, sup_workflow_id) do
|
||||||
quote do
|
quote do
|
||||||
fragment(
|
fragment(
|
||||||
"""
|
"""
|
||||||
(SELECT dep->>1
|
(SELECT dep->>1
|
||||||
FROM oban_jobs j2,
|
FROM ?.oban_jobs j2,
|
||||||
LATERAL jsonb_array_elements(j2.meta->'deps') AS dep
|
LATERAL jsonb_array_elements(j2.meta->'deps') AS dep
|
||||||
WHERE j2.meta->>'workflow_id' = ?
|
WHERE j2.meta->>'workflow_id' = ?
|
||||||
AND jsonb_typeof(dep) = 'array'
|
AND jsonb_typeof(dep) = 'array'
|
||||||
AND dep->>0 = ?
|
AND dep->>0 = ?
|
||||||
LIMIT 1)
|
LIMIT 1)
|
||||||
""",
|
""",
|
||||||
|
identifier(^unquote(prefix)),
|
||||||
unquote(sub_workflow_id),
|
unquote(sub_workflow_id),
|
||||||
unquote(sup_workflow_id)
|
unquote(sup_workflow_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacrop sub_workflow_states(parent_id) do
|
defmacrop sub_workflow_states(prefix, parent_id) do
|
||||||
quote do
|
quote do
|
||||||
fragment(
|
fragment(
|
||||||
"""
|
"""
|
||||||
(SELECT COALESCE(array_agg(state), '{}') FROM oban_workflows WHERE parent_id = ?)
|
(SELECT COALESCE(array_agg(state), '{}') FROM ?.oban_workflows WHERE parent_id = ?)
|
||||||
""",
|
""",
|
||||||
|
identifier(^unquote(prefix)),
|
||||||
unquote(parent_id)
|
unquote(parent_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -279,18 +281,19 @@ defmodule Oban.Web.WorkflowQuery do
|
||||||
|
|
||||||
# Querying
|
# Querying
|
||||||
|
|
||||||
def all_workflows(params, conf) do
|
def all_workflows(conf, params) do
|
||||||
limit = Map.get(params, :limit, 10)
|
limit = Map.get(params, :limit, 10)
|
||||||
sort_by = Map.get(params, :sort_by, "inserted")
|
sort_by = Map.get(params, :sort_by, "inserted")
|
||||||
sort_dir = Map.get(params, :sort_dir, "desc")
|
sort_dir = Map.get(params, :sort_dir, "desc")
|
||||||
dir = String.to_existing_atom(sort_dir)
|
dir = String.to_existing_atom(sort_dir)
|
||||||
|
prefix = conf.prefix
|
||||||
|
|
||||||
Workflow
|
Workflow
|
||||||
|> where([wf], is_nil(wf.parent_id))
|
|> where([wf], is_nil(wf.parent_id))
|
||||||
|> apply_filters(params)
|
|> apply_filters(params)
|
||||||
|> apply_sort(sort_by, dir)
|
|> apply_sort(sort_by, dir)
|
||||||
|> limit(^limit)
|
|> limit(^limit)
|
||||||
|> select([wf], {wf, sub_workflow_states(wf.id)})
|
|> select([wf], {wf, sub_workflow_states(prefix, wf.id)})
|
||||||
|> then(&Repo.all(conf, &1))
|
|> then(&Repo.all(conf, &1))
|
||||||
|> Enum.map(&build_workflow/1)
|
|> Enum.map(&build_workflow/1)
|
||||||
end
|
end
|
||||||
|
|
@ -446,6 +449,8 @@ defmodule Oban.Web.WorkflowQuery do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp workflow_graph_subs(conf, workflow_id) do
|
defp workflow_graph_subs(conf, workflow_id) do
|
||||||
|
prefix = conf.prefix
|
||||||
|
|
||||||
base_query =
|
base_query =
|
||||||
Job
|
Job
|
||||||
|> where([j], has_sup_workflow_id(j.meta, ^workflow_id))
|
|> where([j], has_sup_workflow_id(j.meta, ^workflow_id))
|
||||||
|
|
@ -464,7 +469,7 @@ defmodule Oban.Web.WorkflowQuery do
|
||||||
workflow_name: s.workflow_name,
|
workflow_name: s.workflow_name,
|
||||||
sub_name: s.sub_name,
|
sub_name: s.sub_name,
|
||||||
state: s.state,
|
state: s.state,
|
||||||
parent_dep: sub_workflow_parent_dep(s.workflow_id, ^workflow_id)
|
parent_dep: sub_workflow_parent_dep(prefix, s.workflow_id, ^workflow_id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
55
vendor/oban_web/lib/oban/web/resolver.ex
vendored
55
vendor/oban_web/lib/oban/web/resolver.ex
vendored
|
|
@ -41,6 +41,13 @@ defmodule Oban.Web.Resolver do
|
||||||
|> inspect(charlists: :as_lists, pretty: true)
|
|> inspect(charlists: :as_lists, pretty: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def format_signal(signal, _job) do
|
||||||
|
signal
|
||||||
|
|> Oban.Web.Resolver.decode_signal()
|
||||||
|
|> inspect(charlists: :as_lists, pretty: true)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def jobs_query_limit(_state), do: 100_000
|
def jobs_query_limit(_state), do: 100_000
|
||||||
|
|
||||||
|
|
@ -138,6 +145,7 @@ defmodule Oban.Web.Resolver do
|
||||||
| :scale_queues
|
| :scale_queues
|
||||||
| :stop_queues
|
| :stop_queues
|
||||||
| :update_crons
|
| :update_crons
|
||||||
|
| :update_jobs
|
||||||
|
|
||||||
@type qualifier :: :args | :meta | :nodes | :queues | :tags | :workers
|
@type qualifier :: :args | :meta | :nodes | :queues | :tags | :workers
|
||||||
|
|
||||||
|
|
@ -234,6 +242,35 @@ defmodule Oban.Web.Resolver do
|
||||||
"""
|
"""
|
||||||
@callback format_recorded(recorded :: term(), job :: Job.t()) :: iodata()
|
@callback format_recorded(recorded :: term(), job :: Job.t()) :: iodata()
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Customize the formatting of awaitable signal payloads wherever they are displayed.
|
||||||
|
|
||||||
|
This callback is similar to `c:format_recorded/2`, but it accepts both the signal binary and
|
||||||
|
the job to help augment the output.
|
||||||
|
|
||||||
|
Note that you **must decode the signal binary** prior to inspecting it.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Disable pretty printing and change the output width to 98 characters:
|
||||||
|
|
||||||
|
def format_signal(signal, _job) do
|
||||||
|
signal
|
||||||
|
|> Oban.Web.Resolver.decode_signal()
|
||||||
|
|> inspect(pretty: false, width: 98)
|
||||||
|
end
|
||||||
|
|
||||||
|
Decode the signal value without the `:safe` flag set, to allow decoding terms with unknown
|
||||||
|
atoms:
|
||||||
|
|
||||||
|
def format_signal(signal, _job) do
|
||||||
|
signal
|
||||||
|
|> Oban.Web.Resolver.decode_signal([])
|
||||||
|
|> inspect(pretty: false, width: 98)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
@callback format_signal(signal :: term(), job :: Job.t()) :: iodata()
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Extract the current user from a `Plug.Conn` when the dashboard mounts.
|
Extract the current user from a `Plug.Conn` when the dashboard mounts.
|
||||||
|
|
||||||
|
|
@ -283,6 +320,7 @@ defmodule Oban.Web.Resolver do
|
||||||
* `:scale_queues`
|
* `:scale_queues`
|
||||||
* `:stop_queues`
|
* `:stop_queues`
|
||||||
* `:update_crons`
|
* `:update_crons`
|
||||||
|
* `:update_jobs`
|
||||||
|
|
||||||
Actions which aren't listed are considered disabled.
|
Actions which aren't listed are considered disabled.
|
||||||
|
|
||||||
|
|
@ -411,6 +449,7 @@ defmodule Oban.Web.Resolver do
|
||||||
@optional_callbacks format_job_args: 1,
|
@optional_callbacks format_job_args: 1,
|
||||||
format_job_meta: 1,
|
format_job_meta: 1,
|
||||||
format_recorded: 2,
|
format_recorded: 2,
|
||||||
|
format_signal: 2,
|
||||||
bulk_action_limit: 1,
|
bulk_action_limit: 1,
|
||||||
hint_query_limit: 1,
|
hint_query_limit: 1,
|
||||||
jobs_query_limit: 1,
|
jobs_query_limit: 1,
|
||||||
|
|
@ -451,6 +490,15 @@ defmodule Oban.Web.Resolver do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Decode an awaitable signal payload from a compressed, base64 binary into proper terms.
|
||||||
|
|
||||||
|
Signals share the same wire format as recorded output, so this is a thin alias around
|
||||||
|
`decode_recorded/2` provided for documentation symmetry.
|
||||||
|
"""
|
||||||
|
@spec decode_signal(binary(), [:safe]) :: term()
|
||||||
|
def decode_signal(bin, opts \\ [:safe]), do: decode_recorded(bin, opts)
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def call_with_fallback(resolver, fun, args) when is_atom(fun) and is_list(args) do
|
def call_with_fallback(resolver, fun, args) when is_atom(fun) and is_list(args) do
|
||||||
resolver =
|
resolver =
|
||||||
|
|
@ -482,6 +530,13 @@ defmodule Oban.Web.Resolver do
|
||||||
|> inspect(@inspect_opts)
|
|> inspect(@inspect_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def format_signal(signal, _job) do
|
||||||
|
signal
|
||||||
|
|> decode_signal()
|
||||||
|
|> inspect(@inspect_opts)
|
||||||
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def resolve_user(_conn), do: nil
|
def resolve_user(_conn), do: nil
|
||||||
|
|
||||||
|
|
|
||||||
2
vendor/oban_web/mix.exs
vendored
2
vendor/oban_web/mix.exs
vendored
|
|
@ -2,7 +2,7 @@ defmodule Oban.Web.MixProject do
|
||||||
use Mix.Project
|
use Mix.Project
|
||||||
|
|
||||||
@source_url "https://github.com/oban-bg/oban_web"
|
@source_url "https://github.com/oban-bg/oban_web"
|
||||||
@version "2.12.3"
|
@version "2.12.5"
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue