meerschaum 2.3.5.dev0__py3-none-any.whl → 2.4.0__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/arguments/__init__.py +2 -1
- meerschaum/_internal/arguments/_parse_arguments.py +88 -12
- meerschaum/_internal/docs/index.py +3 -2
- meerschaum/_internal/entry.py +42 -20
- meerschaum/_internal/shell/Shell.py +38 -44
- meerschaum/_internal/term/TermPageHandler.py +2 -3
- meerschaum/_internal/term/__init__.py +13 -11
- meerschaum/actions/api.py +26 -23
- meerschaum/actions/bootstrap.py +38 -11
- meerschaum/actions/copy.py +3 -3
- meerschaum/actions/delete.py +4 -1
- meerschaum/actions/register.py +1 -3
- meerschaum/actions/stack.py +24 -19
- meerschaum/actions/start.py +41 -41
- meerschaum/actions/sync.py +53 -52
- meerschaum/api/__init__.py +48 -14
- meerschaum/api/_events.py +26 -17
- meerschaum/api/_oauth2.py +2 -2
- meerschaum/api/_websockets.py +5 -4
- meerschaum/api/dash/__init__.py +7 -16
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/dashboard.py +52 -58
- meerschaum/api/dash/callbacks/jobs.py +15 -16
- meerschaum/api/dash/callbacks/login.py +16 -10
- meerschaum/api/dash/callbacks/pipes.py +41 -0
- meerschaum/api/dash/callbacks/plugins.py +1 -1
- meerschaum/api/dash/callbacks/register.py +15 -11
- meerschaum/api/dash/components.py +54 -59
- meerschaum/api/dash/jobs.py +5 -9
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/pipes.py +19 -0
- meerschaum/api/dash/pipes.py +86 -58
- meerschaum/api/dash/plugins.py +6 -4
- meerschaum/api/dash/sessions.py +176 -0
- meerschaum/api/dash/users.py +3 -41
- meerschaum/api/dash/webterm.py +12 -17
- meerschaum/api/resources/static/js/terminado.js +1 -1
- meerschaum/api/routes/_actions.py +4 -118
- meerschaum/api/routes/_jobs.py +45 -24
- meerschaum/api/routes/_login.py +4 -4
- meerschaum/api/routes/_pipes.py +3 -3
- meerschaum/api/routes/_webterm.py +5 -6
- meerschaum/config/_default.py +15 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +64 -21
- meerschaum/config/static/__init__.py +6 -0
- meerschaum/connectors/{Connector.py → _Connector.py} +19 -13
- meerschaum/connectors/__init__.py +24 -14
- meerschaum/connectors/api/{APIConnector.py → _APIConnector.py} +3 -1
- meerschaum/connectors/api/__init__.py +2 -1
- meerschaum/connectors/api/_actions.py +22 -36
- meerschaum/connectors/api/_jobs.py +1 -0
- meerschaum/connectors/parse.py +18 -16
- meerschaum/connectors/poll.py +30 -24
- meerschaum/connectors/sql/__init__.py +3 -1
- meerschaum/connectors/sql/_pipes.py +172 -197
- meerschaum/connectors/sql/_plugins.py +45 -43
- meerschaum/connectors/sql/_users.py +46 -38
- meerschaum/connectors/valkey/_ValkeyConnector.py +535 -0
- meerschaum/connectors/valkey/__init__.py +10 -0
- meerschaum/connectors/valkey/_fetch.py +75 -0
- meerschaum/connectors/valkey/_pipes.py +844 -0
- meerschaum/connectors/valkey/_plugins.py +265 -0
- meerschaum/connectors/valkey/_users.py +305 -0
- meerschaum/core/Pipe/__init__.py +3 -0
- meerschaum/core/Pipe/_attributes.py +1 -2
- meerschaum/core/Pipe/_clear.py +16 -13
- meerschaum/core/Pipe/_copy.py +106 -0
- meerschaum/core/Pipe/_data.py +165 -101
- meerschaum/core/Pipe/_drop.py +4 -4
- meerschaum/core/Pipe/_dtypes.py +14 -14
- meerschaum/core/Pipe/_edit.py +15 -14
- meerschaum/core/Pipe/_sync.py +134 -53
- meerschaum/core/Pipe/_verify.py +11 -11
- meerschaum/core/User/_User.py +14 -12
- meerschaum/jobs/_Job.py +27 -14
- meerschaum/jobs/__init__.py +7 -2
- meerschaum/jobs/systemd.py +20 -8
- meerschaum/plugins/_Plugin.py +17 -13
- meerschaum/utils/_get_pipes.py +14 -20
- meerschaum/utils/dataframe.py +291 -101
- meerschaum/utils/dtypes/__init__.py +31 -6
- meerschaum/utils/dtypes/sql.py +4 -4
- meerschaum/utils/formatting/_shell.py +5 -6
- meerschaum/utils/misc.py +3 -3
- meerschaum/utils/packages/__init__.py +14 -9
- meerschaum/utils/packages/_packages.py +2 -0
- meerschaum/utils/prompt.py +1 -1
- meerschaum/utils/schedule.py +1 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/METADATA +7 -1
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/RECORD +98 -89
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/WHEEL +1 -1
- meerschaum/api/dash/actions.py +0 -255
- /meerschaum/connectors/sql/{SQLConnector.py → _SQLConnector.py} +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/zip-safe +0 -0
@@ -7,28 +7,13 @@ Execute Meerschaum Actions via the API
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
|
11
|
-
import
|
12
|
-
import shlex
|
13
|
-
from functools import partial
|
14
|
-
from datetime import datetime, timezone
|
15
|
-
|
16
|
-
from fastapi import WebSocket, WebSocketDisconnect
|
17
|
-
from websockets.exceptions import ConnectionClosedError
|
18
|
-
|
19
|
-
from meerschaum.utils.misc import generate_password
|
20
|
-
from meerschaum.jobs import Job
|
21
|
-
from meerschaum.utils.warnings import warn
|
22
|
-
from meerschaum.utils.typing import SuccessTuple, Union, List, Dict, Any
|
10
|
+
|
11
|
+
from meerschaum.utils.typing import SuccessTuple, List, Dict, Any
|
23
12
|
from meerschaum.api import (
|
24
13
|
fastapi, app, endpoints, get_api_connector, debug, manager, private, no_auth
|
25
14
|
)
|
26
15
|
from meerschaum.actions import actions
|
27
|
-
import meerschaum.core
|
28
16
|
from meerschaum.config import get_config
|
29
|
-
from meerschaum._internal.arguments._parse_arguments import parse_dict_to_sysargs, parse_arguments
|
30
|
-
from meerschaum.api.routes._jobs import clean_sysargs
|
31
|
-
from meerschaum.jobs._Job import StopMonitoringLogs
|
32
17
|
|
33
18
|
actions_endpoint = endpoints['actions']
|
34
19
|
|
@@ -67,104 +52,6 @@ def get_actions(
|
|
67
52
|
return list(actions)
|
68
53
|
|
69
54
|
|
70
|
-
async def notify_client(client, content: str):
|
71
|
-
"""
|
72
|
-
Send a line of text to a client.
|
73
|
-
"""
|
74
|
-
try:
|
75
|
-
await client.send_text(content)
|
76
|
-
except (RuntimeError, WebSocketDisconnect, Exception):
|
77
|
-
raise StopMonitoringLogs
|
78
|
-
|
79
|
-
_temp_jobs = {}
|
80
|
-
@app.websocket(actions_endpoint + '/ws')
|
81
|
-
async def do_action_websocket(websocket: WebSocket):
|
82
|
-
"""
|
83
|
-
Execute an action and stream the output to the client.
|
84
|
-
"""
|
85
|
-
await websocket.accept()
|
86
|
-
|
87
|
-
stop_event = asyncio.Event()
|
88
|
-
job_name = '.' + generate_password(12)
|
89
|
-
job = None
|
90
|
-
|
91
|
-
async def monitor_logs(job):
|
92
|
-
success, msg = job.start()
|
93
|
-
await job.monitor_logs_async(
|
94
|
-
partial(notify_client, websocket),
|
95
|
-
stop_event=stop_event,
|
96
|
-
stop_on_exit=True,
|
97
|
-
)
|
98
|
-
|
99
|
-
try:
|
100
|
-
token = await websocket.receive_text()
|
101
|
-
user = await manager.get_current_user(token) if not no_auth else None
|
102
|
-
if user is None and not no_auth:
|
103
|
-
stop_event.set()
|
104
|
-
if job is not None:
|
105
|
-
job.delete()
|
106
|
-
_ = _temp_jobs.pop(job_name, None)
|
107
|
-
raise fastapi.HTTPException(
|
108
|
-
status_code=401,
|
109
|
-
detail="Invalid credentials.",
|
110
|
-
)
|
111
|
-
|
112
|
-
auth_success, auth_msg = (
|
113
|
-
is_user_allowed_to_execute(user)
|
114
|
-
if not no_auth
|
115
|
-
else (True, "Success")
|
116
|
-
)
|
117
|
-
auth_payload = {
|
118
|
-
'is_authenticated': auth_success,
|
119
|
-
'timestamp': datetime.now(timezone.utc).isoformat(),
|
120
|
-
}
|
121
|
-
await websocket.send_json(auth_payload)
|
122
|
-
if not auth_success:
|
123
|
-
stop_event.set()
|
124
|
-
job.stop()
|
125
|
-
await websocket.close()
|
126
|
-
|
127
|
-
sysargs = clean_sysargs(await websocket.receive_json())
|
128
|
-
job = Job(
|
129
|
-
job_name,
|
130
|
-
sysargs,
|
131
|
-
executor_keys='local',
|
132
|
-
_properties={
|
133
|
-
'logs': {
|
134
|
-
'write_timestamps': False,
|
135
|
-
},
|
136
|
-
},
|
137
|
-
)
|
138
|
-
_temp_jobs[job_name] = job
|
139
|
-
monitor_task = asyncio.create_task(monitor_logs(job))
|
140
|
-
|
141
|
-
### NOTE: Await incoming text to trigger `WebSocketDisconnect`.
|
142
|
-
while True:
|
143
|
-
await websocket.receive_text()
|
144
|
-
|
145
|
-
except fastapi.HTTPException:
|
146
|
-
await websocket.send_text("Invalid credentials.")
|
147
|
-
await websocket.close()
|
148
|
-
except (WebSocketDisconnect, asyncio.CancelledError):
|
149
|
-
stop_event.set()
|
150
|
-
job.stop()
|
151
|
-
except Exception:
|
152
|
-
stop_event.set()
|
153
|
-
job.stop()
|
154
|
-
warn(f"Error in logs websocket:\n{traceback.format_exc()}")
|
155
|
-
finally:
|
156
|
-
stop_event.set()
|
157
|
-
job.stop()
|
158
|
-
monitor_task.cancel()
|
159
|
-
if job is not None:
|
160
|
-
job.delete()
|
161
|
-
_ = _temp_jobs.pop(job_name, None)
|
162
|
-
try:
|
163
|
-
await websocket.close()
|
164
|
-
except RuntimeError:
|
165
|
-
pass
|
166
|
-
|
167
|
-
|
168
55
|
@app.post(actions_endpoint + "/{action}", tags=['Actions'])
|
169
56
|
def do_action_legacy(
|
170
57
|
action: str,
|
@@ -180,7 +67,7 @@ def do_action_legacy(
|
|
180
67
|
----------
|
181
68
|
action: str
|
182
69
|
The action to perform.
|
183
|
-
|
70
|
+
|
184
71
|
keywords: Dict[str, Any]
|
185
72
|
The keywords dictionary to pass to the action.
|
186
73
|
|
@@ -189,7 +76,6 @@ def do_action_legacy(
|
|
189
76
|
A `SuccessTuple`.
|
190
77
|
"""
|
191
78
|
if curr_user is not None and curr_user.type != 'admin':
|
192
|
-
from meerschaum.config import get_config
|
193
79
|
allow_non_admin = get_config(
|
194
80
|
'system', 'api', 'permissions', 'actions', 'non_admin', patch=True
|
195
81
|
)
|
@@ -201,7 +87,7 @@ def do_action_legacy(
|
|
201
87
|
+ "and search for 'permissions'. "
|
202
88
|
+ "\nUnder the keys 'api:permissions:actions', "
|
203
89
|
+ "you can allow non-admin users to perform actions."
|
204
|
-
|
90
|
+
)
|
205
91
|
|
206
92
|
if action not in actions:
|
207
93
|
return False, f"Invalid action '{action}'."
|
meerschaum/api/routes/_jobs.py
CHANGED
@@ -9,7 +9,6 @@ Manage jobs via the Meerschaum API.
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
11
|
import os
|
12
|
-
import select
|
13
12
|
import asyncio
|
14
13
|
import traceback
|
15
14
|
from datetime import datetime
|
@@ -18,12 +17,10 @@ from functools import partial
|
|
18
17
|
|
19
18
|
from fastapi import WebSocket, WebSocketDisconnect
|
20
19
|
|
21
|
-
from meerschaum.utils.typing import Dict, Any, SuccessTuple, List,
|
20
|
+
from meerschaum.utils.typing import Dict, Any, SuccessTuple, List, Union
|
22
21
|
from meerschaum.jobs import (
|
23
22
|
get_jobs as _get_jobs,
|
24
23
|
Job,
|
25
|
-
StopMonitoringLogs,
|
26
|
-
get_executor_keys_from_context,
|
27
24
|
)
|
28
25
|
from meerschaum.utils.warnings import warn
|
29
26
|
|
@@ -32,15 +29,23 @@ from meerschaum.api import (
|
|
32
29
|
app,
|
33
30
|
endpoints,
|
34
31
|
manager,
|
35
|
-
debug,
|
36
32
|
no_auth,
|
37
|
-
private,
|
38
33
|
)
|
39
34
|
from meerschaum.config.static import STATIC_CONFIG
|
40
35
|
|
41
36
|
JOBS_STDIN_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stdin_message']
|
42
37
|
JOBS_STOP_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stop_message']
|
43
|
-
|
38
|
+
NONINTERACTIVE_ENV: str = STATIC_CONFIG['environment']['noninteractive']
|
39
|
+
EXECUTOR_KEYS: str = 'local'
|
40
|
+
|
41
|
+
|
42
|
+
def _get_job(name: str):
|
43
|
+
systemd_job = Job(name, executor_keys='systemd')
|
44
|
+
if systemd_job.exists():
|
45
|
+
return systemd_job
|
46
|
+
|
47
|
+
job = Job(name, executor_keys=EXECUTOR_KEYS)
|
48
|
+
return job
|
44
49
|
|
45
50
|
|
46
51
|
@app.get(endpoints['jobs'], tags=['Jobs'])
|
@@ -52,7 +57,7 @@ def get_jobs(
|
|
52
57
|
"""
|
53
58
|
Return metadata about the current jobs.
|
54
59
|
"""
|
55
|
-
jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=
|
60
|
+
jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=True)
|
56
61
|
return {
|
57
62
|
name: {
|
58
63
|
'sysargs': job.sysargs,
|
@@ -83,7 +88,7 @@ def get_job(
|
|
83
88
|
"""
|
84
89
|
Return metadata for a single job.
|
85
90
|
"""
|
86
|
-
job =
|
91
|
+
job = _get_job(name)
|
87
92
|
if not job.exists():
|
88
93
|
raise fastapi.HTTPException(
|
89
94
|
status_code=404,
|
@@ -142,6 +147,10 @@ def create_job(
|
|
142
147
|
name,
|
143
148
|
clean_sysargs(sysargs),
|
144
149
|
executor_keys=EXECUTOR_KEYS,
|
150
|
+
env={
|
151
|
+
NONINTERACTIVE_ENV: '1',
|
152
|
+
**dict(os.environ)
|
153
|
+
},
|
145
154
|
_properties=properties,
|
146
155
|
)
|
147
156
|
if job.exists():
|
@@ -163,7 +172,7 @@ def delete_job(
|
|
163
172
|
"""
|
164
173
|
Delete a job.
|
165
174
|
"""
|
166
|
-
job =
|
175
|
+
job = _get_job(name)
|
167
176
|
return job.delete()
|
168
177
|
|
169
178
|
|
@@ -177,7 +186,7 @@ def get_job_exists(
|
|
177
186
|
"""
|
178
187
|
Return whether a job exists.
|
179
188
|
"""
|
180
|
-
job =
|
189
|
+
job = _get_job(name)
|
181
190
|
return job.exists()
|
182
191
|
|
183
192
|
|
@@ -192,7 +201,7 @@ def get_logs(
|
|
192
201
|
Return a job's log text.
|
193
202
|
To stream log text, connect to the WebSocket endpoint `/logs/{name}/ws`.
|
194
203
|
"""
|
195
|
-
job =
|
204
|
+
job = _get_job(name)
|
196
205
|
if not job.exists():
|
197
206
|
raise fastapi.HTTPException(
|
198
207
|
status_code=404,
|
@@ -212,7 +221,7 @@ def start_job(
|
|
212
221
|
"""
|
213
222
|
Start a job if stopped.
|
214
223
|
"""
|
215
|
-
job =
|
224
|
+
job = _get_job(name)
|
216
225
|
if not job.exists():
|
217
226
|
raise fastapi.HTTPException(
|
218
227
|
status_code=404,
|
@@ -231,7 +240,7 @@ def stop_job(
|
|
231
240
|
"""
|
232
241
|
Stop a job if running.
|
233
242
|
"""
|
234
|
-
job =
|
243
|
+
job = _get_job(name)
|
235
244
|
if not job.exists():
|
236
245
|
raise fastapi.HTTPException(
|
237
246
|
status_code=404,
|
@@ -250,7 +259,7 @@ def pause_job(
|
|
250
259
|
"""
|
251
260
|
Pause a job if running.
|
252
261
|
"""
|
253
|
-
job =
|
262
|
+
job = _get_job(name)
|
254
263
|
if not job.exists():
|
255
264
|
raise fastapi.HTTPException(
|
256
265
|
status_code=404,
|
@@ -269,7 +278,7 @@ def get_stop_time(
|
|
269
278
|
"""
|
270
279
|
Get the timestamp when the job was manually stopped.
|
271
280
|
"""
|
272
|
-
job =
|
281
|
+
job = _get_job(name)
|
273
282
|
return job.stop_time
|
274
283
|
|
275
284
|
|
@@ -283,12 +292,13 @@ def get_is_blocking_on_stdin(
|
|
283
292
|
"""
|
284
293
|
Return whether a job is blocking on stdin.
|
285
294
|
"""
|
286
|
-
job =
|
295
|
+
job = _get_job(name)
|
287
296
|
return job.is_blocking_on_stdin()
|
288
297
|
|
289
298
|
|
290
299
|
_job_clients = defaultdict(lambda: [])
|
291
300
|
_job_stop_events = defaultdict(lambda: asyncio.Event())
|
301
|
+
_job_queues = defaultdict(lambda: asyncio.Queue())
|
292
302
|
async def notify_clients(name: str, websocket: WebSocket, content: str):
|
293
303
|
"""
|
294
304
|
Write the given content to all connected clients.
|
@@ -315,12 +325,16 @@ async def get_input_from_clients(name: str, websocket: WebSocket) -> str:
|
|
315
325
|
async def _read_client(client):
|
316
326
|
try:
|
317
327
|
await client.send_text(JOBS_STDIN_MESSAGE)
|
318
|
-
data = await
|
328
|
+
data = await _job_queues[name].get()
|
319
329
|
except WebSocketDisconnect:
|
320
330
|
if client in _job_clients[name]:
|
321
331
|
_job_clients[name].remove(client)
|
332
|
+
if not _job_clients[name]:
|
333
|
+
_job_stop_events[name].set()
|
322
334
|
except Exception:
|
323
335
|
pass
|
336
|
+
finally:
|
337
|
+
_job_queues[name].task_done()
|
324
338
|
return data
|
325
339
|
|
326
340
|
read_tasks = [
|
@@ -357,10 +371,12 @@ async def logs_websocket(name: str, websocket: WebSocket):
|
|
357
371
|
Stream logs from a job over a websocket.
|
358
372
|
"""
|
359
373
|
await websocket.accept()
|
360
|
-
job =
|
374
|
+
job = _get_job(name)
|
361
375
|
_job_clients[name].append(websocket)
|
362
376
|
|
377
|
+
_task = None
|
363
378
|
async def monitor_logs():
|
379
|
+
nonlocal _task
|
364
380
|
try:
|
365
381
|
callback_function = partial(
|
366
382
|
notify_clients,
|
@@ -377,16 +393,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
|
|
377
393
|
name,
|
378
394
|
websocket,
|
379
395
|
)
|
380
|
-
|
396
|
+
_task = asyncio.create_task(job.monitor_logs_async(
|
381
397
|
callback_function=callback_function,
|
382
398
|
input_callback_function=input_callback_function,
|
383
399
|
stop_callback_function=stop_callback_function,
|
384
400
|
stop_event=_job_stop_events[name],
|
385
401
|
stop_on_exit=True,
|
386
402
|
accept_input=True,
|
387
|
-
)
|
403
|
+
))
|
388
404
|
except Exception:
|
389
405
|
warn(traceback.format_exc())
|
406
|
+
_task.cancel()
|
390
407
|
|
391
408
|
try:
|
392
409
|
token = await websocket.receive_text()
|
@@ -397,13 +414,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
|
|
397
414
|
detail="Invalid credentials.",
|
398
415
|
)
|
399
416
|
monitor_task = asyncio.create_task(monitor_logs())
|
400
|
-
|
417
|
+
while True:
|
418
|
+
text = await websocket.receive_text()
|
419
|
+
await _job_queues[name].put(text)
|
420
|
+
|
401
421
|
except fastapi.HTTPException:
|
402
422
|
await websocket.send_text("Invalid credentials.")
|
403
423
|
await websocket.close()
|
404
424
|
except WebSocketDisconnect:
|
405
|
-
|
406
|
-
|
425
|
+
if not _job_clients[name]:
|
426
|
+
_job_stop_events[name].set()
|
427
|
+
monitor_task.cancel()
|
407
428
|
except asyncio.CancelledError:
|
408
429
|
pass
|
409
430
|
except Exception:
|
meerschaum/api/routes/_login.py
CHANGED
@@ -23,8 +23,8 @@ from meerschaum.api._oauth2 import CustomOAuth2PasswordRequestForm
|
|
23
23
|
|
24
24
|
@manager.user_loader()
|
25
25
|
def load_user(
|
26
|
-
|
27
|
-
|
26
|
+
username: str
|
27
|
+
) -> User:
|
28
28
|
"""
|
29
29
|
Create the `meerschaum.core.User` object from the username.
|
30
30
|
"""
|
@@ -33,8 +33,8 @@ def load_user(
|
|
33
33
|
|
34
34
|
@app.post(endpoints['login'], tags=['Users'])
|
35
35
|
def login(
|
36
|
-
|
37
|
-
|
36
|
+
data: CustomOAuth2PasswordRequestForm = fastapi.Depends()
|
37
|
+
) -> Dict[str, Any]:
|
38
38
|
"""
|
39
39
|
Login and set the session token.
|
40
40
|
"""
|
meerschaum/api/routes/_pipes.py
CHANGED
@@ -474,9 +474,9 @@ def get_pipe_data(
|
|
474
474
|
df[col] = df[col].apply(lambda x: f'{x:f}' if isinstance(x, Decimal) else x)
|
475
475
|
|
476
476
|
json_content = df.to_json(
|
477
|
-
date_format
|
478
|
-
orient
|
479
|
-
date_unit
|
477
|
+
date_format='iso',
|
478
|
+
orient='records',
|
479
|
+
date_unit='us',
|
480
480
|
)
|
481
481
|
|
482
482
|
return fastapi.Response(
|
@@ -8,10 +8,9 @@ Routes to the Webterm proxy.
|
|
8
8
|
|
9
9
|
import asyncio
|
10
10
|
from meerschaum.utils.typing import Optional
|
11
|
-
from meerschaum.api import app,
|
11
|
+
from meerschaum.api import app, endpoints
|
12
12
|
from meerschaum.utils.packages import attempt_import
|
13
|
-
from meerschaum.api.dash import
|
14
|
-
from meerschaum.api.dash.users import is_session_authenticated
|
13
|
+
from meerschaum.api.dash.sessions import is_session_authenticated
|
15
14
|
fastapi, fastapi_responses = attempt_import('fastapi', 'fastapi.responses')
|
16
15
|
import starlette
|
17
16
|
httpx = attempt_import('httpx')
|
@@ -25,9 +24,9 @@ PlainTextResponse = fastapi_responses.PlainTextResponse
|
|
25
24
|
|
26
25
|
@app.get(endpoints['webterm'], tags=["Webterm"])
|
27
26
|
async def get_webterm(
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
request: Request,
|
28
|
+
s: Optional[str] = None,
|
29
|
+
) -> HTMLResponse:
|
31
30
|
"""
|
32
31
|
Get the main HTML template for the Webterm.
|
33
32
|
"""
|
meerschaum/config/_default.py
CHANGED
@@ -16,7 +16,6 @@ default_meerschaum_config = {
|
|
16
16
|
'api_instance': 'MRSM{meerschaum:instance}',
|
17
17
|
'web_instance': 'MRSM{meerschaum:instance}',
|
18
18
|
'default_repository': 'api:mrsm',
|
19
|
-
# 'default_executor': 'local',
|
20
19
|
'connectors': {
|
21
20
|
'sql': {
|
22
21
|
'default': {},
|
@@ -53,6 +52,14 @@ default_meerschaum_config = {
|
|
53
52
|
'protocol': 'https',
|
54
53
|
},
|
55
54
|
},
|
55
|
+
'valkey': {
|
56
|
+
'main': {
|
57
|
+
'host': 'localhost',
|
58
|
+
'username': 'default',
|
59
|
+
'password': 'mrsm',
|
60
|
+
'port': 6379,
|
61
|
+
},
|
62
|
+
},
|
56
63
|
},
|
57
64
|
}
|
58
65
|
default_system_config = {
|
@@ -75,7 +82,7 @@ default_system_config = {
|
|
75
82
|
},
|
76
83
|
},
|
77
84
|
|
78
|
-
'api'
|
85
|
+
'api': {
|
79
86
|
},
|
80
87
|
},
|
81
88
|
### not to be confused with system_config['connectors']['api'], this is the configuration
|
@@ -89,6 +96,10 @@ default_system_config = {
|
|
89
96
|
'proxy_headers': True,
|
90
97
|
'forwarded_allow_ips': '*',
|
91
98
|
},
|
99
|
+
'cache': {
|
100
|
+
'connector': 'valkey:main',
|
101
|
+
'session_expires_minutes': 43200,
|
102
|
+
},
|
92
103
|
'permissions': {
|
93
104
|
'registration': {
|
94
105
|
'users': True,
|
@@ -98,7 +109,7 @@ default_system_config = {
|
|
98
109
|
'actions': {
|
99
110
|
'non_admin': True,
|
100
111
|
},
|
101
|
-
'chaining'
|
112
|
+
'chaining': {
|
102
113
|
'insecure_parent_instance': False,
|
103
114
|
'child_apis': False,
|
104
115
|
},
|
@@ -113,6 +124,7 @@ default_system_config = {
|
|
113
124
|
'inplace_sync': True,
|
114
125
|
'uv_pip': True,
|
115
126
|
'systemd_healthcheck': False,
|
127
|
+
'valkey_session_cache': True,
|
116
128
|
},
|
117
129
|
}
|
118
130
|
default_pipes_config = {
|
meerschaum/config/_version.py
CHANGED
@@ -31,21 +31,29 @@ db_host = 'MRSM{stack:' + str(STACK_COMPOSE_FILENAME) + ':services:db:hostname}'
|
|
31
31
|
api_port = "MRSM{meerschaum:connectors:api:main:port}"
|
32
32
|
api_host = "api"
|
33
33
|
|
34
|
+
valkey_hostname = "valkey"
|
35
|
+
valkey_host = 'MRSM{stack:' + str(STACK_COMPOSE_FILENAME) + ':services:valkey:hostname}'
|
36
|
+
valkey_port = "MRSM{meerschaum:connectors:valkey:main:port}"
|
37
|
+
valkey_username = 'MRSM{meerschaum:connectors:valkey:main:username}'
|
38
|
+
valkey_password = 'MRSM{meerschaum:connectors:valkey:main:password}'
|
39
|
+
|
34
40
|
env_dict = {
|
35
|
-
'COMPOSE_PROJECT_NAME'
|
36
|
-
'TIMESCALEDB_VERSION'
|
37
|
-
'POSTGRES_USER'
|
38
|
-
'POSTGRES_PASSWORD'
|
39
|
-
'POSTGRES_DB'
|
40
|
-
'
|
41
|
-
'
|
42
|
-
'
|
41
|
+
'COMPOSE_PROJECT_NAME': 'mrsm',
|
42
|
+
'TIMESCALEDB_VERSION': 'latest-pg16-oss',
|
43
|
+
'POSTGRES_USER': db_user,
|
44
|
+
'POSTGRES_PASSWORD': db_pass,
|
45
|
+
'POSTGRES_DB': db_base,
|
46
|
+
'VALKEY_USERNAME': valkey_username,
|
47
|
+
'VALKEY_PASSWORD': valkey_password,
|
48
|
+
'MEERSCHAUM_API_HOSTNAME': api_host,
|
49
|
+
'ALLOW_IP_RANGE': '0.0.0.0/0',
|
50
|
+
'MEERSCHAUM_API_CONFIG_RESOURCES': '/meerschaum',
|
43
51
|
}
|
44
52
|
### apply patch to host config to change hostname to the Docker service name
|
45
53
|
env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
|
46
54
|
{
|
47
|
-
'meerschaum'
|
48
|
-
'system'
|
55
|
+
'meerschaum': 'MRSM{!meerschaum}',
|
56
|
+
'system': 'MRSM{!system}',
|
49
57
|
},
|
50
58
|
indent = 4,
|
51
59
|
).replace(
|
@@ -56,9 +64,9 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
|
|
56
64
|
|
57
65
|
volumes = {
|
58
66
|
'api_root': '/meerschaum',
|
59
|
-
'api_user_local': '/home/meerschaum/.local',
|
60
67
|
'meerschaum_db_data': '/var/lib/postgresql/data',
|
61
68
|
'grafana_storage': '/var/lib/grafana',
|
69
|
+
'valkey_data': '/bitnami/valkey/data',
|
62
70
|
}
|
63
71
|
networks = {
|
64
72
|
'frontend': None,
|
@@ -74,13 +82,19 @@ env_dict['MEERSCHAUM_API_PATCH'] = json.dumps(
|
|
74
82
|
'port': 5432,
|
75
83
|
},
|
76
84
|
'local': {
|
77
|
-
'database': volumes['api_root'] + '/sqlite/mrsm_local.db'
|
85
|
+
'database': volumes['api_root'] + '/sqlite/mrsm_local.db',
|
86
|
+
},
|
87
|
+
},
|
88
|
+
'valkey': {
|
89
|
+
'main': {
|
90
|
+
'host': valkey_host,
|
91
|
+
'port': 6379,
|
78
92
|
},
|
79
93
|
},
|
80
94
|
},
|
81
95
|
},
|
82
96
|
},
|
83
|
-
indent
|
97
|
+
indent=4,
|
84
98
|
)
|
85
99
|
|
86
100
|
compose_header = """
|
@@ -114,19 +128,19 @@ default_docker_compose_config = {
|
|
114
128
|
],
|
115
129
|
'interval': '5s',
|
116
130
|
'timeout': '3s',
|
117
|
-
'retries':
|
131
|
+
'retries': 5
|
118
132
|
},
|
119
133
|
'restart': 'always',
|
120
|
-
'image'
|
121
|
-
'ports'
|
134
|
+
'image': 'timescale/timescaledb:' + env_dict['TIMESCALEDB_VERSION'],
|
135
|
+
'ports': [
|
122
136
|
f'{db_port}:5432',
|
123
137
|
],
|
124
|
-
'hostname'
|
125
|
-
'volumes'
|
138
|
+
'hostname': db_hostname,
|
139
|
+
'volumes': [
|
126
140
|
'meerschaum_db_data:' + volumes['meerschaum_db_data'],
|
127
141
|
],
|
128
142
|
'shm_size': '1024m',
|
129
|
-
'networks'
|
143
|
+
'networks': [
|
130
144
|
'backend',
|
131
145
|
],
|
132
146
|
},
|
@@ -157,10 +171,39 @@ default_docker_compose_config = {
|
|
157
171
|
'db': {
|
158
172
|
'condition': 'service_healthy',
|
159
173
|
},
|
174
|
+
'valkey': {
|
175
|
+
'condition': 'service_healthy',
|
176
|
+
},
|
160
177
|
},
|
161
|
-
'volumes'
|
178
|
+
'volumes': [
|
162
179
|
'api_root:' + volumes['api_root'],
|
163
|
-
|
180
|
+
],
|
181
|
+
},
|
182
|
+
'valkey': {
|
183
|
+
'image': 'bitnami/valkey:latest',
|
184
|
+
'restart': 'always',
|
185
|
+
'environment': {
|
186
|
+
'VALKEY_PASSWORD': '<DOLLAR>VALKEY_PASSWORD',
|
187
|
+
'VALKEY_RDB_POLICY_DISABLED': 'no',
|
188
|
+
'VALKEY_RDB_POLICY': '900#1 600#5 300#10 120#50 60#1000 30#10000',
|
189
|
+
},
|
190
|
+
'hostname': valkey_hostname,
|
191
|
+
'ports': [
|
192
|
+
f'{valkey_port}:6379',
|
193
|
+
],
|
194
|
+
'volumes': [
|
195
|
+
'valkey_data:' + volumes['valkey_data'],
|
196
|
+
],
|
197
|
+
'healthcheck': {
|
198
|
+
'test': [
|
199
|
+
'CMD', 'valkey-cli', 'ping',
|
200
|
+
],
|
201
|
+
'interval': '5s',
|
202
|
+
'timeout': '3s',
|
203
|
+
'retries': 5,
|
204
|
+
},
|
205
|
+
'networks': [
|
206
|
+
'backend',
|
164
207
|
],
|
165
208
|
},
|
166
209
|
'grafana': {
|
@@ -46,12 +46,16 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
46
46
|
'stdin_message': 'MRSM_STDIN',
|
47
47
|
'stop_message': 'MRSM_STOP',
|
48
48
|
'metadata_cache_seconds': 5,
|
49
|
+
'temp_prefix': '.api-temp-',
|
49
50
|
},
|
50
51
|
},
|
51
52
|
'sql': {
|
52
53
|
'internal_schema': '_mrsm_internal',
|
53
54
|
'instance_schema': 'mrsm',
|
54
55
|
},
|
56
|
+
'valkey': {
|
57
|
+
'colon': '-_',
|
58
|
+
},
|
55
59
|
'environment': {
|
56
60
|
'config': 'MRSM_CONFIG',
|
57
61
|
'config_dir': 'MRSM_CONFIG_DIR',
|
@@ -68,11 +72,13 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
68
72
|
'uid': 'MRSM_UID',
|
69
73
|
'gid': 'MRSM_GID',
|
70
74
|
'noask': 'MRSM_NOASK',
|
75
|
+
'noninteractive': 'MRSM_NONINTERACTIVE',
|
71
76
|
'id': 'MRSM_SERVER_ID',
|
72
77
|
'daemon_id': 'MRSM_DAEMON_ID',
|
73
78
|
'systemd_log_path': 'MRSM_SYSTEMD_LOG_PATH',
|
74
79
|
'systemd_stdin_path': 'MRSM_SYSTEMD_STDIN_PATH',
|
75
80
|
'systemd_result_path': 'MRSM_SYSTEMD_RESULT_PATH',
|
81
|
+
'systemd_delete_job': 'MRSM_SYSTEMD_DELETE_JOB',
|
76
82
|
'uri_regex': r'MRSM_([a-zA-Z0-9]*)_(\d*[a-zA-Z][a-zA-Z0-9-_+]*$)',
|
77
83
|
'prefix': 'MRSM_',
|
78
84
|
},
|