I may not be using the correct terminology but here goes..
I'm displaying IPS alerts on a dashboard app and there are many duplicate lines. For example, if one script kiddie is trying to brute force an RDP server, I could get 150 Alerts but could be slimmed down to about 5 because that's how many hosts they are going after. So I'm trying to remove the duplicate alerts, and I'm looking to use the sid, src_addr, and dst_addr as my metrics to determine if they are duplicates.
Currently I display #filtered_snort_detail_query using this code:
This is my view
<% if #filtered_snort_detail_query.count > 0 %>
<table>
<tr>
<th>Timestamp</th>
<th>Tag Info</th>
<th>Message</th>
</tr>
<% #filtered_snort_detail_query.each do |d|
text_msg = d['_source']['message']
if d['_source']['message'].nil?
end
%>
<tr>
<td class='timestamp'><%= d['_source']['#timestamp'].to_time %></td>
<td class='tags'><%= d['_source']['tags'] %></td>
<td class='message'><%= text_msg %></td>
</tr>
<% end %>
</table>
<% else %>
<div> No Results Returned. </div>
<% end %>
Here is my controller
if #es_snort_detail_query.count > 0
#filtered_snort_detail_query = Array.new
#es_snort_detail_query.each do |ips_detail|
next if ips_detail['_source']['type'] != 'snort-ips'
next if ips_detail['_source']['#timestamp'] < #ts
#filtered_snort_detail_query.push(ips_detail)
end
end
Here is what I think I need to do to get the metrics I need to compare lines in my controller.
I'm just not sure the best way to look at each line of #filtered_snort_detail_query and build a new array to display in my view using these parameters:
show me all lines, but not if sid_data, src_ip_data, and dst_ip_data happen two or more times.
if #es_snort_detail_query.count > 0
#filtered_snort_detail_query = Array.new
#es_snort_detail_query.each do |ips_detail|
next if ips_detail['_source']['type'] != 'snort-ips'
next if ips_detail['_source']['#timestamp'] < #ts
#filtered_snort_detail_query.push(ips_detail)
end
if #filtered_snort_detail_query.count > 0
ip_src = Array.new
ip_dst = Array.new
sid = Array.new
#filtered_snort_detail_query.each do |find_ip, find_sid|
unless find_ip.nil?
sid_data = find_sid.scan(/\[\d+\:\d+\:\d+\]/)
src_ip_data = find_ip.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
dst_ip_data = find_ip.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
sid.push(sid_data[0]) unless sid_data[0].nil?
ip_src.push(src_ip_data[0]) unless src_ip_data[0].nil?
ip_dst.push(dst_ip_data[1]) unless dst_ip_data[1].nil?
end
end
end
end
Sorry if I misunderstood the question.
If you have a bunch of objects in an array, and you want to remove duplicates based on a subset of their properties, you can use the uniq method with a block:
queries.uniq do |query|
[query.sid_data, query.src_ip_data, query.dst_ip_data]
end
It will compare the queries based on the array created in the block, and remove duplicates.
Go to http://www.compileonline.com/execute_ruby_online.php, copy and paste the code below, and click execute script in the top left.
queries = [
{ :a => "ab", :b => "ba" },
{ :a => "ab", :b => "xy" },
{ :a => "xy", :b => "xy" }
]
unique_queries = queries.uniq do |q|
q[:a]
end
puts unique_queries
See? The comparaison was done only based on the value of the :a key. That's the same principle.
Related
I'm having a hard time grasping the .uniq! method. I'm trying to remove duplicate ips alerts in my view.
If I use the code in Original Code: I receive all alerts from the IPS in my Index View:.
This will show all alerts, for example; I will receive 500 alerts that can be condensed down to 1 alert based on the signature ID (sid), Source IP (ip_src), and Destination IP (ip_dst). If I just append .uniq! (if thats even how it should be used) I don't get any different results, I assume it does not work because the timestamps and source ports are not the same, so it already is unique. Here are two sample messages that should be one instead of two.
04/04-16:13:47.451062 [**] [1:10000001:1] <dna0:dna1> drop - WP-Admin attempt [**] [Classification: Web Application Attack] [Priority: 1] {TCP} 10.17.21.37:55749 -> 173.239.96.163:80
04/04-16:13:28.474894 [**] [1:10000001:1] <dna0:dna1> drop - WP-Admin attempt [**] [Classification: Web Application Attack] [Priority: 1] {TCP} 10.17.21.37:55707 -> 173.239.96.163:80
I would like to use the signature ID (sid), Source IP (ip_src), and Destination IP (ip_dst) of every message, and remove the duplicates.
I used the .scan method to find the signature ID, source IP, and destination IP. They are buit as sid, ip_src, ip_dst. I'm stuck on the line #filtered_snort_detail_query.push(ips_detail).uniq! and do not really know how I need to use the information in sid, ip_src, ip_dst to make #filtered_snort_detail_query pass unique alerts to my view.
Index View:
<% if #filtered_snort_detail_query.count > 0 %>
<table>
<tr>
<th>Timestamp</th>
<th>Tag Info</th>
<th>Message</th>
</tr>
<% #filtered_snort_detail_query.each do |d|
text_msg = d['_source']['message']
if d['_source']['message'].nil?
end
%>
<tr>
<td class='timestamp'><%= d['_source']['#timestamp'].to_time %></td>
<td class='tags'><%= d['_source']['tags'] %></td>
<td class='message'><%= text_msg %></td>
</tr>
<% end %>
</table>
<% else %>
<div> No Results Returned. </div>
<% end %>
Original Code:
if #es_snort_detail_query.count > 0
#filtered_snort_detail_query = Array.new
#es_snort_detail_query.each do |ips_detail|
next if ips_detail['_source']['type'] != 'snort-ips'
next if ips_detail['_source']['#timestamp'] < #ts
#filtered_snort_detail_query.push(ips_detail)
end
end
Modified Code:
if #es_snort_detail_query.count > 0
sid = Array.new
ip_src = Array.new
ip_dst = Array.new
#filtered_snort_detail_query = Array.new
#es_snort_detail_query.each do |ips_detail|
next if ips_detail['_source']['type'] != 'snort-ips'
next if ips_detail['_source']['#timestamp'] < #ts
if ips_detail['_source']['message'].nil?
text_msg = ips_detail['_source']['message']
else
text_msg = ips_detail['_source']['message']
end
unless text_msg.nil?
sid_data = text_msg.scan(/\[\d+:\d+:\d+\]/)
src_ip_data = text_msg.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
dst_ip_data = text_msg.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
sid.push(sid_data[0]) unless sid_data[0].nil?
ip_src.push(src_ip_data[0]) unless src_ip_data[0].nil?
ip_dst.push(dst_ip_data[1]) unless dst_ip_data[1].nil?
#filtered_snort_detail_query.push(ips_detail).uniq!
#[{:unique_ids => sid}, {:unique_ids => ip_src}, {:unique_ids => ip_dst}]
end
end
end
You can pass a block to uniq to tell it how you want to dedup your array:
#filtered_snort_detail_query = #es_snort_detail_query.reject do |ips_detail|
ips_detail['_source']['type'] != 'snort-ips' || ips_detail['_source']['#timestamp'] < #ts
end.uniq do |ips_detail|
if ips_detail['_source']['message'].nil?
text_msg = ips_detail['_source']['message']
else
text_msg = ips_detail['_source']['message']
end
unless text_msg.nil?
sid_data = text_msg.scan(/\[\d+:\d+:\d+\]/)
src_ip_data = text_msg.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
dst_ip_data = text_msg.scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
[sid_data, src_ip_data, dst_ip_data]
end
end
I have a restaurant with many employees and each employee has many customer ratings.
I want to create a stats page that shows the employees ordered by their monthly ratings average.
In the employee model:
def avg_rating
date = Date.today
ratings_total = self.ratings.sum(:score, :conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
ratings_count = self.ratings.count(:conditions => {:created_at => (date.beginning_of_month..date.end_of_month)}).to_f
return (ratings_total/ ratings_count)
end
In the restaurant controller I have:
def restaurant_stats
#restaurant = Restaurant.find(params[:restaurant_id])
#employees = #restaurant.employees.all
end
In the restaurant stats view:
<table>
<% #employees.each do |employee| %>
<tr>
<td><%= employee.name %></td>
<td><%= employee.avg_rating %></td>
</tr>
<% end %>
</table>
I'm not sure how to get the employees in the correct order? I assume I would have to retrieve the values in the correct order in the restaurant_stats action instead of just #restaurant.employees.all but I'm not sure how to because of the functions used in the employees model
You could do, from the controller:
#employees = #restaurant.employees.all.sort_by {|employee| employee.avg_rating}
or more concisely
#employees = #restaurant.employees.all.sort_by(&:avg_rating)
Note that this will load all employees in memory for sorting.
Try in the restaurant controller:
#employees = #restaurant.employees.all.sort {|x,y| y.avg_rating <=> x.avg_rating }
or
#employees = #restaurant.employees.all.sort_by(:avg_rating)
you could create an array and sort it
I think below works but haven't checked it
#employees = #restaurant.employees.collect {|p| [ p.name, p.avg_rating ]}
#employees.sort!{ |a,b| (a[1] <=> b[1]) }
I have simple app that is meant to pull Adwords account metrics (via the adwordsapi) and present it to the user in a table inside a Rails view. It is working properly pulling down all info for multiple campaigns except for one issue..
I am unable to get the totals of each field (total cost, total impressions, etc.). I am able to serve impressions for each campaign within the account, but am unable to get the totals for the account.
I apologize ahead of time for the noob code to follow :)
Here is the adwordscampaign_controller.rb
class AdwordscampaignController < ApplicationController
PAGE_SIZE = 50
def index()
#selected_account = selected_account
if #selected_account
response = request_campaigns_list()
if response
#campaigns = Adwordscampaign.get_campaigns_list(response)
#campaign_count = response[:total_num_entries]
#start = params[:start]
#end = params[:end]
#myhash = Adwordscampaign.get_campaigns_list(response)
end
end
end
private
def request_campaigns_list()
# Prepare start and end date for the last week.
if params[:start].nil?
start_date = DateTime.parse((Date.today - 7).to_s).strftime("%Y%m%d")
end_date = DateTime.parse((Date.today - 1).to_s).strftime("%Y%m%d")
else
start_date = params[:start]
end_date = params[:end]
end
api = get_adwords_api()
service = api.service(:CampaignService, get_api_version())
selector = {
:fields => ['Id', 'Name', 'Status', 'Cost', 'Impressions', 'Clicks', 'Ctr', 'Conversions', 'Amount'],
:ordering => [{:field => 'Id', :sort_order => 'ASCENDING'}],
:date_range => {:min => start_date, :max => end_date},
:paging => {:start_index => 0, :number_results => PAGE_SIZE}
}
result = nil
begin
result = service.get(selector)
rescue AdwordsApi::Errors::ApiException => e
logger.fatal("Exception occurred: %s\n%s" % [e.to_s, e.message])
flash.now[:alert] =
'API request failed with an error, see logs for details'
end
return result
end
end
the relevant model: adwordscampaign.rb
class Adwordscampaign
attr_reader :id
attr_reader :name
attr_reader :status
attr_reader :cost
attr_reader :impressions
attr_reader :clicks
attr_reader :ctr
attr_reader :costdecimal
attr_reader :costperconversiondecimal
def initialize(api_campaign)
#id = api_campaign[:id]
#name = api_campaign[:name]
#status = api_campaign[:status]
budget = api_campaign[:budget]
stats = api_campaign[:campaign_stats]
#cost = (stats[:cost][:micro_amount] / 10000)
#costdecimal = (#cost * 10000).round.to_f / 1000000
#impressions = stats[:impressions]
#clicks = stats[:clicks]
#ctr = (stats[:ctr] * 100)
end
def self.get_campaigns_list(response)
result = {}
if response[:entries]
response[:entries].each do |api_campaign|
campaign = Adwordscampaign.new(api_campaign)
result[campaign.id] = campaign
end
end
return result
end
end
The table from the views\adwordscampaign\index.html.erb
<table class="table table-striped table-bordered">
<tr>
<th>ID
<th>Name
<th>Status
<th>Impressions
<th>Clicks
<th>CTR
<th>Cost
<% #campaigns.each do |id, campaign| %>
<tr>
<td><%= campaign.id %></td>
<td><%= campaign.name %></td>
<td><%= campaign.status %></td>
<td><%= number_with_delimiter(campaign.impressions) %></td>
<td><%= number_with_delimiter(campaign.clicks) %></td>
<td><%= number_with_precision(campaign.ctr, precision: 2) %>%</td>
<td>$<%= number_with_delimiter(campaign.costdecimal) %></td>
<% end %>
<td><%= #campaigns %></td>
</table>
I threw #campaigns in the view (at the bottom) so I could see what the output of that was. The syntax is a little unfamiliar to me but it appears to be hashes nested in a hash (correct?)
output of #campaigns in the view
{109886905=>#<Adwordscampaign:0x4528ba0 #id=109879905, #name="Upholstery Cleaning", #status="ACTIVE", #cost=2702, #costdecimal=27.02, #impressions=824, #clicks=7, #ctr=0.8495145631067961>, 103480025=>#<Adwordscampaign:0x7028b28 #id=109880025, #name="Carpet Cleaning", #status="ACTIVE", #cost=16739, #costdecimal=167.39, #impressions=4457, #clicks=29, #ctr=0.6506618801884676>, 104560145=>#<Adwordscampaign:0x3e9ibac8 #id=109880145, #name="Competitors", #status="ACTIVE", #cost=1596, #costdecimal=15.96, #impressions=515, #clicks=5, #ctr=0.9708737864077669>
Finally to the question - How would I get the total #clicks (or #impressions, #cost, etc.) for each of the 3 campaigns found here?
I've been searching for things like "how to sum identical hash keys/values" or "how to merge nested hashes" to no avail.
Thanks in advance!
#campaigns looks like a simple hash of { id -> Adwordscampaign }. The #<ObjectName:data> notation is Ruby doing its best to give you something readable for that object.
To sum clicks, for example, is a simple map-reduce:
total_clicks = #campaigns.map { |id, campaign| campaign.clicks } .reduce(&:+)
This creates an array of all campaign.clicks values, then combines them all using the + operator.
This assumes there is a #clicks on those objects. If not, tweak accordingly.
If all you're doing is summing various attributes, you can simplify using it multiple times like so:
camp_list = #campaigns.map { |id, campaign| campaign }
total_clicks = camp_list.map(&:clicks).reduce(&:+)
total_cost = camp_list.map(&:cost).reduce(&:+)
total_impressions = camp_list.map(&:impressions).reduce(&:+)
Reducing it further is an exercise to the reader. :)
i have a single search field that is querying three different columns from two different tables: "companies" and "industries" from a positions table and "schools" from an educations table. it is successfully returning all users that meet ALL fields entered into the search field (using select_tag). this is from my view:
<%= form_tag(search_path, :method => :get, :id => "people_search") do %>
<div class="row-fluid">
<div class="span4">
<table>
<tr>
<td>
<div class="search-table" style="padding-left:55px">
<%= select_tag "all", options_for_select((#visible_companies + #visible_industries + #visible_schools).uniq, params[:all]), { :placeholder => "Search by companies, industries and schools...", :multiple => true, :js => "if (evt.keyCode == 13) {form.submit();}" } %>
</div>
</td>
<td>
<%= submit_tag "Add", id: "send-button", style:"width:175px;" %>
</td>
</tr>
</table>
</div>
<% end %>
</div>
and controller:
#visible_positions = Position.where{ is_visible.eq('true') }
#visible_educations = Education.where{ is_visible.eq('true') }
#visible_companies = #visible_positions.order("LOWER(company)").map(&:company).uniq
#visible_industries = #visible_positions.order("LOWER(industry)").map(&:industry).uniq
#visible_schools = #visible_educations.order("LOWER(school)").map(&:school).uniq
#c = #visible_positions.where{company.in(my{params[:all]})}.map(&:user_id)
#i = #visible_positions.where{industry.in(my{params[:all]})}.map(&:user_id)
#s = #visible_educations.where{school.in(my{params[:all]})}.map(&:user_id)
#C = #visible_positions.where{company.in(my{params[:all]})}.map(&:company)
#I = #visible_positions.where{industry.in(my{params[:all]})}.map(&:industry)
#S = #visible_educations.where{school.in(my{params[:all]})}.map(&:school)
#blacklist = []
#cis = #c + #i + #s
#experiences = ([#C,#I,#S].reject(&:empty?).reduce(:&))
#cis.uniq.each do |user_id|
unless #C.empty?
#C.uniq.each do |company|
unless Position.find_all_by_company(company).map(&:user_id).include?(user_id) || Position.find_all_by_industry(company).map(&:user_id).include?(user_id) || Education.find_all_by_school(company).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #I.empty?
#I.uniq.each do |industry|
unless Position.find_all_by_industry(industry).map(&:user_id).include?(user_id) || Position.find_all_by_company(industry).map(&:user_id).include?(user_id) || Education.find_all_by_school(industry).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
unless #S.empty?
#S.each do |school|
unless Education.find_all_by_school(school).map(&:user_id).include?(user_id) || Position.find_all_by_company(school).map(&:user_id).include?(user_id) || Position.find_all_by_industry(school).map(&:user_id).include?(user_id)
#blacklist << user_id
end
end
end
end
unless #c.empty? && #i.empty? && #s.empty?
#users = User.find(#cis - #blacklist)
end
the search looks like this (notice the single field), with a sample query included (notice the AND filter...i'm the only user in the database that fits all search terms ['dartmouth college' for school, 'world health organization' for company, 'internet' for industry]):
i realize this is not an efficient query and am thinking of ways to speed it up, but could use some ideas at this point.
happy turkey day :)
Based on your description rather then on understanding your code I figured out something like this
User.joins(:positions, :educations).where("lower(positions.company) like lower(?) and lower(positions.industry) like lower(?) and lower(educations.school) like lower(?) and positions.is_visible and educations.is_visible", "%#{company}%", "%#{industry}%", "%#{school}%")
or if there is only one company or industry in column
User.joins(:positions, :educations).where("(lower(positions.company) = lower(?) or lower(positions.industry) = lower(?)) and lower(educations.school) = lower(?) and positions.is_visible and educations.is_visible", company,industry, school)
But to put many industries, companies, schools as params will be more complicated
and create indexes
create index positions_lower_company on positions (lower(company));
create index positions_lower_industry on positions (lower(industry));
create index educations_lower_school on educations (lower(school));
I hope it will help somehow.
Is there a way with ActiveRecord to execute a custom SQL query and have it return an array of arrays where the first row is the column names and each following row is the row data? I want to execute something like:
connection.select_rows_with_headers "SELECT id, concat(first_name, ' ', last_name) as name, email FROM users"
And have it return:
[["id","name","email"],["1","Bob Johnson","bob#example.com"],["2","Joe Smith","joe#example.com"]]
This would allow me to print the results of the custom query in an HTML table like this:
<table>
<% result.each_with_index do |r,i| %>
<tr>
<% r.each do |c| %>
<% if i == 0 %>
<th><%=h c %></th>
<% else %>
<td><%=h c %></td>
<% end %>
<% end %>
</tr>
<% end %>
</table>
Note that select_all doesn't work because the keys in each hash are unordered, so you've lost the ordering of the results as specified in the query.
Not EXACTLY what you're looking for, but maybe:
connection.execute('select * from users').all_hashes
and you'll get back
[{:id => 1, :name => 'Bob', :email => 'bob#example.com'},{:id => 1, :name => 'Joe', :email => 'joe#example.com'}]
and you could do:
results = connection.execute('select * from users').all_hashes
munged_results = []
columns = results.first.keys.map(&:to_s)
munged_results << results.first.keys.map(&:to_s)
munged_results += results.map{|r| columns.map{|c| r[c]} }
something like that
edit:
results = connection.execute('select * from users').all_hashes
munged_results = []
columns = User.column_names
munged_results << columns
munged_results += results.map{|r| columns.map{|c| r[c]} }
That should be ordered properly.
Beyond that there is the result object that is returned from #execute that can be interrogated for bits of information. Methods like #fetch_fields get you the fields in order and #fetch_row will get you each row of the result set as an array (works like an iterator).
edit again:
OK, here's a good solution, modify for whatever DB you're using:
class Mysql::Result
def all_arrays
results = []
results << fetch_fields.map{|f| f.name}
while r = fetch_row
results << r
end
results
end
end
That will get them without a ton of overhead.
Use it like this:
connection.execute('select salt, id from users').all_arrays