log_sense 1.9.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -81,6 +81,197 @@ module LogSense
81
81
  }
82
82
  end
83
83
 
84
+ #
85
+ # Reports shared between rails and apache/nginx
86
+ #
87
+
88
+ def time_distribution(data, header: %w[Hour Hits], column_alignment: %i[left right], color: "#d30001")
89
+ {
90
+ title: "Time Distribution",
91
+ header:,
92
+ column_alignment:,
93
+ rows: data[:time_distribution],
94
+ echarts_spec: "{
95
+ xAxis: {
96
+ type: 'category',
97
+ data: SERIES_DATA.map(row => row['Hour'])
98
+ },
99
+ yAxis: {
100
+ type: 'value'
101
+ },
102
+ tooltip: {
103
+ trigger: 'axis'
104
+ },
105
+ series: [
106
+ {
107
+ data: SERIES_DATA.map(row => row['Hits']),
108
+ type: 'bar',
109
+ color: '#{color}',
110
+ label: {
111
+ show: true,
112
+ position: 'top'
113
+ },
114
+ }
115
+ ]
116
+ }",
117
+ }
118
+ end
119
+
120
+ def browsers(data, header: %w[Browser Visits], column_alignment: %i[left right], color: "#D30001")
121
+ {
122
+ title: "Browsers",
123
+ header:,
124
+ column_alignment:,
125
+ rows: data[:browsers],
126
+ echarts_spec: "{
127
+ toolbox: {
128
+ feature: {
129
+ saveAsImage: {},
130
+ }
131
+ },
132
+ tooltip: {
133
+ trigger: 'axis'
134
+ },
135
+ xAxis: {
136
+ type: 'category',
137
+ data: SERIES_DATA.sort(order_by_name).map(row => row['Browser']),
138
+ showGrid: true,
139
+ axisLabel: {
140
+ rotate: 45 // Rotate the labels (degrees)
141
+ }
142
+ },
143
+ yAxis: {
144
+ type: 'value',
145
+ name: 'Browser Visits',
146
+ showGrid: true,
147
+ },
148
+ series: [
149
+ {
150
+ name: 'Hits',
151
+ data: SERIES_DATA.sort(order_by_name).map(row => row['Visits']),
152
+ type: 'bar',
153
+ color: '#{color}',
154
+ label: {
155
+ show: true,
156
+ position: 'top'
157
+ },
158
+ },
159
+ ]
160
+ }
161
+ function order_by_name(a, b) {
162
+ return a['Browser'] < b['Browser'] ? -1 : 1
163
+ }
164
+ ",
165
+ }
166
+ end
167
+
168
+ def platforms(data, header: %w[Platform Visits], column_alignment: %i[left right], color: "#d30001")
169
+ {
170
+ title: "Platforms",
171
+ header:,
172
+ column_alignment:,
173
+ rows: data[:platforms],
174
+ echarts_spec: "{
175
+ toolbox: {
176
+ feature: {
177
+ saveAsImage: {},
178
+ }
179
+ },
180
+ tooltip: {
181
+ trigger: 'axis'
182
+ },
183
+ xAxis: {
184
+ type: 'category',
185
+ data: SERIES_DATA.sort(order_by_platform).map(row => row['Platform']),
186
+ showGrid: true,
187
+ axisLabel: {
188
+ rotate: 45 // Rotate the labels by 90 degrees
189
+ }
190
+ },
191
+ yAxis: {
192
+ type: 'value',
193
+ name: 'Platform Visits',
194
+ showGrid: true,
195
+ },
196
+ series: [
197
+ {
198
+ name: 'Visits',
199
+ data: SERIES_DATA.sort(order_by_platform).map(row => row['Visits']),
200
+ type: 'bar',
201
+ color: '#{color}',
202
+ label: {
203
+ show: true,
204
+ position: 'top'
205
+ },
206
+ },
207
+ ]
208
+ }
209
+ function order_by_platform(a, b) {
210
+ return a['Platform'] < b['Platform'] ? -1 : 1
211
+ }",
212
+ }
213
+ end
214
+
215
+ def ips(data, header: %w[IPs Hits Country], column_alignment: %i[left right left], palette: :rails)
216
+ {
217
+ title: "IPs",
218
+ header:,
219
+ column_alignment:,
220
+ # must be like raw_html_height below
221
+ raw_html_height: "500px",
222
+ raw_html: "
223
+ <style>
224
+ #{countries_css_styles(data[:countries], palette:)}
225
+ </style>
226
+ #{File.read(File.join(File.dirname(__FILE__), "templates", "world.svg"))}
227
+ ",
228
+ rows: data[:ips]
229
+ }
230
+ end
231
+
232
+ def countries(data, header: ["Country", "Hits", "IPs", "IP List"], column_alignment: %i[left right left left], color: "#D30001")
233
+ {
234
+ title: "Countries",
235
+ header:,
236
+ column_alignment: ,
237
+ rows: countries_table(data[:countries]),
238
+ # must be like raw_html_height above
239
+ echarts_height: "500px",
240
+ echarts_spec: "{
241
+ tooltip: {
242
+ trigger: 'axis',
243
+ axisPointer: {
244
+ type: 'shadow'
245
+ }
246
+ },
247
+ xAxis: {
248
+ type: 'value',
249
+ boundaryGap: [0, 0.01]
250
+ },
251
+ yAxis: {
252
+ type: 'category',
253
+ data: SERIES_DATA.sort(order_by_hits).map(row => row['Country'] ),
254
+ },
255
+ series: [
256
+ {
257
+ type: 'bar',
258
+ data: SERIES_DATA.sort(order_by_hits).map(row => row['Hits'] ),
259
+ color: '#{color}',
260
+ label: {
261
+ show: true,
262
+ position: 'right'
263
+ },
264
+ },
265
+ ]
266
+ };
267
+
268
+ function order_by_hits(a, b) {
269
+ return Number(a['Hits']) < Number(b['Hits']) ? -1 : 1
270
+ }
271
+ "
272
+ }
273
+ end
274
+
84
275
  def session_report_spec(data)
85
276
  {
86
277
  title: "Sessions",
@@ -96,10 +287,182 @@ module LogSense
96
287
  {
97
288
  title: "IP per hour",
98
289
  header: ["IP"] + (0..23).map { |hour| hour.to_s },
99
- column_alignment: [:left] + [:right] * 24,
290
+ column_alignment: %i[left] + (%i[right] * 24),
100
291
  rows: data,
101
292
  col: "small-12 cell"
102
293
  }
103
294
  end
295
+
296
+ def total_statuses(data)
297
+ {
298
+ title: "Statuses",
299
+ header: %w[Status Count],
300
+ column_alignment: %i[left right],
301
+ rows: data[:statuses],
302
+ echarts_spec: "{
303
+ xAxis: {
304
+ type: 'category',
305
+ data: SERIES_DATA.map(row => row['Status'])
306
+ },
307
+ yAxis: {
308
+ type: 'value'
309
+ },
310
+ tooltip: {
311
+ trigger: 'axis'
312
+ },
313
+ series: [
314
+ {
315
+ data: SERIES_DATA.map(row => {
316
+ var color;
317
+ var first_char = row['Status'].slice(0, 1)
318
+ switch (first_char) {
319
+ case '2':
320
+ color = '#218521';
321
+ break;
322
+ case '3':
323
+ color = '#FF8C00';
324
+ break;
325
+ case '4':
326
+ color = '#A52A2A';
327
+ break
328
+ case '5':
329
+ color = '#000000';
330
+ break;
331
+ default:
332
+ color = '#4C78A8';
333
+ }
334
+ return {
335
+ value: row['Count'],
336
+ itemStyle: { color: color }
337
+ }
338
+ }),
339
+ type: 'bar',
340
+ label: {
341
+ show: true,
342
+ position: 'top'
343
+ },
344
+ }
345
+ ]
346
+ }",
347
+ }
348
+ end
349
+
350
+ def daily_statuses(data)
351
+ {
352
+ title: "Statuses by Day",
353
+ header: %w[Date S_2xx S_3xx S_4xx S_5xx],
354
+ column_alignment: %i[left right right right right],
355
+ rows: data[:statuses_by_day],
356
+ echarts_spec: "{
357
+ xAxis: {
358
+ type: 'category',
359
+ data: SERIES_DATA.map(row => row['Date'])
360
+ },
361
+ yAxis: {
362
+ type: 'value'
363
+ },
364
+ tooltip: {
365
+ trigger: 'axis'
366
+ },
367
+ series: [
368
+ {
369
+ data: SERIES_DATA.map(row => row['S_2xx']),
370
+ type: 'bar',
371
+ color: '#218521',
372
+ stack: 'total',
373
+ label: {
374
+ show: true,
375
+ position: 'right'
376
+ },
377
+ },
378
+ {
379
+ data: SERIES_DATA.map(row => row['S_3xx']),
380
+ type: 'bar',
381
+ color: '#FF8C00',
382
+ stack: 'total',
383
+ label: {
384
+ show: true,
385
+ position: 'right'
386
+ },
387
+ },
388
+ {
389
+ data: SERIES_DATA.map(row => row['S_4xx']),
390
+ type: 'bar',
391
+ color: '#A52A2A',
392
+ stack: 'total',
393
+ label: {
394
+ show: true,
395
+ position: 'right'
396
+ },
397
+ },
398
+ {
399
+ data: SERIES_DATA.map(row => row['S_5xx']),
400
+ type: 'bar',
401
+ color: '#000000',
402
+ stack: 'total',
403
+ label: {
404
+ show: true,
405
+ position: 'right'
406
+ },
407
+ },
408
+ ]
409
+ }",
410
+ }
411
+ end
412
+
413
+ private
414
+
415
+ def countries_css_styles(country_data, palette: :rails)
416
+ country_and_hits = country_data&.map { |k, v|
417
+ [k, v.map { |x| x[1] }.inject(&:+)]
418
+ }
419
+ max = country_and_hits.map { |x| x[1] }.max
420
+
421
+ country_and_hits.map do |element|
422
+ underscored = (element[0] || "").gsub(" ", "_")
423
+ bin = bin(element[1], max:)
424
+ <<-EOS
425
+ /* bin: #{bin} */
426
+ .#{underscored}, ##{underscored}, path[name="#{underscored}"] {
427
+ fill: #{fill_color(bin, palette:)}
428
+ }
429
+ EOS
430
+ end.join("\n")
431
+ end
432
+
433
+ # return the fill colors for the map
434
+ # https://www.learnui.design/tools/data-color-picker.html#single
435
+ def fill_color(bin, palette: :rails)
436
+ colors = if palette == :rails
437
+ ["#fff2e2", "#f9ddbe", "#f4c79b", "#efb07b", "#ea985e",
438
+ "#e57f43", "#e0632a", "#da4314", "#d30001"]
439
+ else
440
+ ["#e3ffff", "#cbedf1", "#b3dce4", "#9dcad7", "#87b8cc",
441
+ "#74a6c0", "#6394b5", "#5482a9", "#48709c"]
442
+ end
443
+
444
+ colors[bin] || colors.last
445
+ end
446
+
447
+ # make number_of_bins bins between 0 and max and return in which bin
448
+ # number belongs to
449
+ def bin(number, number_of_bins: 9, max: 100)
450
+ ((number / max.to_f) * number_of_bins).to_i
451
+ end
452
+
453
+ # { country => [[ip, visit, country], ...]
454
+ def countries_table(data, limit: 15)
455
+ by_country = data&.map { |k, v|
456
+ [
457
+ k || "-",
458
+ v.map { |x| x[1] }.inject(&:+),
459
+ v.map { |x| x[0] }.uniq.size,
460
+ v.map { |x| x[0] }.join(WORDS_SEPARATOR)
461
+ ]
462
+ }&.sort { |x, y| x[0] <=> y[0] }
463
+
464
+ # return the first limit countries
465
+ (by_country || [])[0..limit - 1]
466
+ end
104
467
  end
105
468
  end
@@ -0,0 +1,35 @@
1
+ :root {
2
+ --color: #222222;
3
+ --link-color: #4878C7;
4
+ --link-hover: #20365A;
5
+ --background: white;
6
+
7
+ --sidebar-background: #222222;
8
+ --sidebar-color: white;
9
+ --sidebar-link-color: white;
10
+ --sidebar-link-hover: #FFF5C3;
11
+
12
+ --accent-1: #222222;
13
+ --row-background: white;
14
+ --row-stripe: #FAFAFA;
15
+ --row-border: #D0D0D0;
16
+ }
17
+
18
+ @media (prefers-color-scheme: dark) {
19
+ :root {
20
+ --color: white;
21
+ --link-color: #7FA1D8;
22
+ --link-hover: #B6C9E9;
23
+ --background: #222222;
24
+
25
+ --sidebar-background: #444444;
26
+ --sidebar-color: white;
27
+ --sidebar-link-color: white;
28
+ --sidebar-link-hover: #FFF5C3;
29
+
30
+ --accent-1: white;
31
+ --row-background: #222222;
32
+ --row-stripe: #2F2F2F;
33
+ --row-border: #444444;
34
+ }
35
+ }
@@ -12,11 +12,11 @@
12
12
  <% end %>
13
13
  </ul>
14
14
 
15
- <p>
15
+ <div class="generated-by">
16
16
  Generated by
17
17
  <a href="https://github.com/avillafiorita/log_sense">LogSense</a><br />
18
18
  version <%= LogSense::VERSION %><br />
19
19
  on <%= DateTime.now.strftime("%Y-%m-%d %H:%M") %>.<br />
20
20
  <a href='https://db-ip.com'>IP Geolocation by DB-IP</a>
21
- </p>
21
+ </div>
22
22
  </nav>
@@ -0,0 +1,36 @@
1
+ :root {
2
+ --color: #222222;
3
+ --link-color: #D30001;
4
+ --link-hover: #5F0000;
5
+ --background: white;
6
+
7
+ --sidebar-background: #222222;
8
+ --sidebar-color: white;
9
+ --sidebar-link-color: white;
10
+ --sidebar-link-hover: #FFF5C3;
11
+
12
+ --accent-1: #222222;
13
+ --row-background: white;
14
+ --row-stripe: #FAFAFA;
15
+ --row-border: #D0D0D0;
16
+ }
17
+
18
+ @media (prefers-color-scheme: dark) {
19
+ :root {
20
+ --color: white;
21
+ --link-color: #D30001;
22
+ --link-hover: #ED9999;
23
+ --background: #222222;
24
+
25
+ --sidebar-background: #444444;
26
+ --sidebar-color: white;
27
+ --sidebar-link-color: white;
28
+ --sidebar-link-hover: #FFF5C3;
29
+
30
+ --accent-1: white;
31
+ --row-background: #222222;
32
+ --row-stripe: #2F2F2F;
33
+ --row-border: #444444;
34
+ }
35
+ }
36
+
@@ -1,35 +1,26 @@
1
- :root {
2
- --color: #222222;
3
- --background: white;
4
- --main-accent: #1C1C1C;
5
- }
6
-
7
- /*
8
- @media (prefers-color-scheme: dark) {
9
- :root {
10
- --color: white;
11
- --background: #222222;
12
- --main-accent: #E3E3E3
13
- }
14
- }
15
- */
16
-
17
1
  :root {
18
2
  --font-family: Inter, Helvetica, Arial, sans-serif;
19
-
20
3
  }
21
4
 
22
5
  body {
23
6
  font-family: var(--font-family);
24
- /* font-size: 12px; */
25
7
  font-size: 0.86rem;
26
8
  color: var(--color);
27
9
  background: var(--background);
28
10
  }
29
11
 
12
+ a {
13
+ color: var(--link-color);
14
+ }
15
+
16
+ a:hover {
17
+ color: var(--link-hover);
18
+ text-decoration: underline;
19
+ }
20
+
30
21
  #offCanvas {
31
- color: var(--background);
32
- background: var(--color);
22
+ color: var(--sidebar-color);
23
+ background: var(--sidebar-background);
33
24
  border-right: none;
34
25
  box-shadow: none;
35
26
  padding: 0.5rem;
@@ -38,7 +29,11 @@ body {
38
29
  }
39
30
 
40
31
  #offCanvas a {
41
- color: var(--main-accent);
32
+ color: var(--sidebar-link-color);
33
+ }
34
+
35
+ #offCanvas a:hover {
36
+ color: var(--sidebar-link-hover);
42
37
  }
43
38
 
44
39
  nav {
@@ -47,11 +42,15 @@ nav {
47
42
 
48
43
  nav h2 {
49
44
  padding-left: 0rem;
50
- }
45
+ }
51
46
 
52
- #toggle-button {
53
- font-size: 2rem;
54
- margin-left: 1rem;
47
+ .nav-item {
48
+ margin-bottom: 0.35rem;
49
+ }
50
+
51
+ .generated-by {
52
+ position: fixed;
53
+ bottom: 0.5rem;
55
54
  }
56
55
 
57
56
  .main-section {
@@ -61,28 +60,55 @@ h1 {
61
60
  font-family: var(--font-family);
62
61
  font-size: 2rem;
63
62
  font-weight: bold;
64
- color: var(--main-accent);
63
+ color: var(--accent-1);
64
+ }
65
+
66
+ /* the hamburger menu */
67
+ h1 button {
68
+ color: var(--color);
65
69
  }
66
70
 
67
71
  h2 {
68
72
  font-family: var(--font-family);
69
73
  font-size: 1.6rem;
70
74
  font-weight: bold;
71
- color: var(--main-accent);
75
+ color: var(--accent-1);
72
76
  }
73
77
 
74
- th, td {
75
- padding-top: 0.2rem !important;
76
- padding-bottom: 0.2rem !important;
78
+ .dt-length label, .dt-length select, .dt-search label, .dt-search input {
79
+ color: var(--color);
80
+ background: var(--background-color);
77
81
  }
78
82
 
79
- .dataTables_wrapper {
80
- width: 100%;
81
- padding: 0.5rem;
83
+ .dt-search input:focus {
84
+ color: var(--color);
85
+ background: var(--background-color);
86
+ }
87
+
88
+ table thead, table tbody, table tfoot {
89
+ border-color: var(--row-border) !important;
90
+ }
91
+
92
+ table tr {
93
+ border-color: var(--row-border) !important;
94
+ }
95
+
96
+ table tr td, table tr th {
97
+ color: var(--color) !important;
98
+ background: var(--row-background) !important;
82
99
  }
83
100
 
84
- table.dataTable > tbody > tr:nth-child(2n) {
85
- border-shadow: none !important;
101
+ table tr td {
102
+ border-color: var(--row-border) !important;
103
+ }
104
+
105
+ table > tbody > tr:nth-child(2n) > * {
106
+ box-shadow:inset 0 0 0 9999px var(--row-stripe) !important;
107
+ }
108
+
109
+ th, td {
110
+ padding-top: 0.2rem !important;
111
+ padding-bottom: 0.2rem !important;
86
112
  }
87
113
 
88
114
  article {
@@ -100,11 +126,6 @@ input, select {
100
126
  padding: 0.2rem 0.4rem 0.2rem 0.4rem !important;
101
127
  }
102
128
 
103
- .dataTables_info {
104
- font-size: small;
105
- color: rgb(202, 202, 202);
106
- }
107
-
108
129
  ul.pagination, li.paginate_button {
109
130
  font-size: small;
110
131
  margin-top: 0px !important;
@@ -123,18 +144,18 @@ ul.pagination, li.paginate_button {
123
144
  }
124
145
 
125
146
  .stats-list .stats-list-positive {
126
- color: #228b22;
147
+ color: #228B22;
127
148
  }
128
149
 
129
150
  .stats-list .stats-list-negative {
130
- color: #a52a2a;
151
+ color: #A52A2A;
131
152
  }
132
153
 
133
154
  .stats-list > li {
134
155
  display: inline-block;
135
156
  margin-right: 10px;
136
157
  padding-right: 10px;
137
- border-right: 1px solid #CACACA;
158
+ border-right: 1px solid var(--color);
138
159
  text-align: center;
139
160
  font-size: 1.2em;
140
161
  font-weight: bold;
@@ -161,4 +182,3 @@ td {
161
182
  vertical-align: top;
162
183
  }
163
184
 
164
-
@@ -14,25 +14,27 @@
14
14
  <%= render "cdn_links.html.erb" %>
15
15
 
16
16
  <style>
17
+ <%= render @format_specific_theme %>
17
18
  <%= render "stylesheet.css" %>
18
-
19
19
  <%= render @format_specific_css %>
20
20
  </style>
21
21
  </head>
22
22
 
23
23
  <body>
24
24
  <div class="off-canvas-wrapper">
25
- <div class="off-canvas position-left" id="offCanvas" data-off-canvas>
25
+ <div class="off-canvas position-left reveal-for-large" id="offCanvas" data-off-canvas>
26
26
  <%= render "navigation.html.erb",
27
27
  menus: @reports.map { |x| x[:title] } %>
28
28
  </div>
29
29
  <div class="off-canvas-content" data-off-canvas-content>
30
- <button id="toggle-button" type="button" data-toggle="offCanvas">
31
- &#9776;
32
- </button>
33
30
 
34
31
  <section class="main-section grid-container fluid">
35
- <h1><%= options[:title] || "Log Sense #{@report_title} Log Report" %></h1>
32
+ <h1>
33
+ <button id="toggle-button" type="button" data-toggle="offCanvas" class="hide-for-large">
34
+ &#9776;
35
+ </button>
36
+ <%= options[:title] || "Log Sense #{@report_title} Log Report" %>
37
+ </h1>
36
38
 
37
39
  <p>
38
40
  <b>Input File(s):</b>
@@ -89,6 +91,12 @@
89
91
 
90
92
  </script>
91
93
  <% end %>
94
+ <% if report[:raw_html] %>
95
+ <% height = report[:raw_html_height] || "400px"%>
96
+ <div id="raw-html-#{index}" style="width: 100%;height: <%= height %>">
97
+ <%= report[:raw_html] %>
98
+ </div>
99
+ <% end %>
92
100
  <% if report[:rows] %>
93
101
  <%= render "output_table.html.erb", report: report, index: index %>
94
102
  <% end %>