Add WafPolicies

This commit is contained in:
Dan Milne
2025-11-10 14:10:37 +11:00
parent af7413c899
commit 772fae7e8b
22 changed files with 1784 additions and 147 deletions

View File

@@ -239,7 +239,11 @@
<!-- Network Intelligence -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Network Intelligence</h3>
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">Network Intelligence</h3>
<%= link_to "Detailed Network Analytics →", analytics_networks_path,
class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
</div>
</div>
<div class="p-6">
<div class="space-y-3">

View File

@@ -0,0 +1,283 @@
<% content_for :title, "Network Analytics - Baffle Hub" %>
<div class="space-y-6">
<!-- Header -->
<div>
<h1 class="text-3xl font-bold text-gray-900">Network Analytics</h1>
<p class="mt-2 text-gray-600">Detailed traffic analysis and network intelligence insights</p>
</div>
<!-- Time Period Selector -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">Time Period</h3>
<div class="flex space-x-2">
<% [:hour, :day, :week, :month].each do |period| %>
<%= link_to period.to_s.humanize, analytics_networks_path(period: period),
class: "px-3 py-1 rounded-md text-sm font-medium #{ @time_period == period ? 'bg-blue-100 text-blue-800' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100' }" %>
<% end %>
</div>
</div>
</div>
</div>
<!-- Network Type Overview -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-white shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Standard Networks</p>
<p class="text-2xl font-bold text-gray-900">
<%= number_with_delimiter(@network_breakdown.dig('standard', :networks) || 0) %>
</p>
<p class="text-xs text-gray-500">
<%= number_with_delimiter(@network_breakdown.dig('standard', :events) || 0) %> events
(<%= @network_breakdown.dig('standard', :percentage) || 0 %>%)
</p>
</div>
</div>
</div>
</div>
<div class="bg-white shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-orange-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Datacenter Networks</p>
<p class="text-2xl font-bold text-gray-900">
<%= number_with_delimiter(@network_breakdown.dig('datacenter', :networks) || 0) %>
</p>
<p class="text-xs text-gray-500">
<%= number_with_delimiter(@network_breakdown.dig('datacenter', :events) || 0) %> events
(<%= @network_breakdown.dig('datacenter', :percentage) || 0 %>%)
</p>
</div>
</div>
</div>
</div>
<div class="bg-white shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-purple-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">VPN Networks</p>
<p class="text-2xl font-bold text-gray-900">
<%= number_with_delimiter(@network_breakdown.dig('vpn', :networks) || 0) %>
</p>
<p class="text-xs text-gray-500">
<%= number_with_delimiter(@network_breakdown.dig('vpn', :events) || 0) %> events
(<%= @network_breakdown.dig('vpn', :percentage) || 0 %>%)
</p>
</div>
</div>
</div>
</div>
<div class="bg-white shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Proxy Networks</p>
<p class="text-2xl font-bold text-gray-900">
<%= number_with_delimiter(@network_breakdown.dig('proxy', :networks) || 0) %>
</p>
<p class="text-xs text-gray-500">
<%= number_with_delimiter(@network_breakdown.dig('proxy', :events) || 0) %> events
(<%= @network_breakdown.dig('proxy', :percentage) || 0 %>%)
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Top Networks by Traffic -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Top Networks by Traffic Volume</h3>
<p class="text-sm text-gray-500">Networks with the most requests in the selected time period</p>
</div>
<div class="overflow-x-auto">
<% if @top_networks.any? %>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Network</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Company</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Events</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Unique IPs</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<% @top_networks.each do |network| %>
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm">
<div>
<%= link_to network.cidr, network_range_path(network),
class: "text-blue-600 hover:text-blue-800 hover:underline font-mono font-medium" %>
</div>
<div class="text-xs text-gray-500">
<% if network.country.present? %>
🏳️ <%= network.country %>
<% end %>
<% if network.asn.present? %>
• ASN <%= network.asn %>
<% end %>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= network.company || 'Unknown' %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<% if network.is_datacenter? %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800">Datacenter</span>
<% elsif network.is_vpn? %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">VPN</span>
<% elsif network.is_proxy? %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">Proxy</span>
<% else %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">Standard</span>
<% end %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= number_with_delimiter(network.event_count) %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<%= number_with_delimiter(network.unique_ips) %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<%= link_to "View Events", events_path(network_cidr: network.cidr),
class: "text-blue-600 hover:text-blue-800 text-sm" %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="px-6 py-12 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">No network traffic</h3>
<p class="mt-1 text-sm text-gray-500">No network activity found in the selected time period.</p>
</div>
<% end %>
</div>
</div>
<!-- Additional Analytics Sections -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Top Companies -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Top Companies by Traffic</h3>
<p class="text-sm text-gray-500">Companies generating the most traffic</p>
</div>
<div class="p-6">
<% if @top_companies.any? %>
<div class="space-y-4">
<% @top_companies.each do |company| %>
<div class="flex items-center justify-between">
<div class="flex-1">
<div class="flex items-center">
<div class="font-medium text-gray-900"><%= company.company %></div>
<div class="ml-2 text-sm text-gray-500">
<%= number_with_delimiter(company.network_count) %> networks
</div>
</div>
<div class="mt-1">
<div class="flex items-center text-sm text-gray-600">
<span><%= number_with_delimiter(company.event_count) %> events</span>
<span class="mx-2">•</span>
<span><%= number_with_delimiter(company.unique_ips) %> unique IPs</span>
</div>
</div>
</div>
<div class="ml-4">
<%= link_to "Filter Events", events_path(company: company.company),
class: "text-blue-600 hover:text-blue-800 text-sm font-medium" %>
</div>
</div>
<% end %>
</div>
<% else %>
<p class="text-sm text-gray-500">No company data available for this time period.</p>
<% end %>
</div>
</div>
<!-- Top ASNs -->
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Top Autonomous Systems</h3>
<p class="text-sm text-gray-500">ASNs with the most traffic</p>
</div>
<div class="p-6">
<% if @top_asns.any? %>
<div class="space-y-4">
<% @top_asns.each do |asn| %>
<div class="flex items-center justify-between">
<div class="flex-1">
<div class="font-medium text-gray-900">
ASN <%= asn.asn %>
<% if asn.asn_org.present? %>
<span class="ml-2 text-gray-600">• <%= asn.asn_org.truncate(30) %></span>
<% end %>
</div>
<div class="mt-1 text-sm text-gray-600">
<span><%= number_with_delimiter(asn.event_count) %> events</span>
<span class="mx-2">•</span>
<span><%= number_with_delimiter(asn.unique_ips) %> unique IPs</span>
<span class="mx-2">•</span>
<span><%= number_with_delimiter(asn.network_count) %> networks</span>
</div>
</div>
<div class="ml-4">
<%= link_to "Filter Events", events_path(asn: asn.asn),
class: "text-blue-600 hover:text-blue-800 text-sm font-medium" %>
</div>
</div>
<% end %>
</div>
<% else %>
<p class="text-sm text-gray-500">No ASN data available for this time period.</p>
<% end %>
</div>
</div>
</div>
<!-- Navigation -->
<div class="flex items-center justify-between">
<div>
<%= link_to "← Back to Dashboard", analytics_path,
class: "text-blue-600 hover:text-blue-800 font-medium" %>
</div>
<div class="text-sm text-gray-500">
Showing network analytics for the <%= @time_period.to_s.humanize.downcase %>
</div>
</div>
</div>

View File

@@ -14,6 +14,7 @@
</div>
<div class="p-6">
<%= form_with url: events_path, method: :get, local: true, class: "space-y-4" do |form| %>
<!-- Basic Filters Row -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<%= form.label :ip, "IP Address", class: "block text-sm font-medium text-gray-700" %>
@@ -40,6 +41,43 @@
class: "inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
</div>
</div>
<!-- Network Intelligence Filters Row -->
<div class="border-t border-gray-200 pt-4">
<h4 class="text-sm font-medium text-gray-900 mb-3">Network Intelligence Filters</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<%= form.label :company, "Company", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_field :company, value: params[:company],
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: "e.g., Amazon, Google" %>
</div>
<div>
<%= form.label :network_type, "Network Type", class: "block text-sm font-medium text-gray-700" %>
<%= form.select :network_type,
options_for_select([
['All', ''],
['Standard ( Residential/Business )', 'standard'],
['Datacenter', 'datacenter'],
['VPN', 'vpn'],
['Proxy', 'proxy']
], params[:network_type]),
{ }, { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
</div>
<div>
<%= form.label :asn, "ASN", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_field :asn, value: params[:asn],
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: "Autonomous System Number" %>
</div>
<div>
<%= form.label :network_cidr, "Network CIDR", class: "block text-sm font-medium text-gray-700" %>
<%= form.text_field :network_cidr, value: params[:network_cidr],
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
placeholder: "e.g., 192.168.1.0/24" %>
</div>
</div>
</div>
<% end %>
</div>
</div>
@@ -52,6 +90,8 @@
<div class="flex items-center space-x-4">
<%= link_to "📊 Analytics Dashboard", analytics_path,
class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
<%= link_to "🌐 Network Analytics", analytics_networks_path,
class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
<% if @pagy.pages > 1 %>
<span class="text-sm text-gray-500">
Page <%= @pagy.page %> of <%= @pagy.pages %>
@@ -92,8 +132,45 @@
<%= event.timestamp.strftime("%Y-%m-%d") %>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">
<%= event.ip_address %>
<td class="px-6 py-4 text-sm text-gray-900">
<% network_range = @network_ranges_by_ip[event.ip_address.to_s] %>
<% if network_range %>
<%= link_to event.ip_address, network_range_path(network_range),
class: "text-blue-600 hover:text-blue-800 hover:underline font-mono" %>
<!-- Network Intelligence Summary -->
<div class="mt-1 space-y-1">
<% if network_range.company.present? %>
<div class="text-xs text-gray-600 font-medium">
<%= network_range.company %>
</div>
<% end %>
<% if network_range.is_datacenter? || network_range.is_vpn? || network_range.is_proxy? %>
<div class="flex flex-wrap gap-1">
<% if network_range.is_datacenter? %>
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-800" title="Datacenter">DC</span>
<% end %>
<% if network_range.is_vpn? %>
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800" title="VPN">VPN</span>
<% end %>
<% if network_range.is_proxy? %>
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800" title="Proxy">PROXY</span>
<% end %>
</div>
<% end %>
<div class="text-xs text-gray-500">
<%= network_range.cidr %>
<% if network_range.asn.present? %>
• ASN <%= network_range.asn %>
<% end %>
</div>
</div>
<% else %>
<span class="font-mono"><%= event.ip_address %></span>
<div class="mt-1 text-xs text-gray-400">Unknown network</div>
<% end %>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@@ -117,9 +194,9 @@
<%= event.response_status || '-' %>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<% if event.country_code.present? %>
<% if event.lookup_country.present? %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
<%= event.country_code %>
<%= event.lookup_country %>
</span>
<% else %>
<span class="text-gray-400">-</span>

View File

@@ -68,6 +68,8 @@
class: nav_link_class(events_path) %>
<%= link_to "⚙️ Rules", rules_path,
class: nav_link_class(rules_path) %>
<%= link_to "🛡️ WAF Policies", waf_policies_path,
class: nav_link_class(waf_policies_path) %>
<%= link_to "🌐 Network Ranges", network_ranges_path,
class: nav_link_class(network_ranges_path) %>
@@ -157,6 +159,8 @@
class: mobile_nav_link_class(events_path) %>
<%= link_to "⚙️ Rules", rules_path,
class: mobile_nav_link_class(rules_path) %>
<%= link_to "🛡️ WAF Policies", waf_policies_path,
class: mobile_nav_link_class(waf_policies_path) %>
<%= link_to "🌐 Network Ranges", network_ranges_path,
class: mobile_nav_link_class(network_ranges_path) %>

View File

@@ -22,6 +22,14 @@
</nav>
<div class="mt-2 flex items-center space-x-3">
<h1 class="text-3xl font-bold text-gray-900"><%= @network_range.cidr %></h1>
<% if @network_range.virtual? %>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
Virtual
</span>
<% end %>
<% if @network_range.ipv4? %>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">IPv4</span>
<% else %>
@@ -30,8 +38,12 @@
</div>
</div>
<div class="flex space-x-3">
<%= link_to "Edit", edit_network_range_path(@network_range), class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
<%= link_to "Create Rule", new_rule_path(network_range_id: @network_range.id), class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700" %>
<% if @network_range.virtual? %>
<%= link_to "Create Network", new_network_range_path(network: @network_range.cidr), class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700" %>
<% else %>
<%= link_to "Edit", edit_network_range_path(@network_range), class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
<%= link_to "Create Rule", new_rule_path(network_range_id: @network_range.id), class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700" %>
<% end %>
</div>
</div>
</div>
@@ -86,20 +98,32 @@
</div>
<% end %>
<div>
<dt class="text-sm font-medium text-gray-500">Source</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @network_range.source %></dd>
</div>
<% if @network_range.persisted? %>
<div>
<dt class="text-sm font-medium text-gray-500">Source</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @network_range.source %></dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Created</dt>
<dd class="mt-1 text-sm text-gray-900"><%= time_ago_in_words(@network_range.created_at) %> ago</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Created</dt>
<dd class="mt-1 text-sm text-gray-900"><%= time_ago_in_words(@network_range.created_at) %> ago</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Updated</dt>
<dd class="mt-1 text-sm text-gray-900"><%= time_ago_in_words(@network_range.updated_at) %> ago</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Updated</dt>
<dd class="mt-1 text-sm text-gray-900"><%= time_ago_in_words(@network_range.updated_at) %> ago</dd>
</div>
<% else %>
<div>
<dt class="text-sm font-medium text-gray-500">Status</dt>
<dd class="mt-1 text-sm text-gray-900">Virtual Network</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Events Found</dt>
<dd class="mt-1 text-sm text-gray-900"><%= @traffic_stats[:total_requests] %> requests</dd>
</div>
<% end %>
<!-- Classification Flags -->
<div class="md:col-span-2 lg:col-span-3">
@@ -200,17 +224,22 @@
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">Associated Rules (<%= @associated_rules.count %>)</h3>
<button type="button" onclick="toggleQuickCreateRule()" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg class="w-4 h-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Quick Create Rule
</button>
<% if @network_range.persisted? %>
<button type="button" onclick="toggleQuickCreateRule()" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg class="w-4 h-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Quick Create Rule
</button>
<% else %>
<span class="text-sm text-gray-500">Create this network to add rules</span>
<% end %>
</div>
</div>
<!-- Quick Create Rule Form -->
<div id="quick_create_rule" class="hidden border-b border-gray-200">
<% if @network_range.persisted? %>
<div id="quick_create_rule" class="hidden border-b border-gray-200">
<div class="px-6 py-4 bg-blue-50">
<%= form_with(model: Rule.new, url: rules_path, local: true,
class: "space-y-4",
@@ -384,6 +413,7 @@
<% end %>
</div>
</div>
<% end %>
<!-- Rules List -->
<% if @associated_rules.any? %>
@@ -436,8 +466,13 @@
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">No rules yet</h3>
<p class="mt-1 text-sm text-gray-500">Get started by creating a rule for this network range.</p>
<% if @network_range.virtual? %>
<h3 class="mt-2 text-sm font-medium text-gray-900">Virtual Network</h3>
<p class="mt-1 text-sm text-gray-500">Create this network range to add rules and manage it permanently.</p>
<% else %>
<h3 class="mt-2 text-sm font-medium text-gray-900">No rules yet</h3>
<p class="mt-1 text-sm text-gray-500">Get started by creating a rule for this network range.</p>
<% end %>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,231 @@
<% content_for :title, "WAF Policies" %>
<div class="space-y-6">
<!-- Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-gray-900">WAF Policies</h1>
<p class="mt-2 text-gray-600">High-level firewall policies that automatically generate rules</p>
</div>
<div class="flex space-x-3">
<%= link_to "🌍 Block Countries", new_country_waf_policies_path,
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700" %>
<%= link_to "Create Policy", new_waf_policy_path,
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700" %>
</div>
</div>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Total Policies</dt>
<dd class="text-lg font-medium text-gray-900"><%= number_with_delimiter(@waf_policies.count) %></dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Active Policies</dt>
<dd class="text-lg font-medium text-gray-900"><%= number_with_delimiter(@waf_policies.active.count) %></dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Generated Rules</dt>
<dd class="text-lg font-medium text-gray-900">
<%= number_with_delimiter(Rule.policy_generated.count) %>
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Deny Policies</dt>
<dd class="text-lg font-medium text-gray-900">
<%= number_with_delimiter(@waf_policies.where(action: 'deny').count) %>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Policies Table -->
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">Firewall Policies</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">
High-level policies that automatically generate specific WAF rules when matching network ranges are discovered.
</p>
</div>
<div class="border-t border-gray-200">
<% if @waf_policies.any? %>
<ul class="divide-y divide-gray-200">
<% @waf_policies.each do |policy| %>
<li class="hover:bg-gray-50">
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="flex-shrink-0">
<% if policy.country_policy? %>
<span class="text-2xl">🌍</span>
<% elsif policy.asn_policy? %>
<span class="text-2xl">🏢</span>
<% elsif policy.company_policy? %>
<span class="text-2xl">🏭</span>
<% elsif policy.network_type_policy? %>
<span class="text-2xl">🌐</span>
<% end %>
</div>
<div class="ml-4">
<div class="flex items-center">
<%= link_to policy.name, waf_policy_path(policy),
class: "text-sm font-medium text-gray-900 hover:text-blue-600" %>
<!-- Status Badge -->
<% if policy.active? %>
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
Active
</span>
<% else %>
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
Inactive
</span>
<% end %>
<!-- Action Badge -->
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
<%= case policy.action
when 'deny' then 'bg-red-100 text-red-800'
when 'allow' then 'bg-green-100 text-green-800'
when 'redirect' then 'bg-yellow-100 text-yellow-800'
when 'challenge' then 'bg-purple-100 text-purple-800'
end %>">
<%= policy.action.upcase %>
</span>
</div>
<div class="mt-1 text-sm text-gray-500">
<%= policy.policy_type.humanize %> policy targeting
<% if policy.targets.length > 3 %>
<%= policy.targets.length %> items
<% else %>
<%= policy.targets.join(', ') %>
<% end %>
• <%= policy.generated_rules_count %> rules generated
</div>
<% if policy.description.present? %>
<div class="mt-1 text-sm text-gray-600">
<%= policy.description %>
</div>
<% end %>
</div>
</div>
<div class="flex items-center space-x-2">
<%= link_to "View", waf_policy_path(policy),
class: "inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50" %>
<% if policy.active? %>
<%= link_to "Deactivate", deactivate_waf_policy_path(policy),
method: :post,
data: { confirm: "Are you sure you want to deactivate this policy?" },
class: "inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50" %>
<% else %>
<%= link_to "Activate", activate_waf_policy_path(policy),
method: :post,
class: "inline-flex items-center px-3 py-1 border border-transparent shadow-sm text-xs font-medium rounded text-white bg-green-600 hover:bg-green-700" %>
<% end %>
<%= link_to "Edit", edit_waf_policy_path(policy),
class: "inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50" %>
</div>
</div>
</div>
</li>
<% end %>
</ul>
<% else %>
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">No policies</h3>
<p class="mt-1 text-sm text-gray-500">Get started by creating your first WAF policy.</p>
<div class="mt-6">
<%= link_to "Create Policy", new_waf_policy_path,
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700" %>
</div>
</div>
<% end %>
</div>
</div>
<!-- Pagination -->
<% if @waf_policies.respond_to?(:total_pages) && @waf_policies.total_pages > 1 %>
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
<div class="flex-1 flex justify-between sm:hidden">
<%= link_to_previous_page @waf_policies, "Previous", class: "relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
<%= link_to_next_page @waf_policies, "Next", class: "ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
Showing
<span class="font-medium"><%= (@waf_policies.current_page - 1) * @waf_policies.limit_value + 1 %></span>
to
<span class="font-medium"><%= [@waf_policies.current_page * @waf_policies.limit_value, @waf_policies.total_count].min %></span>
of
<span class="font-medium"><%= number_with_delimiter(@waf_policies.total_count) %></span>
results
</p>
</div>
<div>
<%== pagy_nav(@pagy) %>
</div>
</div>
</div>
<% end %>
</div>