rails-console 0.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 +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +118 -0
- data/app/assets/rails_console/rails_console.css +230 -0
- data/app/assets/rails_console/rails_console.js +11 -0
- data/app/assets/rails_console/src/application.css +12 -0
- data/app/assets/rails_console/src/console.js +135 -0
- data/app/channels/rails_console/console_channel.rb +70 -0
- data/app/controllers/rails_console/consoles_controller.rb +19 -0
- data/app/models/rails_console/application_record.rb +7 -0
- data/app/models/rails_console/log_line.rb +13 -0
- data/app/models/rails_console/session.rb +21 -0
- data/app/views/layouts/rails_console/application.html.erb +16 -0
- data/app/views/rails_console/consoles/show.html.erb +1 -0
- data/bin/rails_console +8 -0
- data/config/routes.rb +5 -0
- data/lib/generators/rails_console/install_generator.rb +55 -0
- data/lib/generators/rails_console/templates/db/migrate/create_rails_console_log_lines.rb +14 -0
- data/lib/generators/rails_console/templates/db/migrate/create_rails_console_sessions.rb +16 -0
- data/lib/generators/rails_console/templates/rails_console.rb +14 -0
- data/lib/rails-console/broker.rb +336 -0
- data/lib/rails-console/config.rb +41 -0
- data/lib/rails-console/engine.rb +26 -0
- data/lib/rails-console/logging.rb +15 -0
- data/lib/rails-console/pty_session.rb +46 -0
- data/lib/rails-console/unsafe.rb +11 -0
- data/lib/rails-console/user.rb +13 -0
- data/lib/rails-console/version.rb +5 -0
- data/lib/rails-console.rb +14 -0
- metadata +129 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'socket'
|
|
6
|
+
|
|
7
|
+
module RailsConsole
|
|
8
|
+
# Owns the single PTY console session per container. Puma workers reach it over a local
|
|
9
|
+
# Unix socket, so any worker can serve the browser's WebSocket and hit the same session.
|
|
10
|
+
class Broker
|
|
11
|
+
include Logging
|
|
12
|
+
|
|
13
|
+
# Ctrl-L: tells the console to redraw its prompt so a just-attached client sees it.
|
|
14
|
+
REDRAW = "\f"
|
|
15
|
+
|
|
16
|
+
# Sent to clients when the session is replaced (safe!/unsafe!), so the frontend resets the
|
|
17
|
+
# screen and shows the booting spinner again. NUL-delimited so it never collides with real
|
|
18
|
+
# terminal output.
|
|
19
|
+
RELOAD_MARKER = "\x00CONSOLE_RELOAD\x00"
|
|
20
|
+
|
|
21
|
+
# The console prints this to ask for sandbox (safe) mode. NUL bytes never appear in terminal
|
|
22
|
+
# output, so it can't be triggered by accident.
|
|
23
|
+
SAFE_MARKER = "\x00CONSOLE_SAFE_REQUEST\x00"
|
|
24
|
+
|
|
25
|
+
# Seconds to wait for a freshly spawned broker to start listening.
|
|
26
|
+
SPAWN_TIMEOUT = 15
|
|
27
|
+
|
|
28
|
+
# The console prints this to ask for write (unsafe) mode. NUL bytes never appear in terminal
|
|
29
|
+
# output, so it can't be triggered by accident.
|
|
30
|
+
UNSAFE_MARKER = "\x00CONSOLE_UNSAFE_REQUEST\x00"
|
|
31
|
+
|
|
32
|
+
attr_reader :console_session, :owner_label
|
|
33
|
+
|
|
34
|
+
def self.boot(socket_path: nil) # rubocop:disable Naming/PredicateMethod
|
|
35
|
+
socket_path ||= RailsConsole.socket_path
|
|
36
|
+
|
|
37
|
+
return true if running?(socket_path:)
|
|
38
|
+
|
|
39
|
+
broker_bin = Gem.bin_path('rails-console', 'rails_console')
|
|
40
|
+
|
|
41
|
+
Process.spawn('bundle', 'exec', broker_bin, chdir: Rails.root.to_s)
|
|
42
|
+
|
|
43
|
+
SPAWN_TIMEOUT.times do
|
|
44
|
+
return true if running?(socket_path:)
|
|
45
|
+
|
|
46
|
+
sleep(1)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.running?(socket_path: nil)
|
|
53
|
+
socket_path ||= RailsConsole.socket_path
|
|
54
|
+
|
|
55
|
+
UNIXSocket.new(socket_path.to_s).close
|
|
56
|
+
|
|
57
|
+
true
|
|
58
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(safe_command: nil, socket_path: nil, unsafe_command: nil)
|
|
63
|
+
@clients = []
|
|
64
|
+
@console_session = nil
|
|
65
|
+
@idle_timer = nil
|
|
66
|
+
@mutex = Mutex.new
|
|
67
|
+
@owner_label = nil
|
|
68
|
+
@pty_session = nil
|
|
69
|
+
@safe_command = safe_command
|
|
70
|
+
@server = nil
|
|
71
|
+
@shutting_down = false
|
|
72
|
+
@socket_path = socket_path || RailsConsole.socket_path
|
|
73
|
+
@unsafe_command = unsafe_command
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run
|
|
77
|
+
return log("Already listening on #{@socket_path}, aborting.") if already_running?
|
|
78
|
+
|
|
79
|
+
prepare_socket_path
|
|
80
|
+
|
|
81
|
+
@server = UNIXServer.new(@socket_path)
|
|
82
|
+
|
|
83
|
+
log("Listening on #{@socket_path}")
|
|
84
|
+
|
|
85
|
+
loop { Thread.new(@server.accept) { |client| handle_client(client) } }
|
|
86
|
+
rescue IOError
|
|
87
|
+
nil
|
|
88
|
+
ensure
|
|
89
|
+
@server&.close
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def shutdown
|
|
93
|
+
@shutting_down = true
|
|
94
|
+
|
|
95
|
+
@mutex.synchronize do
|
|
96
|
+
@idle_timer&.kill
|
|
97
|
+
@idle_timer = nil
|
|
98
|
+
|
|
99
|
+
@clients.each do |item|
|
|
100
|
+
item.close
|
|
101
|
+
rescue IOError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@clients.clear
|
|
106
|
+
|
|
107
|
+
@pty_session&.kill!
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@server&.close
|
|
111
|
+
@server = nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def already_running?
|
|
117
|
+
self.class.running?(socket_path: @socket_path)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def attach_client(client)
|
|
121
|
+
@mutex.synchronize do
|
|
122
|
+
@idle_timer&.kill
|
|
123
|
+
@idle_timer = nil
|
|
124
|
+
|
|
125
|
+
@clients << client
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def broadcast(bytes)
|
|
130
|
+
@mutex.synchronize do
|
|
131
|
+
@clients.each { |item| item.write(bytes) }
|
|
132
|
+
rescue IOError, Errno::EPIPE
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def claim(label)
|
|
138
|
+
@mutex.synchronize do
|
|
139
|
+
if !@pty_session&.alive?
|
|
140
|
+
@owner_label = label
|
|
141
|
+
|
|
142
|
+
:start
|
|
143
|
+
elsif owner_label == label
|
|
144
|
+
:attach
|
|
145
|
+
else
|
|
146
|
+
:reject
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def detach_client(client)
|
|
152
|
+
@mutex.synchronize do
|
|
153
|
+
@clients.delete(client)
|
|
154
|
+
|
|
155
|
+
schedule_idle_shutdown if @clients.empty?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
client.close
|
|
159
|
+
rescue IOError
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def end_session
|
|
164
|
+
@mutex.synchronize do
|
|
165
|
+
next if @clients.any?
|
|
166
|
+
|
|
167
|
+
log("Ending idle session for #{owner_label}")
|
|
168
|
+
|
|
169
|
+
@pty_session&.kill!
|
|
170
|
+
console_session&.update(ended_at: Time.current)
|
|
171
|
+
|
|
172
|
+
@console_session = nil
|
|
173
|
+
@owner_label = nil
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def handle_client(client)
|
|
178
|
+
start_message = read_start_message(client)
|
|
179
|
+
|
|
180
|
+
return if start_message.nil?
|
|
181
|
+
|
|
182
|
+
action = claim(start_message.fetch('label'))
|
|
183
|
+
|
|
184
|
+
return reject_client(client) if action == :reject
|
|
185
|
+
|
|
186
|
+
if action == :start
|
|
187
|
+
start_session(
|
|
188
|
+
ip_address: start_message['ip_address'],
|
|
189
|
+
user_id: start_message.fetch('user_id'),
|
|
190
|
+
user_label: start_message.fetch('label')
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
attach_client(client)
|
|
195
|
+
|
|
196
|
+
# Only redraw when joining a console that's already up; on :start the boot output
|
|
197
|
+
# itself fills the screen, and a Ctrl-L would just be echoed as "^L" mid-boot.
|
|
198
|
+
wake_pty if action == :attach
|
|
199
|
+
|
|
200
|
+
relay_client_input(client)
|
|
201
|
+
ensure
|
|
202
|
+
detach_client(client)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def idle_timeout
|
|
206
|
+
RailsConsole.idle_timeout
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def prepare_socket_path
|
|
210
|
+
FileUtils.mkdir_p(File.dirname(@socket_path))
|
|
211
|
+
FileUtils.rm_f(@socket_path)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def read_loop(pty_session)
|
|
215
|
+
loop do
|
|
216
|
+
break if !pty_session.equal?(@pty_session)
|
|
217
|
+
break if !pty_session.alive?
|
|
218
|
+
|
|
219
|
+
bytes = pty_session.read_nonblock
|
|
220
|
+
|
|
221
|
+
next sleep(0.02) if bytes.nil?
|
|
222
|
+
|
|
223
|
+
requested = requested_mode(bytes)
|
|
224
|
+
|
|
225
|
+
relay_pty_output(bytes.gsub(SAFE_MARKER, '').gsub(UNSAFE_MARKER, ''))
|
|
226
|
+
|
|
227
|
+
next if requested.nil?
|
|
228
|
+
|
|
229
|
+
switch_mode(sandbox: requested == :safe)
|
|
230
|
+
|
|
231
|
+
break
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def read_start_message(client)
|
|
236
|
+
line = client.gets&.chomp
|
|
237
|
+
|
|
238
|
+
return if line.nil?
|
|
239
|
+
|
|
240
|
+
JSON.parse(line)
|
|
241
|
+
rescue IOError, JSON::ParserError
|
|
242
|
+
nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def record_log_line(content:, direction:)
|
|
246
|
+
return if !RailsConsole.audit
|
|
247
|
+
return if console_session.nil?
|
|
248
|
+
return if content.blank?
|
|
249
|
+
|
|
250
|
+
RailsConsole::LogLine.create(content:, direction:, session: console_session)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def reject_client(client)
|
|
254
|
+
client.write("Session in use by #{owner_label}, please try again later.\n")
|
|
255
|
+
client.close
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def relay_client_input(client)
|
|
259
|
+
loop do
|
|
260
|
+
bytes = client.readpartial(RailsConsole::PtySession::READ_SIZE)
|
|
261
|
+
|
|
262
|
+
@pty_session&.write(bytes)
|
|
263
|
+
|
|
264
|
+
record_log_line(content: bytes, direction: :input)
|
|
265
|
+
end
|
|
266
|
+
rescue IOError, Errno::ECONNRESET
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def relay_pty_output(bytes)
|
|
271
|
+
broadcast(bytes)
|
|
272
|
+
record_log_line(content: bytes, direction: :output)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Which mode the console asked to switch to, or nil when the output has no marker.
|
|
276
|
+
def requested_mode(bytes)
|
|
277
|
+
return :safe if bytes.include?(SAFE_MARKER)
|
|
278
|
+
return :unsafe if bytes.include?(UNSAFE_MARKER)
|
|
279
|
+
|
|
280
|
+
nil
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def schedule_idle_shutdown
|
|
284
|
+
return if @shutting_down
|
|
285
|
+
return if @idle_timer&.alive?
|
|
286
|
+
|
|
287
|
+
@idle_timer = Thread.new do
|
|
288
|
+
sleep(idle_timeout)
|
|
289
|
+
|
|
290
|
+
end_session unless @shutting_down
|
|
291
|
+
end
|
|
292
|
+
rescue ThreadError
|
|
293
|
+
nil
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def start_session(ip_address:, user_id:, user_label:)
|
|
297
|
+
@console_session = RailsConsole::Session.create!(
|
|
298
|
+
ip_address:,
|
|
299
|
+
started_at: Time.current,
|
|
300
|
+
user_id:,
|
|
301
|
+
user_label:
|
|
302
|
+
)
|
|
303
|
+
@pty_session = RailsConsole::PtySession.new(command: @safe_command, sandbox: true)
|
|
304
|
+
|
|
305
|
+
log("Session started for #{owner_label}, pid=#{@pty_session.pid}, sandbox=true")
|
|
306
|
+
|
|
307
|
+
Thread.new { read_loop(@pty_session) }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def switch_mode(sandbox:)
|
|
311
|
+
mode = sandbox ? 'SAFE' : 'UNSAFE'
|
|
312
|
+
command = sandbox ? @safe_command : @unsafe_command
|
|
313
|
+
|
|
314
|
+
log_warn("Switching to #{mode} mode for #{owner_label}")
|
|
315
|
+
|
|
316
|
+
console_session.update(unsafe_at: Time.current) if !sandbox
|
|
317
|
+
record_log_line(content: "Switched to #{mode} mode", direction: :transition)
|
|
318
|
+
|
|
319
|
+
broadcast(RELOAD_MARKER)
|
|
320
|
+
|
|
321
|
+
old_session = @pty_session
|
|
322
|
+
@pty_session = RailsConsole::PtySession.new(command:, sandbox:)
|
|
323
|
+
|
|
324
|
+
old_session.kill!
|
|
325
|
+
|
|
326
|
+
log_warn("Session is now #{mode}, pid=#{@pty_session.pid}")
|
|
327
|
+
|
|
328
|
+
Thread.new { read_loop(@pty_session) }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Nudges the console to redraw so a just-attached client isn't left staring at a blank screen.
|
|
332
|
+
def wake_pty
|
|
333
|
+
@pty_session&.write(REDRAW)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
|
4
|
+
|
|
5
|
+
module RailsConsole
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :audit,
|
|
8
|
+
:authorize,
|
|
9
|
+
:command,
|
|
10
|
+
:current_user,
|
|
11
|
+
:idle_timeout,
|
|
12
|
+
:sandbox_command,
|
|
13
|
+
:user_class
|
|
14
|
+
|
|
15
|
+
attr_writer :socket_path
|
|
16
|
+
|
|
17
|
+
def config
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure
|
|
22
|
+
yield(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default_socket_path
|
|
26
|
+
Rails.root.join('tmp/sockets/rails_console.sock')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def socket_path
|
|
30
|
+
@socket_path || default_socket_path
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
self.audit = true
|
|
35
|
+
self.authorize = ->(_user) { false }
|
|
36
|
+
self.command = 'bundle exec rails console'
|
|
37
|
+
self.current_user = ->(user) { { id: user&.id, label: user.try(:email) || 'unknown' } }
|
|
38
|
+
self.idle_timeout = 10.minutes
|
|
39
|
+
self.sandbox_command = 'bundle exec rails console --sandbox'
|
|
40
|
+
self.user_class = 'User'
|
|
41
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsole
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace RailsConsole
|
|
6
|
+
|
|
7
|
+
# Pagers hang inside the web console PTY, so disable them on whichever REPL the host app uses.
|
|
8
|
+
console do
|
|
9
|
+
IRB.conf[:USE_PAGER] = false if defined?(IRB)
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
require 'pry'
|
|
13
|
+
|
|
14
|
+
Pry.config.pager = false
|
|
15
|
+
rescue LoadError
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
initializer 'rails_console.assets' do |app|
|
|
21
|
+
next if !app.config.respond_to?(:assets)
|
|
22
|
+
|
|
23
|
+
app.config.assets.precompile += %w[rails_console.css rails_console.js]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsole
|
|
4
|
+
module Logging
|
|
5
|
+
PREFIX = '[RailsConsole]'
|
|
6
|
+
|
|
7
|
+
def log(message)
|
|
8
|
+
Rails.logger.info("#{PREFIX} #{message}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def log_warn(message)
|
|
12
|
+
Rails.logger.warn("#{PREFIX} #{message}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pty'
|
|
4
|
+
|
|
5
|
+
module RailsConsole
|
|
6
|
+
class PtySession
|
|
7
|
+
READ_SIZE = 8192
|
|
8
|
+
|
|
9
|
+
attr_reader :pid
|
|
10
|
+
|
|
11
|
+
delegate :write, to: :@input
|
|
12
|
+
|
|
13
|
+
def initialize(command: nil, sandbox: true)
|
|
14
|
+
command ||= sandbox ? RailsConsole.sandbox_command : RailsConsole.command
|
|
15
|
+
|
|
16
|
+
@output, @input, @pid = PTY.spawn(self.class.spawn_env, command)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Pagers hang inside the web terminal, so force a pass-through one.
|
|
20
|
+
def self.spawn_env
|
|
21
|
+
{
|
|
22
|
+
'PAGER' => 'cat',
|
|
23
|
+
'TERM' => 'xterm-256color',
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def alive?
|
|
28
|
+
Process.waitpid(pid, Process::WNOHANG).nil?
|
|
29
|
+
rescue Errno::ECHILD
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def kill!
|
|
34
|
+
Process.kill('TERM', pid)
|
|
35
|
+
Process.waitpid(pid)
|
|
36
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_nonblock(size = READ_SIZE)
|
|
41
|
+
@output.read_nonblock(size)
|
|
42
|
+
rescue IO::WaitReadable, EOFError
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsole
|
|
4
|
+
def self.authorized?(context)
|
|
5
|
+
config.authorize.call(user_from(context))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.user_from(context)
|
|
9
|
+
return context.current_user if context.respond_to?(:current_user)
|
|
10
|
+
|
|
11
|
+
context.env['warden']&.user
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsole
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
require 'rails-console/version'
|
|
7
|
+
require 'rails-console/config'
|
|
8
|
+
require 'rails-console/user'
|
|
9
|
+
require 'rails-console/logging'
|
|
10
|
+
require 'rails-console/unsafe'
|
|
11
|
+
require 'rails-console/pty_session'
|
|
12
|
+
require 'rails-console/broker'
|
|
13
|
+
|
|
14
|
+
require 'rails-console/engine' if defined?(::Rails::Engine)
|
metadata
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails-console
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Washington Botelho
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: actioncable
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: actionpack
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activerecord
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: railties
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: A safe, browser-based Rails console you mount on your app.
|
|
69
|
+
email: wbotelhos@gmail.com
|
|
70
|
+
executables:
|
|
71
|
+
- rails_console
|
|
72
|
+
extensions: []
|
|
73
|
+
extra_rdoc_files:
|
|
74
|
+
- CHANGELOG.md
|
|
75
|
+
- LICENSE
|
|
76
|
+
- README.md
|
|
77
|
+
files:
|
|
78
|
+
- CHANGELOG.md
|
|
79
|
+
- LICENSE
|
|
80
|
+
- README.md
|
|
81
|
+
- app/assets/rails_console/rails_console.css
|
|
82
|
+
- app/assets/rails_console/rails_console.js
|
|
83
|
+
- app/assets/rails_console/src/application.css
|
|
84
|
+
- app/assets/rails_console/src/console.js
|
|
85
|
+
- app/channels/rails_console/console_channel.rb
|
|
86
|
+
- app/controllers/rails_console/consoles_controller.rb
|
|
87
|
+
- app/models/rails_console/application_record.rb
|
|
88
|
+
- app/models/rails_console/log_line.rb
|
|
89
|
+
- app/models/rails_console/session.rb
|
|
90
|
+
- app/views/layouts/rails_console/application.html.erb
|
|
91
|
+
- app/views/rails_console/consoles/show.html.erb
|
|
92
|
+
- bin/rails_console
|
|
93
|
+
- config/routes.rb
|
|
94
|
+
- lib/generators/rails_console/install_generator.rb
|
|
95
|
+
- lib/generators/rails_console/templates/db/migrate/create_rails_console_log_lines.rb
|
|
96
|
+
- lib/generators/rails_console/templates/db/migrate/create_rails_console_sessions.rb
|
|
97
|
+
- lib/generators/rails_console/templates/rails_console.rb
|
|
98
|
+
- lib/rails-console.rb
|
|
99
|
+
- lib/rails-console/broker.rb
|
|
100
|
+
- lib/rails-console/config.rb
|
|
101
|
+
- lib/rails-console/engine.rb
|
|
102
|
+
- lib/rails-console/logging.rb
|
|
103
|
+
- lib/rails-console/pty_session.rb
|
|
104
|
+
- lib/rails-console/unsafe.rb
|
|
105
|
+
- lib/rails-console/user.rb
|
|
106
|
+
- lib/rails-console/version.rb
|
|
107
|
+
homepage: https://github.com/wbotelhos/rails-console
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata:
|
|
111
|
+
rubygems_mfa_required: 'true'
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: '3.3'
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubygems_version: 3.6.9
|
|
127
|
+
specification_version: 4
|
|
128
|
+
summary: A safe, browser-based Rails console you mount on your app.
|
|
129
|
+
test_files: []
|