207 lines
8.1 KiB
Text
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>
|