From 68e1140b0d75f9a808344ec2842fe14a6aa2db24 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 11 Nov 2019 19:41:09 -0600 Subject: [PATCH] /ip_addresses: allow grouping IPs by subnet. --- app/controllers/ip_addresses_controller.rb | 4 ++-- app/models/ip_address.rb | 21 +++++++++++++++++-- .../ip_addresses/_index_by_ip_addr.html.erb | 6 +++--- app/views/ip_addresses/index.html.erb | 4 ++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/controllers/ip_addresses_controller.rb b/app/controllers/ip_addresses_controller.rb index a1f382f97..c910cecf0 100644 --- a/app/controllers/ip_addresses_controller.rb +++ b/app/controllers/ip_addresses_controller.rb @@ -4,10 +4,10 @@ class IpAddressesController < ApplicationController def index if search_params[:group_by] == "ip_addr" - @ip_addresses = IpAddress.search(search_params).group_by_ip_addr + @ip_addresses = IpAddress.search(search_params).group_by_ip_addr(search_params[:ipv4_masklen], search_params[:ipv6_masklen]).paginate(params[:page], limit: params[:limit] || 1000) respond_with(@ip_addresses) elsif search_params[:group_by] == "user" - @ip_addresses = IpAddress.search(search_params).group_by_user + @ip_addresses = IpAddress.includes(:user).search(search_params).group_by_user.paginate(params[:page], limit: params[:limit] || 1000) respond_with(@ip_addresses) else @ip_addresses = IpAddress.includes(:user, :model).paginated_search(params) diff --git a/app/models/ip_address.rb b/app/models/ip_address.rb index ecb9c9580..33c35fe06 100644 --- a/app/models/ip_address.rb +++ b/app/models/ip_address.rb @@ -14,14 +14,31 @@ class IpAddress < ApplicationRecord q.order(created_at: :desc) end - def self.group_by_ip_addr - group(:ip_addr).select("ip_addr, COUNT(*) AS count_all").reorder("count_all DESC, ip_addr") + def self.group_by_ip_addr(ipv4_masklen = nil, ipv6_masklen = nil) + ipv4_masklen ||= 32 + ipv6_masklen ||= 128 + + q = select(sanitize_sql([<<~SQL, ipv4_masklen, ipv6_masklen])) + CASE + WHEN family(ip_addr) = 4 + THEN network(set_masklen(ip_addr, ?)) + ELSE network(set_masklen(ip_addr, ?)) + END AS ip_addr, + COUNT(*) AS count_all + SQL + + q.group("1").reorder("count_all DESC, ip_addr") end def self.group_by_user group(:user_id).select("user_id, COUNT(*) AS count_all").reorder("count_all DESC, user_id") end + def to_s + # include the subnet mask only when the IP denotes a subnet. + ip_addr.size > 1 ? ip_addr.to_string : ip_addr.to_s + end + def readonly? true end diff --git a/app/views/ip_addresses/_index_by_ip_addr.html.erb b/app/views/ip_addresses/_index_by_ip_addr.html.erb index 27f58645a..bd7518593 100644 --- a/app/views/ip_addresses/_index_by_ip_addr.html.erb +++ b/app/views/ip_addresses/_index_by_ip_addr.html.erb @@ -9,10 +9,10 @@ <% @ip_addresses.each do |ip| %> - <%= link_to ip.ip_addr, ip_addresses_path(search: { ip_addr: ip.ip_addr }) %> - <%= link_to "»", ip_addresses_path(search: { ip_addr: ip.ip_addr, group_by: "user" }) %> + <%= link_to ip.to_s, ip_addresses_path(search: { ip_addr: ip.to_s }) %> + <%= link_to "»", ip_addresses_path(search: { ip_addr: ip.to_s, group_by: "user" }) %> - <%= link_to ip.count_all, ip_addresses_path(search: { ip_addr: ip.ip_addr }) %> + <%= link_to ip.count_all, ip_addresses_path(search: { ip_addr: ip.to_s }) %> <% end %> diff --git a/app/views/ip_addresses/index.html.erb b/app/views/ip_addresses/index.html.erb index 1f11d3d65..d5afb1d6a 100644 --- a/app/views/ip_addresses/index.html.erb +++ b/app/views/ip_addresses/index.html.erb @@ -7,6 +7,10 @@ <%= f.input :created_at, label: "Date", input_html: { value: params[:search][:created_at] } %> <%= f.input :model_type, label: "Source", collection: IpAddress.model_types, include_blank: true, selected: params[:search][:model_type] %> <%= f.input :group_by, label: "Group By", collection: [["User", "user"], ["IP Address", "ip_addr"]], include_blank: true, selected: params[:search][:group_by] %> + <% if params[:search][:group_by] == "ip_addr" %> + <%= f.input :ipv4_masklen, label: "IPv4 Subnet", collection: [["/32", 32], ["/24", 24], ["/16", 16], ["/8", 8]], include_blank: false, selected: params[:search][:ipv4_masklen], hint: "Lower to group together IPs with the same prefix." %> + <%= f.input :ipv6_masklen, label: "IPv6 Subnet", collection: [["/128", 128], ["/80", 80], ["/64", 64], ["/48", 48]], include_blank: false, selected: params[:search][:ipv6_masklen] %> + <% end %> <%= f.submit "Search" %> <% end %>