towerops/lib/towerops_web/live/admin/security_live/index.html.heex

209 lines
8.3 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?")}
class="text-sm text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300"
>
{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>