towerops/lib/towerops_web/live/admin/security_live/index.html.heex
Graham McIntire 4e3f732f21
ui: comprehensive button styling audit - convert all text-styled actions to proper buttons
Fixed 10 text-styled action buttons across 7 files:

Agent Management:
- Delete Agent buttons (2x) → danger variant
- View Setup links (already fixed) → secondary variant

Organization Settings:
- Remove Member button → danger variant

Admin Pages:
- Edit Overrides button → secondary variant
- Delete Organization button → danger variant
- Remove IP/CIDR button → danger variant
- Delete User button → danger variant

Device Management:
- Delete Backup button → danger variant

All action buttons now use proper .button component with appropriate
variants (danger for destructive actions, secondary for neutral actions).
This provides consistent visual hierarchy and makes destructive actions
more clearly identifiable.
2026-03-09 15:32:42 -05:00

209 lines
8.2 KiB
Text

<Layouts.admin flash={@flash} timezone={@timezone}>
<div class="space-y-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("IP Access Control")}</h1>
<p class="text-gray-600 dark:text-gray-400">
{t("Manage IP allowlist and view denied IPs")}
</p>
</div>
<!-- Tabs -->
<div class="border-b border-gray-200 dark:border-white/10">
<nav class="-mb-px flex space-x-8">
<button
phx-click="change_tab"
phx-value-tab="whitelist"
class={[
"border-b-2 py-4 px-1 text-sm font-medium",
if(@current_tab == "whitelist",
do: "border-blue-500 text-blue-600 dark:text-blue-400",
else:
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
)
]}
>
{t("Allowlist")}
</button>
<button
phx-click="change_tab"
phx-value-tab="blocked"
class={[
"border-b-2 py-4 px-1 text-sm font-medium",
if(@current_tab == "blocked",
do: "border-blue-500 text-blue-600 dark:text-blue-400",
else:
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
)
]}
>
{t("Denylist")}
</button>
</nav>
</div>
<!-- Whitelist Tab -->
<%= if @current_tab == "whitelist" do %>
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{t("Allowed IPs and CIDR Ranges")}
</h2>
<.button phx-click="show_whitelist_form">
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> Add to Allowlist
</.button>
</div>
<%= if @show_whitelist_form do %>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
<.form
for={@whitelist_form}
phx-submit="add_whitelist"
class="space-y-4"
>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<.input
field={@whitelist_form[:ip_or_cidr]}
type="text"
label={t("IP Address or CIDR")}
placeholder="192.168.1.100 or 10.0.0.0/8"
required
/>
<.input
field={@whitelist_form[:description]}
type="text"
label={t("Description")}
placeholder={t("Office network")}
/>
</div>
<div class="flex gap-2">
<.button type="submit">{t("Add to Allowlist")}</.button>
<.button type="button" phx-click="hide_whitelist_form">
{t("Cancel")}
</.button>
</div>
</.form>
</div>
<% end %>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
<%= if Enum.empty?(@whitelist) do %>
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
{t("No allowed IPs or CIDR ranges")}
</div>
<% else %>
<.table id="whitelist" rows={@whitelist}>
<:col :let={entry} label={t("IP/CIDR")}>{entry.ip_or_cidr}</:col>
<:col :let={entry} label={t("Description")}>{entry.description}</:col>
<:col :let={entry} label={t("Added By")}>
<%= if entry.added_by do %>
{entry.added_by.email}
<% else %>
<span class="text-gray-400">Unknown</span>
<% end %>
</:col>
<:col :let={entry} label={t("Added")}>
{ToweropsWeb.TimeHelpers.format_date(entry.inserted_at, @timezone)}
</:col>
<:col :let={entry} label="">
<.button
phx-click="remove_whitelist"
phx-value-id={entry.id}
data-confirm={t("Remove this IP/CIDR from the allowlist?")}
variant="danger"
>
{t("Remove")}
</.button>
</:col>
</.table>
<% end %>
</div>
</div>
<% end %>
<!-- Blocked IPs Tab -->
<%= if @current_tab == "blocked" do %>
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{t("Denied IP Addresses")}
</h2>
<div class="flex items-center gap-2">
<label class="text-sm text-gray-600 dark:text-gray-400">Filter:</label>
<select
phx-change="change_filter"
name="filter"
class="rounded-md border-gray-300 dark:border-white/10 dark:bg-gray-900 text-sm"
>
<option value="all" selected={@filter == "all"}>All Bans</option>
<option value="permanent" selected={@filter == "permanent"}>Permanent Only</option>
<option value="temporary" selected={@filter == "temporary"}>Temporary Only</option>
</select>
</div>
</div>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
<%= if Enum.empty?(@blocked_ips) do %>
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
{t("No denied IPs")}
</div>
<% else %>
<.table id="blocked_ips" rows={@blocked_ips}>
<:col :let={block} label={t("IP Address")}>{block.ip_address}</:col>
<:col :let={block} label={t("Offense Count")}>
<span class={[
"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium",
case block.offense_count do
1 ->
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
2 ->
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
_ ->
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
end
]}>
{block.offense_count}
</span>
</:col>
<:col :let={block} label={t("Status")}>
<%= if Towerops.Security.IpBlock.permanent?(block) do %>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400">
{t("Permanent")}
</span>
<% else %>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
{Towerops.Security.IpBlock.ban_duration(block)}
</span>
<% end %>
</:col>
<:col :let={block} label={t("Last Violation")}>
{ToweropsWeb.TimeHelpers.format_time_ago(block.last_violation_at)}
</:col>
<:col :let={block} label={t("Reason")}>
<div class="text-sm text-gray-600 dark:text-gray-400 max-w-xs truncate">
{block.reason}
</div>
</:col>
<:col :let={block} label="">
<button
phx-click="unblock_ip"
phx-value-ip={block.ip_address}
data-confirm={
t("Unblock this IP address? This will remove all offense history.")
}
class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
>
{t("Unblock")}
</button>
</:col>
</.table>
<% end %>
</div>
</div>
<% end %>
</div>
</Layouts.admin>