- Add retry: false to VISP Client HTTP requests - Add retry: false to ReleaseChecker GitHub API requests - Remove unused Plug.Conn import from RemoteIpLogger - Remove unused default parameter from req_get/2 Results: - VISP sync test: 7007ms → 113ms (62x faster) - ReleaseChecker test: 7004ms → 17ms (402x faster) - UserResetPasswordLive test: 1495ms → 284ms (5x faster) Req's default retry behavior (1s, 2s, 4s exponential backoff) was causing 7-second delays for HTTP 500/503 error responses in tests. For these clients, immediate failure is preferred over retries.
144 lines
5.3 KiB
Text
144 lines
5.3 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_scope={@current_scope}
|
|
active_page="maintenance"
|
|
>
|
|
<div class="mb-6">
|
|
<.link
|
|
navigate={~p"/maintenance"}
|
|
class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
>
|
|
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
|
{t("Back to Maintenance Windows")}
|
|
</.link>
|
|
</div>
|
|
|
|
<% status = status_for(@window) %>
|
|
|
|
<%!-- Active banner --%>
|
|
<%= if status == :active do %>
|
|
<div class="mb-6 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 p-4">
|
|
<div class="flex items-center gap-3">
|
|
<.icon name="hero-wrench-screwdriver" class="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-green-800 dark:text-green-300">
|
|
{t("Currently Active")}
|
|
</h3>
|
|
<p class="text-sm text-green-700 dark:text-green-400">
|
|
{t("This maintenance window is currently in effect. Alerts are being suppressed.")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="flex items-start justify-between mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{@window.name}</h1>
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium mt-2",
|
|
status == :active &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
|
|
status == :upcoming && "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300",
|
|
status == :past && "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
|
]}>
|
|
{status_label(status)}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<.link
|
|
navigate={~p"/maintenance/#{@window.id}/edit"}
|
|
class="inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
|
|
>
|
|
<.icon name="hero-pencil" class="h-4 w-4" />
|
|
{t("Edit")}
|
|
</.link>
|
|
<button
|
|
type="button"
|
|
phx-click="delete"
|
|
data-confirm={t("Are you sure you want to delete this maintenance window?")}
|
|
class="inline-flex items-center gap-2 rounded-lg bg-red-100 px-4 py-2 text-sm font-medium text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-300 dark:hover:bg-red-900/50 transition-colors"
|
|
>
|
|
<.icon name="hero-trash" class="h-4 w-4" />
|
|
{t("Delete")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-white/10">
|
|
<%!-- Reason --%>
|
|
<%= if @window.reason do %>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Reason")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white whitespace-pre-wrap">
|
|
{@window.reason}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%!-- Scope --%>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Scope")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white">{scope_label(@window)}</dd>
|
|
</div>
|
|
|
|
<%!-- Time Range --%>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Time Range")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white">
|
|
{format_datetime(@window.starts_at, @timezone)} — {format_datetime(
|
|
@window.ends_at,
|
|
@timezone
|
|
)}
|
|
</dd>
|
|
</div>
|
|
|
|
<%!-- Recurring --%>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Recurring")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white">
|
|
<%= if @window.recurring do %>
|
|
{t("Yes")} — {@window.recurrence_rule || t("No rule specified")}
|
|
<% else %>
|
|
{t("No")}
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
|
|
<%!-- Suppress Alerts --%>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Suppress Alerts")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white">
|
|
<%= if @window.suppress_alerts do %>
|
|
<span class="inline-flex items-center gap-1 text-green-700 dark:text-green-400">
|
|
<.icon name="hero-check-circle" class="h-4 w-4" /> {t("Yes")}
|
|
</span>
|
|
<% else %>
|
|
<span class="inline-flex items-center gap-1 text-gray-500 dark:text-gray-400">
|
|
<.icon name="hero-x-circle" class="h-4 w-4" /> {t("No")}
|
|
</span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
|
|
<%!-- Created By --%>
|
|
<div class="px-6 py-4">
|
|
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("Created By")}
|
|
</dt>
|
|
<dd class="text-sm text-gray-900 dark:text-white">
|
|
{if @window.created_by, do: @window.created_by.email, else: "—"}
|
|
</dd>
|
|
</div>
|
|
</div>
|
|
</Layouts.authenticated>
|