Add WafPolicies
This commit is contained in:
@@ -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">
|
||||
|
||||
283
app/views/analytics/networks.html.erb
Normal file
283
app/views/analytics/networks.html.erb
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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) %>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
231
app/views/waf_policies/index.html.erb
Normal file
231
app/views/waf_policies/index.html.erb
Normal 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>
|
||||
Reference in New Issue
Block a user