- Index page with All/Active/Upcoming/Past filter tabs - Show page with active banner, edit/delete actions - Form page with scope picker (org-wide/site/device), datetime fields - Nav link added to desktop and mobile nav
231 lines
10 KiB
Text
231 lines
10 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>
|
|
|
|
<h1 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{@page_title}</h1>
|
|
|
|
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-6">
|
|
<.form
|
|
for={@changeset}
|
|
phx-change="validate"
|
|
phx-submit="save"
|
|
id="maintenance-form"
|
|
>
|
|
<div class="space-y-6">
|
|
<%!-- Name --%>
|
|
<div>
|
|
<label for="maintenance_window_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
{t("Name")} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="maintenance_window[name]"
|
|
id="maintenance_window_name"
|
|
value={Ecto.Changeset.get_field(@changeset, :name) || ""}
|
|
required
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
|
placeholder={t("e.g., Tower firmware upgrade")}
|
|
/>
|
|
<%= for {msg, _} <- (@changeset.errors[:name] || []) do %>
|
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%!-- Reason --%>
|
|
<div>
|
|
<label for="maintenance_window_reason" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
{t("Reason")}
|
|
</label>
|
|
<textarea
|
|
name="maintenance_window[reason]"
|
|
id="maintenance_window_reason"
|
|
rows="3"
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
|
placeholder={t("Optional description of the maintenance work")}
|
|
>{Ecto.Changeset.get_field(@changeset, :reason) || ""}</textarea>
|
|
</div>
|
|
|
|
<%!-- Scope --%>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{t("Scope")}</label>
|
|
<div class="flex items-center gap-4 mb-3">
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="scope_type"
|
|
value="org"
|
|
checked={@scope_type == "org"}
|
|
phx-click="change_scope"
|
|
phx-value-scope_type="org"
|
|
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
|
/>
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Org-wide")}</span>
|
|
</label>
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="scope_type"
|
|
value="site"
|
|
checked={@scope_type == "site"}
|
|
phx-click="change_scope"
|
|
phx-value-scope_type="site"
|
|
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
|
/>
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Site")}</span>
|
|
</label>
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="scope_type"
|
|
value="device"
|
|
checked={@scope_type == "device"}
|
|
phx-click="change_scope"
|
|
phx-value-scope_type="device"
|
|
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
|
/>
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Device")}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<%= if @scope_type == "site" do %>
|
|
<select
|
|
name="maintenance_window[site_id]"
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
|
>
|
|
<option value="">{t("Select a site...")}</option>
|
|
<%= for site <- @sites do %>
|
|
<option value={site.id} selected={Ecto.Changeset.get_field(@changeset, :site_id) == site.id}>
|
|
{site.name}
|
|
</option>
|
|
<% end %>
|
|
</select>
|
|
<% end %>
|
|
|
|
<%= if @scope_type == "device" do %>
|
|
<select
|
|
name="maintenance_window[device_id]"
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
|
>
|
|
<option value="">{t("Select a device...")}</option>
|
|
<%= for device <- @devices do %>
|
|
<option value={device.id} selected={Ecto.Changeset.get_field(@changeset, :device_id) == device.id}>
|
|
{device.name}
|
|
</option>
|
|
<% end %>
|
|
</select>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%!-- Time Range --%>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="maintenance_window_starts_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
{t("Starts At")} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="datetime-local"
|
|
name="maintenance_window[starts_at]"
|
|
id="maintenance_window_starts_at"
|
|
value={format_datetime_local(Ecto.Changeset.get_field(@changeset, :starts_at))}
|
|
required
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
|
/>
|
|
<%= for {msg, _} <- (@changeset.errors[:starts_at] || []) do %>
|
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
|
<% end %>
|
|
</div>
|
|
<div>
|
|
<label for="maintenance_window_ends_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
{t("Ends At")} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="datetime-local"
|
|
name="maintenance_window[ends_at]"
|
|
id="maintenance_window_ends_at"
|
|
value={format_datetime_local(Ecto.Changeset.get_field(@changeset, :ends_at))}
|
|
required
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
|
/>
|
|
<%= for {msg, _} <- (@changeset.errors[:ends_at] || []) do %>
|
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<%!-- Suppress Alerts --%>
|
|
<div>
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input type="hidden" name="maintenance_window[suppress_alerts]" value="false" />
|
|
<input
|
|
type="checkbox"
|
|
name="maintenance_window[suppress_alerts]"
|
|
value="true"
|
|
checked={Ecto.Changeset.get_field(@changeset, :suppress_alerts) == true}
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
|
/>
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Suppress alerts during this window")}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<%!-- Recurring --%>
|
|
<div>
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input type="hidden" name="maintenance_window[recurring]" value="false" />
|
|
<input
|
|
type="checkbox"
|
|
name="maintenance_window[recurring]"
|
|
value="true"
|
|
checked={Ecto.Changeset.get_field(@changeset, :recurring) == true}
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
|
/>
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Recurring")}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<%!-- Recurrence Rule --%>
|
|
<%= if Ecto.Changeset.get_field(@changeset, :recurring) == true do %>
|
|
<div>
|
|
<label for="maintenance_window_recurrence_rule" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
{t("Recurrence Rule")}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="maintenance_window[recurrence_rule]"
|
|
id="maintenance_window_recurrence_rule"
|
|
value={Ecto.Changeset.get_field(@changeset, :recurrence_rule) || ""}
|
|
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
|
placeholder={t("e.g., FREQ=WEEKLY;BYDAY=SU")}
|
|
/>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%!-- Submit --%>
|
|
<div class="flex items-center justify-end gap-3 pt-4 border-t border-gray-200 dark:border-white/10">
|
|
<.link
|
|
navigate={~p"/maintenance"}
|
|
class="rounded-lg px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
{t("Cancel")}
|
|
</.link>
|
|
<button
|
|
type="submit"
|
|
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
|
>
|
|
{t("Save")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</.form>
|
|
</div>
|
|
</Layouts.authenticated>
|