osa 0.1.3 → 0.2.0
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 +33 -13
- data/README.md +9 -0
- data/exe/osa +4 -1
- data/lib/osa/migrations/00004_create_reports.rb +16 -0
- data/lib/osa/scripts/dashboard_server.rb +43 -0
- data/lib/osa/scripts/scan_junk_folder.rb +11 -3
- data/lib/osa/util/db.rb +3 -0
- data/lib/osa/version.rb +1 -1
- data/lib/osa/views/index.erb +296 -0
- data/lib/osa/views/layout.erb +55 -0
- data/osa.gemspec +2 -0
- metadata +35 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c74cb930be462e745a7682afed8e2715e10bfb391f188d5f0f885c3b8150d435
|
4
|
+
data.tar.gz: e41a5966566a34b0f48f5eb40c9df7742fe56a8aa75299ce5db1cd0e64aeaa76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a7fd8af344acc7f7839f4b9165cc39c913a9edca0e94155a68b231d773b918da69637a1c79dcd292654adf8248f30991c630f31b03588641ff1a9ca9e9bdc85
|
7
|
+
data.tar.gz: 9cc8eab12bacc5010f712ed0dbb5a2bbdf056c16ec97b98e1a90bcb93e9eede72af274bb91379ef3552a11022894dd2c4da0b853d843e43a9f2e6658e086e7bb
|
data/Gemfile.lock
CHANGED
@@ -1,44 +1,52 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
osa (0.
|
4
|
+
osa (0.2.0)
|
5
5
|
activerecord (~> 6.0)
|
6
6
|
faraday (~> 1.1)
|
7
7
|
public_suffix (~> 4.0)
|
8
|
+
sinatra (~> 2.1.0)
|
9
|
+
sinatra-contrib (~> 2.1.0)
|
8
10
|
sqlite3 (~> 1.4)
|
9
11
|
tty-prompt (~> 0.22)
|
10
12
|
|
11
13
|
GEM
|
12
14
|
remote: https://rubygems.org/
|
13
15
|
specs:
|
14
|
-
activemodel (6.1.
|
15
|
-
activesupport (= 6.1.
|
16
|
-
activerecord (6.1.
|
17
|
-
activemodel (= 6.1.
|
18
|
-
activesupport (= 6.1.
|
19
|
-
activesupport (6.1.
|
16
|
+
activemodel (6.1.2.1)
|
17
|
+
activesupport (= 6.1.2.1)
|
18
|
+
activerecord (6.1.2.1)
|
19
|
+
activemodel (= 6.1.2.1)
|
20
|
+
activesupport (= 6.1.2.1)
|
21
|
+
activesupport (6.1.2.1)
|
20
22
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
23
|
i18n (>= 1.6, < 2)
|
22
24
|
minitest (>= 5.1)
|
23
25
|
tzinfo (~> 2.0)
|
24
26
|
zeitwerk (~> 2.3)
|
25
27
|
ast (2.4.1)
|
26
|
-
concurrent-ruby (1.1.
|
28
|
+
concurrent-ruby (1.1.8)
|
27
29
|
faraday (1.3.0)
|
28
30
|
faraday-net_http (~> 1.0)
|
29
31
|
multipart-post (>= 1.2, < 3)
|
30
32
|
ruby2_keywords
|
31
|
-
faraday-net_http (1.0.
|
32
|
-
i18n (1.8.
|
33
|
+
faraday-net_http (1.0.1)
|
34
|
+
i18n (1.8.9)
|
33
35
|
concurrent-ruby (~> 1.0)
|
34
|
-
minitest (5.14.
|
36
|
+
minitest (5.14.3)
|
37
|
+
multi_json (1.15.0)
|
35
38
|
multipart-post (2.1.1)
|
39
|
+
mustermann (1.1.1)
|
40
|
+
ruby2_keywords (~> 0.0.1)
|
36
41
|
parallel (1.20.0)
|
37
42
|
parser (2.7.2.0)
|
38
43
|
ast (~> 2.4.1)
|
39
44
|
pastel (0.8.0)
|
40
45
|
tty-color (~> 0.5)
|
41
46
|
public_suffix (4.0.6)
|
47
|
+
rack (2.2.3)
|
48
|
+
rack-protection (2.1.0)
|
49
|
+
rack
|
42
50
|
rainbow (3.0.0)
|
43
51
|
regexp_parser (1.8.2)
|
44
52
|
rexml (3.2.4)
|
@@ -57,8 +65,20 @@ GEM
|
|
57
65
|
rubocop (>= 0.90.0, < 2.0)
|
58
66
|
rubocop-ast (>= 0.4.0)
|
59
67
|
ruby-progressbar (1.10.1)
|
60
|
-
ruby2_keywords (0.0.
|
68
|
+
ruby2_keywords (0.0.4)
|
69
|
+
sinatra (2.1.0)
|
70
|
+
mustermann (~> 1.0)
|
71
|
+
rack (~> 2.2)
|
72
|
+
rack-protection (= 2.1.0)
|
73
|
+
tilt (~> 2.0)
|
74
|
+
sinatra-contrib (2.1.0)
|
75
|
+
multi_json
|
76
|
+
mustermann (~> 1.0)
|
77
|
+
rack-protection (= 2.1.0)
|
78
|
+
sinatra (= 2.1.0)
|
79
|
+
tilt (~> 2.0)
|
61
80
|
sqlite3 (1.4.2)
|
81
|
+
tilt (2.0.10)
|
62
82
|
tty-color (0.6.0)
|
63
83
|
tty-cursor (0.7.1)
|
64
84
|
tty-prompt (0.23.0)
|
@@ -84,4 +104,4 @@ DEPENDENCIES
|
|
84
104
|
rubocop-performance
|
85
105
|
|
86
106
|
BUNDLED WITH
|
87
|
-
2.1.
|
107
|
+
2.1.4
|
data/README.md
CHANGED
@@ -65,6 +65,15 @@ Process your junk folder:
|
|
65
65
|
osa scan-junk
|
66
66
|
```
|
67
67
|
|
68
|
+
OSA also provides you a nice administration dashboard you. You can access the dashboard by running
|
69
|
+
|
70
|
+
```sh
|
71
|
+
osa dashboard
|
72
|
+
```
|
73
|
+
|
74
|
+
You are now able to access the dashboard on `http://localhost:8080`. You can also change the port of the server by
|
75
|
+
providing the `SERVER_PORT` environment variable.
|
76
|
+
|
68
77
|
## Contributing
|
69
78
|
|
70
79
|
Bug reports and pull requests are welcome on GitHub at https://github.com/moray95/osa.
|
data/exe/osa
CHANGED
@@ -12,7 +12,10 @@ when 'login'
|
|
12
12
|
OSA::AuthService.login(OSA::Config.first || OSA::Config.new)
|
13
13
|
when 'scan-junk'
|
14
14
|
require 'osa/scripts/scan_junk_folder'
|
15
|
+
when 'dashboard'
|
16
|
+
require 'osa/scripts/dashboard_server'
|
17
|
+
DashboardServer.start!
|
15
18
|
else
|
16
|
-
$stderr.puts "Usage: #{File.basename($0)} [setup|login|scan-junk]"
|
19
|
+
$stderr.puts "Usage: #{File.basename($0)} [setup|login|scan-junk|dashboard]"
|
17
20
|
exit 1
|
18
21
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_record/migration'
|
3
|
+
|
4
|
+
class CreateReports < ActiveRecord::Migration[5.0]
|
5
|
+
def change
|
6
|
+
create_table :reports do |t|
|
7
|
+
t.text :sender, null: false
|
8
|
+
t.text :sender_domain, null: false
|
9
|
+
t.text :subject, null: false
|
10
|
+
t.boolean :flagged, null: false
|
11
|
+
t.boolean :blacklisted, null: false
|
12
|
+
t.datetime :received_at, null: false
|
13
|
+
t.datetime :reported_at, null: false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sinatra/json'
|
4
|
+
require 'osa/util/db'
|
5
|
+
|
6
|
+
class DashboardServer < Sinatra::Base
|
7
|
+
set :views, File.absolute_path(File.dirname(__FILE__) + '/../views')
|
8
|
+
set :port, ENV['SERVER_PORT'] || 8080
|
9
|
+
|
10
|
+
get '/' do
|
11
|
+
erb :index
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/api/stats/summary' do
|
15
|
+
blacklist_count = OSA::Blacklist.count
|
16
|
+
total_reported = OSA::Report.count
|
17
|
+
today = DateTime.now.to_date
|
18
|
+
today_reported = OSA::Report.where('reported_at > ?', today).count
|
19
|
+
week = DateTime.now - 1.week
|
20
|
+
week_reported = OSA::Report.where('reported_at > ?', week).count
|
21
|
+
month = DateTime.now - 30.days
|
22
|
+
month_reported = OSA::Report.where('reported_at > ?', month).count
|
23
|
+
mean_report_time = OSA::Report.select('avg(julianday(reported_at) - julianday(received_at)) * 86400.0 as avg').first['avg']
|
24
|
+
|
25
|
+
json blacklist_count: blacklist_count,
|
26
|
+
total_reported: total_reported,
|
27
|
+
today_reported: today_reported,
|
28
|
+
week_reported: week_reported,
|
29
|
+
month_reported: month_reported,
|
30
|
+
mean_report_time: mean_report_time
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
get '/api/stats/spammers' do
|
35
|
+
spammers = OSA::Report.select('sender_domain as domain', 'COUNT(*) as count').limit(50).order(count: :desc).group(:sender_domain)
|
36
|
+
json spammers
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/api/stats/reports/historical' do
|
40
|
+
historical_data = OSA::Report.select("strftime('%Y-%m-%d', reported_at) as date", 'count(*) as count').group(:date)
|
41
|
+
json historical_data
|
42
|
+
end
|
43
|
+
end
|
@@ -21,7 +21,7 @@ while continue
|
|
21
21
|
|
22
22
|
flagged = mail['flag']['flagStatus'] == 'flagged'
|
23
23
|
blacklisted = nil
|
24
|
-
blacklisted = OSA::Blacklist.where(value: email_address).or(OSA::Blacklist.where(value: domain)).exists?
|
24
|
+
blacklisted = OSA::Blacklist.where(value: email_address).or(OSA::Blacklist.where(value: domain)).exists?
|
25
25
|
|
26
26
|
if flagged
|
27
27
|
puts "Email from #{email_address} is flagged, reporting and deleting"
|
@@ -39,9 +39,9 @@ while continue
|
|
39
39
|
puts "deleting spam from #{email_address}"
|
40
40
|
context.graph_client.delete_mail(mail['id'])
|
41
41
|
|
42
|
-
|
43
|
-
domain = PublicSuffix.domain(email_address.split('@', 2)[1])
|
42
|
+
domain = PublicSuffix.domain(email_address.split('@', 2)[1])
|
44
43
|
|
44
|
+
if flagged
|
45
45
|
is_free_provider = OSA::EmailProvider.where(value: domain).exists?
|
46
46
|
if is_free_provider
|
47
47
|
puts "#{email_address} is using a free provider, blacklisting full address"
|
@@ -51,6 +51,14 @@ while continue
|
|
51
51
|
OSA::Blacklist.find_or_create_by(value: domain).save!
|
52
52
|
end
|
53
53
|
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)
|
54
62
|
end
|
55
63
|
mails = mails.next
|
56
64
|
end
|
data/lib/osa/util/db.rb
CHANGED
data/lib/osa/version.rb
CHANGED
@@ -0,0 +1,296 @@
|
|
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 class="m-0">Overview</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
|
+
<div class="col-lg">
|
20
|
+
<!-- small card -->
|
21
|
+
<div class="small-box bg-info">
|
22
|
+
<div class="inner">
|
23
|
+
<h3 id="blacklist-count"></h3>
|
24
|
+
<p>entries in blacklist</p>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
<div class="col-lg">
|
29
|
+
<!-- small card -->
|
30
|
+
<div class="small-box bg-info">
|
31
|
+
<div class="inner">
|
32
|
+
<h3 id="reporting-time"></h3>
|
33
|
+
<p>average reporting time</p>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<!-- ./col -->
|
38
|
+
<div class="col-lg">
|
39
|
+
<!-- small card -->
|
40
|
+
<div class="small-box bg-success">
|
41
|
+
<div class="inner">
|
42
|
+
<h3 id="today-count"></h3>
|
43
|
+
<p>emails reported today</p>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
<!-- ./col -->
|
48
|
+
<div class="col-lg">
|
49
|
+
<!-- small card -->
|
50
|
+
<div class="small-box bg-success">
|
51
|
+
<div class="inner">
|
52
|
+
<h3 id="week-count"></h3>
|
53
|
+
<p>emails reported in the last 7 days</p>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
<!-- ./col -->
|
58
|
+
<div class="col-lg">
|
59
|
+
<!-- small card -->
|
60
|
+
<div class="small-box bg-success">
|
61
|
+
<div class="inner">
|
62
|
+
<h3 id="month-count"></h3>
|
63
|
+
<p>emails reported in the last 30 days</p>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
<!-- ./col -->
|
68
|
+
<div class="col-lg">
|
69
|
+
<!-- small card -->
|
70
|
+
<div class="small-box bg-warning">
|
71
|
+
<div class="inner">
|
72
|
+
<h3 id="total-count"></h3>
|
73
|
+
<p>emails reported in total</p>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
<!-- ./col -->
|
78
|
+
</div>
|
79
|
+
|
80
|
+
<div class="row">
|
81
|
+
<div class="card">
|
82
|
+
<div class="card-header">
|
83
|
+
<h3 class="card-title">Spams reported</h3>
|
84
|
+
</div>
|
85
|
+
<!-- /.card-header -->
|
86
|
+
<div class="card-body">
|
87
|
+
<div class="chart">
|
88
|
+
<div class="chartjs-size-monitor">
|
89
|
+
<div class="chartjs-size-monitor-expand">
|
90
|
+
<div class=""></div>
|
91
|
+
</div>
|
92
|
+
<div class="chartjs-size-monitor-shrink">
|
93
|
+
<div class=""></div>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
<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>
|
97
|
+
</div>
|
98
|
+
</div>
|
99
|
+
<!-- /.card-body -->
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
|
103
|
+
<div class="row">
|
104
|
+
<div class="card">
|
105
|
+
<div class="card-header">
|
106
|
+
<h3 class="card-title">Top spammers</h3>
|
107
|
+
</div>
|
108
|
+
<!-- /.card-header -->
|
109
|
+
<div class="card-body p-0">
|
110
|
+
<table class="table table-striped">
|
111
|
+
<thead>
|
112
|
+
<tr>
|
113
|
+
<th style="width: 10px">#</th>
|
114
|
+
<th>Domain</th>
|
115
|
+
<th>Emails reported</th>
|
116
|
+
<th>Spam ratio</th>
|
117
|
+
<th style="width: 40px"></th>
|
118
|
+
</tr>
|
119
|
+
</thead>
|
120
|
+
<tbody id="top-spammers-table-body">
|
121
|
+
</tbody>
|
122
|
+
</table>
|
123
|
+
</div>
|
124
|
+
<!-- /.card-body -->
|
125
|
+
</div>
|
126
|
+
</div>
|
127
|
+
|
128
|
+
|
129
|
+
</div> <!-- /.container-fluid -->
|
130
|
+
|
131
|
+
|
132
|
+
</div>
|
133
|
+
<!-- /.content -->
|
134
|
+
</div>
|
135
|
+
<!-- /.content-wrapper -->
|
136
|
+
|
137
|
+
<script>
|
138
|
+
let totalReportCount = 0;
|
139
|
+
|
140
|
+
function sec2time(timeInSeconds) {
|
141
|
+
const pad = function (num, size) {
|
142
|
+
return ('000' + num).slice(size * -1);
|
143
|
+
}
|
144
|
+
const time = parseFloat(timeInSeconds).toFixed(3);
|
145
|
+
const hours = Math.floor(time / 60 / 60);
|
146
|
+
const minutes = Math.floor(time / 60) % 60;
|
147
|
+
|
148
|
+
const units = [{value: hours, unit: 'h'}, {value: minutes, unit: 'm'}].filter((value) => value.value > 0 );
|
149
|
+
|
150
|
+
return units.map((value) => `${pad(value.value, 2)}${value.unit}`).join(' ');
|
151
|
+
}
|
152
|
+
|
153
|
+
async function updateStats() {
|
154
|
+
const response = await fetch('/api/stats/summary');
|
155
|
+
if (response.ok) {
|
156
|
+
const body = await response.json();
|
157
|
+
document.getElementById('blacklist-count').innerText = body['blacklist_count'];
|
158
|
+
document.getElementById('today-count').innerText = body['today_reported'];
|
159
|
+
document.getElementById('week-count').innerText = body['week_reported'];
|
160
|
+
document.getElementById('month-count').innerText = body['month_reported'];
|
161
|
+
document.getElementById('total-count').innerText = body['total_reported'];
|
162
|
+
const meanReportTime = body['mean_report_time'];
|
163
|
+
if (meanReportTime !== null) {
|
164
|
+
document.getElementById('reporting-time').innerText = sec2time(meanReportTime);
|
165
|
+
} else {
|
166
|
+
document.getElementById('reporting-time').innerText = 'N/A';
|
167
|
+
}
|
168
|
+
totalReportCount = body['total_reported'];
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
function createProgressDiv(fillRatio) {
|
173
|
+
const progressDiv = document.createElement("div");
|
174
|
+
progressDiv.className = "progress progress-xs";
|
175
|
+
|
176
|
+
const progressBarDiv = document.createElement("div");
|
177
|
+
progressBarDiv.className = "progress-bar progress-bar-danger";
|
178
|
+
progressBarDiv.style.width = `${fillRatio}%`;
|
179
|
+
|
180
|
+
progressDiv.appendChild(progressBarDiv);
|
181
|
+
return progressDiv;
|
182
|
+
}
|
183
|
+
|
184
|
+
async function updateSpammers() {
|
185
|
+
const response = await fetch('/api/stats/spammers');
|
186
|
+
if (response.ok) {
|
187
|
+
const body = await response.json();
|
188
|
+
|
189
|
+
const table = body.map((spammer, index) => {
|
190
|
+
const tr = document.createElement("tr");
|
191
|
+
|
192
|
+
const indexTd = document.createElement("td");
|
193
|
+
indexTd.innerText = index + 1;
|
194
|
+
tr.appendChild(indexTd);
|
195
|
+
|
196
|
+
const domainTd = document.createElement("td");
|
197
|
+
domainTd.innerText = spammer['domain'];
|
198
|
+
tr.appendChild(domainTd);
|
199
|
+
|
200
|
+
const reportCountTd = document.createElement("td");
|
201
|
+
reportCountTd.innerText = spammer['count'];
|
202
|
+
tr.appendChild(reportCountTd);
|
203
|
+
|
204
|
+
const spamRatio = spammer['count'] / totalReportCount * 100;
|
205
|
+
|
206
|
+
const spamRatioProgressTd = document.createElement("td");
|
207
|
+
spamRatioProgressTd.append(createProgressDiv(spamRatio));
|
208
|
+
tr.appendChild(spamRatioProgressTd);
|
209
|
+
|
210
|
+
const spamRatioTd = document.createElement("td");
|
211
|
+
spamRatioTd.innerText = `${spamRatio}%`;
|
212
|
+
tr.appendChild(spamRatioTd);
|
213
|
+
|
214
|
+
return tr;
|
215
|
+
});
|
216
|
+
const tableBody = document.getElementById("top-spammers-table-body")
|
217
|
+
tableBody.innerHTML = "";
|
218
|
+
table.forEach((row) => {
|
219
|
+
tableBody.appendChild(row);
|
220
|
+
});
|
221
|
+
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
function createHistoricalChart() {
|
226
|
+
const options = {
|
227
|
+
maintainAspectRatio: false,
|
228
|
+
responsive: true,
|
229
|
+
legend: {
|
230
|
+
display: false
|
231
|
+
},
|
232
|
+
scales: {
|
233
|
+
xAxes: [{
|
234
|
+
gridLines: {
|
235
|
+
display: false,
|
236
|
+
}
|
237
|
+
}],
|
238
|
+
yAxes: [{
|
239
|
+
gridLines: {
|
240
|
+
display: false,
|
241
|
+
}
|
242
|
+
}]
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
const chartData = {
|
247
|
+
labels: [],
|
248
|
+
datasets: [
|
249
|
+
{
|
250
|
+
label: 'Spams reported',
|
251
|
+
backgroundColor: 'rgba(60,141,188,0.9)',
|
252
|
+
borderColor: 'rgba(60,141,188,0.8)',
|
253
|
+
pointColor: '#3b8bba',
|
254
|
+
pointStrokeColor: 'rgba(60,141,188,1)',
|
255
|
+
pointHighlightFill: '#fff',
|
256
|
+
pointHighlightStroke: 'rgba(60,141,188,1)',
|
257
|
+
data: []
|
258
|
+
}
|
259
|
+
]
|
260
|
+
}
|
261
|
+
const ctx = document.getElementById('report-history-chart').getContext('2d');
|
262
|
+
|
263
|
+
return new Chart(ctx, {
|
264
|
+
type: 'line',
|
265
|
+
options: options,
|
266
|
+
data: chartData
|
267
|
+
});
|
268
|
+
}
|
269
|
+
|
270
|
+
const historicalChart = createHistoricalChart();
|
271
|
+
|
272
|
+
async function updateHistoricalChart() {
|
273
|
+
const response = await fetch("/api/stats/reports/historical");
|
274
|
+
if (response.ok) {
|
275
|
+
const body = await response.json();
|
276
|
+
|
277
|
+
const labels = body.map((data) => data['date']);
|
278
|
+
const values = body.map((data) => data['count']);
|
279
|
+
|
280
|
+
historicalChart.data.labels = labels
|
281
|
+
historicalChart.data.datasets[0].data = values
|
282
|
+
historicalChart.update();
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
function update() {
|
287
|
+
updateStats()
|
288
|
+
.then(updateSpammers)
|
289
|
+
.then(updateHistoricalChart)
|
290
|
+
.then(() => {
|
291
|
+
setTimeout(update, 10 * 60 * 1000);
|
292
|
+
})
|
293
|
+
}
|
294
|
+
|
295
|
+
update();
|
296
|
+
</script>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Outlook Spam Automator Admin Panel</title>
|
6
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
7
|
+
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
8
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js" integrity="sha512-d9xgZrVZpmmQlfonhQUvTR7lMPtO7NkZMkA0ABN3PHCbKA5nqylQ/yWlFAyY6hYgdF1Qh6nYiuADWwKB4C2WSw==" crossorigin="anonymous"></script>
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.0/dist/js/adminlte.min.js" crossorigin="anonymous"></script>
|
10
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.0/dist/css/adminlte.min.css">
|
11
|
+
<body class="sidebar-mini" style="height: auto;">
|
12
|
+
<div class="wrapper">
|
13
|
+
<!-- Main Sidebar Container -->
|
14
|
+
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
15
|
+
<!-- Brand Logo -->
|
16
|
+
<a href="index.html" class="brand-link">
|
17
|
+
<span class="brand-text font-weight-light">Outlook Spam Automator</span>
|
18
|
+
</a>
|
19
|
+
|
20
|
+
<!-- Sidebar -->
|
21
|
+
<div class="sidebar">
|
22
|
+
|
23
|
+
<!-- Sidebar Menu -->
|
24
|
+
<nav class="mt-2">
|
25
|
+
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
26
|
+
<li class="nav-item">
|
27
|
+
<a href="#" class="nav-link">
|
28
|
+
<i class="nav-icon fas fa-th"></i>
|
29
|
+
<p>Overview</p>
|
30
|
+
</a>
|
31
|
+
</li>
|
32
|
+
</ul>
|
33
|
+
</nav>
|
34
|
+
<!-- /.sidebar-menu -->
|
35
|
+
</div>
|
36
|
+
<!-- /.sidebar -->
|
37
|
+
</aside>
|
38
|
+
|
39
|
+
<%= yield %>
|
40
|
+
|
41
|
+
<!-- Main Footer -->
|
42
|
+
<footer class="main-footer">
|
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>
|
51
|
+
<!-- ./wrapper -->
|
52
|
+
|
53
|
+
</body>
|
54
|
+
|
55
|
+
</html>
|
data/osa.gemspec
CHANGED
@@ -26,4 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_dependency 'public_suffix', '~> 4.0'
|
27
27
|
spec.add_dependency 'sqlite3', '~> 1.4'
|
28
28
|
spec.add_dependency 'tty-prompt', '~> 0.22'
|
29
|
+
spec.add_dependency 'sinatra', '~> 2.1.0'
|
30
|
+
spec.add_dependency 'sinatra-contrib', '~> 2.1.0'
|
29
31
|
end
|
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.
|
4
|
+
version: 0.2.0
|
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-
|
11
|
+
date: 2021-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.22'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sinatra
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sinatra-contrib
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.1.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.1.0
|
83
111
|
description: Get rid of spam on your Outlook account
|
84
112
|
email:
|
85
113
|
- contact@moraybaruh.com
|
@@ -108,7 +136,9 @@ files:
|
|
108
136
|
- lib/osa/migrations/00001_create_blacklists.rb
|
109
137
|
- lib/osa/migrations/00002_create_email_providers.rb
|
110
138
|
- lib/osa/migrations/00003_create_config.rb
|
139
|
+
- lib/osa/migrations/00004_create_reports.rb
|
111
140
|
- lib/osa/migrations/free-email-providers.txt
|
141
|
+
- lib/osa/scripts/dashboard_server.rb
|
112
142
|
- lib/osa/scripts/scan_junk_folder.rb
|
113
143
|
- lib/osa/services/auth_service.rb
|
114
144
|
- lib/osa/services/setup_service.rb
|
@@ -117,6 +147,8 @@ files:
|
|
117
147
|
- lib/osa/util/db.rb
|
118
148
|
- lib/osa/util/paginated.rb
|
119
149
|
- lib/osa/version.rb
|
150
|
+
- lib/osa/views/index.erb
|
151
|
+
- lib/osa/views/layout.erb
|
120
152
|
- osa.gemspec
|
121
153
|
- release.sh
|
122
154
|
- web/login.html
|
@@ -139,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
171
|
- !ruby/object:Gem::Version
|
140
172
|
version: '0'
|
141
173
|
requirements: []
|
142
|
-
rubygems_version: 3.
|
174
|
+
rubygems_version: 3.2.3
|
143
175
|
signing_key:
|
144
176
|
specification_version: 4
|
145
177
|
summary: Outlook Spam Automator
|