log_sense 1.9.0 → 2.0.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.
@@ -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 %>