meerschaum 2.4.11__py3-none-any.whl → 2.4.13__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/_parse_arguments.py +15 -1
- meerschaum/_internal/docs/index.py +1 -0
- meerschaum/_internal/entry.py +1 -0
- meerschaum/_internal/shell/Shell.py +19 -9
- meerschaum/_internal/shell/ShellCompleter.py +11 -6
- meerschaum/actions/bootstrap.py +119 -16
- meerschaum/actions/clear.py +41 -30
- meerschaum/actions/delete.py +1 -1
- meerschaum/actions/edit.py +118 -3
- meerschaum/actions/sh.py +11 -10
- meerschaum/actions/start.py +61 -4
- meerschaum/actions/sync.py +14 -16
- meerschaum/actions/upgrade.py +5 -4
- meerschaum/api/dash/callbacks/dashboard.py +2 -1
- meerschaum/api/dash/callbacks/jobs.py +53 -7
- meerschaum/api/dash/callbacks/pipes.py +1 -1
- meerschaum/api/dash/jobs.py +86 -60
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/job.py +21 -0
- meerschaum/api/routes/_jobs.py +3 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/sql/_fetch.py +67 -61
- meerschaum/connectors/sql/_pipes.py +36 -29
- meerschaum/plugins/__init__.py +6 -2
- meerschaum/plugins/bootstrap.py +15 -15
- meerschaum/utils/formatting/__init__.py +32 -16
- meerschaum/utils/formatting/_pipes.py +1 -1
- meerschaum/utils/formatting/_shell.py +4 -3
- meerschaum/utils/process.py +18 -8
- meerschaum/utils/prompt.py +16 -15
- meerschaum/utils/sql.py +107 -35
- meerschaum/utils/venv/__init__.py +35 -24
- meerschaum/utils/warnings.py +7 -7
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/METADATA +1 -1
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/RECORD +41 -40
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/WHEEL +1 -1
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/LICENSE +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/NOTICE +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/top_level.txt +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/zip-safe +0 -0
meerschaum/actions/start.py
CHANGED
@@ -9,6 +9,7 @@ Start subsystems (API server, logging daemon, etc.).
|
|
9
9
|
from __future__ import annotations
|
10
10
|
from meerschaum.utils.typing import SuccessTuple, Optional, List, Any, Union, Dict
|
11
11
|
|
12
|
+
|
12
13
|
def start(
|
13
14
|
action: Optional[List[str]] = None,
|
14
15
|
**kw: Any,
|
@@ -375,8 +376,8 @@ def _start_gui(
|
|
375
376
|
webview.create_window(
|
376
377
|
'Meerschaum Shell',
|
377
378
|
f'http://127.0.0.1:{port}',
|
378
|
-
height
|
379
|
-
width
|
379
|
+
height=650,
|
380
|
+
width=1000
|
380
381
|
)
|
381
382
|
webview.start(debug=debug)
|
382
383
|
except Exception as e:
|
@@ -540,6 +541,7 @@ def _start_pipeline(
|
|
540
541
|
action: Optional[List[str]] = None,
|
541
542
|
loop: bool = False,
|
542
543
|
min_seconds: Union[float, int, None] = 1.0,
|
544
|
+
timeout_seconds: Optional[int] = None,
|
543
545
|
params: Optional[Dict[str, Any]] = None,
|
544
546
|
**kwargs
|
545
547
|
) -> SuccessTuple:
|
@@ -556,10 +558,39 @@ def _start_pipeline(
|
|
556
558
|
`show version + show arguments :: --loop`
|
557
559
|
|
558
560
|
"""
|
561
|
+
import json
|
559
562
|
import time
|
563
|
+
import sys
|
560
564
|
from meerschaum._internal.entry import entry
|
561
565
|
from meerschaum.utils.warnings import info, warn
|
562
566
|
from meerschaum.utils.misc import is_int
|
567
|
+
from meerschaum.utils.venv import venv_exec
|
568
|
+
from meerschaum.utils.process import poll_process
|
569
|
+
fence_begin, fence_end = '<MRSM_RESULT>', '</MRSM_RESULT>'
|
570
|
+
|
571
|
+
success, msg = False, "Did not run pipeline."
|
572
|
+
def write_line(line):
|
573
|
+
nonlocal success, msg
|
574
|
+
decoded = line.decode('utf-8')
|
575
|
+
begin_index, end_index = decoded.find(fence_begin), decoded.find(fence_end)
|
576
|
+
|
577
|
+
### Found the beginning of the return value.
|
578
|
+
### Don't write the parsed success tuple message.
|
579
|
+
if begin_index >= 0:
|
580
|
+
success, msg = tuple(json.loads(
|
581
|
+
decoded[begin_index + len(fence_begin):end_index]
|
582
|
+
))
|
583
|
+
return
|
584
|
+
|
585
|
+
print(decoded)
|
586
|
+
|
587
|
+
def timeout_handler(*args, **kw):
|
588
|
+
nonlocal success, msg
|
589
|
+
success, msg = False, (
|
590
|
+
f"Failed to execute pipeline within {timeout_seconds} second"
|
591
|
+
+ ('s' if timeout_seconds != 1 else '') + '.'
|
592
|
+
)
|
593
|
+
write_line((fence_begin + json.dumps((success, msg)) + fence_end).encode('utf-8'))
|
563
594
|
|
564
595
|
do_n_times = (
|
565
596
|
int(action[0].lstrip('x'))
|
@@ -577,12 +608,38 @@ def _start_pipeline(
|
|
577
608
|
if min_seconds is None:
|
578
609
|
min_seconds = 1.0
|
579
610
|
|
611
|
+
def do_entry() -> None:
|
612
|
+
nonlocal success, msg
|
613
|
+
if timeout_seconds is None:
|
614
|
+
success, msg = entry(sub_args_line, _patch_args=patch_args)
|
615
|
+
return
|
616
|
+
|
617
|
+
sub_args_line_escaped = sub_args_line.replace("'", "<QUOTE>")
|
618
|
+
patch_args_escaped_str = json.dumps(patch_args).replace("'", "<QUOTE>")
|
619
|
+
src = (
|
620
|
+
"import json\n"
|
621
|
+
"from meerschaum._internal.entry import entry\n\n"
|
622
|
+
f"sub_args_line = '{sub_args_line_escaped}'.replace(\"<QUOTE>\", \"'\")\n"
|
623
|
+
f"patch_args = json.loads('{patch_args_escaped_str}'.replace('<QUOTE>', \"'\"))\n"
|
624
|
+
"success, msg = entry(sub_args_line, _patch_args=patch_args)\n"
|
625
|
+
f"print('{fence_begin}' + json.dumps((success, msg)) + '{fence_end}')"
|
626
|
+
)
|
627
|
+
proc = venv_exec(src, venv=None, as_proc=True)
|
628
|
+
poll_process(
|
629
|
+
proc,
|
630
|
+
write_line,
|
631
|
+
timeout_seconds,
|
632
|
+
timeout_handler,
|
633
|
+
)
|
634
|
+
|
580
635
|
ran_n_times = 0
|
581
|
-
success, msg = False, "Did not run pipeline."
|
582
636
|
def run_loop():
|
583
637
|
nonlocal ran_n_times, success, msg
|
584
638
|
while True:
|
585
|
-
|
639
|
+
try:
|
640
|
+
do_entry()
|
641
|
+
except Exception as e:
|
642
|
+
warn(e)
|
586
643
|
ran_n_times += 1
|
587
644
|
|
588
645
|
if not loop and do_n_times == 1:
|
meerschaum/actions/sync.py
CHANGED
@@ -129,7 +129,6 @@ def _pipes_lap(
|
|
129
129
|
stack=False,
|
130
130
|
)
|
131
131
|
|
132
|
-
|
133
132
|
def _task_label(count: int):
|
134
133
|
return f"[cyan]Syncing {count} pipe{'s' if count != 1 else ''}..."
|
135
134
|
|
@@ -137,7 +136,6 @@ def _pipes_lap(
|
|
137
136
|
_progress.add_task(_task_label(len(pipes)), start=True, total=len(pipes))
|
138
137
|
) if _progress is not None else None
|
139
138
|
|
140
|
-
|
141
139
|
def worker_fn():
|
142
140
|
while not stop_event.is_set():
|
143
141
|
try:
|
@@ -188,7 +186,7 @@ def _pipes_lap(
|
|
188
186
|
decoded[begin_index + len(fence_begin):end_index]
|
189
187
|
))
|
190
188
|
return
|
191
|
-
|
189
|
+
print(decoded)
|
192
190
|
|
193
191
|
def timeout_handler(p, *args, **kw):
|
194
192
|
success, msg = False, (
|
@@ -227,7 +225,7 @@ def _pipes_lap(
|
|
227
225
|
write_line,
|
228
226
|
timeout_seconds,
|
229
227
|
timeout_handler,
|
230
|
-
(p,)
|
228
|
+
(p,),
|
231
229
|
)
|
232
230
|
return _success_tuple
|
233
231
|
|
@@ -249,18 +247,18 @@ def _pipes_lap(
|
|
249
247
|
|
250
248
|
|
251
249
|
def _sync_pipes(
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
250
|
+
loop: bool = False,
|
251
|
+
min_seconds: int = 1,
|
252
|
+
unblock: bool = False,
|
253
|
+
verify: bool = False,
|
254
|
+
deduplicate: bool = False,
|
255
|
+
bounded: Optional[bool] = None,
|
256
|
+
chunk_interval: Union[timedelta, int, None] = None,
|
257
|
+
shell: bool = False,
|
258
|
+
nopretty: bool = False,
|
259
|
+
debug: bool = False,
|
260
|
+
**kw: Any
|
261
|
+
) -> SuccessTuple:
|
264
262
|
"""
|
265
263
|
Fetch and sync new data for pipes.
|
266
264
|
|
meerschaum/actions/upgrade.py
CHANGED
@@ -9,16 +9,17 @@ Upgrade your current Meerschaum environment
|
|
9
9
|
from __future__ import annotations
|
10
10
|
from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Union
|
11
11
|
|
12
|
+
|
12
13
|
def upgrade(
|
13
14
|
action: Optional[List[str]] = None,
|
14
15
|
**kw: Any
|
15
16
|
) -> SuccessTuple:
|
16
17
|
"""
|
17
18
|
Upgrade Meerschaum, plugins, or packages.
|
18
|
-
|
19
|
+
|
19
20
|
Command:
|
20
21
|
`upgrade {option}`
|
21
|
-
|
22
|
+
|
22
23
|
Example:
|
23
24
|
`upgrade meerschaum`
|
24
25
|
"""
|
@@ -77,7 +78,7 @@ def _upgrade_meerschaum(
|
|
77
78
|
if force:
|
78
79
|
answer = True
|
79
80
|
else:
|
80
|
-
answer = yes_no(
|
81
|
+
answer = yes_no("Take down the stack?", default='n', yes=yes, noask=noask)
|
81
82
|
|
82
83
|
if answer:
|
83
84
|
if debug:
|
@@ -95,7 +96,7 @@ def _upgrade_meerschaum(
|
|
95
96
|
if debug:
|
96
97
|
dprint('Upgrade meerschaum with dependencies: \"' + f'{dependencies}' + '\"')
|
97
98
|
if not pip_install(install_name, venv=None, debug=debug):
|
98
|
-
return False,
|
99
|
+
return False, "Failed to upgrade Meerschaum via pip."
|
99
100
|
|
100
101
|
if debug:
|
101
102
|
dprint("Pulling new Docker images...")
|
@@ -98,6 +98,7 @@ _paths = {
|
|
98
98
|
'plugins' : pages.plugins.layout,
|
99
99
|
'register': pages.register.layout,
|
100
100
|
'pipes' : pages.pipes.layout,
|
101
|
+
'job' : pages.job.layout,
|
101
102
|
}
|
102
103
|
_required_login = {''}
|
103
104
|
|
@@ -121,7 +122,7 @@ def update_page_layout_div(
|
|
121
122
|
----------
|
122
123
|
pathname: str
|
123
124
|
The path in the browser.
|
124
|
-
|
125
|
+
|
125
126
|
session_store_data: Dict[str, Any]:
|
126
127
|
The stored session data.
|
127
128
|
|
@@ -12,6 +12,7 @@ import json
|
|
12
12
|
import time
|
13
13
|
import traceback
|
14
14
|
from datetime import datetime, timezone
|
15
|
+
|
15
16
|
from meerschaum.utils.typing import Optional, Dict, Any
|
16
17
|
from meerschaum.api import CHECK_UPDATE
|
17
18
|
from meerschaum.api.dash import dash_app
|
@@ -19,17 +20,19 @@ from meerschaum.api.dash.sessions import get_username_from_session
|
|
19
20
|
from meerschaum.utils.packages import attempt_import, import_dcc, import_html
|
20
21
|
from meerschaum.api.dash.components import alert_from_success_tuple
|
21
22
|
from meerschaum.api.dash.jobs import (
|
23
|
+
build_job_card,
|
22
24
|
build_manage_job_buttons_div_children,
|
23
25
|
build_status_children,
|
24
26
|
build_process_timestamps_children,
|
25
27
|
)
|
26
|
-
from meerschaum.
|
28
|
+
from meerschaum.api.routes._jobs import _get_job
|
27
29
|
from meerschaum.api.dash.sessions import is_session_authenticated
|
28
30
|
dash = attempt_import('dash', lazy=False, check_update=CHECK_UPDATE)
|
29
31
|
html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
|
30
32
|
from dash.exceptions import PreventUpdate
|
31
33
|
from dash.dependencies import Input, Output, State, ALL, MATCH
|
32
34
|
import dash_bootstrap_components as dbc
|
35
|
+
from dash import no_update
|
33
36
|
|
34
37
|
|
35
38
|
@dash_app.callback(
|
@@ -53,6 +56,13 @@ def download_job_logs(n_clicks):
|
|
53
56
|
|
54
57
|
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
55
58
|
job_name = component_dict['index']
|
59
|
+
try:
|
60
|
+
job = _get_job(job_name)
|
61
|
+
except Exception:
|
62
|
+
job = None
|
63
|
+
if job is None or not job.exists():
|
64
|
+
raise PreventUpdate
|
65
|
+
|
56
66
|
now = datetime.now(timezone.utc)
|
57
67
|
filename = job_name + '_' + str(int(now.timestamp())) + '.log'
|
58
68
|
return {
|
@@ -69,7 +79,7 @@ def download_job_logs(n_clicks):
|
|
69
79
|
Input({'type': 'manage-job-button', 'action': ALL, 'index': MATCH}, 'n_clicks'),
|
70
80
|
State('session-store', 'data'),
|
71
81
|
State({'type': 'job-label-p', 'index': MATCH}, 'children'),
|
72
|
-
prevent_initial_call
|
82
|
+
prevent_initial_call=True,
|
73
83
|
)
|
74
84
|
def manage_job_button_click(
|
75
85
|
n_clicks: Optional[int] = None,
|
@@ -102,10 +112,10 @@ def manage_job_button_click(
|
|
102
112
|
job_name = component_dict['index']
|
103
113
|
manage_job_action = component_dict['action']
|
104
114
|
try:
|
105
|
-
job =
|
106
|
-
except Exception
|
115
|
+
job = _get_job(job_name, job_label.replace('\n', ' ') if job_label else None)
|
116
|
+
except Exception:
|
107
117
|
job = None
|
108
|
-
if job is None:
|
118
|
+
if job is None or not job.exists():
|
109
119
|
raise PreventUpdate
|
110
120
|
|
111
121
|
manage_functions = {
|
@@ -191,7 +201,7 @@ dash_app.clientside_callback(
|
|
191
201
|
Output({'type': 'process-timestamps-div', 'index': ALL}, 'children'),
|
192
202
|
Input('refresh-jobs-interval', 'n_intervals'),
|
193
203
|
State('session-store', 'data'),
|
194
|
-
prevent_initial_call
|
204
|
+
prevent_initial_call=True,
|
195
205
|
)
|
196
206
|
def refresh_jobs_on_interval(
|
197
207
|
n_intervals: Optional[int] = None,
|
@@ -209,7 +219,7 @@ def refresh_jobs_on_interval(
|
|
209
219
|
]
|
210
220
|
|
211
221
|
### NOTE: The job may have been deleted, but the card may still exist.
|
212
|
-
jobs = [
|
222
|
+
jobs = [_get_job(name) for name in job_names]
|
213
223
|
|
214
224
|
return (
|
215
225
|
[
|
@@ -229,3 +239,39 @@ def refresh_jobs_on_interval(
|
|
229
239
|
for job in jobs
|
230
240
|
],
|
231
241
|
)
|
242
|
+
|
243
|
+
|
244
|
+
@dash_app.callback(
|
245
|
+
Output('job-output-div', 'children'),
|
246
|
+
Input('job-location', 'pathname'),
|
247
|
+
State('session-store', 'data'),
|
248
|
+
)
|
249
|
+
def render_job_page_from_url(
|
250
|
+
pathname: str,
|
251
|
+
session_data: Optional[Dict[str, Any]],
|
252
|
+
):
|
253
|
+
"""
|
254
|
+
Load the `/job/{name}` page.
|
255
|
+
"""
|
256
|
+
if not str(pathname).startswith('/dash/job'):
|
257
|
+
return no_update
|
258
|
+
|
259
|
+
session_id = (session_data or {}).get('session-id', None)
|
260
|
+
authenticated = is_session_authenticated(str(session_id))
|
261
|
+
|
262
|
+
job_name = pathname.replace('/dash/job', '').lstrip('/').rstrip('/')
|
263
|
+
if not job_name:
|
264
|
+
return no_update
|
265
|
+
|
266
|
+
job = _get_job(job_name)
|
267
|
+
if not job.exists():
|
268
|
+
return [
|
269
|
+
html.Br(),
|
270
|
+
html.H2("404: Job does not exist."),
|
271
|
+
]
|
272
|
+
|
273
|
+
return [
|
274
|
+
html.Br(),
|
275
|
+
build_job_card(job, authenticated=authenticated, include_follow=False),
|
276
|
+
html.Br(),
|
277
|
+
]
|
@@ -26,7 +26,7 @@ html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHEC
|
|
26
26
|
State('pipes-location', 'search'),
|
27
27
|
State('session-store', 'data'),
|
28
28
|
)
|
29
|
-
def
|
29
|
+
def render_pipe_page_from_url(
|
30
30
|
pathname: str,
|
31
31
|
pipe_search: str,
|
32
32
|
session_data: Optional[Dict[str, Any]],
|
meerschaum/api/dash/jobs.py
CHANGED
@@ -31,6 +31,7 @@ STATUS_EMOJI: Dict[str, str] = {
|
|
31
31
|
|
32
32
|
EXECUTOR_KEYS: str = get_executor_keys_from_context()
|
33
33
|
|
34
|
+
|
34
35
|
def get_jobs_cards(state: WebState):
|
35
36
|
"""
|
36
37
|
Build cards and alerts lists for jobs.
|
@@ -42,71 +43,96 @@ def get_jobs_cards(state: WebState):
|
|
42
43
|
cards = []
|
43
44
|
|
44
45
|
for name, job in jobs.items():
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
46
|
+
cards.append(build_job_card(job, authenticated=is_authenticated))
|
47
|
+
|
48
|
+
return cards, []
|
49
|
+
|
50
|
+
|
51
|
+
def build_job_card(
|
52
|
+
job: Job,
|
53
|
+
authenticated: bool = False,
|
54
|
+
include_follow: bool = True,
|
55
|
+
):
|
56
|
+
"""
|
57
|
+
Return a card for a given job.
|
58
|
+
"""
|
59
|
+
footer_children = html.Div(
|
60
|
+
build_process_timestamps_children(job),
|
61
|
+
id={'type': 'process-timestamps-div', 'index': job.name},
|
62
|
+
)
|
63
|
+
follow_logs_button = dbc.DropdownMenuItem(
|
64
|
+
"Follow logs",
|
65
|
+
id={'type': 'follow-logs-button', 'index': job.name},
|
66
|
+
)
|
67
|
+
download_logs_button = dbc.DropdownMenuItem(
|
68
|
+
"Download logs",
|
69
|
+
id={'type': 'job-download-logs-button', 'index': job.name},
|
70
|
+
)
|
71
|
+
logs_menu_children = (
|
72
|
+
([follow_logs_button] if include_follow else []) + [download_logs_button]
|
73
|
+
if authenticated
|
74
|
+
else []
|
75
|
+
)
|
76
|
+
header_children = [
|
77
|
+
html.Div(
|
78
|
+
build_status_children(job),
|
79
|
+
id={'type': 'manage-job-status-div', 'index': job.name},
|
80
|
+
style={'float': 'left'},
|
81
|
+
),
|
82
|
+
] + ([
|
83
|
+
html.Div(
|
84
|
+
dbc.DropdownMenu(
|
85
|
+
logs_menu_children,
|
86
|
+
label="Logs",
|
87
|
+
size="sm",
|
88
|
+
align_end=True,
|
89
|
+
color="secondary",
|
90
|
+
menu_variant='dark',
|
65
91
|
),
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
92
|
+
style={'float': 'right'},
|
93
|
+
),
|
94
|
+
] if authenticated else [])
|
95
|
+
|
96
|
+
body_children = [
|
97
|
+
html.H4(
|
98
|
+
html.B(
|
99
|
+
html.A(
|
100
|
+
"🔗 " + job.name,
|
101
|
+
href=f"/dash/job/{job.name}",
|
102
|
+
target="_blank",
|
103
|
+
style={
|
104
|
+
'color': 'white',
|
105
|
+
'text-decoration': 'none',
|
106
|
+
},
|
107
|
+
)
|
76
108
|
),
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
html.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
style={"word-wrap": "break-word"},
|
86
|
-
id={'type': 'job-label-p', 'index': name},
|
87
|
-
),
|
88
|
-
style={"white-space": "pre-wrap"},
|
109
|
+
className="card-title",
|
110
|
+
),
|
111
|
+
html.Div(
|
112
|
+
html.P(
|
113
|
+
job.label,
|
114
|
+
className="card-text job-card-text",
|
115
|
+
style={"word-wrap": "break-word"},
|
116
|
+
id={'type': 'job-label-p', 'index': job.name},
|
89
117
|
),
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
118
|
+
style={"white-space": "pre-wrap"},
|
119
|
+
),
|
120
|
+
html.Div(
|
121
|
+
(
|
122
|
+
build_manage_job_buttons_div_children(job)
|
123
|
+
if authenticated
|
124
|
+
else []
|
97
125
|
),
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
dbc.Card([
|
103
|
-
dbc.CardHeader(header_children),
|
104
|
-
dbc.CardBody(body_children),
|
105
|
-
dbc.CardFooter(footer_children),
|
106
|
-
])
|
107
|
-
)
|
126
|
+
id={'type': 'manage-job-buttons-div', 'index': job.name},
|
127
|
+
),
|
128
|
+
html.Div(id={'type': 'manage-job-alert-div', 'index': job.name}),
|
129
|
+
]
|
108
130
|
|
109
|
-
return
|
131
|
+
return dbc.Card([
|
132
|
+
dbc.CardHeader(header_children),
|
133
|
+
dbc.CardBody(body_children),
|
134
|
+
dbc.CardFooter(footer_children),
|
135
|
+
])
|
110
136
|
|
111
137
|
|
112
138
|
def build_manage_job_buttons_div_children(job: Job):
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Display pipes via a shareable URL.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from meerschaum.api import CHECK_UPDATE
|
9
|
+
from meerschaum.utils.packages import import_html, import_dcc
|
10
|
+
|
11
|
+
html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
|
12
|
+
import dash_bootstrap_components as dbc
|
13
|
+
|
14
|
+
from meerschaum.api.dash.components import download_logs, refresh_jobs_interval
|
15
|
+
|
16
|
+
layout = dbc.Container([
|
17
|
+
dcc.Location('job-location'),
|
18
|
+
html.Div(id='job-output-div'),
|
19
|
+
download_logs,
|
20
|
+
refresh_jobs_interval,
|
21
|
+
])
|
meerschaum/api/routes/_jobs.py
CHANGED
@@ -39,12 +39,12 @@ NONINTERACTIVE_ENV: str = STATIC_CONFIG['environment']['noninteractive']
|
|
39
39
|
EXECUTOR_KEYS: str = 'local'
|
40
40
|
|
41
41
|
|
42
|
-
def _get_job(name: str):
|
43
|
-
systemd_job = Job(name, executor_keys='systemd')
|
42
|
+
def _get_job(name: str, sysargs: Union[str, List[str], None] = None):
|
43
|
+
systemd_job = Job(name, sysargs, executor_keys='systemd')
|
44
44
|
if systemd_job.exists():
|
45
45
|
return systemd_job
|
46
46
|
|
47
|
-
job = Job(name, executor_keys=EXECUTOR_KEYS)
|
47
|
+
job = Job(name, sysargs, executor_keys=EXECUTOR_KEYS)
|
48
48
|
return job
|
49
49
|
|
50
50
|
|
meerschaum/config/_version.py
CHANGED