osa 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +11 -7
- data/README.md +7 -0
- data/lib/osa/clients/ms_graph_client.rb +7 -0
- data/lib/osa/migrations/00005_create_dns_blacklists.rb +35 -0
- data/lib/osa/scripts/dashboard_server.rb +27 -2
- data/lib/osa/scripts/scan_junk_folder.rb +25 -13
- data/lib/osa/util/db.rb +9 -0
- data/lib/osa/version.rb +1 -1
- data/lib/osa/views/index.erb +6 -6
- data/lib/osa/views/layout.erb +4 -13
- data/lib/osa/views/spammer.erb +209 -0
- data/osa.gemspec +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1008becc098b639309f218c9f763e57d4d01f201b28fd105c478cd46534de913
|
4
|
+
data.tar.gz: 2c285065f454aeaa7fcc1f9dd6f72e92d3423dbe292d06ef36afba6a8db6f9eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b53ba423fc0b0da3bc8f49188d34370cf49d12e968a13a436ffaa6c849dceddedcf10c7e2b833d7316dbc0045fe487c44891b6eb50b98fb63477f63b8542a0e
|
7
|
+
data.tar.gz: ba063539fe384116422310df5a0d3a14c324a0560674932dd39998951d433236fb929a86aae981895adff04a29d71d0780c8fc4a2a9d11fa817359ad56c10ff0
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
osa (0.2.
|
4
|
+
osa (0.2.1)
|
5
5
|
activerecord (~> 6.0)
|
6
6
|
faraday (~> 1.1)
|
7
|
+
mail (~> 2.7.1)
|
7
8
|
public_suffix (~> 4.0)
|
8
9
|
sinatra (~> 2.1.0)
|
9
10
|
sinatra-contrib (~> 2.1.0)
|
@@ -13,12 +14,12 @@ PATH
|
|
13
14
|
GEM
|
14
15
|
remote: https://rubygems.org/
|
15
16
|
specs:
|
16
|
-
activemodel (6.1.
|
17
|
-
activesupport (= 6.1.
|
18
|
-
activerecord (6.1.
|
19
|
-
activemodel (= 6.1.
|
20
|
-
activesupport (= 6.1.
|
21
|
-
activesupport (6.1.
|
17
|
+
activemodel (6.1.3)
|
18
|
+
activesupport (= 6.1.3)
|
19
|
+
activerecord (6.1.3)
|
20
|
+
activemodel (= 6.1.3)
|
21
|
+
activesupport (= 6.1.3)
|
22
|
+
activesupport (6.1.3)
|
22
23
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
23
24
|
i18n (>= 1.6, < 2)
|
24
25
|
minitest (>= 5.1)
|
@@ -33,6 +34,9 @@ GEM
|
|
33
34
|
faraday-net_http (1.0.1)
|
34
35
|
i18n (1.8.9)
|
35
36
|
concurrent-ruby (~> 1.0)
|
37
|
+
mail (2.7.1)
|
38
|
+
mini_mime (>= 0.1.1)
|
39
|
+
mini_mime (1.0.2)
|
36
40
|
minitest (5.14.3)
|
37
41
|
multi_json (1.15.0)
|
38
42
|
multipart-post (2.1.1)
|
data/README.md
CHANGED
@@ -25,6 +25,13 @@ be blacklisted. However, to prevent millions of users to go blacklisted because
|
|
25
25
|
list of free email providers (which includes domains like gmail.com, outlook.com among others). If the sender uses a free
|
26
26
|
email provider, the full address is blacklisted.
|
27
27
|
|
28
|
+
OSA also supports Domain Name System Blacklist. In fact it comes bundled with 3 DNSBL:
|
29
|
+
1. [Spamcop Blocking List](https://www.spamcop.net/fom-serve/cache/297.html)
|
30
|
+
2. [Spamhaus Block List](https://www.spamhaus.org/sbl)
|
31
|
+
3. [Passive Spam Block List](https://psbl.org)
|
32
|
+
|
33
|
+
You can remove these or add more blacklists, from the database, after you configure OSA.
|
34
|
+
|
28
35
|
## Installation
|
29
36
|
|
30
37
|
You can install OSA from RubyGems:
|
@@ -6,6 +6,7 @@ require 'osa/util/paginated'
|
|
6
6
|
require 'osa/clients/http_client'
|
7
7
|
require 'active_support/core_ext/numeric/bytes'
|
8
8
|
require 'active_support'
|
9
|
+
require 'mail'
|
9
10
|
|
10
11
|
module OSA
|
11
12
|
class MSGraphClient
|
@@ -44,6 +45,12 @@ module OSA
|
|
44
45
|
@authenticated.get("/v1.0/me/messages/#{mail_id}/$value")
|
45
46
|
end
|
46
47
|
|
48
|
+
def sender_ip(mail_id)
|
49
|
+
content = raw_mail(mail_id)
|
50
|
+
mail = Mail.new(content)
|
51
|
+
mail.header['x-sender-ip']
|
52
|
+
end
|
53
|
+
|
47
54
|
def forward_mail_as_attachment(mail_id, to)
|
48
55
|
raw_mail = self.raw_mail(mail_id)
|
49
56
|
forward_message = create_forward_message(mail_id)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_record/migration'
|
3
|
+
|
4
|
+
class CreateDnsBlacklists < ActiveRecord::Migration[5.0]
|
5
|
+
def change
|
6
|
+
create_table :dns_blacklists do |t|
|
7
|
+
t.text :name, null: false
|
8
|
+
t.text :server, null: false
|
9
|
+
end
|
10
|
+
|
11
|
+
reversible do |dir|
|
12
|
+
dir.up do
|
13
|
+
execute <<~SQL
|
14
|
+
insert into dns_blacklists (name, server) values
|
15
|
+
('spamcop', 'bl.spamcop.net'),
|
16
|
+
('sbl', 'sbl.spamhaus.org'),
|
17
|
+
('psbl', 'psbl.surriel.org');
|
18
|
+
SQL
|
19
|
+
add_column :reports, :blacklist, :string, null: true
|
20
|
+
execute <<~SQL
|
21
|
+
update reports set blacklist = 'db' where blacklisted = true;
|
22
|
+
SQL
|
23
|
+
remove_column :reports, :blacklisted
|
24
|
+
end
|
25
|
+
|
26
|
+
dir.down do
|
27
|
+
add_column :reports, :blacklisted, :boolean, default: false
|
28
|
+
execute <<~SQL
|
29
|
+
update reports set blacklisted = true where blacklist = 'db';
|
30
|
+
SQL
|
31
|
+
remove_column :reports, :blacklist
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -11,6 +11,10 @@ class DashboardServer < Sinatra::Base
|
|
11
11
|
erb :index
|
12
12
|
end
|
13
13
|
|
14
|
+
get '/spammers/:spammer' do
|
15
|
+
erb :spammer
|
16
|
+
end
|
17
|
+
|
14
18
|
get '/api/stats/summary' do
|
15
19
|
blacklist_count = OSA::Blacklist.count
|
16
20
|
total_reported = OSA::Report.count
|
@@ -31,13 +35,34 @@ class DashboardServer < Sinatra::Base
|
|
31
35
|
|
32
36
|
end
|
33
37
|
|
38
|
+
get '/api/stats/spammers/:spammer/summary' do |spammer|
|
39
|
+
reports = OSA::Report.where(sender: spammer).or(OSA::Report.where(sender_domain: spammer))
|
40
|
+
total_reported = reports.count
|
41
|
+
today = DateTime.now.to_date
|
42
|
+
today_reported = reports.where('reported_at > ?', today).count
|
43
|
+
week = DateTime.now - 1.week
|
44
|
+
week_reported = reports.where('reported_at > ?', week).count
|
45
|
+
month = DateTime.now - 30.days
|
46
|
+
month_reported = reports.where('reported_at > ?', month).count
|
47
|
+
domains = reports.select(:sender).distinct.map { |e| e.sender.split('@', 2)[1] }.uniq
|
48
|
+
|
49
|
+
json total_reported: total_reported,
|
50
|
+
today_reported: today_reported,
|
51
|
+
week_reported: week_reported,
|
52
|
+
month_reported: month_reported,
|
53
|
+
domains: domains
|
54
|
+
end
|
55
|
+
|
34
56
|
get '/api/stats/spammers' do
|
35
57
|
spammers = OSA::Report.select('sender_domain as domain', 'COUNT(*) as count').limit(50).order(count: :desc).group(:sender_domain)
|
36
58
|
json spammers
|
37
59
|
end
|
38
60
|
|
39
61
|
get '/api/stats/reports/historical' do
|
40
|
-
historical_data = OSA::Report.select("strftime('%Y-%m-%d', reported_at) as date", 'count(*) as count')
|
41
|
-
|
62
|
+
historical_data = OSA::Report.select("strftime('%Y-%m-%d', reported_at) as date", 'count(*) as count')
|
63
|
+
unless params[:spammer].blank?
|
64
|
+
historical_data = historical_data.where(sender: params[:spammer]).or(OSA::Report.where(sender_domain: params[:spammer]))
|
65
|
+
end
|
66
|
+
json historical_data.group(:date)
|
42
67
|
end
|
43
68
|
end
|
@@ -9,6 +9,18 @@ context = OSA::Context.new
|
|
9
9
|
|
10
10
|
continue = true
|
11
11
|
|
12
|
+
dns_blacklists = OSA::DnsBlacklist.all
|
13
|
+
|
14
|
+
def resolve_blacklist(mail_id, email_address, domain, context, dns_blacklists)
|
15
|
+
return 'db' if OSA::Blacklist.where(value: email_address).or(OSA::Blacklist.where(value: domain)).exists?
|
16
|
+
ip = context.graph_client.sender_ip(mail_id)
|
17
|
+
if ip.nil?
|
18
|
+
puts "Couldn't detect ip for email from #{email_address}"
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
dns_blacklists.find { |bl| bl.blacklisted?(ip) }&.server
|
22
|
+
end
|
23
|
+
|
12
24
|
while continue
|
13
25
|
mails = context.graph_client.mails(context.config.junk_folder_id)
|
14
26
|
continue = false
|
@@ -20,13 +32,12 @@ while continue
|
|
20
32
|
domain = PublicSuffix.domain(email_address.split('@', 2)[1])
|
21
33
|
|
22
34
|
flagged = mail['flag']['flagStatus'] == 'flagged'
|
23
|
-
|
24
|
-
blacklisted = OSA::Blacklist.where(value: email_address).or(OSA::Blacklist.where(value: domain)).exists?
|
35
|
+
blacklist = (resolve_blacklist(mail['id'], email_address, domain, context, dns_blacklists) unless flagged)
|
25
36
|
|
26
37
|
if flagged
|
27
38
|
puts "Email from #{email_address} is flagged, reporting and deleting"
|
28
|
-
elsif
|
29
|
-
puts "#{email_address} is blacklisted, reporting and deleting"
|
39
|
+
elsif !blacklist.nil?
|
40
|
+
puts "#{email_address} is blacklisted by #{blacklist}, reporting and deleting"
|
30
41
|
else
|
31
42
|
puts "Skipping mail from #{email_address}, its not blacklisted"
|
32
43
|
next
|
@@ -39,8 +50,16 @@ while continue
|
|
39
50
|
puts "deleting spam from #{email_address}"
|
40
51
|
context.graph_client.delete_mail(mail['id'])
|
41
52
|
|
42
|
-
|
53
|
+
OSA::Report.create!(sender: email_address,
|
54
|
+
sender_domain: domain,
|
55
|
+
subject: mail['subject'],
|
56
|
+
flagged: flagged,
|
57
|
+
blacklist: blacklist,
|
58
|
+
received_at: Time.iso8601(mail['receivedDateTime']),
|
59
|
+
reported_at: Time.now)
|
43
60
|
|
61
|
+
# Do not add to the blacklist if the it's blacklisted by the db (it's already present)
|
62
|
+
# or blacklisted by DNSBLs (these blacklists are only supposed to be temporary).
|
44
63
|
if flagged
|
45
64
|
is_free_provider = OSA::EmailProvider.where(value: domain).exists?
|
46
65
|
if is_free_provider
|
@@ -51,15 +70,8 @@ while continue
|
|
51
70
|
OSA::Blacklist.find_or_create_by(value: domain).save!
|
52
71
|
end
|
53
72
|
end
|
54
|
-
|
55
|
-
OSA::Report.create!(sender: email_address,
|
56
|
-
sender_domain: domain,
|
57
|
-
subject: mail['subject'],
|
58
|
-
flagged: flagged,
|
59
|
-
blacklisted: blacklisted,
|
60
|
-
received_at: Time.iso8601(mail['receivedDateTime']),
|
61
|
-
reported_at: Time.now)
|
62
73
|
end
|
74
|
+
|
63
75
|
mails = mails.next
|
64
76
|
end
|
65
77
|
end
|
data/lib/osa/util/db.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'active_record'
|
3
|
+
require 'resolv'
|
3
4
|
|
4
5
|
ActiveRecord::Base.establish_connection(
|
5
6
|
adapter: 'sqlite3',
|
@@ -21,4 +22,12 @@ module OSA
|
|
21
22
|
|
22
23
|
class Report < ActiveRecord::Base
|
23
24
|
end
|
25
|
+
|
26
|
+
class DnsBlacklist < ActiveRecord::Base
|
27
|
+
def blacklisted?(ip)
|
28
|
+
::Resolv.getaddress("#{ip}.#{server}") != "0.0.0.0"
|
29
|
+
rescue ::Resolv::ResolvError
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
end
|
24
33
|
end
|
data/lib/osa/version.rb
CHANGED
data/lib/osa/views/index.erb
CHANGED
@@ -138,16 +138,13 @@
|
|
138
138
|
let totalReportCount = 0;
|
139
139
|
|
140
140
|
function sec2time(timeInSeconds) {
|
141
|
-
const pad = function (num, size) {
|
142
|
-
return ('000' + num).slice(size * -1);
|
143
|
-
}
|
144
141
|
const time = parseFloat(timeInSeconds).toFixed(3);
|
145
142
|
const hours = Math.floor(time / 60 / 60);
|
146
143
|
const minutes = Math.floor(time / 60) % 60;
|
147
144
|
|
148
145
|
const units = [{value: hours, unit: 'h'}, {value: minutes, unit: 'm'}].filter((value) => value.value > 0 );
|
149
146
|
|
150
|
-
return units.map((value) => `${
|
147
|
+
return units.map((value) => `${value.value}${value.unit}`).join(' ');
|
151
148
|
}
|
152
149
|
|
153
150
|
async function updateStats() {
|
@@ -194,7 +191,10 @@
|
|
194
191
|
tr.appendChild(indexTd);
|
195
192
|
|
196
193
|
const domainTd = document.createElement("td");
|
197
|
-
|
194
|
+
const domainRef = document.createElement("a");
|
195
|
+
domainRef.setAttribute("href", `/spammers/${encodeURI(spammer['domain'])}`);
|
196
|
+
domainRef.innerText = spammer['domain']
|
197
|
+
domainTd.appendChild(domainRef);
|
198
198
|
tr.appendChild(domainTd);
|
199
199
|
|
200
200
|
const reportCountTd = document.createElement("td");
|
@@ -208,7 +208,7 @@
|
|
208
208
|
tr.appendChild(spamRatioProgressTd);
|
209
209
|
|
210
210
|
const spamRatioTd = document.createElement("td");
|
211
|
-
spamRatioTd.innerText = `${spamRatio}%`;
|
211
|
+
spamRatioTd.innerText = `${spamRatio.toFixed(1)}%`;
|
212
212
|
tr.appendChild(spamRatioTd);
|
213
213
|
|
214
214
|
return tr;
|
data/lib/osa/views/layout.erb
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
<!-- Main Sidebar Container -->
|
14
14
|
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
15
15
|
<!-- Brand Logo -->
|
16
|
-
<a href="
|
16
|
+
<a href="/" class="brand-link">
|
17
17
|
<span class="brand-text font-weight-light">Outlook Spam Automator</span>
|
18
18
|
</a>
|
19
19
|
|
@@ -24,8 +24,7 @@
|
|
24
24
|
<nav class="mt-2">
|
25
25
|
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
26
26
|
<li class="nav-item">
|
27
|
-
<a href="
|
28
|
-
<i class="nav-icon fas fa-th"></i>
|
27
|
+
<a href="/" class="nav-link">
|
29
28
|
<p>Overview</p>
|
30
29
|
</a>
|
31
30
|
</li>
|
@@ -38,16 +37,8 @@
|
|
38
37
|
|
39
38
|
<%= yield %>
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
<!-- To the right -->
|
44
|
-
<div class="float-right d-none d-sm-inline">
|
45
|
-
Anything you want
|
46
|
-
</div>
|
47
|
-
<!-- Default to the left -->
|
48
|
-
<strong>Copyright © 2014-2020 <a href="https://adminlte.io">AdminLTE.io</a>.</strong> All rights reserved.
|
49
|
-
</footer>
|
50
|
-
<div id="sidebar-overlay"></div></div>
|
40
|
+
<div id="sidebar-overlay"></div>
|
41
|
+
</div>
|
51
42
|
<!-- ./wrapper -->
|
52
43
|
|
53
44
|
</body>
|
@@ -0,0 +1,209 @@
|
|
1
|
+
<!-- Content Wrapper. Contains page content -->
|
2
|
+
<div class="content-wrapper" style="min-height: 1233px;">
|
3
|
+
<!-- Content Header (Page header) -->
|
4
|
+
<div class="content-header">
|
5
|
+
<div class="container-fluid">
|
6
|
+
<div class="row mb-2">
|
7
|
+
<div class="col-sm-6">
|
8
|
+
<h1 id="title" class="m-0">Spammer Details</h1>
|
9
|
+
</div><!-- /.col -->
|
10
|
+
</div><!-- /.row -->
|
11
|
+
</div><!-- /.container-fluid -->
|
12
|
+
</div>
|
13
|
+
<!-- /.content-header -->
|
14
|
+
|
15
|
+
<!-- Main content -->
|
16
|
+
<div class="content">
|
17
|
+
<div class="container-fluid">
|
18
|
+
<div class="row">
|
19
|
+
<!-- ./col -->
|
20
|
+
<div class="col-lg">
|
21
|
+
<!-- small card -->
|
22
|
+
<div class="small-box bg-success">
|
23
|
+
<div class="inner">
|
24
|
+
<h3 id="today-count"></h3>
|
25
|
+
<p>emails reported today</p>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<!-- ./col -->
|
30
|
+
<div class="col-lg">
|
31
|
+
<!-- small card -->
|
32
|
+
<div class="small-box bg-success">
|
33
|
+
<div class="inner">
|
34
|
+
<h3 id="week-count"></h3>
|
35
|
+
<p>emails reported in the last 7 days</p>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<!-- ./col -->
|
40
|
+
<div class="col-lg">
|
41
|
+
<!-- small card -->
|
42
|
+
<div class="small-box bg-success">
|
43
|
+
<div class="inner">
|
44
|
+
<h3 id="month-count"></h3>
|
45
|
+
<p>emails reported in the last 30 days</p>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
<!-- ./col -->
|
50
|
+
<div class="col-lg">
|
51
|
+
<!-- small card -->
|
52
|
+
<div class="small-box bg-warning">
|
53
|
+
<div class="inner">
|
54
|
+
<h3 id="total-count"></h3>
|
55
|
+
<p>emails reported in total</p>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
<!-- ./col -->
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div class="row">
|
63
|
+
<div class="card">
|
64
|
+
<div class="card-header">
|
65
|
+
<h3 class="card-title">Spams reported</h3>
|
66
|
+
</div>
|
67
|
+
<!-- /.card-header -->
|
68
|
+
<div class="card-body">
|
69
|
+
<div class="chart">
|
70
|
+
<div class="chartjs-size-monitor">
|
71
|
+
<div class="chartjs-size-monitor-expand">
|
72
|
+
<div class=""></div>
|
73
|
+
</div>
|
74
|
+
<div class="chartjs-size-monitor-shrink">
|
75
|
+
<div class=""></div>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
<canvas id="report-history-chart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%; display: block; width: 1732px;" width="1732" height="250" class="chartjs-render-monitor"></canvas>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
<!-- /.card-body -->
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<div class="row">
|
86
|
+
<div class="card">
|
87
|
+
<div class="card-header">
|
88
|
+
<h3 class="card-title">Domains</h3>
|
89
|
+
</div>
|
90
|
+
<!-- /.card-header -->
|
91
|
+
<div class="card-body p-0">
|
92
|
+
<table class="table table-striped">
|
93
|
+
<thead>
|
94
|
+
</thead>
|
95
|
+
<tbody id="domains-table-body">
|
96
|
+
</tbody>
|
97
|
+
</table>
|
98
|
+
</div>
|
99
|
+
<!-- /.card-body -->
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
|
103
|
+
|
104
|
+
</div> <!-- /.container-fluid -->
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
<!-- /.content -->
|
108
|
+
</div>
|
109
|
+
<!-- /.content-wrapper -->
|
110
|
+
|
111
|
+
<script>
|
112
|
+
const pathComponents = window.location.pathname.split('/');
|
113
|
+
const spammer = pathComponents[pathComponents.length - 1];
|
114
|
+
document.getElementById("title").innerText = `${spammer} Details`;
|
115
|
+
|
116
|
+
async function updateStats() {
|
117
|
+
const response = await fetch(`/api/stats/spammers/${spammer}/summary`);
|
118
|
+
if (response.ok) {
|
119
|
+
const body = await response.json();
|
120
|
+
document.getElementById('today-count').innerText = body['today_reported'];
|
121
|
+
document.getElementById('week-count').innerText = body['week_reported'];
|
122
|
+
document.getElementById('month-count').innerText = body['month_reported'];
|
123
|
+
document.getElementById('total-count').innerText = body['total_reported'];
|
124
|
+
totalReportCount = body['total_reported'];
|
125
|
+
|
126
|
+
const tableBody = document.getElementById("domains-table-body");
|
127
|
+
tableBody.innerHTML = "";
|
128
|
+
|
129
|
+
body['domains'].forEach((domain) => {
|
130
|
+
const tr = document.createElement("tr");
|
131
|
+
const td = document.createElement("td");
|
132
|
+
td.innerText = domain;
|
133
|
+
tr.appendChild(td);
|
134
|
+
tableBody.appendChild(tr);
|
135
|
+
});
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
function createHistoricalChart() {
|
140
|
+
const options = {
|
141
|
+
maintainAspectRatio: false,
|
142
|
+
responsive: true,
|
143
|
+
legend: {
|
144
|
+
display: false
|
145
|
+
},
|
146
|
+
scales: {
|
147
|
+
xAxes: [{
|
148
|
+
gridLines: {
|
149
|
+
display: false,
|
150
|
+
}
|
151
|
+
}],
|
152
|
+
yAxes: [{
|
153
|
+
gridLines: {
|
154
|
+
display: false,
|
155
|
+
}
|
156
|
+
}]
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
const chartData = {
|
161
|
+
labels: [],
|
162
|
+
datasets: [
|
163
|
+
{
|
164
|
+
label: 'Spams reported',
|
165
|
+
backgroundColor: 'rgba(60,141,188,0.9)',
|
166
|
+
borderColor: 'rgba(60,141,188,0.8)',
|
167
|
+
pointColor: '#3b8bba',
|
168
|
+
pointStrokeColor: 'rgba(60,141,188,1)',
|
169
|
+
pointHighlightFill: '#fff',
|
170
|
+
pointHighlightStroke: 'rgba(60,141,188,1)',
|
171
|
+
data: []
|
172
|
+
}
|
173
|
+
]
|
174
|
+
}
|
175
|
+
const ctx = document.getElementById('report-history-chart').getContext('2d');
|
176
|
+
|
177
|
+
return new Chart(ctx, {
|
178
|
+
type: 'line',
|
179
|
+
options: options,
|
180
|
+
data: chartData
|
181
|
+
});
|
182
|
+
}
|
183
|
+
|
184
|
+
const historicalChart = createHistoricalChart();
|
185
|
+
|
186
|
+
async function updateHistoricalChart() {
|
187
|
+
const response = await fetch(`/api/stats/reports/historical?spammer=${spammer}`);
|
188
|
+
if (response.ok) {
|
189
|
+
const body = await response.json();
|
190
|
+
|
191
|
+
const labels = body.map((data) => data['date']);
|
192
|
+
const values = body.map((data) => data['count']);
|
193
|
+
|
194
|
+
historicalChart.data.labels = labels
|
195
|
+
historicalChart.data.datasets[0].data = values
|
196
|
+
historicalChart.update();
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
function update() {
|
201
|
+
updateStats()
|
202
|
+
.then(updateHistoricalChart)
|
203
|
+
.then(() => {
|
204
|
+
setTimeout(update, 10 * 60 * 1000);
|
205
|
+
})
|
206
|
+
}
|
207
|
+
|
208
|
+
update();
|
209
|
+
</script>
|
data/osa.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: osa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moray Baruh
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 2.1.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mail
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.7.1
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.7.1
|
111
125
|
description: Get rid of spam on your Outlook account
|
112
126
|
email:
|
113
127
|
- contact@moraybaruh.com
|
@@ -137,6 +151,7 @@ files:
|
|
137
151
|
- lib/osa/migrations/00002_create_email_providers.rb
|
138
152
|
- lib/osa/migrations/00003_create_config.rb
|
139
153
|
- lib/osa/migrations/00004_create_reports.rb
|
154
|
+
- lib/osa/migrations/00005_create_dns_blacklists.rb
|
140
155
|
- lib/osa/migrations/free-email-providers.txt
|
141
156
|
- lib/osa/scripts/dashboard_server.rb
|
142
157
|
- lib/osa/scripts/scan_junk_folder.rb
|
@@ -149,6 +164,7 @@ files:
|
|
149
164
|
- lib/osa/version.rb
|
150
165
|
- lib/osa/views/index.erb
|
151
166
|
- lib/osa/views/layout.erb
|
167
|
+
- lib/osa/views/spammer.erb
|
152
168
|
- osa.gemspec
|
153
169
|
- release.sh
|
154
170
|
- web/login.html
|
@@ -171,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
187
|
- !ruby/object:Gem::Version
|
172
188
|
version: '0'
|
173
189
|
requirements: []
|
174
|
-
rubygems_version: 3.
|
190
|
+
rubygems_version: 3.1.4
|
175
191
|
signing_key:
|
176
192
|
specification_version: 4
|
177
193
|
summary: Outlook Spam Automator
|