lesli_audit 1.0.2 → 1.1.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/app/controllers/lesli_audit/users_controller.rb +1 -2
- data/app/controllers/lesli_audit/visitors_controller.rb +14 -0
- data/app/interfaces/lesli_audit/logger_interface.rb +22 -29
- data/app/services/lesli_audit/user_service.rb +105 -12
- data/app/services/lesli_audit/{analytic_service.rb → visitor_service.rb} +121 -24
- data/app/views/lesli_audit/partials/_navigation.html.erb +1 -1
- data/app/views/lesli_audit/requests/index.html.erb +1 -1
- data/app/views/lesli_audit/users/index.html.erb +36 -4
- data/app/views/lesli_audit/visitors/index.html.erb +118 -0
- data/config/routes.rb +1 -1
- data/db/migrate/v1.0/0501110210_create_lesli_audit_account_devices.rb +2 -1
- data/db/seed/devices.rb +23 -15
- data/db/seed/users.rb +117 -0
- data/db/seeds.rb +2 -2
- data/lib/lesli_audit/version.rb +2 -2
- metadata +10 -50
- data/app/assets/config/lesli_audit_manifest.js +0 -37
- data/app/controllers/lesli_audit/analytics_controller.rb +0 -23
- data/app/controllers/lesli_audit/user_journals_controller.rb +0 -60
- data/app/helpers/lesli_audit/analytics_helper.rb +0 -4
- data/app/views/lesli_audit/analytics/index.html.erb +0 -44
- data/app/views/lesli_audit/user_journals/_form.html.erb +0 -17
- data/app/views/lesli_audit/user_journals/_user_journal.html.erb +0 -2
- data/app/views/lesli_audit/user_journals/edit.html.erb +0 -12
- data/app/views/lesli_audit/user_journals/index.html.erb +0 -16
- data/app/views/lesli_audit/user_journals/new.html.erb +0 -11
- data/app/views/lesli_audit/user_journals/show.html.erb +0 -10
- data/app/views/lesli_audit/users/show.html.erb +0 -10
- data/lib/scss/application.scss +0 -31
- data/lib/vue/application.js +0 -146
- data/lib/vue/apps/accounts/activities.vue +0 -48
- data/lib/vue/apps/analytics/index.vue +0 -56
- data/lib/vue/apps/analytics/trends.vue +0 -148
- data/lib/vue/apps/dashboards/components/roles.vue +0 -71
- data/lib/vue/apps/dashboards/components/users.vue +0 -71
- data/lib/vue/apps/dashboards/show.vue +0 -8
- data/lib/vue/apps/requests/index.vue +0 -125
- data/lib/vue/apps/security/passwords.vue +0 -52
- data/lib/vue/apps/security/sessions.vue +0 -63
- data/lib/vue/apps/users/activities.vue +0 -49
- data/lib/vue/apps/users/index.vue +0 -89
- data/lib/vue/apps/users/logs.vue +0 -49
- data/lib/vue/apps/users/registrations.vue +0 -73
- data/lib/vue/apps/users/roles.vue +0 -70
- data/lib/vue/apps/users/workingHours.vue +0 -52
- data/lib/vue/components/requests.vue +0 -63
- data/lib/vue/components/visitors.vue +0 -76
- data/lib/vue/stores/accounts/activities.js +0 -87
- data/lib/vue/stores/analytics.js +0 -127
- data/lib/vue/stores/request.js +0 -70
- data/lib/vue/stores/security/password.js +0 -75
- data/lib/vue/stores/security/session.js +0 -78
- data/lib/vue/stores/translations.json +0 -162
- data/lib/vue/stores/users/activities.js +0 -76
- data/lib/vue/stores/users/logs.js +0 -74
- data/lib/vue/stores/users/registrations.js +0 -61
- data/lib/vue/stores/users/roles.js +0 -52
- data/lib/vue/stores/users/working_hours.js +0 -92
- data/lib/vue/stores/users.js +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a84c6870b0006a5609054e1c8a311a0b9d57a5220dc8c75c7841eae7ffd1e8c
|
|
4
|
+
data.tar.gz: 882412cf089844f7c36bb58d492396c521e673374e2a13da8dcd466c23a5dc7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3d60b2ebcd66549072ace0988c2c4ca40b832c2c50173960b9db99b9c4184d4c65488197633718aacaa568b10144d592d842f667430732cc57d8665e1055fc9
|
|
7
|
+
data.tar.gz: 7f2c3f56961cb0aaf60dab530de6a7fd30eaf29a64cfafbc84623ee96c50e7162581c9728adf5894fd68f1d290946f49e1136456a913c00cf80cd8ebe4128fd4
|
|
@@ -34,8 +34,7 @@ module LesliAudit
|
|
|
34
34
|
|
|
35
35
|
# GET /users
|
|
36
36
|
def index
|
|
37
|
-
@
|
|
38
|
-
@registrations = UserService.new(current_user, query).registrations
|
|
37
|
+
@users = UserService.new(current_user, query).users
|
|
39
38
|
@working_hours = UserService.new(current_user, query).working_hours
|
|
40
39
|
end
|
|
41
40
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module LesliAudit
|
|
2
|
+
class VisitorsController < ApplicationController
|
|
3
|
+
|
|
4
|
+
# GET /analytics
|
|
5
|
+
def index
|
|
6
|
+
@visits = VisitorService.new(current_user, query).visits
|
|
7
|
+
@visitors = VisitorService.new(current_user, query).visitors
|
|
8
|
+
@requests = VisitorService.new(current_user, query).requests
|
|
9
|
+
@browsers = VisitorService.new(current_user, query).browsers
|
|
10
|
+
@devices = VisitorService.new(current_user, query).devices
|
|
11
|
+
@os = VisitorService.new(current_user, query).os
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -29,35 +29,28 @@ Building a better future, one line of code at a time.
|
|
|
29
29
|
// · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
|
|
30
30
|
// ·
|
|
31
31
|
=end
|
|
32
|
-
|
|
32
|
+
require 'device_detector'
|
|
33
33
|
module LesliAudit
|
|
34
34
|
module LoggerInterface
|
|
35
35
|
def get_user_agent(as_string=true)
|
|
36
36
|
|
|
37
|
-
http_user_agent = request.env["HTTP_USER_AGENT"]
|
|
38
|
-
|
|
39
37
|
# parse user agent
|
|
40
|
-
user_agent =
|
|
38
|
+
user_agent = DeviceDetector.new(request.env["HTTP_USER_AGENT"])
|
|
41
39
|
|
|
42
|
-
user_agent_version = user_agent.version.to_a.first(2).join(".")
|
|
40
|
+
#user_agent_version = user_agent.version.to_a.first(2).join(".")
|
|
43
41
|
|
|
44
42
|
# return user agent as object
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
version: user_agent_version
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# return user agent info as string
|
|
55
|
-
"#{user_agent.platform} #{user_agent.os} - #{user_agent.browser} #{user_agent_version}"
|
|
43
|
+
{
|
|
44
|
+
platform: user_agent.os_name,
|
|
45
|
+
browser: user_agent.name,
|
|
46
|
+
device: user_agent.device_type
|
|
47
|
+
}
|
|
56
48
|
end
|
|
57
49
|
|
|
58
50
|
def log_requests
|
|
59
51
|
log_account_requests
|
|
60
52
|
log_user_requests
|
|
53
|
+
log_devices
|
|
61
54
|
end
|
|
62
55
|
|
|
63
56
|
def log_account_requests
|
|
@@ -86,17 +79,7 @@ module LesliAudit
|
|
|
86
79
|
def log_user_requests
|
|
87
80
|
return unless current_user
|
|
88
81
|
return unless session[:user_session_id]
|
|
89
|
-
|
|
90
|
-
current_user.account.audit.user_journals.create({
|
|
91
|
-
request_controller: controller_path,
|
|
92
|
-
request_action: action_name,
|
|
93
|
-
session_id: session[:user_session_id],
|
|
94
|
-
user_id: current_user.id,
|
|
95
|
-
date: Date2.new.date.to_s
|
|
96
|
-
}) if Lesli.config.audit.dig(:enable_journals)
|
|
97
|
-
|
|
98
|
-
# Determine the correct SQL "now" keyword based on the database connection
|
|
99
|
-
now_func = ActiveRecord::Base.connection.adapter_name =~ /sqlite/i ? 'CURRENT_TIMESTAMP' : 'NOW()'
|
|
82
|
+
return unless Lesli.config.audit.dig(:enable_analytics)
|
|
100
83
|
|
|
101
84
|
# Try to save a unique record for this request configuration
|
|
102
85
|
current_user.account.audit.user_requests.upsert(
|
|
@@ -118,7 +101,16 @@ module LesliAudit
|
|
|
118
101
|
on_duplicate: Arel.sql(
|
|
119
102
|
"request_count = lesli_audit_user_requests.request_count + 1,updated_at = #{LesliDate::Compatibility.db_now}"
|
|
120
103
|
)
|
|
121
|
-
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return unless Lesli.config.audit.dig(:enable_journals)
|
|
107
|
+
current_user.account.audit.user_journals.create({
|
|
108
|
+
request_controller: controller_path,
|
|
109
|
+
request_action: action_name,
|
|
110
|
+
session_id: session[:user_session_id],
|
|
111
|
+
user_id: current_user.id,
|
|
112
|
+
date: Date2.new.date.to_s
|
|
113
|
+
})
|
|
122
114
|
end
|
|
123
115
|
|
|
124
116
|
def log_devices
|
|
@@ -133,11 +125,12 @@ module LesliAudit
|
|
|
133
125
|
:created_at => Date2.new.date.to_s,
|
|
134
126
|
:agent_platform => user_agent&.dig(:platform) || "unknown",
|
|
135
127
|
:agent_browser => user_agent&.dig(:browser) || "unknown",
|
|
128
|
+
:agent_device => user_agent&.dig(:device) || "unknown",
|
|
136
129
|
:agent_count => 1
|
|
137
130
|
},
|
|
138
131
|
|
|
139
132
|
# group of columns to consider a request as unique
|
|
140
|
-
unique_by: %i[agent_platform agent_browser created_at account_id],
|
|
133
|
+
unique_by: %i[agent_platform agent_browser agent_device created_at account_id],
|
|
141
134
|
|
|
142
135
|
# if request id is not unique
|
|
143
136
|
# - increase the counter for this configuration
|
|
@@ -33,17 +33,104 @@ Building a better future, one line of code at a time.
|
|
|
33
33
|
module LesliAudit
|
|
34
34
|
class UserService < Lesli::ApplicationLesliService
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
current_user.account.
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
36
|
+
def users
|
|
37
|
+
users = current_user.account.users
|
|
38
|
+
|
|
39
|
+
now = Time.current
|
|
40
|
+
last_30_days = 30.days.ago
|
|
41
|
+
|
|
42
|
+
stats = users.pick(
|
|
43
|
+
# total users
|
|
44
|
+
Arel.sql("COUNT(*)"),
|
|
45
|
+
|
|
46
|
+
# total deactivated users
|
|
47
|
+
Arel.sql("SUM(CASE WHEN active = 0 AND deleted_at IS NULL THEN 1 ELSE 0 END)"),
|
|
48
|
+
|
|
49
|
+
# total locked users
|
|
50
|
+
Arel.sql(
|
|
51
|
+
"SUM(CASE WHEN (
|
|
52
|
+
locked_at IS NOT NULL
|
|
53
|
+
OR locked_until > #{ActiveRecord::Base.connection.quote(Time.current)}
|
|
54
|
+
) AND deleted_at IS NULL THEN 1 ELSE 0 END)"
|
|
55
|
+
),
|
|
56
|
+
|
|
57
|
+
# total not confirmed users
|
|
58
|
+
Arel.sql("SUM(CASE WHEN confirmed_at IS NULL AND deleted_at IS NULL THEN 1 ELSE 0 END)"),
|
|
59
|
+
|
|
60
|
+
# total deleted users
|
|
61
|
+
Arel.sql("SUM(CASE WHEN deleted_at IS NOT NULL THEN 1 ELSE 0 END)"),
|
|
62
|
+
|
|
63
|
+
# users that never signed in
|
|
64
|
+
Arel.sql("SUM(CASE WHEN last_sign_in_at IS NULL AND deleted_at IS NULL THEN 1 ELSE 0 END)"),
|
|
65
|
+
|
|
66
|
+
# total active users for the last 30 days
|
|
67
|
+
Arel.sql("SUM(CASE WHEN last_sign_in_at >= #{ActiveRecord::Base.connection.quote(last_30_days)} AND deleted_at IS NULL THEN 1 ELSE 0 END)"),
|
|
68
|
+
|
|
69
|
+
# users with failed logins
|
|
70
|
+
Arel.sql("SUM(CASE WHEN failed_attempts > 0 AND deleted_at IS NULL THEN 1 ELSE 0 END)"),
|
|
46
71
|
)
|
|
72
|
+
|
|
73
|
+
summary = {
|
|
74
|
+
total_users: stats[0].to_i,
|
|
75
|
+
total_users_deactivated: stats[1].to_i,
|
|
76
|
+
total_users_locked: stats[2].to_i,
|
|
77
|
+
total_users_unconfirmed: stats[3].to_i,
|
|
78
|
+
total_users_deleted: stats[4].to_i,
|
|
79
|
+
|
|
80
|
+
total_users_never_signed_in: stats[5].to_i,
|
|
81
|
+
total_users_active_last_30_days: stats[6].to_i,
|
|
82
|
+
total_users_with_failed_attempts: stats[7].to_i
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
overview: [{
|
|
87
|
+
label: "Total users",
|
|
88
|
+
value: summary[:total_users],
|
|
89
|
+
percentage: 100,
|
|
90
|
+
color: "info"
|
|
91
|
+
}, {
|
|
92
|
+
label: "Deactivated users",
|
|
93
|
+
value: summary[:total_users_deactivated],
|
|
94
|
+
percentage: percentage(summary[:total_users_deactivated], summary[:total_users]),
|
|
95
|
+
color: "warning"
|
|
96
|
+
}, {
|
|
97
|
+
label: "Locked users",
|
|
98
|
+
value: summary[:total_users_locked],
|
|
99
|
+
percentage: percentage(summary[:total_users_locked], summary[:total_users]),
|
|
100
|
+
color: "danger"
|
|
101
|
+
}, {
|
|
102
|
+
label: "Unconfirmed users",
|
|
103
|
+
value: summary[:total_users_unconfirmed],
|
|
104
|
+
percentage: percentage(summary[:total_users_unconfirmed], summary[:total_users]),
|
|
105
|
+
color: "warning",
|
|
106
|
+
}, {
|
|
107
|
+
label: "Deleted users",
|
|
108
|
+
value: summary[:total_users_deleted],
|
|
109
|
+
percentage: percentage(summary[:total_users_deleted], summary[:total_users]),
|
|
110
|
+
color: "danger"
|
|
111
|
+
}],
|
|
112
|
+
attention_needed: [{
|
|
113
|
+
label: "Unconfirmed users",
|
|
114
|
+
value: summary[:total_users_unconfirmed],
|
|
115
|
+
color: "info",
|
|
116
|
+
icon: "user-add-line"
|
|
117
|
+
}, {
|
|
118
|
+
label: "Never signed in",
|
|
119
|
+
value: summary[:total_users_never_signed_in],
|
|
120
|
+
color: "warning",
|
|
121
|
+
icon: "time-line"
|
|
122
|
+
}, {
|
|
123
|
+
label: "Failed login attempts",
|
|
124
|
+
value: summary[:total_users_with_failed_attempts],
|
|
125
|
+
color: "danger",
|
|
126
|
+
icon: "shield-keyhole-line"
|
|
127
|
+
}, {
|
|
128
|
+
label: "Locked users",
|
|
129
|
+
value: summary[:total_users_locked],
|
|
130
|
+
color: "danger",
|
|
131
|
+
icon: "lock-line"
|
|
132
|
+
}]
|
|
133
|
+
}
|
|
47
134
|
end
|
|
48
135
|
|
|
49
136
|
def working_hours
|
|
@@ -72,8 +159,7 @@ module LesliAudit
|
|
|
72
159
|
"sum(lesli_audit_user_requests.request_count) requests"
|
|
73
160
|
)
|
|
74
161
|
|
|
75
|
-
user_requests = user_requests
|
|
76
|
-
.group(:user_id, group_by)
|
|
162
|
+
user_requests = user_requests.group(:user_id, group_by)
|
|
77
163
|
|
|
78
164
|
user_requests.map do |request|
|
|
79
165
|
request[:first_activity] = Date2.new(request[:first_activity]).time
|
|
@@ -118,5 +204,12 @@ module LesliAudit
|
|
|
118
204
|
|
|
119
205
|
registrations
|
|
120
206
|
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
def percentage(value, total)
|
|
211
|
+
return 0 if total.to_i.zero?
|
|
212
|
+
((value.to_f / total) * 100).round
|
|
213
|
+
end
|
|
121
214
|
end
|
|
122
215
|
end
|
|
@@ -31,15 +31,22 @@ Building a better future, one line of code at a time.
|
|
|
31
31
|
=end
|
|
32
32
|
|
|
33
33
|
module LesliAudit
|
|
34
|
-
class
|
|
34
|
+
class VisitorService < Lesli::ApplicationLesliService
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
def visits
|
|
37
|
+
requests = current_user.account.audit.account_requests
|
|
38
|
+
|
|
39
|
+
controllers, total_requests = requests.pick(
|
|
40
|
+
Arel.sql("COUNT(DISTINCT request_controller)"),
|
|
41
|
+
Arel.sql("SUM(request_count)")
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
controllers: controllers.to_i,
|
|
46
|
+
requests: total_requests.to_i
|
|
47
|
+
}
|
|
48
|
+
end
|
|
37
49
|
|
|
38
|
-
# @overwrite
|
|
39
|
-
# @return {Hash} Paginated list of the records
|
|
40
|
-
# @param {query} Has of the formated queries/filters that will be applied to filter data
|
|
41
|
-
# @description
|
|
42
|
-
# @example
|
|
43
50
|
def visitors
|
|
44
51
|
#Rails.cache.fetch(cache_key_for_account(__method__), expires_in: 1.hour) do
|
|
45
52
|
group = 'day'
|
|
@@ -70,7 +77,7 @@ module LesliAudit
|
|
|
70
77
|
|
|
71
78
|
def requests
|
|
72
79
|
requests = current_user.account.audit.account_requests
|
|
73
|
-
.group("request_controller")
|
|
80
|
+
.group("request_controller")
|
|
74
81
|
|
|
75
82
|
requests = apply_filters(requests, query)
|
|
76
83
|
|
|
@@ -80,32 +87,122 @@ module LesliAudit
|
|
|
80
87
|
).as_json
|
|
81
88
|
end
|
|
82
89
|
|
|
90
|
+
def os
|
|
91
|
+
device_counts = current_user.account.audit.account_devices
|
|
92
|
+
.group(:agent_platform)
|
|
93
|
+
.sum(:agent_count)
|
|
94
|
+
|
|
95
|
+
total = device_counts.values.sum
|
|
96
|
+
|
|
97
|
+
device_counts.map do |device, count|
|
|
98
|
+
{
|
|
99
|
+
device: device,
|
|
100
|
+
count: count,
|
|
101
|
+
icon: platform_icon(device),
|
|
102
|
+
percentage: total.zero? ? 0 : ((count.to_f / total) * 100).round()
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
83
107
|
def devices
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
device_counts = current_user.account.audit.account_devices
|
|
109
|
+
.group(:agent_device)
|
|
110
|
+
.sum(:agent_count)
|
|
111
|
+
|
|
112
|
+
total = device_counts.values.sum
|
|
113
|
+
|
|
114
|
+
device_counts.map do |device, count|
|
|
115
|
+
{
|
|
116
|
+
device: device,
|
|
117
|
+
count: count,
|
|
118
|
+
icon: device_icon(device),
|
|
119
|
+
percentage: total.zero? ? 0 : ((count.to_f / total) * 100).round()
|
|
120
|
+
}
|
|
121
|
+
end
|
|
93
122
|
end
|
|
94
123
|
|
|
95
124
|
def browsers
|
|
125
|
+
browser_counts = current_user.account.audit.account_devices
|
|
126
|
+
.group(:agent_browser)
|
|
127
|
+
.sum(:agent_count)
|
|
128
|
+
|
|
129
|
+
total = browser_counts.values.sum
|
|
130
|
+
|
|
131
|
+
browser_counts.map do |browser, count|
|
|
132
|
+
{
|
|
133
|
+
browser: browser,
|
|
134
|
+
count: count,
|
|
135
|
+
icon: browser_icon(browser),
|
|
136
|
+
percentage: total.zero? ? 0 : ((count.to_f / total) * 100).round()
|
|
137
|
+
}
|
|
138
|
+
end
|
|
96
139
|
#Rails.cache.fetch(cache_key_for_account(__method__), expires_in: 4.hour) do
|
|
97
|
-
current_user.account.audit.account_devices
|
|
98
|
-
.group(:agent_browser, :created_at)
|
|
99
|
-
.select(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
).as_json
|
|
140
|
+
# current_user.account.audit.account_devices
|
|
141
|
+
# .group(:agent_browser, :created_at)
|
|
142
|
+
# .select(
|
|
143
|
+
# 'created_at as xaxiskey',
|
|
144
|
+
# 'agent_browser as dataname',
|
|
145
|
+
# 'sum(agent_count) as yaxiskey'
|
|
146
|
+
# ).as_json
|
|
104
147
|
#end
|
|
105
148
|
end
|
|
106
149
|
|
|
107
150
|
private
|
|
108
151
|
|
|
152
|
+
LIMIT=15
|
|
153
|
+
|
|
154
|
+
DEFAULT_PLATFORM_ICON_CLASS = "computer-fill"
|
|
155
|
+
PLATFORM_ICON_CLASSES = {
|
|
156
|
+
/chrome os|cros/i => "chrome-fill",
|
|
157
|
+
/ios|ipad|iphone|mac/i => "apple-fill",
|
|
158
|
+
/windows/i => "windows-fill",
|
|
159
|
+
/ubuntu/i => "ubuntu-fill",
|
|
160
|
+
/debian|fedora|linux/i => "linux-fill",
|
|
161
|
+
/android/i => "android-fill"
|
|
162
|
+
}.freeze
|
|
163
|
+
|
|
164
|
+
DEFAULT_BROWSER_ICON_CLASS = "global-fill"
|
|
165
|
+
BROWSER_ICON_CLASSES = {
|
|
166
|
+
/chrome|chromium/i => "chrome-fill",
|
|
167
|
+
/firefox/i => "firefox-fill",
|
|
168
|
+
/safari/i => "safari-fill",
|
|
169
|
+
/edge/i => "edge-fill",
|
|
170
|
+
/opera|opr/i => "opera-fill",
|
|
171
|
+
/internet explorer|msie/i => "ie-fill",
|
|
172
|
+
/brave/i => "brave-fill"
|
|
173
|
+
}.freeze
|
|
174
|
+
|
|
175
|
+
DEVICE_ICON_CLASSES = {
|
|
176
|
+
"desktop" => "mac-line",
|
|
177
|
+
"tablet" => "tablet-line",
|
|
178
|
+
"smartphone" => "smartphone-line",
|
|
179
|
+
"unknown" => "file-unknow-line"
|
|
180
|
+
}.freeze
|
|
181
|
+
|
|
182
|
+
def platform_icon(platform)
|
|
183
|
+
return DEFAULT_PLATFORM_ICON_CLASS if platform.blank?
|
|
184
|
+
|
|
185
|
+
match = PLATFORM_ICON_CLASSES.find do |pattern, _icon_class|
|
|
186
|
+
platform.match?(pattern)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
match ? match.last : DEFAULT_PLATFORM_ICON_CLASS
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def browser_icon(browser)
|
|
193
|
+
return DEFAULT_BROWSER_ICON_CLASS if browser.blank?
|
|
194
|
+
|
|
195
|
+
match = BROWSER_ICON_CLASSES.find do |pattern, _icon_class|
|
|
196
|
+
browser.match?(pattern)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
match ? match.last : DEFAULT_BROWSER_ICON_CLASS
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def device_icon(device)
|
|
203
|
+
DEVICE_ICON_CLASSES.fetch(device.to_s, DEVICE_ICON_CLASSES["unknown"])
|
|
204
|
+
end
|
|
205
|
+
|
|
109
206
|
def apply_filters requests, params
|
|
110
207
|
|
|
111
208
|
return requests
|
|
@@ -33,6 +33,6 @@ Building a better future, one line of code at a time.
|
|
|
33
33
|
%>
|
|
34
34
|
|
|
35
35
|
<%= navigation_item(lesli_audit.users_path, 'Users', 'ri-team-line'); %>
|
|
36
|
-
<%= navigation_item(lesli_audit.
|
|
36
|
+
<%= navigation_item(lesli_audit.visitors_path, 'Visitors', 'ri-line-chart-fill'); %>
|
|
37
37
|
<%= navigation_item(lesli_audit.requests_path, 'Requests', 'ri-arrow-left-right-fill'); %>
|
|
38
38
|
<%= navigation_item(lesli_audit.logs_path, 'Logs', 'ri-history-line'); %>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<%= render(LesliView::Layout::Container.new("audit-requests")) do %>
|
|
2
2
|
<%= render(LesliView::Components::Header.new("Requests")) %>
|
|
3
|
-
<%= render
|
|
3
|
+
<%= render(LesliView::Components::Toolbar.new()) %>
|
|
4
4
|
<%= render(LesliView::Elements::Table.new(
|
|
5
5
|
columns: [{
|
|
6
6
|
label: "ID",
|
|
@@ -3,11 +3,41 @@
|
|
|
3
3
|
<%= render(LesliView::Components::Header.new("Users")) %>
|
|
4
4
|
|
|
5
5
|
<div class="columns">
|
|
6
|
+
<% @users[:overview].each do |user| %>
|
|
7
|
+
<div class="column">
|
|
8
|
+
<div class="box">
|
|
9
|
+
<h4><%= user[:label] %></h4>
|
|
10
|
+
<p class="is-size-3 has-text-<%= user[:color] %>"><%= user[:value] %></p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="columns">
|
|
6
17
|
<div class="column">
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
<div class="box">
|
|
19
|
+
<%= render(LesliView::Elements::Table.new(columns: [])) do |table| %>
|
|
20
|
+
<% @users[:attention_needed].each do |user| %>
|
|
21
|
+
<% table.with_row(css_class:"is-white") do |row| %>
|
|
22
|
+
<% row.with_cell(css_class:"") do %>
|
|
23
|
+
<span class="icon has-text-<%= user[:color] %>">
|
|
24
|
+
<span class="is-size-4 ri-<%= user[:icon] %>"></span>
|
|
25
|
+
</span>
|
|
26
|
+
<% end %>
|
|
27
|
+
<% row.with_cell(css_class:"is-fullwidth") do %>
|
|
28
|
+
<%= user[:label] %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% row.with_cell(css_class:"has-text-right") do %>
|
|
31
|
+
<span class="is-size-6 has-text-weight-bold">
|
|
32
|
+
<%= user[:value] %>
|
|
33
|
+
</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="column is-three-fifths">
|
|
11
41
|
</div>
|
|
12
42
|
</div>
|
|
13
43
|
|
|
@@ -40,4 +70,6 @@
|
|
|
40
70
|
)) %>
|
|
41
71
|
</div>
|
|
42
72
|
</div>
|
|
73
|
+
|
|
74
|
+
<br>
|
|
43
75
|
<% end %>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
|
|
2
|
+
<% @visitors = @visitors.reverse %>
|
|
3
|
+
<% top_days = (@visitors.last(3)) %>
|
|
4
|
+
|
|
5
|
+
<%= render(LesliView::Layout::Container.new("audit-analytics")) do %>
|
|
6
|
+
<%= render(LesliView::Components::Header.new("Analytics")) %>
|
|
7
|
+
|
|
8
|
+
<div class="columns">
|
|
9
|
+
<div class="column">
|
|
10
|
+
<div class="box">
|
|
11
|
+
<h4>Controllers</h4>
|
|
12
|
+
<p class="is-size-3"><%= @visits[:controllers] %></p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="column">
|
|
16
|
+
<div class="box">
|
|
17
|
+
<h4>Requests</h4>
|
|
18
|
+
<p class="is-size-3"><%= @visits[:requests] %></p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<% top_days.each do |day| %>
|
|
22
|
+
<div class="column">
|
|
23
|
+
<div class="box">
|
|
24
|
+
<h4><%= day['xaxiskey'] %></h4>
|
|
25
|
+
<p class="is-size-3"><%= day['yaxiskey'] %></p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="box">
|
|
32
|
+
<%= render(LesliView::Charts::Line.new(
|
|
33
|
+
title:"Visitors",
|
|
34
|
+
database_to_dataset: @visitors
|
|
35
|
+
))%>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="columns">
|
|
39
|
+
<div class="column">
|
|
40
|
+
<%= render(LesliView::Elements::Table.new(columns: [{label:'Browsers', align:'left'},{label:'Visitors', align:'center'}])) do |table| %>
|
|
41
|
+
<% @browsers.each do |browser| %>
|
|
42
|
+
<% table.with_row(css_class:"") do |row| %>
|
|
43
|
+
<% row.with_cell(css_class:"p-0") do %>
|
|
44
|
+
<p style="padding:8px;background: linear-gradient(to right, #E5EAF0 <%= browser[:percentage] %>%, transparent 0%);">
|
|
45
|
+
<span class="icon-text">
|
|
46
|
+
<span class="icon">
|
|
47
|
+
<span class="ri-<%= browser[:icon] %>"></span>
|
|
48
|
+
</span>
|
|
49
|
+
<span><%= browser[:browser] %></span>
|
|
50
|
+
</span>
|
|
51
|
+
</p>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% row.with_cell(css_class:"has-text-centered") do %>
|
|
54
|
+
<%= browser[:count] %>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% end %>
|
|
57
|
+
<% end %>
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="column">
|
|
61
|
+
<%= render(LesliView::Elements::Table.new(columns: [{label:'OS', align:'left'},{label:'Visitors', align:'center'}])) do |table| %>
|
|
62
|
+
<% @os.each do |device| %>
|
|
63
|
+
<% table.with_row(css_class:"") do |row| %>
|
|
64
|
+
<% row.with_cell(css_class:"p-0") do %>
|
|
65
|
+
<p style="padding:8px;background: linear-gradient(to right, #E5EAF0 <%= device[:percentage] %>%, transparent 0%);">
|
|
66
|
+
<span class="icon-text">
|
|
67
|
+
<span class="icon">
|
|
68
|
+
<span class="ri-<%= device[:icon].downcase %>"></span>
|
|
69
|
+
</span>
|
|
70
|
+
<span><%= device[:device] %></span>
|
|
71
|
+
</span>
|
|
72
|
+
</p>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% row.with_cell(css_class:"has-text-centered") do %>
|
|
75
|
+
<%= device[:count] %>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% end %>
|
|
78
|
+
<% end %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="column">
|
|
82
|
+
<%= render(LesliView::Elements::Table.new(columns: [{label:'Device', align:'left'},{label:'Visitors', align:'center'}])) do |table| %>
|
|
83
|
+
<% @devices.each do |device| %>
|
|
84
|
+
<% table.with_row(css_class:"") do |row| %>
|
|
85
|
+
<% row.with_cell(css_class:"p-0") do %>
|
|
86
|
+
<p style="padding:8px;background: linear-gradient(to right, #E5EAF0 <%= device[:percentage] %>%, transparent 0%);">
|
|
87
|
+
<span class="icon-text">
|
|
88
|
+
<span class="icon">
|
|
89
|
+
<span class="ri-<%= device[:icon] %>"></span>
|
|
90
|
+
</span>
|
|
91
|
+
<span><%= device[:device] %></span>
|
|
92
|
+
</span>
|
|
93
|
+
</p>
|
|
94
|
+
<% end %>
|
|
95
|
+
<% row.with_cell(css_class:"has-text-centered") do %>
|
|
96
|
+
<%= device[:count] %>
|
|
97
|
+
<% end %>
|
|
98
|
+
<% end %>
|
|
99
|
+
<% end %>
|
|
100
|
+
<% end %>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="columns">
|
|
105
|
+
<div class="column">
|
|
106
|
+
<%= render(LesliView::Elements::Table.new(
|
|
107
|
+
columns: [{
|
|
108
|
+
label: "Top controllers",
|
|
109
|
+
field: "request_controller"
|
|
110
|
+
},{
|
|
111
|
+
label:"Requests",
|
|
112
|
+
field:"requests"
|
|
113
|
+
}],
|
|
114
|
+
records: @requests
|
|
115
|
+
)) %>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -35,11 +35,12 @@ class CreateLesliAuditAccountDevices < ActiveRecord::Migration[8.1]
|
|
|
35
35
|
create_table :lesli_audit_account_devices do |t|
|
|
36
36
|
t.string :agent_platform
|
|
37
37
|
t.string :agent_browser
|
|
38
|
+
t.string :agent_device
|
|
38
39
|
t.integer :agent_count
|
|
39
40
|
t.date :created_at
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
add_reference(:lesli_audit_account_devices, :account, foreign_key: { to_table: :lesli_audit_accounts })
|
|
43
|
-
add_index(:lesli_audit_account_devices, %i[agent_platform agent_browser created_at account_id], unique: true, name: "lesli_audit_devices_index")
|
|
44
|
+
add_index(:lesli_audit_account_devices, %i[agent_platform agent_browser agent_device created_at account_id], unique: true, name: "lesli_audit_devices_index")
|
|
44
45
|
end
|
|
45
46
|
end
|