brainzlab 0.1.2 → 0.1.3
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/LICENSE +6 -21
- data/README.md +16 -2
- data/lib/brainzlab/beacon/client.rb +38 -40
- data/lib/brainzlab/beacon/provisioner.rb +1 -1
- data/lib/brainzlab/beacon.rb +15 -15
- data/lib/brainzlab/configuration.rb +92 -90
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/client.rb +29 -31
- data/lib/brainzlab/cortex/provisioner.rb +1 -1
- data/lib/brainzlab/cortex.rb +7 -11
- data/lib/brainzlab/dendrite/client.rb +42 -44
- data/lib/brainzlab/dendrite/provisioner.rb +1 -1
- data/lib/brainzlab/dendrite.rb +4 -4
- data/lib/brainzlab/devtools/data/collector.rb +22 -22
- data/lib/brainzlab/devtools/middleware/asset_server.rb +14 -14
- data/lib/brainzlab/devtools/middleware/database_handler.rb +52 -55
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +19 -19
- data/lib/brainzlab/devtools/middleware/error_page.rb +45 -44
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +39 -35
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +13 -9
- data/lib/brainzlab/devtools.rb +11 -11
- data/lib/brainzlab/flux/buffer.rb +3 -3
- data/lib/brainzlab/flux/client.rb +14 -16
- data/lib/brainzlab/flux/provisioner.rb +13 -13
- data/lib/brainzlab/flux.rb +8 -8
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/active_record.rb +13 -15
- data/lib/brainzlab/instrumentation/aws.rb +43 -39
- data/lib/brainzlab/instrumentation/dalli.rb +20 -20
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +27 -27
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +28 -28
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +23 -24
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +37 -41
- data/lib/brainzlab/instrumentation/stripe.rb +36 -37
- data/lib/brainzlab/instrumentation/typhoeus.rb +19 -17
- data/lib/brainzlab/instrumentation.rb +20 -20
- data/lib/brainzlab/nerve/client.rb +38 -40
- data/lib/brainzlab/nerve/provisioner.rb +1 -1
- data/lib/brainzlab/nerve.rb +6 -6
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +61 -57
- data/lib/brainzlab/pulse/propagation.rb +28 -28
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +3 -3
- data/lib/brainzlab/pulse.rb +13 -13
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -76
- data/lib/brainzlab/rails/railtie.rb +66 -89
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +16 -16
- data/lib/brainzlab/recall.rb +11 -13
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +29 -29
- data/lib/brainzlab/sentinel/client.rb +40 -42
- data/lib/brainzlab/sentinel/provisioner.rb +1 -1
- data/lib/brainzlab/sentinel.rb +5 -5
- data/lib/brainzlab/signal/client.rb +12 -14
- data/lib/brainzlab/signal/provisioner.rb +12 -12
- data/lib/brainzlab/signal.rb +7 -7
- data/lib/brainzlab/synapse/client.rb +42 -44
- data/lib/brainzlab/synapse/provisioner.rb +1 -1
- data/lib/brainzlab/synapse.rb +6 -6
- data/lib/brainzlab/utilities/circuit_breaker.rb +37 -41
- data/lib/brainzlab/utilities/health_check.rb +53 -55
- data/lib/brainzlab/utilities/log_formatter.rb +38 -40
- data/lib/brainzlab/utilities/rate_limiter.rb +5 -5
- data/lib/brainzlab/utilities.rb +4 -4
- data/lib/brainzlab/vault/cache.rb +1 -1
- data/lib/brainzlab/vault/client.rb +39 -41
- data/lib/brainzlab/vault/provisioner.rb +1 -1
- data/lib/brainzlab/vault.rb +19 -25
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +20 -20
- data/lib/brainzlab/vision/provisioner.rb +21 -21
- data/lib/brainzlab/vision.rb +17 -19
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +22 -24
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- metadata +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'cgi'
|
|
7
7
|
|
|
8
8
|
module BrainzLab
|
|
9
9
|
module Dendrite
|
|
10
10
|
class Client
|
|
11
11
|
def initialize(config)
|
|
12
12
|
@config = config
|
|
13
|
-
@base_url = config.dendrite_url ||
|
|
13
|
+
@base_url = config.dendrite_url || 'https://dendrite.brainzlab.ai'
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Connect a repository
|
|
17
|
-
def connect_repository(url:, name: nil, branch:
|
|
17
|
+
def connect_repository(url:, name: nil, branch: 'main', **options)
|
|
18
18
|
response = request(
|
|
19
19
|
:post,
|
|
20
|
-
|
|
20
|
+
'/api/v1/repositories',
|
|
21
21
|
body: {
|
|
22
22
|
url: url,
|
|
23
23
|
name: name,
|
|
@@ -30,7 +30,7 @@ module BrainzLab
|
|
|
30
30
|
|
|
31
31
|
JSON.parse(response.body, symbolize_names: true)
|
|
32
32
|
rescue StandardError => e
|
|
33
|
-
log_error(
|
|
33
|
+
log_error('connect_repository', e)
|
|
34
34
|
nil
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -39,7 +39,7 @@ module BrainzLab
|
|
|
39
39
|
response = request(:post, "/api/v1/repositories/#{repo_id}/sync")
|
|
40
40
|
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
41
41
|
rescue StandardError => e
|
|
42
|
-
log_error(
|
|
42
|
+
log_error('sync_repository', e)
|
|
43
43
|
false
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -51,20 +51,20 @@ module BrainzLab
|
|
|
51
51
|
|
|
52
52
|
JSON.parse(response.body, symbolize_names: true)
|
|
53
53
|
rescue StandardError => e
|
|
54
|
-
log_error(
|
|
54
|
+
log_error('get_repository', e)
|
|
55
55
|
nil
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# List repositories
|
|
59
59
|
def list_repositories
|
|
60
|
-
response = request(:get,
|
|
60
|
+
response = request(:get, '/api/v1/repositories')
|
|
61
61
|
|
|
62
62
|
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
63
63
|
|
|
64
64
|
data = JSON.parse(response.body, symbolize_names: true)
|
|
65
65
|
data[:repositories] || []
|
|
66
66
|
rescue StandardError => e
|
|
67
|
-
log_error(
|
|
67
|
+
log_error('list_repositories', e)
|
|
68
68
|
[]
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -76,7 +76,7 @@ module BrainzLab
|
|
|
76
76
|
|
|
77
77
|
JSON.parse(response.body, symbolize_names: true)
|
|
78
78
|
rescue StandardError => e
|
|
79
|
-
log_error(
|
|
79
|
+
log_error('get_wiki', e)
|
|
80
80
|
nil
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -88,7 +88,7 @@ module BrainzLab
|
|
|
88
88
|
|
|
89
89
|
JSON.parse(response.body, symbolize_names: true)
|
|
90
90
|
rescue StandardError => e
|
|
91
|
-
log_error(
|
|
91
|
+
log_error('get_wiki_page', e)
|
|
92
92
|
nil
|
|
93
93
|
end
|
|
94
94
|
|
|
@@ -96,7 +96,7 @@ module BrainzLab
|
|
|
96
96
|
def search(repo_id, query, limit: 10)
|
|
97
97
|
response = request(
|
|
98
98
|
:get,
|
|
99
|
-
|
|
99
|
+
'/api/v1/search',
|
|
100
100
|
params: { repo_id: repo_id, q: query, limit: limit }
|
|
101
101
|
)
|
|
102
102
|
|
|
@@ -105,7 +105,7 @@ module BrainzLab
|
|
|
105
105
|
data = JSON.parse(response.body, symbolize_names: true)
|
|
106
106
|
data[:results] || []
|
|
107
107
|
rescue StandardError => e
|
|
108
|
-
log_error(
|
|
108
|
+
log_error('search', e)
|
|
109
109
|
[]
|
|
110
110
|
end
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ module BrainzLab
|
|
|
113
113
|
def ask(repo_id, question, session_id: nil)
|
|
114
114
|
response = request(
|
|
115
115
|
:post,
|
|
116
|
-
|
|
116
|
+
'/api/v1/chat',
|
|
117
117
|
body: {
|
|
118
118
|
repo_id: repo_id,
|
|
119
119
|
question: question,
|
|
@@ -125,7 +125,7 @@ module BrainzLab
|
|
|
125
125
|
|
|
126
126
|
JSON.parse(response.body, symbolize_names: true)
|
|
127
127
|
rescue StandardError => e
|
|
128
|
-
log_error(
|
|
128
|
+
log_error('ask', e)
|
|
129
129
|
nil
|
|
130
130
|
end
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ module BrainzLab
|
|
|
133
133
|
def explain(repo_id, path, symbol: nil)
|
|
134
134
|
response = request(
|
|
135
135
|
:post,
|
|
136
|
-
|
|
136
|
+
'/api/v1/explain',
|
|
137
137
|
body: {
|
|
138
138
|
repo_id: repo_id,
|
|
139
139
|
path: path,
|
|
@@ -145,7 +145,7 @@ module BrainzLab
|
|
|
145
145
|
|
|
146
146
|
JSON.parse(response.body, symbolize_names: true)
|
|
147
147
|
rescue StandardError => e
|
|
148
|
-
log_error(
|
|
148
|
+
log_error('explain', e)
|
|
149
149
|
nil
|
|
150
150
|
end
|
|
151
151
|
|
|
@@ -153,10 +153,10 @@ module BrainzLab
|
|
|
153
153
|
def generate_diagram(repo_id, type:, scope: nil)
|
|
154
154
|
response = request(
|
|
155
155
|
:post,
|
|
156
|
-
|
|
156
|
+
'/api/v1/diagrams',
|
|
157
157
|
body: {
|
|
158
158
|
repo_id: repo_id,
|
|
159
|
-
type: type,
|
|
159
|
+
type: type, # class, er, sequence, architecture
|
|
160
160
|
scope: scope
|
|
161
161
|
}
|
|
162
162
|
)
|
|
@@ -165,21 +165,21 @@ module BrainzLab
|
|
|
165
165
|
|
|
166
166
|
JSON.parse(response.body, symbolize_names: true)
|
|
167
167
|
rescue StandardError => e
|
|
168
|
-
log_error(
|
|
168
|
+
log_error('generate_diagram', e)
|
|
169
169
|
nil
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def provision(project_id:, app_name:)
|
|
173
173
|
response = request(
|
|
174
174
|
:post,
|
|
175
|
-
|
|
175
|
+
'/api/v1/projects/provision',
|
|
176
176
|
body: { project_id: project_id, app_name: app_name },
|
|
177
177
|
use_service_key: true
|
|
178
178
|
)
|
|
179
179
|
|
|
180
180
|
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
181
181
|
rescue StandardError => e
|
|
182
|
-
log_error(
|
|
182
|
+
log_error('provision', e)
|
|
183
183
|
false
|
|
184
184
|
end
|
|
185
185
|
|
|
@@ -188,34 +188,32 @@ module BrainzLab
|
|
|
188
188
|
def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
|
|
189
189
|
uri = URI.parse("#{@base_url}#{path}")
|
|
190
190
|
|
|
191
|
-
if params
|
|
192
|
-
uri.query = URI.encode_www_form(params)
|
|
193
|
-
end
|
|
191
|
+
uri.query = URI.encode_www_form(params) if params
|
|
194
192
|
|
|
195
193
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
196
|
-
http.use_ssl = uri.scheme ==
|
|
194
|
+
http.use_ssl = uri.scheme == 'https'
|
|
197
195
|
http.open_timeout = 10
|
|
198
|
-
http.read_timeout = 60
|
|
196
|
+
http.read_timeout = 60 # Longer timeout for AI operations
|
|
199
197
|
|
|
200
198
|
request = case method
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
request[
|
|
212
|
-
request[
|
|
199
|
+
when :get
|
|
200
|
+
Net::HTTP::Get.new(uri)
|
|
201
|
+
when :post
|
|
202
|
+
Net::HTTP::Post.new(uri)
|
|
203
|
+
when :put
|
|
204
|
+
Net::HTTP::Put.new(uri)
|
|
205
|
+
when :delete
|
|
206
|
+
Net::HTTP::Delete.new(uri)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
request['Content-Type'] = 'application/json'
|
|
210
|
+
request['Accept'] = 'application/json'
|
|
213
211
|
|
|
214
212
|
if use_service_key
|
|
215
|
-
request[
|
|
213
|
+
request['X-Service-Key'] = @config.dendrite_master_key || @config.secret_key
|
|
216
214
|
else
|
|
217
215
|
auth_key = @config.dendrite_api_key || @config.secret_key
|
|
218
|
-
request[
|
|
216
|
+
request['Authorization'] = "Bearer #{auth_key}" if auth_key
|
|
219
217
|
end
|
|
220
218
|
|
|
221
219
|
headers.each { |k, v| request[k] = v }
|
data/lib/brainzlab/dendrite.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative 'dendrite/client'
|
|
4
|
+
require_relative 'dendrite/provisioner'
|
|
5
5
|
|
|
6
6
|
module BrainzLab
|
|
7
7
|
module Dendrite
|
|
@@ -19,13 +19,13 @@ module BrainzLab
|
|
|
19
19
|
# branch: "main"
|
|
20
20
|
# )
|
|
21
21
|
#
|
|
22
|
-
def connect(url, name: nil, branch:
|
|
22
|
+
def connect(url, name: nil, branch: 'main', **)
|
|
23
23
|
return nil unless enabled?
|
|
24
24
|
|
|
25
25
|
ensure_provisioned!
|
|
26
26
|
return nil unless BrainzLab.configuration.dendrite_valid?
|
|
27
27
|
|
|
28
|
-
client.connect_repository(url: url, name: name, branch: branch, **
|
|
28
|
+
client.connect_repository(url: url, name: name, branch: branch, **)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Trigger documentation sync for a repository
|
|
@@ -50,7 +50,7 @@ module BrainzLab
|
|
|
50
50
|
logs: data[:logs] || [],
|
|
51
51
|
memory: build_memory_data(data),
|
|
52
52
|
user: context&.user,
|
|
53
|
-
breadcrumbs: context&.breadcrumbs
|
|
53
|
+
breadcrumbs: context&.breadcrumbs.to_a
|
|
54
54
|
}
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -98,15 +98,15 @@ module BrainzLab
|
|
|
98
98
|
|
|
99
99
|
def build_request_data(data, context)
|
|
100
100
|
env = data[:env] || {}
|
|
101
|
-
|
|
101
|
+
env['action_dispatch.request'] || (defined?(ActionDispatch::Request) ? ActionDispatch::Request.new(env) : nil)
|
|
102
102
|
|
|
103
103
|
{
|
|
104
|
-
method: context&.request_method || env[
|
|
105
|
-
path: context&.request_path || env[
|
|
106
|
-
url: context&.request_url || env[
|
|
104
|
+
method: context&.request_method || env['REQUEST_METHOD'],
|
|
105
|
+
path: context&.request_path || env['PATH_INFO'],
|
|
106
|
+
url: context&.request_url || env['REQUEST_URI'],
|
|
107
107
|
params: context&.request_params || {},
|
|
108
108
|
headers: extract_headers(env),
|
|
109
|
-
request_id: context&.request_id || env[
|
|
109
|
+
request_id: context&.request_id || env['action_dispatch.request_id']
|
|
110
110
|
}
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -149,8 +149,8 @@ module BrainzLab
|
|
|
149
149
|
def extract_headers(env)
|
|
150
150
|
headers = {}
|
|
151
151
|
env.each do |key, value|
|
|
152
|
-
if key.start_with?(
|
|
153
|
-
header_name = key.sub(
|
|
152
|
+
if key.start_with?('HTTP_')
|
|
153
|
+
header_name = key.sub('HTTP_', '').split('_').map(&:capitalize).join('-')
|
|
154
154
|
headers[header_name] = value
|
|
155
155
|
end
|
|
156
156
|
end
|
|
@@ -160,23 +160,23 @@ module BrainzLab
|
|
|
160
160
|
def subscribe_to_events
|
|
161
161
|
return unless defined?(ActiveSupport::Notifications)
|
|
162
162
|
|
|
163
|
-
@sql_subscriber = ActiveSupport::Notifications.subscribe(
|
|
163
|
+
@sql_subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
164
164
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
165
|
-
next if event.payload[:name] ==
|
|
166
|
-
next if event.payload[:sql]&.start_with?(
|
|
165
|
+
next if event.payload[:name] == 'SCHEMA'
|
|
166
|
+
next if event.payload[:sql]&.start_with?('PRAGMA')
|
|
167
167
|
|
|
168
168
|
add_sql_query(
|
|
169
169
|
name: event.payload[:name],
|
|
170
170
|
duration: event.duration,
|
|
171
171
|
sql: event.payload[:sql],
|
|
172
|
-
cached: event.payload[:cached] || event.payload[:name] ==
|
|
172
|
+
cached: event.payload[:cached] || event.payload[:name] == 'CACHE',
|
|
173
173
|
source: extract_source(caller)
|
|
174
174
|
)
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
@view_subscriber = ActiveSupport::Notifications.subscribe(/render_.+\.action_view/) do |*args|
|
|
178
178
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
179
|
-
type = event.name.include?(
|
|
179
|
+
type = event.name.include?('partial') ? :partial : :template
|
|
180
180
|
|
|
181
181
|
add_view(
|
|
182
182
|
type: type,
|
|
@@ -214,11 +214,11 @@ module BrainzLab
|
|
|
214
214
|
def normalize_sql(sql)
|
|
215
215
|
return nil unless sql
|
|
216
216
|
|
|
217
|
-
sql.gsub(/\b\d+\b/,
|
|
218
|
-
.gsub(/'[^']*'/,
|
|
219
|
-
.gsub(/\$\d+/,
|
|
220
|
-
.gsub(%r{/\*.*?\*/},
|
|
221
|
-
.gsub(/\s+/,
|
|
217
|
+
sql.gsub(/\b\d+\b/, '?')
|
|
218
|
+
.gsub(/'[^']*'/, '?')
|
|
219
|
+
.gsub(/\$\d+/, '?')
|
|
220
|
+
.gsub(%r{/\*.*?\*/}, '')
|
|
221
|
+
.gsub(/\s+/, ' ')
|
|
222
222
|
.strip
|
|
223
223
|
end
|
|
224
224
|
|
|
@@ -230,11 +230,11 @@ module BrainzLab
|
|
|
230
230
|
|
|
231
231
|
def extract_source(backtrace)
|
|
232
232
|
backtrace.each do |line|
|
|
233
|
-
next if line.include?(
|
|
234
|
-
next if line.include?(
|
|
235
|
-
next if line.include?(
|
|
233
|
+
next if line.include?('/brainzlab')
|
|
234
|
+
next if line.include?('/gems/')
|
|
235
|
+
next if line.include?('/ruby/')
|
|
236
236
|
|
|
237
|
-
if line.include?(
|
|
237
|
+
if line.include?('/app/')
|
|
238
238
|
match = line.match(%r{(app/[^:]+:\d+)})
|
|
239
239
|
return match[1] if match
|
|
240
240
|
end
|
|
@@ -5,11 +5,11 @@ module BrainzLab
|
|
|
5
5
|
module Middleware
|
|
6
6
|
class AssetServer
|
|
7
7
|
MIME_TYPES = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
'.css' => 'text/css; charset=utf-8',
|
|
9
|
+
'.js' => 'application/javascript; charset=utf-8',
|
|
10
|
+
'.svg' => 'image/svg+xml',
|
|
11
|
+
'.png' => 'image/png',
|
|
12
|
+
'.woff2' => 'font/woff2'
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
15
|
def initialize(app)
|
|
@@ -19,11 +19,11 @@ module BrainzLab
|
|
|
19
19
|
def call(env)
|
|
20
20
|
return @app.call(env) unless DevTools.enabled?
|
|
21
21
|
|
|
22
|
-
path = env[
|
|
22
|
+
path = env['PATH_INFO']
|
|
23
23
|
asset_prefix = DevTools.asset_path
|
|
24
24
|
|
|
25
25
|
if path.start_with?("#{asset_prefix}/")
|
|
26
|
-
serve_asset(path.sub("#{asset_prefix}/",
|
|
26
|
+
serve_asset(path.sub("#{asset_prefix}/", ''))
|
|
27
27
|
else
|
|
28
28
|
@app.call(env)
|
|
29
29
|
end
|
|
@@ -33,29 +33,29 @@ module BrainzLab
|
|
|
33
33
|
|
|
34
34
|
def serve_asset(relative_path)
|
|
35
35
|
# Prevent directory traversal
|
|
36
|
-
return not_found if relative_path.include?(
|
|
36
|
+
return not_found if relative_path.include?('..')
|
|
37
37
|
|
|
38
38
|
file_path = File.join(DevTools::ASSETS_PATH, relative_path)
|
|
39
39
|
return not_found unless File.exist?(file_path)
|
|
40
40
|
|
|
41
41
|
ext = File.extname(relative_path)
|
|
42
|
-
content_type = MIME_TYPES[ext] ||
|
|
42
|
+
content_type = MIME_TYPES[ext] || 'application/octet-stream'
|
|
43
43
|
content = File.read(file_path)
|
|
44
44
|
|
|
45
45
|
[
|
|
46
46
|
200,
|
|
47
47
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
'Content-Type' => content_type,
|
|
49
|
+
'Content-Length' => content.bytesize.to_s,
|
|
50
|
+
'Cache-Control' => 'public, max-age=31536000',
|
|
51
|
+
'X-Content-Type-Options' => 'nosniff'
|
|
52
52
|
},
|
|
53
53
|
[content]
|
|
54
54
|
]
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def not_found
|
|
58
|
-
[404, {
|
|
58
|
+
[404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -4,7 +4,7 @@ module BrainzLab
|
|
|
4
4
|
module DevTools
|
|
5
5
|
module Middleware
|
|
6
6
|
class DatabaseHandler
|
|
7
|
-
ENDPOINT =
|
|
7
|
+
ENDPOINT = '/_brainzlab/devtools/database'
|
|
8
8
|
|
|
9
9
|
def initialize(app)
|
|
10
10
|
@app = app
|
|
@@ -22,67 +22,65 @@ module BrainzLab
|
|
|
22
22
|
return false unless DevTools.enabled?
|
|
23
23
|
return false unless DevTools.allowed_environment?
|
|
24
24
|
return false unless DevTools.allowed_ip?(extract_ip(env))
|
|
25
|
-
return false unless env[
|
|
26
|
-
return false unless env[
|
|
25
|
+
return false unless env['PATH_INFO'] == ENDPOINT
|
|
26
|
+
return false unless env['REQUEST_METHOD'] == 'POST'
|
|
27
27
|
|
|
28
28
|
true
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def extract_ip(env)
|
|
32
|
-
forwarded = env[
|
|
33
|
-
return forwarded.split(
|
|
32
|
+
forwarded = env['HTTP_X_FORWARDED_FOR']
|
|
33
|
+
return forwarded.split(',').first.strip if forwarded
|
|
34
34
|
|
|
35
|
-
env[
|
|
35
|
+
env['REMOTE_ADDR']
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def handle_database_request(env)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
json_response({ success: false, output: "Error: #{e.message}\n\n#{e.backtrace&.first(10)&.join("\n")}" })
|
|
61
|
-
end
|
|
39
|
+
body = env['rack.input'].read
|
|
40
|
+
env['rack.input'].rewind
|
|
41
|
+
params = JSON.parse(body)
|
|
42
|
+
action = params['action']
|
|
43
|
+
|
|
44
|
+
result = case action
|
|
45
|
+
when 'migrate'
|
|
46
|
+
run_migrations
|
|
47
|
+
when 'status'
|
|
48
|
+
migration_status
|
|
49
|
+
when 'create'
|
|
50
|
+
create_database
|
|
51
|
+
when 'rollback'
|
|
52
|
+
rollback_migration
|
|
53
|
+
else
|
|
54
|
+
{ success: false, output: "Unknown action: #{action}" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
json_response(result)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
json_response({ success: false, output: "Error: #{e.message}\n\n#{e.backtrace&.first(10)&.join("\n")}" })
|
|
62
60
|
end
|
|
63
61
|
|
|
64
62
|
def run_migrations
|
|
65
|
-
return not_available(
|
|
63
|
+
return not_available('Rails') unless defined?(::Rails)
|
|
66
64
|
|
|
67
65
|
output = capture_output do
|
|
68
66
|
ActiveRecord::MigrationContext.new(
|
|
69
|
-
Rails.root.join(
|
|
67
|
+
::Rails.root.join('db/migrate'),
|
|
70
68
|
ActiveRecord::SchemaMigration
|
|
71
69
|
).migrate
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
{ success: true, output: output.presence ||
|
|
75
|
-
rescue => e
|
|
72
|
+
{ success: true, output: output.presence || 'All migrations completed successfully!' }
|
|
73
|
+
rescue StandardError => e
|
|
76
74
|
{ success: false, output: "Migration failed:\n#{e.message}\n\n#{e.backtrace&.first(10)&.join("\n")}" }
|
|
77
75
|
end
|
|
78
76
|
|
|
79
77
|
def migration_status
|
|
80
|
-
return not_available(
|
|
78
|
+
return not_available('Rails') unless defined?(::Rails)
|
|
81
79
|
|
|
82
80
|
output = StringIO.new
|
|
83
81
|
|
|
84
82
|
context = ActiveRecord::MigrationContext.new(
|
|
85
|
-
Rails.root.join(
|
|
83
|
+
::Rails.root.join('db/migrate'),
|
|
86
84
|
ActiveRecord::SchemaMigration
|
|
87
85
|
)
|
|
88
86
|
|
|
@@ -90,55 +88,54 @@ module BrainzLab
|
|
|
90
88
|
migrations = context.migrations
|
|
91
89
|
|
|
92
90
|
output.puts "database: #{ActiveRecord::Base.connection_db_config.database}"
|
|
93
|
-
output.puts
|
|
94
|
-
output.puts
|
|
95
|
-
output.puts
|
|
91
|
+
output.puts ''
|
|
92
|
+
output.puts ' Status Migration ID Migration Name'
|
|
93
|
+
output.puts '-' * 60
|
|
96
94
|
|
|
97
95
|
migrations.each do |migration|
|
|
98
|
-
status = migrated.include?(migration.version) ?
|
|
96
|
+
status = migrated.include?(migration.version) ? ' up' : ' down'
|
|
99
97
|
output.puts " #{status} #{migration.version} #{migration.name}"
|
|
100
98
|
end
|
|
101
99
|
|
|
102
100
|
pending = migrations.reject { |m| migrated.include?(m.version) }
|
|
101
|
+
output.puts ''
|
|
103
102
|
if pending.any?
|
|
104
|
-
output.puts ""
|
|
105
103
|
output.puts "#{pending.count} pending migration(s)"
|
|
106
104
|
else
|
|
107
|
-
output.puts
|
|
108
|
-
output.puts "All migrations are up to date!"
|
|
105
|
+
output.puts 'All migrations are up to date!'
|
|
109
106
|
end
|
|
110
107
|
|
|
111
108
|
{ success: true, output: output.string }
|
|
112
|
-
rescue => e
|
|
109
|
+
rescue StandardError => e
|
|
113
110
|
{ success: false, output: "Failed to check status:\n#{e.message}" }
|
|
114
111
|
end
|
|
115
112
|
|
|
116
113
|
def create_database
|
|
117
|
-
return not_available(
|
|
114
|
+
return not_available('Rails') unless defined?(::Rails)
|
|
118
115
|
|
|
119
116
|
output = capture_output do
|
|
120
117
|
ActiveRecord::Tasks::DatabaseTasks.create_current
|
|
121
118
|
end
|
|
122
119
|
|
|
123
|
-
{ success: true, output: output.presence ||
|
|
120
|
+
{ success: true, output: output.presence || 'Database created successfully!' }
|
|
124
121
|
rescue ActiveRecord::DatabaseAlreadyExists
|
|
125
|
-
{ success: true, output:
|
|
126
|
-
rescue => e
|
|
122
|
+
{ success: true, output: 'Database already exists.' }
|
|
123
|
+
rescue StandardError => e
|
|
127
124
|
{ success: false, output: "Failed to create database:\n#{e.message}" }
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
def rollback_migration
|
|
131
|
-
return not_available(
|
|
128
|
+
return not_available('Rails') unless defined?(::Rails)
|
|
132
129
|
|
|
133
130
|
output = capture_output do
|
|
134
131
|
ActiveRecord::MigrationContext.new(
|
|
135
|
-
Rails.root.join(
|
|
132
|
+
::Rails.root.join('db/migrate'),
|
|
136
133
|
ActiveRecord::SchemaMigration
|
|
137
134
|
).rollback
|
|
138
135
|
end
|
|
139
136
|
|
|
140
|
-
{ success: true, output: output.presence ||
|
|
141
|
-
rescue => e
|
|
137
|
+
{ success: true, output: output.presence || 'Rollback completed!' }
|
|
138
|
+
rescue StandardError => e
|
|
142
139
|
{ success: false, output: "Rollback failed:\n#{e.message}" }
|
|
143
140
|
end
|
|
144
141
|
|
|
@@ -166,10 +163,10 @@ module BrainzLab
|
|
|
166
163
|
[
|
|
167
164
|
200,
|
|
168
165
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
|
167
|
+
'Content-Length' => body.bytesize.to_s,
|
|
168
|
+
'Cache-Control' => 'no-store',
|
|
169
|
+
'X-Content-Type-Options' => 'nosniff'
|
|
173
170
|
},
|
|
174
171
|
[body]
|
|
175
172
|
]
|