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.
209 lines
8.2 KiB
Text
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>
|