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

207 lines
8.1 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">IP Access Control</h1>
<p class="text-gray-600 dark:text-gray-400">
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"
)
]}
>
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"
)
]}
>
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">
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="IP Address or CIDR"
placeholder="192.168.1.100 or 10.0.0.0/8"
required
/>
<.input
field={@whitelist_form[:description]}
type="text"
label="Description"
placeholder="Office network"
/>
</div>
<div class="flex gap-2">
<.button type="submit">Add to Allowlist</.button>
<.button type="button" phx-click="hide_whitelist_form">
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">
No allowed IPs or CIDR ranges
</div>
<% else %>
<.table id="whitelist" rows={@whitelist}>
<:col :let={entry} label="IP/CIDR">{entry.ip_or_cidr}</:col>
<:col :let={entry} label="Description">{entry.description}</:col>
<:col :let={entry} label="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="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="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"
>
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">
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">
No denied IPs
</div>
<% else %>
<.table id="blocked_ips" rows={@blocked_ips}>
<:col :let={block} label="IP Address">{block.ip_address}</:col>
<:col :let={block} label="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="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">
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="Last Violation">
{ToweropsWeb.TimeHelpers.format_time_ago(block.last_violation_at)}
</:col>
<:col :let={block} label="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="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"
>
Unblock
</button>
</:col>
</.table>
<% end %>
</div>
</div>
<% end %>
</div>
</Layouts.admin>