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.
Files changed (44) hide show
  1. meerschaum/_internal/term/TermPageHandler.py +54 -4
  2. meerschaum/_internal/term/__init__.py +13 -5
  3. meerschaum/_internal/term/tools.py +41 -6
  4. meerschaum/actions/copy.py +1 -0
  5. meerschaum/actions/start.py +25 -10
  6. meerschaum/api/dash/callbacks/dashboard.py +43 -2
  7. meerschaum/api/dash/components.py +13 -6
  8. meerschaum/api/dash/keys.py +82 -108
  9. meerschaum/api/dash/pages/dashboard.py +17 -17
  10. meerschaum/api/dash/sessions.py +1 -0
  11. meerschaum/api/dash/webterm.py +17 -6
  12. meerschaum/api/resources/static/js/terminado.js +0 -2
  13. meerschaum/api/resources/templates/termpage.html +47 -4
  14. meerschaum/api/routes/_webterm.py +15 -11
  15. meerschaum/config/_default.py +6 -0
  16. meerschaum/config/_version.py +1 -1
  17. meerschaum/config/static/__init__.py +2 -2
  18. meerschaum/connectors/sql/_SQLConnector.py +2 -9
  19. meerschaum/connectors/sql/_fetch.py +5 -30
  20. meerschaum/connectors/sql/_pipes.py +7 -4
  21. meerschaum/connectors/sql/_sql.py +56 -31
  22. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -2
  23. meerschaum/core/Pipe/_fetch.py +4 -0
  24. meerschaum/core/Pipe/_sync.py +22 -15
  25. meerschaum/core/Pipe/_verify.py +1 -1
  26. meerschaum/utils/daemon/Daemon.py +24 -11
  27. meerschaum/utils/daemon/RotatingFile.py +3 -3
  28. meerschaum/utils/dataframe.py +42 -12
  29. meerschaum/utils/dtypes/__init__.py +153 -24
  30. meerschaum/utils/dtypes/sql.py +58 -9
  31. meerschaum/utils/formatting/__init__.py +2 -2
  32. meerschaum/utils/formatting/_pprint.py +13 -12
  33. meerschaum/utils/misc.py +32 -18
  34. meerschaum/utils/prompt.py +1 -1
  35. meerschaum/utils/sql.py +26 -8
  36. meerschaum/utils/venv/__init__.py +10 -14
  37. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/METADATA +1 -1
  38. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/RECORD +44 -44
  39. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/LICENSE +0 -0
  40. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/NOTICE +0 -0
  41. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/WHEEL +0 -0
  42. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/entry_points.txt +0 -0
  43. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/top_level.txt +0 -0
  44. {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/zip-safe +0 -0
@@ -105,59 +105,58 @@ action_dropdown_row = html.Div(
105
105
  dbc.Col(
106
106
  html.Div(
107
107
  dcc.Dropdown(
108
- id = 'flags-dropdown',
109
- multi = True,
110
- placeholder = 'Boolean flags',
111
- options = ['--yes'],
112
- value = ['--yes'],
108
+ id='flags-dropdown',
109
+ multi=True,
110
+ placeholder='Boolean flags',
111
+ options=[],
112
+ value=[],
113
113
  ),
114
- id = 'flags-dropdown-div',
115
- className = 'dbc_dark input-text',
114
+ id='flags-dropdown-div',
115
+ className='dbc_dark input-text',
116
116
  ),
117
- width = widths['flags'],
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 = 'show-arguments-collapse-button',
132
- color = 'link',
133
- size = 'md',
134
- outline = True,
135
- style = {'display': 'none'},
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 = 'clear-begin-end-datepicker-button',
143
- color = 'link',
144
- size = 'sm',
141
+ id='clear-begin-end-datepicker-button',
142
+ color='link',
143
+ size='sm',
145
144
  ),
146
145
  dcc.DatePickerRange(
147
- id = 'begin-end-datepicker',
146
+ id='begin-end-datepicker',
148
147
  ),
149
148
  ],
150
- id = 'arguments-collapse',
149
+ id='arguments-collapse',
151
150
  ),
152
151
  ], ### end of div children
153
152
  ),
154
153
  ], ### end of col children
155
- width = widths['arguments'],
154
+ width=widths['arguments'],
156
155
  ),
157
156
  ], ### end of row children
158
157
  ),
159
158
  ], ### end of parent div children
160
- id = 'action-div',
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 = 'connector-keys-dropdown',
171
- options = [],
172
- placeholder = placeholders['ck'],
173
- multi = True,
169
+ id='connector-keys-dropdown',
170
+ options=[],
171
+ placeholder=placeholders['ck'],
172
+ multi=True,
174
173
  ),
175
174
  ],
176
- className = 'dbc_dark',
175
+ className='dbc_dark',
177
176
  ),
178
- lg = 4,
179
- md = 12,
180
- sm = 12,
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 = 'metric-keys-dropdown',
187
- options = [],
188
- placeholder = placeholders['mk'],
189
- multi = True,
185
+ id='metric-keys-dropdown',
186
+ options=[],
187
+ placeholder=placeholders['mk'],
188
+ multi=True,
190
189
  ),
191
190
  ],
192
- className = 'dbc_dark'
191
+ className='dbc_dark'
193
192
  ),
194
- lg = 4,
195
- md = 12,
196
- sm = 12,
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 = 'location-keys-dropdown',
203
- options = [],
204
- placeholder = placeholders['lk'],
205
- multi = True,
201
+ id='location-keys-dropdown',
202
+ options=[],
203
+ placeholder=placeholders['lk'],
204
+ multi=True,
206
205
  ),
207
206
  ],
208
- className = 'dbc_dark'
207
+ className='dbc_dark'
209
208
  ),
210
- lg = 4,
211
- md = 12,
212
- sm = 12,
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 = 'card-text',
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 = 'card-text',
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 = 'clear-connector-keys-input-button',
249
- color = 'link',
250
- size = 'sm',
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 = 'connector-keys-input',
256
- placeholder = placeholders['ck'],
257
- type = 'text',
258
- value = '',
259
- list = 'connector-keys-list',
260
- className = 'dbc_dark'
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 = input_group_sizes['ck'],
258
+ size=input_group_sizes['ck'],
264
259
  )]),
265
- width = 4,
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 = 'clear-metric-keys-input-button',
274
- color = 'link',
275
- size = 'sm',
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 = 'metric-keys-input',
281
- placeholder = placeholders['mk'],
282
- type = 'text',
283
- value = '',
284
- list = 'metric-keys-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 = input_group_sizes['mk'],
279
+ size=input_group_sizes['mk'],
288
280
  ),
289
- width = 4,
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 = 'clear-location-keys-input-button',
298
- color = 'link',
299
- size = 'sm',
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 = 'location-keys-input',
305
- placeholder = placeholders['lk'],
306
- type = 'text',
307
- value = '',
308
- list = 'location-keys-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 = input_group_sizes['lk'],
300
+ size=input_group_sizes['lk'],
312
301
  ),
313
- width = 4,
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 = input_group_sizes['params'],
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 = 'Filter',
69
- id = 'pipes-filter-dropdown-tab',
70
- tab_id = 'dropdown',
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 = 'Text',
75
- id = 'pipes-filter-input-tab',
76
- tab_id = 'input',
77
- tab_style = {"display": "none"},
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 = 'content-col-left',
87
- md = 12,
88
- lg = 6,
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 = 'content-div-right',
101
- children = [console_div],
100
+ id='content-div-right',
101
+ children=[console_div],
102
102
  ),
103
103
  html.Div(id='terminal'),
104
104
  ],
105
- md = 12,
106
- lg = 6,
107
- id = 'content-col-right',
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 = {'display': 'none'}),
114
+ html.P('', id='line-buffer', style={'display': 'none'}),
115
115
  ],
116
116
  )
@@ -97,6 +97,7 @@ def delete_session(session_id: str):
97
97
  """
98
98
  Delete a session if it's been set.
99
99
  """
100
+ ### TODO: Delete webterm sessions.
100
101
  if debug:
101
102
  dprint(f"Deleting {session_id=}")
102
103
  conn = get_cache_connector()
@@ -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 = "console-div",
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
- html.Iframe(
47
- src = f"/webterm?s={session_id}",
48
- id = "webterm-iframe",
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
- var terminal = make_terminal(xterm_div, size, ws_url);
119
- window.terminal = terminal;
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
- s: Optional[str] = None,
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(s):
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
- response = await client.get("http://localhost:8765/")
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 = text,
79
- status_code = response.status_code,
80
- headers = request.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
- async with websockets.connect("ws://localhost:8765/websocket") as ws:
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:
@@ -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,
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.7.7"
5
+ __version__ = "2.7.9"
@@ -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': '/websocket',
35
+ 'webterm': r'/webterm/{session_id}',
36
+ 'webterm_websocket': r'/_websocket/{session_id}',
37
37
  'info': '/info',
38
38
  'healthcheck': '/healthcheck',
39
39
  },