stomp_base 0.2.1 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f766bad393da3ae69fa86a14e37ddd0757eb933c47b209b2ece04e2bc17ad7f0
4
- data.tar.gz: cd1caea93843548baba429391a50b4cfac2852b0be1c5ff4534f38317df35c63
3
+ metadata.gz: 8b134d98cdd58b0e0f96d5045fdc83e9bc07379cd95e2bae7a7ee462d5ec9cc4
4
+ data.tar.gz: 3b1f9f0e9aada4b215c1c2d9277af0478a447ab309492004d76ab43a91171761
5
5
  SHA512:
6
- metadata.gz: 4358c4e43a782e99b8c13d31ae7f00e3124b70a0db5ccb3c804cbebc68ac5b09960eaa22b6c388420e8dd9caeba08f0bc9d3390cc8b783ad4c7e43daf65558b9
7
- data.tar.gz: 764692e137d6292629990b71f87a6e8952161a96fad790d0fe6ebbbdeae3e9133bfe7fe493e85d127956898f2c5aa30e5abd9a2ea7bb4208a4979b27131fb27e
6
+ metadata.gz: 49c1f34fc9b74d9c79e59e95a4acc47c518086f883f75586f04058338f8b27190a5de807569c09f4e1be37a2e765dc84bcf00a13324bfd466798c9e77a66f0c0
7
+ data.tar.gz: 7bd934b72a8ddaf2d29b65f71792d6a72883c9cb53272483b1f39b2c70205d41d65dab12b972328b87e5eda39ff22ccb607cddeb62918b2b93e25890742176eb
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2025-07-27
11
+
12
+ ### Added
13
+ - **Terminal-Style Console Interface**: Complete redesign of the browser console
14
+ - Real terminal-like experience with command history and session persistence
15
+ - Session state management to maintain console state across page reloads
16
+ - Responsive design optimized for both desktop and mobile devices
17
+ - Comprehensive Japanese and English translations
18
+ - **Enhanced Documentation**: Comprehensive documentation for API-only Rails support and authentication features
19
+
20
+ ### Improved
21
+ - **Console Functionality**: Enhanced console session management and user experience
22
+ - **Test Coverage**: Added comprehensive tests for terminal console interface
23
+ - **Responsive Design**: Improved mobile and tablet compatibility for console interface
24
+
25
+ ### Fixed
26
+ - **Timezone Dependencies**: Resolved timezone dependency issues in console specifications
27
+ - **Session Persistence**: Fixed console session state persistence across browser reloads
28
+
10
29
  ## [0.2.1] - 2025-06-28
11
30
 
12
31
  ### Added
@@ -18,7 +37,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
37
  - **Rails Demo API**: Complete API-only Rails demonstration application
19
38
  - Sample models, migrations, and authentication specifications
20
39
  - Proper bin/ directory with Rails binstubs
21
- - Complete environment configurations
22
40
 
23
41
  ### Changed
24
42
  - **Settings Controller**: Improved settings save functionality with proper flash message handling
@@ -83,6 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
83
101
  - 🔧 Easy Integration: Simple gem installation and mounting process
84
102
  - 🔐 Built-in Authentication: Simple authentication options (Basic Auth, API keys, Custom)
85
103
 
104
+ [0.2.2]: https://github.com/snowwshiro/stomp_base/releases/tag/v0.2.2
86
105
  [0.2.1]: https://github.com/snowwshiro/stomp_base/releases/tag/v0.2.1
87
106
  [0.2.0]: https://github.com/snowwshiro/stomp_base/releases/tag/v0.2.0
88
107
  [0.1.0]: https://github.com/snowwshiro/stomp_base/releases/tag/v0.1.0
data/README.md CHANGED
@@ -271,6 +271,9 @@ StompBase.configure do |config|
271
271
  config.locale = :ja # Set interface language
272
272
  config.available_locales = [:en, :ja] # Available language options (read-only)
273
273
 
274
+ # Console configuration
275
+ config.allow_console_in_production = false # Allow console functionality in production (default: false)
276
+
274
277
  # Authentication options
275
278
  config.enable_authentication( # Enable authentication
276
279
  method: :basic_auth, # :basic_auth, :api_key, or :custom
@@ -296,6 +299,20 @@ StompBase.enable_authentication(method: :basic_auth, username: 'admin', password
296
299
  StompBase.disable_authentication
297
300
  ```
298
301
 
302
+ ### Console Configuration
303
+
304
+ For security reasons, the Rails console functionality is disabled in production environments by default. You can enable it if needed:
305
+
306
+ ```ruby
307
+ # config/initializers/stomp_base.rb
308
+ StompBase.configure do |config|
309
+ # Enable console functionality in production (disabled by default for security)
310
+ config.allow_console_in_production = true
311
+ end
312
+ ```
313
+
314
+ **⚠️ Security Warning**: Enabling console functionality in production environments poses significant security risks. Only enable this feature if you fully understand the implications and have proper security measures in place.
315
+
299
316
  ### Direct Configuration
300
317
 
301
318
  ```ruby
@@ -461,6 +478,8 @@ StompBase.configure do |config|
461
478
  method: :api_key,
462
479
  keys: [ENV['STOMP_BASE_API_KEY']]
463
480
  )
481
+ # Console disabled in production by default for security
482
+ # config.allow_console_in_production = true # Uncomment only if absolutely necessary
464
483
  elsif Rails.env.development?
465
484
  # Simple authentication for development
466
485
  config.enable_authentication(
@@ -468,6 +487,7 @@ StompBase.configure do |config|
468
487
  username: 'dev',
469
488
  password: 'dev'
470
489
  )
490
+ # Console enabled in development by default
471
491
  else
472
492
  # Disable authentication for test environment
473
493
  config.disable_authentication
@@ -481,6 +501,8 @@ end
481
501
  - Use HTTPS (especially when using Basic Authentication)
482
502
  - Update API keys regularly
483
503
  - Be careful not to output authentication information to log files
504
+ - **Console functionality should remain disabled in production environments** unless absolutely necessary for debugging
505
+ - If console access is required in production, ensure strong authentication is enabled and access is properly logged
484
506
 
485
507
  ## Implementation Summary
486
508
 
@@ -9,7 +9,7 @@
9
9
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0.5rem;">
10
10
  <% console_examples.each do |example| %>
11
11
  <button
12
- onclick="insertExampleCommand('<%= example[:command] %>')"
12
+ onclick="insertTerminalCommand('<%= example[:command] %>')"
13
13
  class="stomp-base-btn"
14
14
  style="text-align: left; padding: 0.75rem; background-color: #f8fafc; color: #374151; border: 1px solid #e2e8f0; border-radius: 0.375rem; font-family: 'Courier New', monospace; font-size: 0.8125rem; transition: all 0.2s ease;"
15
15
  onmouseover="this.style.backgroundColor='#f1f5f9'; this.style.borderColor='#cbd5e1';"
@@ -0,0 +1,317 @@
1
+ <!-- Terminal-style Console Interface -->
2
+ <div class="stomp-base-card" style="margin-bottom: 0.75rem; background-color: #1e1e1e; border: 1px solid #333; border-radius: 0.5rem; overflow: hidden;">
3
+ <div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; padding: 0.75rem 1rem; background-color: #2d2d2d; border-bottom: 1px solid #333;">
4
+ <div style="display: flex; align-items: center; min-width: 0;">
5
+ <svg style="width: 1.25rem; height: 1.25rem; margin-right: 0.5rem; color: #10b981; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 002 2z"></path>
7
+ </svg>
8
+ <span style="color: #e5e7eb; font-weight: 600; font-size: 0.875rem; white-space: nowrap;">Rails Console</span>
9
+ </div>
10
+ <div style="display: flex; gap: 0.5rem; flex-shrink: 0;">
11
+ <button onclick="clearTerminal()" class="stomp-base-btn" style="padding: 0.375rem 0.75rem; font-size: 0.75rem; background-color: #374151; color: #e5e7eb; border: 1px solid #4b5563; border-radius: 0.375rem; white-space: nowrap;">
12
+ <%= t('stomp_base.console.clear') %>
13
+ </button>
14
+ <button onclick="restartSession()" class="stomp-base-btn" style="padding: 0.375rem 0.75rem; font-size: 0.75rem; background-color: #dc2626; color: #fff; border: 1px solid #dc2626; border-radius: 0.375rem; white-space: nowrap;">
15
+ Restart
16
+ </button>
17
+ </div>
18
+ </div>
19
+
20
+ <!-- Terminal Display Area -->
21
+ <div
22
+ id="terminal-display"
23
+ style="height: 400px; max-height: 60vh; overflow-y: auto; padding: 1rem; background-color: #1e1e1e; font-family: 'Courier New', 'Monaco', 'Consolas', monospace; font-size: 0.875rem; line-height: 1.4; color: #e5e7eb;"
24
+ >
25
+ <div id="terminal-output">
26
+ <div style="color: #10b981; margin-bottom: 0.5rem;"><%= welcome_message %></div>
27
+ </div>
28
+ <div id="terminal-input-line" style="display: flex; align-items: flex-start; gap: 0.25rem;">
29
+ <span id="terminal-prompt" style="color: #10b981; flex-shrink: 0; user-select: none; font-size: 0.875rem; line-height: 1.4;"><%= initial_prompt %></span>
30
+ <div style="flex: 1; min-width: 0; position: relative;">
31
+ <textarea
32
+ id="terminal-input"
33
+ style="width: 100%; min-height: 1.4em; background: transparent; border: none; outline: none; color: #e5e7eb; font-family: inherit; font-size: inherit; line-height: inherit; resize: none; padding: 0; margin: 0; overflow: hidden; word-wrap: break-word;"
34
+ placeholder="<%= t('stomp_base.console.placeholder') %>"
35
+ rows="1"
36
+ onkeydown="handleTerminalKeydown(event)"
37
+ oninput="adjustTerminalInputHeight()"
38
+ ></textarea>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Hidden session ID for JavaScript -->
44
+ <input type="hidden" id="terminal-session-id" value="<%= session_id %>">
45
+ </div>
46
+
47
+ <style>
48
+ @media (max-width: 640px) {
49
+ #terminal-display {
50
+ height: 300px !important;
51
+ max-height: 50vh !important;
52
+ padding: 0.75rem !important;
53
+ font-size: 0.8125rem !important;
54
+ }
55
+
56
+ #terminal-input {
57
+ font-size: 0.8125rem !important;
58
+ }
59
+
60
+ #terminal-prompt {
61
+ font-size: 0.8125rem !important;
62
+ }
63
+ }
64
+
65
+ @media (max-width: 480px) {
66
+ #terminal-display {
67
+ height: 250px !important;
68
+ max-height: 40vh !important;
69
+ padding: 0.5rem !important;
70
+ font-size: 0.75rem !important;
71
+ }
72
+
73
+ #terminal-input {
74
+ font-size: 0.75rem !important;
75
+ }
76
+
77
+ #terminal-prompt {
78
+ font-size: 0.75rem !important;
79
+ }
80
+ }
81
+ </style>
82
+
83
+ <script>
84
+ // Terminal functionality
85
+ let terminalHistory = [];
86
+ let terminalHistoryIndex = -1;
87
+ let terminalCommandCounter = 1;
88
+ let terminalSessionId = document.getElementById('terminal-session-id').value;
89
+
90
+ function handleTerminalKeydown(event) {
91
+ const input = event.target;
92
+
93
+ // Execute on Enter (without Shift)
94
+ if (event.key === 'Enter' && !event.shiftKey) {
95
+ event.preventDefault();
96
+ executeTerminalCommand();
97
+ return;
98
+ }
99
+
100
+ // Allow Shift+Enter for multi-line input
101
+ if (event.key === 'Enter' && event.shiftKey) {
102
+ // Let the default behavior handle the new line
103
+ setTimeout(() => adjustTerminalInputHeight(), 0);
104
+ return;
105
+ }
106
+
107
+ // History navigation with Up/Down arrows
108
+ if (event.key === 'ArrowUp' && terminalHistory.length > 0) {
109
+ event.preventDefault();
110
+ terminalHistoryIndex = Math.min(terminalHistoryIndex + 1, terminalHistory.length - 1);
111
+ input.value = terminalHistory[terminalHistoryIndex];
112
+ adjustTerminalInputHeight();
113
+ } else if (event.key === 'ArrowDown') {
114
+ event.preventDefault();
115
+ if (terminalHistoryIndex > 0) {
116
+ terminalHistoryIndex--;
117
+ input.value = terminalHistory[terminalHistoryIndex];
118
+ } else {
119
+ terminalHistoryIndex = -1;
120
+ input.value = '';
121
+ }
122
+ adjustTerminalInputHeight();
123
+ }
124
+ }
125
+
126
+ function adjustTerminalInputHeight() {
127
+ const input = document.getElementById('terminal-input');
128
+ input.style.height = 'auto';
129
+ input.style.height = input.scrollHeight + 'px';
130
+ }
131
+
132
+ function showSessionPersistenceNotice() {
133
+ const notice = document.createElement('div');
134
+ notice.style.cssText = `
135
+ position: fixed;
136
+ top: 20px;
137
+ right: 20px;
138
+ background: #10b981;
139
+ color: white;
140
+ padding: 12px 16px;
141
+ border-radius: 6px;
142
+ font-size: 14px;
143
+ z-index: 1000;
144
+ opacity: 0;
145
+ transform: translateX(100%);
146
+ transition: all 0.3s ease;
147
+ `;
148
+ notice.textContent = '<%= t('stomp_base.console.variable_persistence') %>';
149
+
150
+ document.body.appendChild(notice);
151
+
152
+ setTimeout(() => {
153
+ notice.style.opacity = '1';
154
+ notice.style.transform = 'translateX(0)';
155
+ }, 100);
156
+
157
+ setTimeout(() => {
158
+ notice.style.opacity = '0';
159
+ notice.style.transform = 'translateX(100%)';
160
+ setTimeout(() => notice.remove(), 300);
161
+ }, 3000);
162
+ }
163
+
164
+ // Show session persistence notice on first command
165
+ let firstCommandExecuted = false;
166
+ function executeTerminalCommand() {
167
+ const input = document.getElementById('terminal-input');
168
+ const command = input.value.trim();
169
+
170
+ if (!command) return;
171
+
172
+ // Show persistence notice on first command
173
+ if (!firstCommandExecuted) {
174
+ showSessionPersistenceNotice();
175
+ firstCommandExecuted = true;
176
+ }
177
+
178
+ // Add to history
179
+ terminalHistory.unshift(command);
180
+ if (terminalHistory.length > 50) {
181
+ terminalHistory = terminalHistory.slice(0, 50);
182
+ }
183
+ terminalHistoryIndex = -1;
184
+
185
+ // Display command in terminal
186
+ displayTerminalCommand(command);
187
+
188
+ // Clear input and reset height
189
+ input.value = '';
190
+ input.style.height = 'auto';
191
+
192
+ // Execute command via AJAX
193
+ fetch('<%= StompBase::Engine.routes.url_helpers.console_execute_path %>', {
194
+ method: 'POST',
195
+ headers: {
196
+ 'Content-Type': 'application/json',
197
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
198
+ },
199
+ body: JSON.stringify({
200
+ command: command,
201
+ session_id: terminalSessionId,
202
+ command_counter: terminalCommandCounter
203
+ })
204
+ })
205
+ .then(response => response.json())
206
+ .then(data => {
207
+ if (data.success) {
208
+ displayTerminalOutput(data.result, data.command_counter);
209
+ } else {
210
+ displayTerminalError(data.error, data.command_counter);
211
+ }
212
+ terminalCommandCounter = data.command_counter ? data.command_counter + 1 : terminalCommandCounter + 1;
213
+ updateTerminalPrompt();
214
+ })
215
+ .catch(error => {
216
+ displayTerminalError(`Network error: ${error.message}`, terminalCommandCounter);
217
+ terminalCommandCounter++;
218
+ updateTerminalPrompt();
219
+ });
220
+ }
221
+
222
+ function displayTerminalCommand(command) {
223
+ const output = document.getElementById('terminal-output');
224
+ const commandDiv = document.createElement('div');
225
+ commandDiv.innerHTML = `<span style="color: #10b981;">${getCurrentPrompt()}</span>${escapeHtml(command)}`;
226
+ output.appendChild(commandDiv);
227
+ scrollTerminalToBottom();
228
+ }
229
+
230
+ function displayTerminalOutput(result, commandCounter) {
231
+ const output = document.getElementById('terminal-output');
232
+ const resultDiv = document.createElement('div');
233
+ resultDiv.style.color = '#e5e7eb';
234
+ resultDiv.style.marginBottom = '0.5rem';
235
+ resultDiv.style.whiteSpace = 'pre-wrap';
236
+ resultDiv.textContent = result;
237
+ output.appendChild(resultDiv);
238
+ scrollTerminalToBottom();
239
+ }
240
+
241
+ function displayTerminalError(error, commandCounter) {
242
+ const output = document.getElementById('terminal-output');
243
+ const errorDiv = document.createElement('div');
244
+ errorDiv.style.color = '#ef4444';
245
+ errorDiv.style.marginBottom = '0.5rem';
246
+ errorDiv.style.whiteSpace = 'pre-wrap';
247
+ errorDiv.textContent = error;
248
+ output.appendChild(errorDiv);
249
+ scrollTerminalToBottom();
250
+ }
251
+
252
+ function getCurrentPrompt() {
253
+ return `irb(main):${String(terminalCommandCounter).padStart(3, '0')}:0> `;
254
+ }
255
+
256
+ function updateTerminalPrompt() {
257
+ const prompt = document.getElementById('terminal-prompt');
258
+ prompt.textContent = getCurrentPrompt();
259
+ }
260
+
261
+ function scrollTerminalToBottom() {
262
+ const display = document.getElementById('terminal-display');
263
+ display.scrollTop = display.scrollHeight;
264
+ }
265
+
266
+ function clearTerminal() {
267
+ const output = document.getElementById('terminal-output');
268
+ output.innerHTML = `<div style="color: #10b981; margin-bottom: 0.5rem;"><%= welcome_message %></div>`;
269
+ terminalCommandCounter = 1;
270
+ updateTerminalPrompt();
271
+ document.getElementById('terminal-input').focus();
272
+ }
273
+
274
+ function restartSession() {
275
+ if (confirm('Are you sure you want to restart the console session? All variables will be lost.')) {
276
+ terminalSessionId = generateSessionId();
277
+ document.getElementById('terminal-session-id').value = terminalSessionId;
278
+ clearTerminal();
279
+
280
+ // Notify server to clear session
281
+ fetch('<%= StompBase::Engine.routes.url_helpers.console_execute_path %>', {
282
+ method: 'POST',
283
+ headers: {
284
+ 'Content-Type': 'application/json',
285
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
286
+ },
287
+ body: JSON.stringify({
288
+ command: '__restart_session__',
289
+ session_id: terminalSessionId
290
+ })
291
+ });
292
+ }
293
+ }
294
+
295
+ function generateSessionId() {
296
+ return Math.random().toString(36).substr(2, 9);
297
+ }
298
+
299
+ function escapeHtml(text) {
300
+ const div = document.createElement('div');
301
+ div.textContent = text;
302
+ return div.innerHTML;
303
+ }
304
+
305
+ function insertTerminalCommand(command) {
306
+ const input = document.getElementById('terminal-input');
307
+ input.value = command;
308
+ input.focus();
309
+ adjustTerminalInputHeight();
310
+ }
311
+
312
+ // Focus input on page load
313
+ document.addEventListener('DOMContentLoaded', function() {
314
+ document.getElementById('terminal-input').focus();
315
+ updateTerminalPrompt();
316
+ });
317
+ </script>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StompBase
4
+ module Console
5
+ class TerminalComponent < StompBase::BaseComponent
6
+ def initialize(session_id: nil)
7
+ super()
8
+ @session_id = session_id || SecureRandom.hex(8)
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :session_id
14
+
15
+ def initial_prompt
16
+ "irb(main):001:0> "
17
+ end
18
+
19
+ def welcome_message
20
+ "#{t("stomp_base.console.terminal_welcome")}\n#{t("stomp_base.console.terminal_help")}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,21 +3,17 @@
3
3
  display_options: {
4
4
  gradient_colors: { from: '#1f2937', to: '#374151' },
5
5
  status_message: t('stomp_base.console.welcome_message'),
6
- custom_button: '<button onclick="clearConsoleOutput()" class="stomp-base-btn" style="display: inline-flex; align-items: center; padding: 0.375rem 0.625rem; font-size: 0.8125rem; background-color: rgba(255,255,255,0.2); color: white; border: 1px solid rgba(255,255,255,0.3); backdrop-filter: blur(10px);">
6
+ custom_button: '<button onclick="restartSession()" class="stomp-base-btn" style="display: inline-flex; align-items: center; padding: 0.375rem 0.625rem; font-size: 0.8125rem; background-color: rgba(220,38,38,0.8); color: white; border: 1px solid rgba(220,38,38,0.3); backdrop-filter: blur(10px);">
7
7
  <svg style="width: 0.75rem; height: 0.75rem; margin-right: 0.375rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
8
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
8
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
9
9
  </svg>
10
- ' + t('stomp_base.console.clear') + '
10
+ Restart Session
11
11
  </button>'.html_safe
12
12
  }
13
13
  ) %>
14
14
 
15
15
  <%= render StompBase::Console::SecurityWarningComponent.new %>
16
16
 
17
- <%= render StompBase::Console::ConsoleInterfaceComponent.new %>
17
+ <%= render StompBase::Console::TerminalComponent.new %>
18
18
 
19
19
  <%= render StompBase::Console::ConsoleExamplesComponent.new %>
20
-
21
- <%= render StompBase::Console::ConsoleJavascriptComponent.new %>
22
-
23
- <%= render StompBase::Console::ConsoleCssComponent.new %>
@@ -43,40 +43,50 @@ module StompBase
43
43
 
44
44
  def execute
45
45
  command = params[:command]&.strip
46
+ session_id = params[:session_id]
47
+ command_counter = params[:command_counter]&.to_i || 1
48
+
46
49
  return render_error(I18n.t("stomp_base.console.error")) if command.blank?
47
50
 
48
- process_console_command(command)
51
+ # Handle session restart
52
+ if command == "__restart_session__"
53
+ clear_session_binding(session_id)
54
+ return render json: { success: true, result: "Session restarted", command_counter: 1 }
55
+ end
56
+
57
+ process_console_command(command, session_id, command_counter)
49
58
  rescue StandardError => e
50
- handle_execution_error(e)
59
+ handle_execution_error(e, command_counter)
51
60
  end
52
61
 
53
- def process_console_command(command)
62
+ def process_console_command(command, session_id, command_counter)
54
63
  Rails.logger.info "StompBase Console Command: #{command}"
55
64
  return render_dangerous_command_error if dangerous_command?(command)
56
65
 
57
- result = execute_in_rails_console(command)
58
- render_success(result)
66
+ result = execute_in_rails_console(command, session_id)
67
+ render_success(result, command_counter)
59
68
  end
60
69
 
61
- def handle_execution_error(error)
70
+ def handle_execution_error(error, command_counter = 1)
62
71
  Rails.logger.error "StompBase Console Error: #{error.message}"
63
- render_error(error.message)
72
+ render_error(error.message, command_counter)
64
73
  end
65
74
 
66
75
  private
67
76
 
68
- def render_error(message)
69
- render json: { success: false, error: message, result: nil }
77
+ def render_error(message, command_counter = 1)
78
+ render json: { success: false, error: message, result: nil, command_counter: command_counter }
70
79
  end
71
80
 
72
- def render_success(result)
73
- render json: { success: true, result: result.to_s, error: false }
81
+ def render_success(result, command_counter = 1)
82
+ render json: { success: true, result: result.to_s, error: false, command_counter: command_counter }
74
83
  end
75
84
 
76
85
  def render_dangerous_command_error
77
86
  render json: {
78
87
  success: false,
79
- error: "Dangerous command detected. Execution denied."
88
+ error: "Dangerous command detected. Execution denied.",
89
+ command_counter: 1
80
90
  }
81
91
  end
82
92
 
@@ -90,10 +100,10 @@ module StompBase
90
100
  DANGEROUS_PATTERNS.any? { |pattern| command.match?(pattern) }
91
101
  end
92
102
 
93
- def execute_in_rails_console(command)
103
+ def execute_in_rails_console(command, session_id)
94
104
  # Evaluate command in secure execution environment
95
- # Simulate basic Rails console environment
96
- binding_context = create_console_binding
105
+ # Maintain session state for each session_id
106
+ binding_context = get_or_create_session_binding(session_id)
97
107
 
98
108
  # Set timeout (10 seconds)
99
109
  result = Timeout.timeout(10) do
@@ -104,6 +114,47 @@ module StompBase
104
114
  format_result(result)
105
115
  end
106
116
 
117
+ # Class-level session storage to persist across requests
118
+ @@session_bindings = {}
119
+ @@session_mutex = Mutex.new
120
+ @@session_timestamps = {}
121
+
122
+ def get_or_create_session_binding(session_id)
123
+ @@session_mutex.synchronize do
124
+ # Clean up old sessions (older than 30 minutes)
125
+ cleanup_old_sessions
126
+
127
+ # Create or retrieve session binding
128
+ unless @@session_bindings[session_id]
129
+ @@session_bindings[session_id] = create_console_binding
130
+ @@session_timestamps[session_id] = Time.current
131
+ end
132
+
133
+ # Update timestamp
134
+ @@session_timestamps[session_id] = Time.current
135
+ @@session_bindings[session_id]
136
+ end
137
+ end
138
+
139
+ def clear_session_binding(session_id)
140
+ @@session_mutex.synchronize do
141
+ @@session_bindings.delete(session_id)
142
+ @@session_timestamps.delete(session_id)
143
+ end
144
+ end
145
+
146
+ def cleanup_old_sessions
147
+ current_time = Time.current
148
+ expired_sessions = @@session_timestamps.select do |_session_id, timestamp|
149
+ current_time - timestamp > 30.minutes
150
+ end
151
+
152
+ expired_sessions.each_key do |session_id|
153
+ @@session_bindings.delete(session_id)
154
+ @@session_timestamps.delete(session_id)
155
+ end
156
+ end
157
+
107
158
  def create_console_binding
108
159
  binding_object = ConsoleBindingHelper.new
109
160
  binding_object.instance_eval { binding }
@@ -207,5 +258,33 @@ module StompBase
207
258
  rescue StandardError => e
208
259
  "#{result} (inspect failed: #{e.message})"
209
260
  end
261
+
262
+ # Session management utility methods
263
+ def active_sessions_count
264
+ @@session_mutex.synchronize do
265
+ @@session_bindings.size
266
+ end
267
+ end
268
+
269
+ def session_info(session_id)
270
+ @@session_mutex.synchronize do
271
+ return nil unless @@session_bindings.key?(session_id)
272
+
273
+ {
274
+ session_id: session_id,
275
+ created_at: @@session_timestamps[session_id],
276
+ last_accessed: @@session_timestamps[session_id],
277
+ active: true
278
+ }
279
+ end
280
+ end
281
+
282
+ def all_sessions_info
283
+ @@session_mutex.synchronize do
284
+ @@session_bindings.keys.map do |session_id|
285
+ session_info(session_id)
286
+ end
287
+ end
288
+ end
210
289
  end
211
290
  end
@@ -82,10 +82,12 @@ en:
82
82
  available: "Available"
83
83
  open_console: "Open Console"
84
84
  welcome_message: "Rails Console"
85
- placeholder: "Enter Ruby code (e.g., User.count)"
85
+ placeholder: "Type your Ruby code here..."
86
86
  execute: "Execute"
87
87
  clear: "Clear"
88
88
  clear_history: "Clear History"
89
+ restart_session: "Restart Session"
90
+ session_restarted: "Session restarted"
89
91
  history: "History"
90
92
  output: "Output"
91
93
  no_output: "No output"
@@ -97,6 +99,8 @@ en:
97
99
  sandbox_mode: "Sandbox Mode"
98
100
  enable_sandbox: "Enable Sandbox"
99
101
  disable_sandbox: "Disable Sandbox"
102
+ terminal_welcome: "Welcome to StompBase Rails Console"
103
+ terminal_help: "Type 'help' for available commands"
100
104
  examples:
101
105
  title: "Example Commands"
102
106
  user_count: "User.count"
@@ -181,3 +185,11 @@ en:
181
185
  table_name_label: "Table Name"
182
186
  record_count_label: "Records"
183
187
  null_value: "NULL"
188
+ session_info: "Session Information"
189
+ session_id: "Session ID"
190
+ session_active: "Active"
191
+ session_created_at: "Created At"
192
+ session_last_accessed: "Last Accessed"
193
+ active_sessions: "Active Sessions Count"
194
+ variable_persistence: "Variable state is maintained between commands"
195
+ session_timeout: "Sessions automatically expire after 30 minutes of inactivity"
@@ -82,10 +82,12 @@ ja:
82
82
  available: "利用可能"
83
83
  open_console: "コンソールを開く"
84
84
  welcome_message: "Rails Console"
85
- placeholder: "Rubyコードを入力してください(例: User.count)"
85
+ placeholder: "Rubyコードを入力してください..."
86
86
  execute: "実行"
87
87
  clear: "クリア"
88
88
  clear_history: "履歴をクリア"
89
+ restart_session: "セッションを再開"
90
+ session_restarted: "セッションが再開されました"
89
91
  history: "履歴"
90
92
  output: "出力"
91
93
  no_output: "出力がありません"
@@ -97,6 +99,7 @@ ja:
97
99
  sandbox_mode: "サンドボックスモード"
98
100
  enable_sandbox: "サンドボックスを有効にする"
99
101
  disable_sandbox: "サンドボックスを無効にする"
102
+ terminal_welcome: "StompBase Rails Console へようこそ"
100
103
  examples:
101
104
  title: "コマンド例"
102
105
  user_count: "User.count"
@@ -186,3 +189,11 @@ ja:
186
189
  table_name_label: "テーブル名"
187
190
  record_count_label: "レコード数"
188
191
  null_value: "NULL"
192
+ session_info: "セッション情報"
193
+ session_id: "セッションID"
194
+ session_active: "アクティブ"
195
+ session_created_at: "作成日時"
196
+ session_last_accessed: "最終アクセス"
197
+ active_sessions: "アクティブセッション数"
198
+ variable_persistence: "変数の状態はセッション間で保持されます"
199
+ session_timeout: "セッションは30分間非アクティブ状態が続くと自動的に終了します"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StompBase
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stomp_base
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - snowwshiro
@@ -69,6 +69,8 @@ files:
69
69
  - app/components/stomp_base/console/console_javascript_component.rb
70
70
  - app/components/stomp_base/console/security_warning_component.html.erb
71
71
  - app/components/stomp_base/console/security_warning_component.rb
72
+ - app/components/stomp_base/console/terminal_component.html.erb
73
+ - app/components/stomp_base/console/terminal_component.rb
72
74
  - app/components/stomp_base/dashboard/database_info_card_component.html.erb
73
75
  - app/components/stomp_base/dashboard/database_info_card_component.rb
74
76
  - app/components/stomp_base/dashboard/performance_metrics_card_component.html.erb