meerschaum 2.7.7__py3-none-any.whl → 2.7.9__py3-none-any.whl
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.
- meerschaum/_internal/term/TermPageHandler.py +54 -4
- meerschaum/_internal/term/__init__.py +13 -5
- meerschaum/_internal/term/tools.py +41 -6
- meerschaum/actions/copy.py +1 -0
- meerschaum/actions/start.py +25 -10
- meerschaum/api/dash/callbacks/dashboard.py +43 -2
- meerschaum/api/dash/components.py +13 -6
- meerschaum/api/dash/keys.py +82 -108
- meerschaum/api/dash/pages/dashboard.py +17 -17
- meerschaum/api/dash/sessions.py +1 -0
- meerschaum/api/dash/webterm.py +17 -6
- meerschaum/api/resources/static/js/terminado.js +0 -2
- meerschaum/api/resources/templates/termpage.html +47 -4
- meerschaum/api/routes/_webterm.py +15 -11
- meerschaum/config/_default.py +6 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +2 -2
- meerschaum/connectors/sql/_SQLConnector.py +2 -9
- meerschaum/connectors/sql/_fetch.py +5 -30
- meerschaum/connectors/sql/_pipes.py +7 -4
- meerschaum/connectors/sql/_sql.py +56 -31
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -2
- meerschaum/core/Pipe/_fetch.py +4 -0
- meerschaum/core/Pipe/_sync.py +22 -15
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/utils/daemon/Daemon.py +24 -11
- meerschaum/utils/daemon/RotatingFile.py +3 -3
- meerschaum/utils/dataframe.py +42 -12
- meerschaum/utils/dtypes/__init__.py +153 -24
- meerschaum/utils/dtypes/sql.py +58 -9
- meerschaum/utils/formatting/__init__.py +2 -2
- meerschaum/utils/formatting/_pprint.py +13 -12
- meerschaum/utils/misc.py +32 -18
- meerschaum/utils/prompt.py +1 -1
- meerschaum/utils/sql.py +26 -8
- meerschaum/utils/venv/__init__.py +10 -14
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/METADATA +1 -1
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/RECORD +44 -44
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/zip-safe +0 -0
meerschaum/api/dash/keys.py
CHANGED
@@ -105,59 +105,58 @@ action_dropdown_row = html.Div(
|
|
105
105
|
dbc.Col(
|
106
106
|
html.Div(
|
107
107
|
dcc.Dropdown(
|
108
|
-
id
|
109
|
-
multi
|
110
|
-
placeholder
|
111
|
-
options
|
112
|
-
value
|
108
|
+
id='flags-dropdown',
|
109
|
+
multi=True,
|
110
|
+
placeholder='Boolean flags',
|
111
|
+
options=[],
|
112
|
+
value=[],
|
113
113
|
),
|
114
|
-
id
|
115
|
-
className
|
114
|
+
id='flags-dropdown-div',
|
115
|
+
className='dbc_dark input-text',
|
116
116
|
),
|
117
|
-
width
|
117
|
+
width=widths['flags'],
|
118
118
|
),
|
119
119
|
],
|
120
120
|
),
|
121
121
|
html.Br(),
|
122
122
|
html.Div(id='input-flags-div'),
|
123
123
|
dbc.Row(
|
124
|
-
children
|
124
|
+
children=[
|
125
125
|
dbc.Col(
|
126
|
-
children
|
126
|
+
children=[
|
127
127
|
html.Div(
|
128
|
-
children
|
128
|
+
children=[
|
129
129
|
dbc.Button(
|
130
130
|
'Additional parameters',
|
131
|
-
id
|
132
|
-
color
|
133
|
-
size
|
134
|
-
outline
|
135
|
-
style
|
131
|
+
id='show-arguments-collapse-button',
|
132
|
+
color='link',
|
133
|
+
size='md',
|
134
|
+
outline=True,
|
135
|
+
style={'display': 'none'},
|
136
136
|
),
|
137
|
-
# html.Br(),
|
138
137
|
dbc.Collapse(
|
139
|
-
children
|
138
|
+
children=[
|
140
139
|
dbc.Button(
|
141
140
|
'Clear',
|
142
|
-
id
|
143
|
-
color
|
144
|
-
size
|
141
|
+
id='clear-begin-end-datepicker-button',
|
142
|
+
color='link',
|
143
|
+
size='sm',
|
145
144
|
),
|
146
145
|
dcc.DatePickerRange(
|
147
|
-
id
|
146
|
+
id='begin-end-datepicker',
|
148
147
|
),
|
149
148
|
],
|
150
|
-
id
|
149
|
+
id='arguments-collapse',
|
151
150
|
),
|
152
151
|
], ### end of div children
|
153
152
|
),
|
154
153
|
], ### end of col children
|
155
|
-
width
|
154
|
+
width=widths['arguments'],
|
156
155
|
),
|
157
156
|
], ### end of row children
|
158
157
|
),
|
159
158
|
], ### end of parent div children
|
160
|
-
id
|
159
|
+
id='action-div',
|
161
160
|
)
|
162
161
|
|
163
162
|
|
@@ -167,49 +166,49 @@ dropdown_keys_row = dbc.Row(
|
|
167
166
|
html.Div(
|
168
167
|
[
|
169
168
|
dcc.Dropdown(
|
170
|
-
id
|
171
|
-
options
|
172
|
-
placeholder
|
173
|
-
multi
|
169
|
+
id='connector-keys-dropdown',
|
170
|
+
options=[],
|
171
|
+
placeholder=placeholders['ck'],
|
172
|
+
multi=True,
|
174
173
|
),
|
175
174
|
],
|
176
|
-
className
|
175
|
+
className='dbc_dark',
|
177
176
|
),
|
178
|
-
lg
|
179
|
-
md
|
180
|
-
sm
|
177
|
+
lg=4,
|
178
|
+
md=12,
|
179
|
+
sm=12,
|
181
180
|
),
|
182
181
|
dbc.Col(
|
183
182
|
html.Div(
|
184
183
|
[
|
185
184
|
dcc.Dropdown(
|
186
|
-
id
|
187
|
-
options
|
188
|
-
placeholder
|
189
|
-
multi
|
185
|
+
id='metric-keys-dropdown',
|
186
|
+
options=[],
|
187
|
+
placeholder=placeholders['mk'],
|
188
|
+
multi=True,
|
190
189
|
),
|
191
190
|
],
|
192
|
-
className
|
191
|
+
className='dbc_dark'
|
193
192
|
),
|
194
|
-
lg
|
195
|
-
md
|
196
|
-
sm
|
193
|
+
lg=4,
|
194
|
+
md=12,
|
195
|
+
sm=12,
|
197
196
|
),
|
198
197
|
dbc.Col(
|
199
198
|
html.Div(
|
200
199
|
[
|
201
200
|
dcc.Dropdown(
|
202
|
-
id
|
203
|
-
options
|
204
|
-
placeholder
|
205
|
-
multi
|
201
|
+
id='location-keys-dropdown',
|
202
|
+
options=[],
|
203
|
+
placeholder=placeholders['lk'],
|
204
|
+
multi=True,
|
206
205
|
),
|
207
206
|
],
|
208
|
-
className
|
207
|
+
className='dbc_dark'
|
209
208
|
),
|
210
|
-
lg
|
211
|
-
md
|
212
|
-
sm
|
209
|
+
lg=4,
|
210
|
+
md=12,
|
211
|
+
sm=12,
|
213
212
|
),
|
214
213
|
] ### end of filters row children
|
215
214
|
)
|
@@ -217,10 +216,9 @@ dropdown_tab_content = html.Div([
|
|
217
216
|
dbc.Card(
|
218
217
|
dbc.CardBody(
|
219
218
|
[
|
220
|
-
# html.P('Pipe Keys'),
|
221
219
|
dropdown_keys_row,
|
222
220
|
], ### end of card children
|
223
|
-
className
|
221
|
+
className='card-text',
|
224
222
|
)
|
225
223
|
),
|
226
224
|
html.Br(),
|
@@ -229,7 +227,7 @@ dropdown_tab_content = html.Div([
|
|
229
227
|
[
|
230
228
|
action_dropdown_row,
|
231
229
|
],
|
232
|
-
className
|
230
|
+
className='card-text',
|
233
231
|
),
|
234
232
|
),
|
235
233
|
])
|
@@ -242,75 +240,66 @@ text_tab_content = dbc.Card(
|
|
242
240
|
dbc.Col(html.Div(className='dbc_dark', children=[
|
243
241
|
dbc.InputGroup(
|
244
242
|
[
|
245
|
-
# dbc.InputGroupAddon(
|
246
243
|
dbc.Button(
|
247
244
|
'Clear',
|
248
|
-
id
|
249
|
-
color
|
250
|
-
size
|
245
|
+
id='clear-connector-keys-input-button',
|
246
|
+
color='link',
|
247
|
+
size='sm',
|
251
248
|
),
|
252
|
-
# addon_type = 'prepend',
|
253
|
-
# ),
|
254
249
|
dbc.Input(
|
255
|
-
id
|
256
|
-
placeholder
|
257
|
-
type
|
258
|
-
value
|
259
|
-
list
|
260
|
-
className
|
250
|
+
id='connector-keys-input',
|
251
|
+
placeholder=placeholders['ck'],
|
252
|
+
type='text',
|
253
|
+
value='',
|
254
|
+
list='connector-keys-list',
|
255
|
+
className='dbc_dark'
|
261
256
|
),
|
262
257
|
],
|
263
|
-
size
|
258
|
+
size=input_group_sizes['ck'],
|
264
259
|
)]),
|
265
|
-
width
|
260
|
+
width=4,
|
266
261
|
),
|
267
262
|
dbc.Col(
|
268
263
|
dbc.InputGroup(
|
269
264
|
[
|
270
|
-
# dbc.InputGroupAddon(
|
271
265
|
dbc.Button(
|
272
266
|
'Clear',
|
273
|
-
id
|
274
|
-
color
|
275
|
-
size
|
267
|
+
id='clear-metric-keys-input-button',
|
268
|
+
color='link',
|
269
|
+
size='sm',
|
276
270
|
),
|
277
|
-
# addon_type = 'prepend',
|
278
|
-
# ),
|
279
271
|
dbc.Input(
|
280
|
-
id
|
281
|
-
placeholder
|
282
|
-
type
|
283
|
-
value
|
284
|
-
list
|
272
|
+
id='metric-keys-input',
|
273
|
+
placeholder=placeholders['mk'],
|
274
|
+
type='text',
|
275
|
+
value='',
|
276
|
+
list='metric-keys-list',
|
285
277
|
),
|
286
278
|
],
|
287
|
-
size
|
279
|
+
size=input_group_sizes['mk'],
|
288
280
|
),
|
289
|
-
width
|
281
|
+
width=4,
|
290
282
|
),
|
291
283
|
dbc.Col(
|
292
284
|
dbc.InputGroup(
|
293
285
|
[
|
294
|
-
# dbc.InputGroupAddon(
|
295
286
|
dbc.Button(
|
296
287
|
'Clear',
|
297
|
-
id
|
298
|
-
color
|
299
|
-
size
|
288
|
+
id='clear-location-keys-input-button',
|
289
|
+
color='link',
|
290
|
+
size='sm',
|
300
291
|
),
|
301
|
-
# addon_type = 'prepend',
|
302
|
-
# ),
|
303
292
|
dbc.Input(
|
304
|
-
id
|
305
|
-
placeholder
|
306
|
-
type
|
307
|
-
value
|
308
|
-
list
|
293
|
+
id='location-keys-input',
|
294
|
+
placeholder=placeholders['lk'],
|
295
|
+
type='text',
|
296
|
+
value='',
|
297
|
+
list='location-keys-list',
|
309
298
|
),
|
310
299
|
],
|
311
|
-
size
|
300
|
+
size=input_group_sizes['lk'],
|
312
301
|
),
|
313
|
-
width
|
302
|
+
width=4,
|
314
303
|
),
|
315
304
|
]
|
316
305
|
),
|
@@ -319,23 +308,9 @@ text_tab_content = dbc.Card(
|
|
319
308
|
dbc.Col(
|
320
309
|
dbc.InputGroup(
|
321
310
|
[
|
322
|
-
# dbc.InputGroupAddon(
|
323
|
-
# dbc.Button(
|
324
|
-
# 'Clear',
|
325
|
-
# id = 'clear-params-textarea-button',
|
326
|
-
# color = 'link',
|
327
|
-
# size = 'sm',
|
328
|
-
# ),
|
329
|
-
# addon_type = 'prepend',
|
330
|
-
# ),
|
331
311
|
search_parameters_editor,
|
332
|
-
# dbc.Textarea(
|
333
|
-
# id = 'params-textarea',
|
334
|
-
# placeholder = placeholders['params'],
|
335
|
-
# value = '',
|
336
|
-
# )
|
337
312
|
],
|
338
|
-
size
|
313
|
+
size=input_group_sizes['params'],
|
339
314
|
)
|
340
315
|
)
|
341
316
|
),
|
@@ -348,4 +323,3 @@ keys_lists_content = html.Div([
|
|
348
323
|
html.Datalist(id='metric-keys-list'),
|
349
324
|
html.Datalist(id='location-keys-list'),
|
350
325
|
], hidden=True)
|
351
|
-
|
@@ -65,16 +65,16 @@ layout = html.Div(
|
|
65
65
|
children = [
|
66
66
|
dbc.Tab(
|
67
67
|
dropdown_tab_content,
|
68
|
-
label
|
69
|
-
id
|
70
|
-
tab_id
|
68
|
+
label='Filters',
|
69
|
+
id='pipes-filter-dropdown-tab',
|
70
|
+
tab_id='dropdown',
|
71
71
|
),
|
72
72
|
dbc.Tab(
|
73
73
|
text_tab_content,
|
74
|
-
label
|
75
|
-
id
|
76
|
-
tab_id
|
77
|
-
tab_style
|
74
|
+
label='Text',
|
75
|
+
id='pipes-filter-input-tab',
|
76
|
+
tab_id='input',
|
77
|
+
tab_style={"display": "none"},
|
78
78
|
),
|
79
79
|
]
|
80
80
|
),
|
@@ -83,12 +83,12 @@ layout = html.Div(
|
|
83
83
|
test_button,
|
84
84
|
html.Div(id='ws-div'),
|
85
85
|
],
|
86
|
-
id
|
87
|
-
md
|
88
|
-
lg
|
86
|
+
id='content-col-left',
|
87
|
+
md=12,
|
88
|
+
lg=6,
|
89
89
|
),
|
90
90
|
dbc.Col(
|
91
|
-
children
|
91
|
+
children=[
|
92
92
|
dbc.Col([
|
93
93
|
html.Div(id='success-alert-div'),
|
94
94
|
html.Div(id='instance-alert-div')
|
@@ -97,20 +97,20 @@ layout = html.Div(
|
|
97
97
|
),
|
98
98
|
html.Div(id='webterm-div'),
|
99
99
|
html.Div(
|
100
|
-
id
|
101
|
-
children
|
100
|
+
id='content-div-right',
|
101
|
+
children=[console_div],
|
102
102
|
),
|
103
103
|
html.Div(id='terminal'),
|
104
104
|
],
|
105
|
-
md
|
106
|
-
lg
|
107
|
-
id
|
105
|
+
md=12,
|
106
|
+
lg=6,
|
107
|
+
id='content-col-right',
|
108
108
|
),
|
109
109
|
],
|
110
110
|
style = {'max-width': '100%', 'padding': '15px'},
|
111
111
|
), ### end of Row
|
112
112
|
className = 'container-fluid',
|
113
113
|
), ### end of Div
|
114
|
-
html.P('', id='line-buffer', style
|
114
|
+
html.P('', id='line-buffer', style={'display': 'none'}),
|
115
115
|
],
|
116
116
|
)
|
meerschaum/api/dash/sessions.py
CHANGED
meerschaum/api/dash/webterm.py
CHANGED
@@ -14,10 +14,12 @@ from meerschaum.utils.typing import WebState, Tuple, Any
|
|
14
14
|
from meerschaum.utils.packages import attempt_import, import_html, import_dcc
|
15
15
|
from meerschaum._internal.term.tools import is_webterm_running
|
16
16
|
from meerschaum.utils.threading import Thread, RLock
|
17
|
+
from meerschaum.utils.misc import is_tmux_available
|
17
18
|
dcc, html = import_dcc(check_update=CHECK_UPDATE), import_html(check_update=CHECK_UPDATE)
|
18
19
|
dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
|
19
20
|
|
20
21
|
MAX_WEBTERM_ATTEMPTS: int = 10
|
22
|
+
TMUX_IS_AVAILABLE: bool = is_tmux_available()
|
21
23
|
|
22
24
|
_locks = {'webterm_thread': RLock()}
|
23
25
|
|
@@ -32,7 +34,7 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
|
|
32
34
|
return (
|
33
35
|
html.Div(
|
34
36
|
html.Pre(msg, id='console-pre'),
|
35
|
-
id
|
37
|
+
id="console-div",
|
36
38
|
),
|
37
39
|
[alert_from_success_tuple((
|
38
40
|
False,
|
@@ -41,12 +43,21 @@ def get_webterm(state: WebState) -> Tuple[Any, Any]:
|
|
41
43
|
)
|
42
44
|
|
43
45
|
for i in range(MAX_WEBTERM_ATTEMPTS):
|
44
|
-
if is_webterm_running('localhost', 8765):
|
46
|
+
if is_webterm_running('localhost', 8765, session_id=(username or session_id)):
|
45
47
|
return (
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
[
|
49
|
+
html.Div(
|
50
|
+
[
|
51
|
+
dbc.Button('Refresh', color='black', id='webterm-refresh-button'),
|
52
|
+
dbc.Button('New Tab', color='black', id='webterm-new-tab-button'),
|
53
|
+
],
|
54
|
+
id='webterm-controls-div',
|
55
|
+
),
|
56
|
+
html.Iframe(
|
57
|
+
src=f"/webterm/{session_id}",
|
58
|
+
id="webterm-iframe",
|
59
|
+
),
|
60
|
+
],
|
50
61
|
[]
|
51
62
|
)
|
52
63
|
time.sleep(1)
|
@@ -15,9 +15,7 @@ function make_terminal(element, size, ws_url) {
|
|
15
15
|
term.attachCustomKeyEventHandler(copyPasteKeyEventHandler);
|
16
16
|
term.open(element);
|
17
17
|
|
18
|
-
sessionStore = localStorage.getItem("session-store");
|
19
18
|
ws.onopen = function (event) {
|
20
|
-
ws.send(sessionStore);
|
21
19
|
ws.send(
|
22
20
|
JSON.stringify([
|
23
21
|
"set_size",
|
@@ -84,6 +84,15 @@ window.addEventListener(
|
|
84
84
|
flags_str += " " + fl + " " + fl_val;
|
85
85
|
}
|
86
86
|
|
87
|
+
let line = "";
|
88
|
+
if (action_str === "__TMUX_NEW_WINDOW") {
|
89
|
+
line = "\x02:";
|
90
|
+
window.terminal.socket.send(JSON.stringify(["stdin", line]));
|
91
|
+
line = "new-window python3 -m meerschaum\r";
|
92
|
+
window.terminal.socket.send(JSON.stringify(["stdin", line]));
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
|
87
96
|
line = (
|
88
97
|
"\x03"
|
89
98
|
+ action_str
|
@@ -98,6 +107,26 @@ window.addEventListener(
|
|
98
107
|
false,
|
99
108
|
);
|
100
109
|
|
110
|
+
let reconnectTimeout;
|
111
|
+
|
112
|
+
function cleanUpWebSocket(socket) {
|
113
|
+
if (!socket) { return; }
|
114
|
+
socket.onopen = null;
|
115
|
+
socket.onmessage = null;
|
116
|
+
socket.onerror = null;
|
117
|
+
socket.onclose = null;
|
118
|
+
|
119
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
120
|
+
socket.close(1000, "Reconnecting");
|
121
|
+
}
|
122
|
+
|
123
|
+
if (reconnectTimeout) {
|
124
|
+
clearTimeout(reconnectTimeout);
|
125
|
+
reconnectTimeout = null;
|
126
|
+
}
|
127
|
+
|
128
|
+
socket = null;
|
129
|
+
}
|
101
130
|
|
102
131
|
window.onload = function() {
|
103
132
|
var termRowHeight = 0.0 + 0.95*document.getElementById("dummy-screen").offsetHeight / 25;
|
@@ -115,15 +144,29 @@ window.onload = function() {
|
|
115
144
|
|
116
145
|
xterm_div = document.getElementById("xterm-div");
|
117
146
|
size = calculate_size(window);
|
118
|
-
|
119
|
-
|
147
|
+
|
148
|
+
function init_webterm() {
|
149
|
+
old_terminal = window.terminal;
|
150
|
+
if (old_terminal){
|
151
|
+
xterm_div.innerHTML = "";
|
152
|
+
cleanUpWebSocket(old_terminal.socket);
|
153
|
+
}
|
154
|
+
window.terminal = make_terminal(xterm_div, size, ws_url);
|
155
|
+
window.terminal.socket.onclose = function (event) {
|
156
|
+
if (!event.wasClean && event.code !== 1005) {
|
157
|
+
reconnectTimeout = setTimeout(init_webterm, 1000);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
init_webterm();
|
120
163
|
|
121
164
|
window.onresize = function() {
|
122
165
|
var geom = calculate_size(window);
|
123
166
|
rows = geom.rows;
|
124
167
|
cols = geom.cols;
|
125
|
-
terminal.term.resize(cols, rows);
|
126
|
-
terminal.socket.send(
|
168
|
+
window.terminal.term.resize(cols, rows);
|
169
|
+
window.terminal.socket.send(
|
127
170
|
JSON.stringify(
|
128
171
|
["set_size", rows, cols, xterm_div.innerHeight, xterm_div.innerWidth]
|
129
172
|
)
|
@@ -10,7 +10,7 @@ import asyncio
|
|
10
10
|
from meerschaum.utils.typing import Optional
|
11
11
|
from meerschaum.api import app, endpoints
|
12
12
|
from meerschaum.utils.packages import attempt_import
|
13
|
-
from meerschaum.api.dash.sessions import is_session_authenticated
|
13
|
+
from meerschaum.api.dash.sessions import is_session_authenticated, get_username_from_session
|
14
14
|
fastapi, fastapi_responses = attempt_import('fastapi', 'fastapi.responses')
|
15
15
|
import starlette
|
16
16
|
|
@@ -27,12 +27,12 @@ PlainTextResponse = fastapi_responses.PlainTextResponse
|
|
27
27
|
@app.get(endpoints['webterm'], tags=["Webterm"])
|
28
28
|
async def get_webterm(
|
29
29
|
request: Request,
|
30
|
-
|
30
|
+
session_id: str,
|
31
31
|
) -> HTMLResponse:
|
32
32
|
"""
|
33
33
|
Get the main HTML template for the Webterm.
|
34
34
|
"""
|
35
|
-
if not is_session_authenticated(
|
35
|
+
if not is_session_authenticated(session_id):
|
36
36
|
return HTMLResponse(
|
37
37
|
"""
|
38
38
|
<html>
|
@@ -69,35 +69,39 @@ async def get_webterm(
|
|
69
69
|
status_code = 401,
|
70
70
|
)
|
71
71
|
|
72
|
+
username = get_username_from_session(session_id)
|
72
73
|
async with httpx.AsyncClient() as client:
|
73
|
-
|
74
|
+
webterm_url = f"http://localhost:8765/webterm/{username or session_id}"
|
75
|
+
response = await client.get(webterm_url)
|
74
76
|
text = response.text
|
75
77
|
if request.url.scheme == 'https':
|
76
78
|
text = text.replace('ws://', 'wss://')
|
79
|
+
text = text.replace(f'_websocket/{username}', f'_websocket/{session_id}')
|
77
80
|
return HTMLResponse(
|
78
|
-
content
|
79
|
-
status_code
|
80
|
-
headers
|
81
|
+
content=text,
|
82
|
+
status_code=response.status_code,
|
83
|
+
headers=request.headers,
|
81
84
|
)
|
82
85
|
|
83
86
|
|
84
87
|
@app.websocket(endpoints['webterm_websocket'])
|
85
|
-
async def webterm_websocket(websocket: WebSocket):
|
88
|
+
async def webterm_websocket(websocket: WebSocket, session_id: str):
|
86
89
|
"""
|
87
90
|
Connect to the Webterm's websocket.
|
88
91
|
"""
|
89
92
|
try:
|
90
93
|
await websocket.accept()
|
91
|
-
session_doc = await websocket.receive_json()
|
92
94
|
except starlette.websockets.WebSocketDisconnect:
|
93
95
|
return
|
94
|
-
session_id = (session_doc or {}).get('session-id', 'no-auth')
|
95
96
|
|
96
97
|
if not is_session_authenticated(session_id):
|
97
98
|
await websocket.close()
|
98
99
|
return
|
99
100
|
|
100
|
-
|
101
|
+
username = get_username_from_session(session_id)
|
102
|
+
|
103
|
+
ws_url = f"ws://localhost:8765/_websocket/{username or session_id}"
|
104
|
+
async with websockets.connect(ws_url) as ws:
|
101
105
|
async def forward_messages():
|
102
106
|
try:
|
103
107
|
while True:
|
meerschaum/config/_default.py
CHANGED
@@ -127,6 +127,12 @@ default_system_config = {
|
|
127
127
|
},
|
128
128
|
'protocol': default_meerschaum_config['connectors']['api']['default']['protocol'],
|
129
129
|
},
|
130
|
+
'webterm': {
|
131
|
+
'tmux': {
|
132
|
+
'enabled': True,
|
133
|
+
'session_suffix': '_mrsm',
|
134
|
+
},
|
135
|
+
},
|
130
136
|
'experimental': {
|
131
137
|
'fetch': False,
|
132
138
|
'cache': True,
|
meerschaum/config/_version.py
CHANGED
@@ -32,8 +32,8 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
32
32
|
'chaining': '/chaining',
|
33
33
|
'websocket': '/ws',
|
34
34
|
'dash': '/dash',
|
35
|
-
'webterm': '/webterm',
|
36
|
-
'webterm_websocket': '/
|
35
|
+
'webterm': r'/webterm/{session_id}',
|
36
|
+
'webterm_websocket': r'/_websocket/{session_id}',
|
37
37
|
'info': '/info',
|
38
38
|
'healthcheck': '/healthcheck',
|
39
39
|
},
|