rails_spotlight 0.2.5 → 0.3.1
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/README.md +58 -5
- data/chrome_ext_private_policy.md +7 -3
- data/lib/rails_spotlight/app_notifications.rb +3 -1
- data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
- data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
- data/lib/rails_spotlight/channels/handlers.rb +29 -0
- data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
- data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
- data/lib/rails_spotlight/channels.rb +2 -2
- data/lib/rails_spotlight/configuration.rb +21 -4
- data/lib/rails_spotlight/event.rb +2 -1
- data/lib/rails_spotlight/log_interceptor.rb +37 -29
- data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
- data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
- data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
- data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
- data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
- data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
- data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
- data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
- data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
- data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
- data/lib/rails_spotlight/railtie.rb +12 -4
- data/lib/rails_spotlight/render_view_reporter.rb +3 -1
- data/lib/rails_spotlight/utils.rb +2 -0
- data/lib/rails_spotlight/version.rb +1 -1
- data/lib/tasks/init.rake +75 -6
- metadata +9 -4
- data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
- data/lib/rails_spotlight/channels/request_completed_channel.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9eca5a5464ed6e44b8c65f206f96ead18ff5cc73a0f9395b2b5cc863a28b270
|
4
|
+
data.tar.gz: 4bc0b0e190af921c926a1f08bb43954dee8b3aa488d867c885b4af84c3816ab7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 552d1619a7c990dbce5c52fa883a45bcb5d5d58fa3a81a947fd244badbffcab5516607bb739dc73803d90ababd44755d78b9efe159d6d7d785181cef9e390b59
|
7
|
+
data.tar.gz: 8173b1bfc3514712a350d08c8441552c8091c4b3559357192cde5de84470aee320dc26be1663ca07aa1fcbe7fb48573001d7d9bffce578886b3c5c75302633d3
|
data/README.md
CHANGED
@@ -39,15 +39,21 @@ file will be created in `config/rails_spotlight.yml`
|
|
39
39
|
MIDDLEWARE_SKIPPED_PATHS: []
|
40
40
|
NOT_ENCODABLE_EVENT_VALUES:
|
41
41
|
SKIP_RENDERED_IVARS: []
|
42
|
+
BLOCK_EDITING_FILES: false
|
43
|
+
BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
|
44
|
+
DIRECTORY_INDEX_IGNORE: ['/.git', '**/*.lock', '**/.DS_Store', '/app/assets/images/**', '/app/assets/fonts/**', '/app/assets/builds/**']
|
45
|
+
RUBOCOP_CONFIG_PATH: '.rubocop.yml'
|
42
46
|
# Rest of the configuration is required for ActionCable. It will be disabled automatically in when ActionCable is not available.
|
43
|
-
|
47
|
+
AUTO_MOUNT_ACTION_CABLE: false
|
48
|
+
ACTION_CABLE_MOUNT_PATH: /cable
|
49
|
+
# Required for all action cable features
|
50
|
+
USE_ACTION_CABLE: false
|
44
51
|
LIVE_CONSOLE_ENABLED: false
|
45
52
|
# Experimental feature.
|
46
53
|
REQUEST_COMPLETED_BROADCAST_ENABLED: false
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
|
54
|
+
LIVE_LOGS_ENABLED: false
|
55
|
+
DEFAULT_RS_SRC: default
|
56
|
+
FORM_JS_EXECUTION_TOKEN: <%= Digest::MD5.hexdigest(Rails.application.class.respond_to?(:module_parent_name) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name)%>
|
51
57
|
```
|
52
58
|
|
53
59
|
## Additional metrics
|
@@ -60,6 +66,53 @@ To enable additional rendering metrics like local variables, instance variables,
|
|
60
66
|
<% end %>
|
61
67
|
```
|
62
68
|
|
69
|
+
## Experimental features
|
70
|
+
Live logs requires action cable to be enabled.
|
71
|
+
|
72
|
+
USE_ACTION_CABLE: true
|
73
|
+
LIVE_LOGS_ENABLED: true
|
74
|
+
|
75
|
+
When you want to use different sources for the cable logs you need to update `cable.yml` file.
|
76
|
+
|
77
|
+
```yaml
|
78
|
+
development:
|
79
|
+
adapter: redis
|
80
|
+
url: redis://localhost:6379/1
|
81
|
+
```
|
82
|
+
**Note:** Redis is required for live logs. so just add it to your Gemfile and run `bundle install`
|
83
|
+
|
84
|
+
modify yor `development.rb` file
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
config.action_cable.allowed_request_origins = ['chrome-extension://chjfnpmbgdbipfogflkhleaceacndaop' ]
|
88
|
+
```
|
89
|
+
|
90
|
+
Now just run your servers with environment variable RS_SRC=my_source_name
|
91
|
+
like `RS_SRC=sidekiq bundle exec sidekiq -C config/sidekiq.yml`
|
92
|
+
or `RS_SRC=web bundle exec puma -C config/puma.rb`
|
93
|
+
|
94
|
+
---
|
95
|
+
|
96
|
+
Forms (Filling forms with scenarios)
|
97
|
+
|
98
|
+
To be able use full potential of the forms you need to generate partial that helps with js code injection.
|
99
|
+
|
100
|
+
Just use `rails rails_spotlight:inject_js_partial` and inject partial to your layout file as instructed in past generation message.
|
101
|
+
setup your `FORM_JS_EXECUTION_TOKEN` or use pregnerated one in your extension settings.
|
102
|
+
|
103
|
+
# Advanced log configuration
|
104
|
+
You can add to your Initializers `config/initializers/rails_spotlight.rb` file with the additional configuration for the logger.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
unless Rails.env.production?
|
108
|
+
# Publish logs to the spotlight (only when implementation is based on the Rails.logger)
|
109
|
+
defined?(Logger) && Logger&.extend(RailsSpotlight::LogInterceptor)
|
110
|
+
# Publish Hutch logs to the spotlight
|
111
|
+
defined?(Hutch::Logging) && Hutch::Logging.logger&.extend(RailsSpotlight::LogInterceptor)
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
|
63
116
|
## Troubleshooting
|
64
117
|
|
65
118
|
Known issue:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
## Privacy Policy for the Rails Spotlight Chrome Extension
|
2
2
|
|
3
|
-
**Last Updated:**
|
3
|
+
**Last Updated:** Aug 18, 2024
|
4
4
|
|
5
5
|
### 1. Introduction
|
6
6
|
|
@@ -30,10 +30,14 @@ We do not sell or share your data with third parties for their promotional purpo
|
|
30
30
|
|
31
31
|
We are committed to ensuring that your information is secure. To prevent unauthorized access or disclosure, we have put in place suitable physical, electronic, and managerial procedures to safeguard and secure the information we collect.
|
32
32
|
|
33
|
-
### 7.
|
33
|
+
### 7. Subscription and Refund Policy
|
34
|
+
|
35
|
+
By purchasing a membership for Rails Spotlight, you gain access to advanced features and contribute to the ongoing development of the tool. Please note that all memberships are non-refundable. Once purchased, no refunds will be issued, so we encourage you to carefully consider your decision before completing the purchase. However, you can cancel your subscription at any time if you no longer wish to use the service. Cancellation will prevent any future charges, but it will not refund the current or past payments.
|
36
|
+
|
37
|
+
### 8. Changes to This Privacy Policy
|
34
38
|
|
35
39
|
We may update our Privacy Policy from time to time. Thus, we advise you to review this document periodically for any changes. We will notify you of any changes by posting the new Privacy Policy in this location. Changes are effective immediately after they are posted.
|
36
40
|
|
37
|
-
###
|
41
|
+
### 9. Contact Us
|
38
42
|
|
39
43
|
If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.
|
@@ -87,7 +87,9 @@ module RailsSpotlight
|
|
87
87
|
# send_file.action_controller: Triggered when a file is sent as a response.
|
88
88
|
# redirect_to.action_controller: Triggered when a redirect response is sent.
|
89
89
|
# halted_callback.action_controller: Triggered when a filter or callback halts the request.
|
90
|
-
# render_collection.action_view: This event is triggered when a collection is rendered using a partial.
|
90
|
+
# render_collection.action_view: This event is triggered when a collection is rendered using a partial.
|
91
|
+
# It includes details about the collection being rendered,
|
92
|
+
# such as the collection name and the partial being used to render each item.
|
91
93
|
end
|
92
94
|
|
93
95
|
def subscribe(event_name)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../rails_command_executor'
|
4
|
+
|
5
|
+
module RailsSpotlight
|
6
|
+
module Channels
|
7
|
+
module Handlers
|
8
|
+
class LiveConsoleHandler
|
9
|
+
TYPE = 'console'
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :data
|
16
|
+
|
17
|
+
def call
|
18
|
+
return unless ::RailsSpotlight.config.live_console_enabled?
|
19
|
+
return unless data['type'] == TYPE
|
20
|
+
|
21
|
+
command = data['command']
|
22
|
+
inspect_types = data['inspect_types']
|
23
|
+
for_project = Array(data['project'])
|
24
|
+
|
25
|
+
raise_project_mismatch_error!(for_project) if for_project.present? && !for_project.include?(project)
|
26
|
+
|
27
|
+
execute_command(command, inspect_types)
|
28
|
+
end
|
29
|
+
|
30
|
+
def executor
|
31
|
+
@executor ||= ::RailsSpotlight::RailsCommandExecutor.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def raise_project_mismatch_error!(for_project)
|
35
|
+
raise ::RailsSpotlight::Channels::Handlers::ResponseError.new(
|
36
|
+
"Project mismatch, The command was intended for the #{for_project} project. This is #{project} project",
|
37
|
+
code: :project_mismatch
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_command(command, inspect_types)
|
42
|
+
RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing command: #{command}") # rubocop:disable Style/SafeNavigation
|
43
|
+
|
44
|
+
executor.execute(command)
|
45
|
+
if executor.execution_successful?
|
46
|
+
{
|
47
|
+
payload: { result: executor.result_as_json(inspect_types: inspect_types) },
|
48
|
+
}
|
49
|
+
else
|
50
|
+
{
|
51
|
+
payload: { failed: executor.result_as_json }
|
52
|
+
}
|
53
|
+
end
|
54
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
55
|
+
{ error: e.message }
|
56
|
+
end
|
57
|
+
|
58
|
+
def project
|
59
|
+
::RailsSpotlight.config.project_name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsSpotlight
|
4
|
+
module Channels
|
5
|
+
module Handlers
|
6
|
+
class LogsHandler
|
7
|
+
TYPE = 'logs'
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :data
|
14
|
+
|
15
|
+
def call
|
16
|
+
return unless ::RailsSpotlight.config.live_console_enabled?
|
17
|
+
return unless data['type'] == TYPE
|
18
|
+
|
19
|
+
for_project = Array(data['project'])
|
20
|
+
raise_project_mismatch_error!(for_project) if for_project.present? && !for_project.include?(project)
|
21
|
+
|
22
|
+
{ payload: data[:payload] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def raise_project_mismatch_error!(for_project)
|
26
|
+
raise ::RailsSpotlight::Channels::Handlers::ResponseError.new(
|
27
|
+
"Project mismatch, Logs from #{for_project} project cannot be forwarded in #{project} project",
|
28
|
+
code: :project_mismatch
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def project
|
33
|
+
::RailsSpotlight.config.project_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'handlers/live_console_handler'
|
4
|
+
require_relative 'handlers/logs_handler'
|
5
|
+
|
6
|
+
module RailsSpotlight
|
7
|
+
module Channels
|
8
|
+
module Handlers
|
9
|
+
ResponseError = Class.new(StandardError) do
|
10
|
+
def initialize(message, code: :error)
|
11
|
+
@code = code
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :code
|
16
|
+
end
|
17
|
+
TYPES = [LiveConsoleHandler::TYPE, LogsHandler::TYPE].freeze
|
18
|
+
|
19
|
+
def self.handle(data)
|
20
|
+
case data['type']
|
21
|
+
when LiveConsoleHandler::TYPE then LiveConsoleHandler.new(data).call
|
22
|
+
# when LogsHandler::TYPE then LogsHandler.new(data).call
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsSpotlight
|
4
|
+
module Channels
|
5
|
+
module SilenceActionCableBroadcasterLogging
|
6
|
+
refine ActionCable::Server::Broadcasting::Broadcaster do
|
7
|
+
def broadcast(message)
|
8
|
+
if broadcasting == RailsSpotlight::Channels::SpotlightChannel::SPOTLIGHT_CHANNEL
|
9
|
+
original_logger = server.logger
|
10
|
+
begin
|
11
|
+
# Replace the logger with a no-op logger to silence the log
|
12
|
+
server.logger = ActiveSupport::Logger.new(nil)
|
13
|
+
super(message)
|
14
|
+
ensure
|
15
|
+
# Restore the original logger after broadcasting
|
16
|
+
server.logger = original_logger
|
17
|
+
end
|
18
|
+
else
|
19
|
+
super(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'handlers'
|
4
|
+
# require_relative 'silence_action_cable_broadcaster_logging'
|
5
|
+
|
6
|
+
module RailsSpotlight
|
7
|
+
module Channels
|
8
|
+
class SpotlightChannel < ActionCable::Channel::Base
|
9
|
+
def self.broadcast(attrs = {})
|
10
|
+
broadcasting = ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL
|
11
|
+
message = {
|
12
|
+
type: attrs[:type],
|
13
|
+
code: attrs[:code] || 'ok',
|
14
|
+
project: ::RailsSpotlight.config.project_name,
|
15
|
+
version: ::RailsSpotlight::VERSION,
|
16
|
+
payload: attrs[:payload] || {}
|
17
|
+
}
|
18
|
+
coder = ::ActiveSupport::JSON
|
19
|
+
encoded = coder ? coder.encode(message) : message
|
20
|
+
ActionCable.server.pubsub.broadcast(broadcasting, encoded)
|
21
|
+
# We do not use the following code because it is triggering logs and can cause an infinite loop
|
22
|
+
# ActionCable.server.broadcast(
|
23
|
+
# ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL,
|
24
|
+
# {
|
25
|
+
# type: attrs[:type],
|
26
|
+
# code: attrs[:code] || 'ok',
|
27
|
+
# project: ::RailsSpotlight.config.project_name,
|
28
|
+
# version: ::RailsSpotlight::VERSION,
|
29
|
+
# payload: attrs[:payload] || {}
|
30
|
+
# }
|
31
|
+
# )
|
32
|
+
rescue StandardError => e
|
33
|
+
RailsSpotlight.config.logger.fatal("#{e.message}\n #{e.backtrace.join("\n ")}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def subscribed
|
37
|
+
stream_from ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL
|
38
|
+
publish({ message: "Your #{project} project is now connected to the spotlight channel.", code: :connected, type: :info })
|
39
|
+
end
|
40
|
+
|
41
|
+
def unsubscribed
|
42
|
+
# Any cleanup needed when channel is unsubscribed
|
43
|
+
end
|
44
|
+
|
45
|
+
def receive(data)
|
46
|
+
return publish({ message: 'Unknown type of request', code: :unknown_type, type: :error }) unless Handlers::TYPES.include?(data['type'])
|
47
|
+
|
48
|
+
result = Handlers.handle(data)
|
49
|
+
publish({ payload: result[:payload], code: result[:code] || :ok, type: data['type'] }) if result[:payload]
|
50
|
+
rescue ::RailsSpotlight::Channels::Handlers::ResponseError => e
|
51
|
+
publish({ message: e.message, code: e.code, type: :error })
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def publish(data)
|
57
|
+
connection.transmit identifier: @identifier, message: data.merge(project: project, version: version)
|
58
|
+
# we do not use transmit because it is triggering logs and can cause an infinite loop
|
59
|
+
# transmit(data.merge(project: project, version: version))
|
60
|
+
end
|
61
|
+
|
62
|
+
def project
|
63
|
+
::RailsSpotlight.config.project_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def version
|
67
|
+
::RailsSpotlight::VERSION
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module RailsSpotlight
|
4
4
|
module Channels
|
5
|
-
|
6
|
-
autoload(:
|
5
|
+
SPOTLIGHT_CHANNEL = 'RailsSpotlight::Channels::SpotlightChannel'.freeze
|
6
|
+
autoload(:SpotlightChannel, 'rails_spotlight/channels/spotlight_channel') if defined?(ActionCable)
|
7
7
|
end
|
8
8
|
end
|
@@ -10,6 +10,10 @@ module RailsSpotlight
|
|
10
10
|
'ActionDispatch' => ['ActionDispatch::Request', 'ActionDispatch::Response']
|
11
11
|
}.freeze
|
12
12
|
|
13
|
+
DEFAULT_DIRECTORY_INDEX_IGNORE = %w[
|
14
|
+
/.git **/*.lock **/.DS_Store /app/assets/images/** /app/assets/fonts/** /app/assets/builds/** **/.keep
|
15
|
+
].freeze
|
16
|
+
|
13
17
|
SKIP_RENDERED_IVARS = %i[
|
14
18
|
@_routes
|
15
19
|
@_config
|
@@ -32,7 +36,9 @@ module RailsSpotlight
|
|
32
36
|
|
33
37
|
attr_reader :project_name, :source_path, :logger, :storage_path, :storage_pool_size, :middleware_skipped_paths,
|
34
38
|
:not_encodable_event_values, :action_cable_mount_path,
|
35
|
-
:block_editing_files, :block_editing_files_outside_of_the_project, :skip_rendered_ivars
|
39
|
+
:block_editing_files, :block_editing_files_outside_of_the_project, :skip_rendered_ivars,
|
40
|
+
:directory_index_ignore, :rubocop_config_path, :use_action_cable, :default_rs_src,
|
41
|
+
:form_js_execution_token
|
36
42
|
|
37
43
|
def initialize(opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
38
44
|
@project_name = opts[:project_name] || detect_project_name
|
@@ -44,27 +50,38 @@ module RailsSpotlight
|
|
44
50
|
@request_completed_broadcast_enabled = opts[:request_completed_broadcast_enabled].nil? ? false : true?(opts[:request_completed_broadcast_enabled])
|
45
51
|
@middleware_skipped_paths = opts[:middleware_skipped_paths] || []
|
46
52
|
@not_encodable_event_values = DEFAULT_NOT_ENCODABLE_EVENT_VALUES.merge(opts[:not_encodable_event_values] || {})
|
53
|
+
@use_action_cable = opts[:use_action_cable].nil? ? false : true?(opts[:use_action_cable])
|
47
54
|
@auto_mount_action_cable = opts[:auto_mount_action_cable].nil? ? false : true?(opts[:auto_mount_action_cable])
|
48
55
|
@action_cable_mount_path = opts[:action_cable_mount_path] || '/cable'
|
49
56
|
@block_editing_files = opts[:block_editing_files].nil? ? false : true?(opts[:block_editing_files])
|
50
57
|
@block_editing_files_outside_of_the_project = opts[:block_editing_files_outside_of_the_project].nil? ? true : true?(opts[:block_editing_files_outside_of_the_project])
|
51
58
|
@skip_rendered_ivars = SKIP_RENDERED_IVARS + (opts[:skip_rendered_ivars] || []).map(&:to_sym)
|
59
|
+
@directory_index_ignore = opts[:directory_index_ignore] || DEFAULT_DIRECTORY_INDEX_IGNORE
|
60
|
+
@rubocop_config_path = opts[:rubocop_config_path] ? File.join(self.class.rails_root, opts[:rubocop_config_path]) : nil
|
61
|
+
@live_logs_enabled = opts[:live_logs_enabled].nil? ? false : true?(opts[:live_logs_enabled])
|
62
|
+
@default_rs_src = opts[:default_rs_src] || 'default'
|
63
|
+
@form_js_execution_token = opts[:form_js_execution_token] || Digest::MD5.hexdigest(detect_project_name)
|
52
64
|
end
|
53
65
|
|
54
66
|
def live_console_enabled
|
55
|
-
@live_console_enabled && action_cable_present?
|
67
|
+
@live_console_enabled && use_action_cable && action_cable_present?
|
68
|
+
end
|
69
|
+
|
70
|
+
def live_logs_enabled
|
71
|
+
@live_logs_enabled && use_action_cable && action_cable_present?
|
56
72
|
end
|
57
73
|
|
58
74
|
alias live_console_enabled? live_console_enabled
|
75
|
+
alias live_logs_enabled? live_logs_enabled
|
59
76
|
|
60
77
|
def request_completed_broadcast_enabled
|
61
|
-
@request_completed_broadcast_enabled && action_cable_present?
|
78
|
+
@request_completed_broadcast_enabled && use_action_cable && action_cable_present?
|
62
79
|
end
|
63
80
|
|
64
81
|
alias request_completed_broadcast_enabled? request_completed_broadcast_enabled
|
65
82
|
|
66
83
|
def auto_mount_action_cable
|
67
|
-
@auto_mount_action_cable && action_cable_present?
|
84
|
+
@auto_mount_action_cable && use_action_cable && action_cable_present?
|
68
85
|
end
|
69
86
|
|
70
87
|
alias auto_mount_action_cable? auto_mount_action_cable
|
@@ -57,8 +57,9 @@ module RailsSpotlight
|
|
57
57
|
def not_encodable?(value)
|
58
58
|
::RailsSpotlight.config.not_encodable_event_values.any? do |module_name, class_names|
|
59
59
|
next unless defined?(module_name.constantize)
|
60
|
-
|
61
60
|
class_names.any? { |class_name| value.is_a?(class_name.constantize) }
|
61
|
+
rescue => e
|
62
|
+
puts "Error in not_encodable? method: #{e.message}"
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
@@ -1,45 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'channels'
|
4
|
+
|
3
5
|
module RailsSpotlight
|
4
6
|
module LogInterceptor
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
SEVERITY = %w[debug info warn error fatal unknown].freeze
|
8
|
+
SEVERITY_MAP = { 0 => 'debug', 1 => 'info', 2 => 'warn', 3 => 'error', 4 => 'fatal', 5 => 'unknown' }.freeze
|
9
|
+
|
10
|
+
def add(severity, message = nil, progname = nil)
|
11
|
+
severity ||= 5
|
12
|
+
return true if @logdev.nil? || severity < level
|
13
|
+
|
14
|
+
progname = @progname if progname.nil?
|
15
|
+
|
16
|
+
if message.nil?
|
17
|
+
if block_given?
|
18
|
+
message = yield
|
19
|
+
else
|
20
|
+
message = progname
|
21
|
+
progname = @progname
|
22
|
+
end
|
23
|
+
end
|
24
|
+
return true if _skip_logging?(message)
|
9
25
|
|
10
|
-
|
11
|
-
|
12
|
-
|
26
|
+
_push_event(SEVERITY_MAP[severity], message, progname)
|
27
|
+
super(severity, message, progname) if defined?(super)
|
28
|
+
true
|
13
29
|
end
|
14
30
|
|
15
|
-
|
16
|
-
push_event(:warn, message)
|
17
|
-
super
|
18
|
-
end
|
31
|
+
private
|
19
32
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
end
|
33
|
+
def _skip_logging?(message)
|
34
|
+
return false unless ::RailsSpotlight.config.live_console_enabled?
|
35
|
+
return false unless message.is_a?(String)
|
24
36
|
|
25
|
-
|
26
|
-
push_event(:fatal, message)
|
27
|
-
super
|
37
|
+
message.include?(::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL)
|
28
38
|
end
|
29
39
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
def _push_event(level, message, progname = nil)
|
41
|
+
callsite = Utils.dev_callsite(caller.drop(1))
|
42
|
+
name = progname.is_a?(String) || progname.is_a?(Symbol) ? progname : nil
|
43
|
+
AppRequest.current.events << Event.new('rsl.notification.log', 0, 0, 0, callsite.merge(message: message, level: level, progname: name)) if AppRequest.current && callsite
|
34
44
|
|
35
|
-
|
45
|
+
return unless ::RailsSpotlight.config.live_console_enabled?
|
46
|
+
return if message.blank?
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
payload = callsite.merge(message: message, level: level)
|
41
|
-
AppRequest.current.events << Event.new('rsl.notification.log', 0, 0, 0, payload)
|
42
|
-
end
|
48
|
+
id = AppRequest.current ? AppRequest.current.id : nil
|
49
|
+
payload = (callsite || {}).merge(msg: message, src: ENV['RS_SRC'] || ::RailsSpotlight.config.default_rs_src, l: level, dt: Time.now.to_f, id: id, pg: name)
|
50
|
+
::RailsSpotlight::Channels::SpotlightChannel.broadcast(type: 'logs', payload: payload)
|
43
51
|
rescue StandardError => e
|
44
52
|
RailsSpotlight.config.logger.fatal("#{e.message}\n #{e.backtrace.join("\n ")}")
|
45
53
|
end
|
@@ -7,6 +7,7 @@ module RailsSpotlight
|
|
7
7
|
IncorrectResponseContentType = Class.new(StandardError)
|
8
8
|
NotFound = Class.new(StandardError)
|
9
9
|
UnprocessableEntity = Class.new(StandardError)
|
10
|
+
Forbidden = Class.new(StandardError)
|
10
11
|
|
11
12
|
def initialize(request_id, request, content_type)
|
12
13
|
@request_id = request_id
|
@@ -17,12 +18,15 @@ module RailsSpotlight
|
|
17
18
|
attr_reader :request_id, :request, :content_type
|
18
19
|
|
19
20
|
def call
|
21
|
+
validate_project! unless skip_project_validation?
|
20
22
|
execute
|
21
23
|
response
|
22
24
|
rescue NotFound => e
|
23
25
|
not_found_response(e.message)
|
24
26
|
rescue UnprocessableEntity => e
|
25
27
|
unprocessed_response(e.message)
|
28
|
+
rescue Forbidden => e
|
29
|
+
forbidden_response(e.message)
|
26
30
|
rescue => e # rubocop:disable Style/RescueStandardError
|
27
31
|
internal_server_error_response(e.message)
|
28
32
|
end
|
@@ -31,6 +35,10 @@ module RailsSpotlight
|
|
31
35
|
|
32
36
|
attr_writer :status, :headers
|
33
37
|
|
38
|
+
def skip_project_validation?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
34
42
|
def headers
|
35
43
|
@headers ||= {}
|
36
44
|
end
|
@@ -57,6 +65,10 @@ module RailsSpotlight
|
|
57
65
|
raise 'Invalid JSON'
|
58
66
|
end
|
59
67
|
|
68
|
+
def body_fetch(*args)
|
69
|
+
json_request_body.fetch(*args)
|
70
|
+
end
|
71
|
+
|
60
72
|
def internal_server_error_response(message)
|
61
73
|
response(500, message_to_body(message))
|
62
74
|
end
|
@@ -65,6 +77,10 @@ module RailsSpotlight
|
|
65
77
|
response(422, message_to_body(message))
|
66
78
|
end
|
67
79
|
|
80
|
+
def forbidden_response(message)
|
81
|
+
response(403, message_to_body(message))
|
82
|
+
end
|
83
|
+
|
68
84
|
def not_found_response(message)
|
69
85
|
response(404, message_to_body(message))
|
70
86
|
end
|
@@ -76,7 +92,8 @@ module RailsSpotlight
|
|
76
92
|
def response_headers(headers = {})
|
77
93
|
{
|
78
94
|
'Content-Type' => content_type == :json ? 'application/json; charset=utf-8' : 'text/plain; charset=utf-8',
|
79
|
-
'X-Rails-Spotlight' =>
|
95
|
+
'X-Rails-Spotlight' => ::RailsSpotlight::VERSION,
|
96
|
+
'X-Rails-Spotlight-Project' => ::RailsSpotlight.config.project_name,
|
80
97
|
'X-Request-Id' => request_id
|
81
98
|
}.merge(headers)
|
82
99
|
end
|
@@ -85,10 +102,25 @@ module RailsSpotlight
|
|
85
102
|
body = if overridden_body.present?
|
86
103
|
content_type == :json ? overridden_body.to_json : overridden_body
|
87
104
|
else
|
88
|
-
content_type == :json ? json_response_body.to_json : text_response_body
|
105
|
+
content_type == :json ? json_response_body.merge({ project: ::RailsSpotlight.config.project_name }).to_json : text_response_body
|
89
106
|
end
|
90
107
|
[overridden_status.present? ? overridden_status : status, response_headers(headers), [body]]
|
91
108
|
end
|
109
|
+
|
110
|
+
def request_spotlight_version
|
111
|
+
@request_spotlight_version ||= request.get_header('HTTP_X_RAILS_SPOTLIGHT')
|
112
|
+
end
|
113
|
+
|
114
|
+
def request_for_projects
|
115
|
+
@request_for_projects ||= (request.get_header('HTTP_X_FOR_PROJECTS') || '').split(',').map(&:strip)
|
116
|
+
end
|
117
|
+
|
118
|
+
def validate_project!
|
119
|
+
return if request_for_projects.blank?
|
120
|
+
return if request_for_projects.include?(::RailsSpotlight.config.project_name)
|
121
|
+
|
122
|
+
raise Forbidden, "Check your settings the current request is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
|
123
|
+
end
|
92
124
|
end
|
93
125
|
end
|
94
126
|
end
|