eyeloupe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +129 -0
  5. data/Rakefile +8 -0
  6. data/app/assets/builds/eyeloupe.css +1 -0
  7. data/app/assets/config/eyeloupe_manifest.js +3 -0
  8. data/app/assets/images/eyeloupe/logo.png +0 -0
  9. data/app/assets/javascripts/eyeloupe/application.js +5 -0
  10. data/app/assets/javascripts/eyeloupe/controllers/application.js +9 -0
  11. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/nav_controller.js +13 -0
  12. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/pause_controller.js +29 -0
  13. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js +53 -0
  14. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/search_controller.js +11 -0
  15. data/app/assets/javascripts/eyeloupe/controllers/index.js +11 -0
  16. data/app/assets/stylesheets/application.tailwind.css +42 -0
  17. data/app/assets/stylesheets/eyeloupe/application.css +16 -0
  18. data/app/controllers/concerns/eyeloupe/searchable.rb +20 -0
  19. data/app/controllers/eyeloupe/application_controller.rb +17 -0
  20. data/app/controllers/eyeloupe/configs_controller.rb +20 -0
  21. data/app/controllers/eyeloupe/data_controller.rb +15 -0
  22. data/app/controllers/eyeloupe/in_requests_controller.rb +24 -0
  23. data/app/controllers/eyeloupe/out_requests_controller.rb +27 -0
  24. data/app/helpers/eyeloupe/application_helper.rb +5 -0
  25. data/app/jobs/eyeloupe/application_job.rb +4 -0
  26. data/app/mailers/eyeloupe/application_mailer.rb +6 -0
  27. data/app/models/eyeloupe/application_record.rb +5 -0
  28. data/app/models/eyeloupe/in_request.rb +4 -0
  29. data/app/models/eyeloupe/out_request.rb +4 -0
  30. data/app/views/eyeloupe/in_requests/_frame.html.erb +44 -0
  31. data/app/views/eyeloupe/in_requests/index.html.erb +18 -0
  32. data/app/views/eyeloupe/in_requests/show.html.erb +82 -0
  33. data/app/views/eyeloupe/out_requests/_frame.html.erb +46 -0
  34. data/app/views/eyeloupe/out_requests/index.html.erb +18 -0
  35. data/app/views/eyeloupe/out_requests/show.html.erb +69 -0
  36. data/app/views/eyeloupe/shared/_status_code.html.erb +21 -0
  37. data/app/views/eyeloupe/shared/_verb.html.erb +17 -0
  38. data/app/views/layouts/eyeloupe/application.html.erb +203 -0
  39. data/config/importmap.rb +4 -0
  40. data/config/routes.rb +12 -0
  41. data/config/tailwind.config.js +22 -0
  42. data/db/migrate/20230518175305_create_eyeloupe_in_requests.rb +22 -0
  43. data/db/migrate/20230525125352_create_eyeloupe_out_requests.rb +18 -0
  44. data/lib/eyeloupe/configuration.rb +20 -0
  45. data/lib/eyeloupe/engine.rb +21 -0
  46. data/lib/eyeloupe/http.rb +19 -0
  47. data/lib/eyeloupe/processors/in_request.rb +148 -0
  48. data/lib/eyeloupe/processors/out_request.rb +76 -0
  49. data/lib/eyeloupe/request_middleware.rb +72 -0
  50. data/lib/eyeloupe/version.rb +4 -0
  51. data/lib/eyeloupe.rb +21 -0
  52. data/lib/tasks/eyeloupe_tasks.rake +9 -0
  53. metadata +195 -0
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+ require 'singleton'
3
+
4
+ module Eyeloupe
5
+ module Processors
6
+ class InRequest
7
+
8
+ include Singleton
9
+
10
+ # @return [ActionDispatch::Request]
11
+ attr_accessor :request
12
+
13
+ # @return [Hash, nil]
14
+ attr_accessor :env
15
+
16
+ # @return [Integer, nil]
17
+ attr_accessor :status
18
+
19
+ # @return [Hash, nil]
20
+ attr_accessor :headers
21
+
22
+ # @return [String, nil, Rack::BodyProxy, ActionDispatch::Response]
23
+ attr_accessor :response
24
+
25
+ # @return [Hash]
26
+ attr_accessor :timings
27
+
28
+ # @return [Time, nil]
29
+ attr_accessor :started_at
30
+
31
+ # @return [Array]
32
+ attr_accessor :subs
33
+
34
+ def initialize
35
+ @env = {}
36
+ @request = ActionDispatch::Request.new(@env)
37
+ @status = nil
38
+ @headers = nil
39
+ @response = nil
40
+ @timings = {}
41
+ @started_at = nil
42
+ @subs = []
43
+ end
44
+
45
+ # @param [ActionDispatch::Request] request The request object
46
+ # @param [Hash, nil] env Rack environment
47
+ # @param [Integer, nil] status HTTP status code
48
+ # @param [Hash, nil] headers HTTP headers
49
+ # @param [String, nil] response HTTP response
50
+ # @return [Eyeloupe::Processors::InRequest]
51
+ def init(request, env, status, headers, response)
52
+ unsubscribe
53
+
54
+ @request = request
55
+ @env = env
56
+ @status = status
57
+ @headers = headers
58
+ @response = response
59
+
60
+ self
61
+ end
62
+
63
+ def start_timer
64
+ @started_at = Time.now
65
+
66
+ subscribe('process_action.action_controller') do |event|
67
+ @timings[:controller_time] = event.duration
68
+ @timings[:db_time] = event.payload[:db_runtime]
69
+ @timings[:view_time] = event.payload[:view_runtime]
70
+ end
71
+ end
72
+
73
+ # @return [Eyeloupe::InRequest]
74
+ def process
75
+ Eyeloupe::InRequest.create(
76
+ verb: @request.request_method,
77
+ hostname: @request.host,
78
+ path: @env["REQUEST_URI"],
79
+ controller: get_controller,
80
+ status: @status,
81
+ format: @request.format,
82
+ duration: get_total_duration,
83
+ db_duration: @timings[:db_time].present? ? @timings[:db_time].round : nil,
84
+ view_duration: @timings[:view_time].present? ? @timings[:view_time].round : nil,
85
+ ip: @request.ip,
86
+ payload: @env['rack.input'].read,
87
+ headers: @headers&.to_json,
88
+ session: (@request.session || {}).to_json,
89
+ response: get_response,
90
+ )
91
+ end
92
+
93
+ protected
94
+
95
+ # @return [Float]
96
+ def get_total_duration
97
+ if @timings[:controller_time].present?
98
+ @timings[:controller_time].round
99
+ elsif @started_at.present?
100
+ (Time.now - @started_at) * 1000
101
+ else
102
+ 0.0
103
+ end
104
+ end
105
+
106
+ # @return [String, nil]
107
+ def get_response
108
+ if @request.format.to_s =~ /html/
109
+ "HTML content"
110
+ elsif @response.is_a?(ActionDispatch::Response)
111
+ @response.body
112
+ elsif @response.is_a?(Rack::BodyProxy)
113
+ @response.first
114
+ else
115
+ @response
116
+ end
117
+ end
118
+
119
+ # @return [String, nil]
120
+ def get_controller
121
+ if @request.controller_class.to_s =~ /PASS_NOT_FOUND/
122
+ nil
123
+ else
124
+ "#{@request.controller_class.to_s}##{@request.path_parameters[:action]}"
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ # @param [String] event The event to subscribe to
131
+ # @param [Proc] block The block to execute when the event is triggered
132
+ # @yield [ActiveSupport::Notifications::Event] The event object
133
+ def subscribe(event, &block)
134
+ @subs << ActiveSupport::Notifications.subscribe(event) do |*args|
135
+ block.call(ActiveSupport::Notifications::Event.new(*args))
136
+ end
137
+ end
138
+
139
+ def unsubscribe
140
+ @subs.each do |sub|
141
+ ActiveSupport::Notifications.unsubscribe(sub)
142
+ end
143
+ @subs = []
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eyeloupe
4
+ module Processors
5
+ class OutRequest
6
+ include Singleton
7
+
8
+ # @return [Net::HTTPRequest, nil]
9
+ attr_accessor :request
10
+
11
+ # @return [String]
12
+ attr_accessor :body
13
+
14
+ # @return [Hash]
15
+ attr_accessor :req_headers
16
+
17
+ # @return [Hash]
18
+ attr_accessor :res_headers
19
+
20
+ # @return [Net::HTTPResponse, nil]
21
+ attr_accessor :response
22
+
23
+ # @return [Time, nil]
24
+ attr_accessor :started_at
25
+
26
+ def initialize
27
+ @request = nil
28
+ @body = ""
29
+ @req_headers = {}
30
+ @res_headers = {}
31
+ @started_at = nil
32
+ @response = nil
33
+ end
34
+
35
+ # @param [Net::HTTPRequest] request The request object
36
+ # @param [String] body The request body
37
+ def init(request, body)
38
+ @request = request
39
+ @body = body
40
+ @started_at = Time.now
41
+ end
42
+
43
+ # @param [Net::HTTPResponse] response The response object
44
+ # @return [Net::HTTPResponse] The response object
45
+ def process(response)
46
+ @response = response
47
+
48
+ Eyeloupe::OutRequest.create(
49
+ verb: @request.method,
50
+ hostname: @request['host'],
51
+ path: @request.path,
52
+ status: @response.code,
53
+ format: @response.content_type,
54
+ duration: (Time.now - @started_at) * 1000,
55
+ payload: @request.body,
56
+ req_headers: (get_headers(@request) || {}).to_json,
57
+ res_headers: (get_headers(@response) || {}).to_json,
58
+ response: @response.body,
59
+ )
60
+ response
61
+ end
62
+
63
+ protected
64
+
65
+ # @param [Net::HTTPRequest, Net::HTTPResponse] el The request or response object to get headers from
66
+ def get_headers(el)
67
+ headers = {}
68
+ el.each_header do |key, value|
69
+ headers[key] = value
70
+ end
71
+ headers
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,72 @@
1
+ module Eyeloupe
2
+
3
+ class RequestMiddleware
4
+
5
+ # @return [Eyeloupe::Processors::InRequest]
6
+ attr_accessor :inrequest_processor
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ @inrequest_processor = Processors::InRequest.instance
11
+ end
12
+
13
+ # @param [Hash] env Rack environment
14
+ def call(env)
15
+
16
+ request = ActionDispatch::Request.new(env)
17
+
18
+ if enabled?(request) && !skip_request?(request)
19
+ @inrequest_processor.start_timer
20
+
21
+ begin
22
+ status, headers, response = @app.call(env)
23
+ [status, headers, response]
24
+ rescue Exception => e
25
+ exception = ActionDispatch::ExceptionWrapper.new(env, e)
26
+ status = exception.status_code
27
+ headers = {}
28
+ response = e.message
29
+ raise
30
+ ensure
31
+ @inrequest_processor.init(request, env, status, headers, response).process
32
+ end
33
+ else
34
+ @app.call(env)
35
+ end
36
+
37
+ end
38
+
39
+ protected
40
+
41
+ # Check if capture is enabled, if so we are looking to the capture cookie, if no cookie present capture is enabled by default
42
+ #
43
+ # @param [ActionDispatch::Request] request
44
+ # @return [Boolean]
45
+ def enabled?(request)
46
+ if Eyeloupe.configuration.capture
47
+ if request.cookies['eyeloupe_capture'].present?
48
+ request.cookies['eyeloupe_capture'] == "true"
49
+ else
50
+ true
51
+ end
52
+ else
53
+ false
54
+ end
55
+ end
56
+
57
+ # Check if the request path is in the excluded paths
58
+ #
59
+ # @param [ActionDispatch::Request] request
60
+ # @return [Boolean]
61
+ def skip_request?(request)
62
+ excluded_paths = %w[mini-profiler eyeloupe active_storage] + Eyeloupe.configuration.excluded_paths
63
+
64
+ excluded_paths.each do |item|
65
+ return true if request.path =~ /#{item}/
66
+ end
67
+
68
+ false
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,4 @@
1
+ module Eyeloupe
2
+ # @return [String]
3
+ VERSION = "0.1.0"
4
+ end
data/lib/eyeloupe.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "eyeloupe/version"
2
+ require "eyeloupe/engine"
3
+
4
+ require 'eyeloupe/request_middleware'
5
+ require 'eyeloupe/configuration'
6
+ require 'eyeloupe/http'
7
+ require 'eyeloupe/processors/in_request'
8
+ require 'eyeloupe/processors/out_request'
9
+
10
+ module Eyeloupe
11
+
12
+ # @return [Eyeloupe::Configuration]
13
+ def self.configuration
14
+ Configuration.instance
15
+ end
16
+
17
+ # @yieldparam [Eyeloupe::Configuration] configuration
18
+ def self.configure
19
+ yield(configuration)
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ desc "Compiling TailwindCSS files"
2
+ task :tailwind_watch do
3
+ require "tailwindcss-rails"
4
+ system "#{Tailwindcss::Engine.root.join("exe/tailwindcss")} \
5
+ -i #{Eyeloupe::Engine.root.join("app/assets/stylesheets/application.tailwind.css")} \
6
+ -o #{Eyeloupe::Engine.root.join("app/assets/builds/eyeloupe.css")} \
7
+ -c #{Eyeloupe::Engine.root.join("config/tailwind.config.js")} \
8
+ --minify -w"
9
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eyeloupe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexandre Lion
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-06-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sprockets-rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: importmap-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pagy
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '6.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.6
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.6
83
+ - !ruby/object:Gem::Dependency
84
+ name: tailwindcss-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: turbo-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.1'
111
+ description: The All in one Rails monitoring tool
112
+ email:
113
+ - dev@alexandrelion.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - MIT-LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - app/assets/builds/eyeloupe.css
123
+ - app/assets/config/eyeloupe_manifest.js
124
+ - app/assets/images/eyeloupe/logo.png
125
+ - app/assets/javascripts/eyeloupe/application.js
126
+ - app/assets/javascripts/eyeloupe/controllers/application.js
127
+ - app/assets/javascripts/eyeloupe/controllers/eyeloupe/nav_controller.js
128
+ - app/assets/javascripts/eyeloupe/controllers/eyeloupe/pause_controller.js
129
+ - app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js
130
+ - app/assets/javascripts/eyeloupe/controllers/eyeloupe/search_controller.js
131
+ - app/assets/javascripts/eyeloupe/controllers/index.js
132
+ - app/assets/stylesheets/application.tailwind.css
133
+ - app/assets/stylesheets/eyeloupe/application.css
134
+ - app/controllers/concerns/eyeloupe/searchable.rb
135
+ - app/controllers/eyeloupe/application_controller.rb
136
+ - app/controllers/eyeloupe/configs_controller.rb
137
+ - app/controllers/eyeloupe/data_controller.rb
138
+ - app/controllers/eyeloupe/in_requests_controller.rb
139
+ - app/controllers/eyeloupe/out_requests_controller.rb
140
+ - app/helpers/eyeloupe/application_helper.rb
141
+ - app/jobs/eyeloupe/application_job.rb
142
+ - app/mailers/eyeloupe/application_mailer.rb
143
+ - app/models/eyeloupe/application_record.rb
144
+ - app/models/eyeloupe/in_request.rb
145
+ - app/models/eyeloupe/out_request.rb
146
+ - app/views/eyeloupe/in_requests/_frame.html.erb
147
+ - app/views/eyeloupe/in_requests/index.html.erb
148
+ - app/views/eyeloupe/in_requests/show.html.erb
149
+ - app/views/eyeloupe/out_requests/_frame.html.erb
150
+ - app/views/eyeloupe/out_requests/index.html.erb
151
+ - app/views/eyeloupe/out_requests/show.html.erb
152
+ - app/views/eyeloupe/shared/_status_code.html.erb
153
+ - app/views/eyeloupe/shared/_verb.html.erb
154
+ - app/views/layouts/eyeloupe/application.html.erb
155
+ - config/importmap.rb
156
+ - config/routes.rb
157
+ - config/tailwind.config.js
158
+ - db/migrate/20230518175305_create_eyeloupe_in_requests.rb
159
+ - db/migrate/20230525125352_create_eyeloupe_out_requests.rb
160
+ - lib/eyeloupe.rb
161
+ - lib/eyeloupe/configuration.rb
162
+ - lib/eyeloupe/engine.rb
163
+ - lib/eyeloupe/http.rb
164
+ - lib/eyeloupe/processors/in_request.rb
165
+ - lib/eyeloupe/processors/out_request.rb
166
+ - lib/eyeloupe/request_middleware.rb
167
+ - lib/eyeloupe/version.rb
168
+ - lib/tasks/eyeloupe_tasks.rake
169
+ homepage: https://github.com/alxlion/eyeloupe
170
+ licenses:
171
+ - MIT
172
+ metadata:
173
+ homepage_uri: https://github.com/alxlion/eyeloupe
174
+ source_code_uri: https://github.com/alxlion/eyeloupe
175
+ changelog_uri: https://github.com/alxlion/eyeloupe/blob/master/CHANGELOG.md
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '2.7'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.3.7
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: The elegant Rails debug assistant
195
+ test_files: []