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 +4 -4
- data/CHANGELOG.md +20 -1
- data/README.md +22 -0
- data/app/components/stomp_base/console/console_examples_component.html.erb +1 -1
- data/app/components/stomp_base/console/terminal_component.html.erb +317 -0
- data/app/components/stomp_base/console/terminal_component.rb +24 -0
- data/app/components/stomp_base/pages/console_component.html.erb +4 -8
- data/app/controllers/stomp_base/console_controller.rb +94 -15
- data/config/locales/en.yml +13 -1
- data/config/locales/ja.yml +12 -1
- data/lib/stomp_base/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b134d98cdd58b0e0f96d5045fdc83e9bc07379cd95e2bae7a7ee462d5ec9cc4
|
4
|
+
data.tar.gz: 3b1f9f0e9aada4b215c1c2d9277af0478a447ab309492004d76ab43a91171761
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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="
|
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="
|
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="
|
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
|
-
|
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::
|
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
|
-
|
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
|
-
#
|
96
|
-
binding_context =
|
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
|
data/config/locales/en.yml
CHANGED
@@ -82,10 +82,12 @@ en:
|
|
82
82
|
available: "Available"
|
83
83
|
open_console: "Open Console"
|
84
84
|
welcome_message: "Rails Console"
|
85
|
-
placeholder: "
|
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"
|
data/config/locales/ja.yml
CHANGED
@@ -82,10 +82,12 @@ ja:
|
|
82
82
|
available: "利用可能"
|
83
83
|
open_console: "コンソールを開く"
|
84
84
|
welcome_message: "Rails Console"
|
85
|
-
placeholder: "Ruby
|
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分間非アクティブ状態が続くと自動的に終了します"
|
data/lib/stomp_base/version.rb
CHANGED
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.
|
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
|