meerschaum 2.2.6__py3-none-any.whl → 2.3.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/__init__.py +6 -1
- meerschaum/__main__.py +9 -9
- meerschaum/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +72 -6
- meerschaum/_internal/arguments/_parser.py +45 -15
- meerschaum/_internal/docs/index.py +265 -8
- meerschaum/_internal/entry.py +167 -37
- meerschaum/_internal/shell/Shell.py +290 -99
- meerschaum/_internal/shell/updates.py +175 -0
- meerschaum/actions/__init__.py +29 -17
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +113 -0
- meerschaum/actions/copy.py +68 -41
- meerschaum/actions/delete.py +112 -50
- meerschaum/actions/edit.py +3 -3
- meerschaum/actions/install.py +40 -32
- meerschaum/actions/pause.py +44 -27
- meerschaum/actions/register.py +19 -5
- meerschaum/actions/restart.py +107 -0
- meerschaum/actions/show.py +130 -159
- meerschaum/actions/start.py +161 -100
- meerschaum/actions/stop.py +78 -42
- meerschaum/actions/sync.py +3 -3
- meerschaum/actions/upgrade.py +28 -36
- meerschaum/api/_events.py +25 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/callbacks/jobs.py +36 -44
- meerschaum/api/dash/jobs.py +89 -78
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +148 -17
- meerschaum/api/routes/_jobs.py +407 -0
- meerschaum/api/routes/_pipes.py +25 -25
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_formatting.py +1 -0
- meerschaum/config/_jobs.py +1 -1
- meerschaum/config/_paths.py +11 -0
- meerschaum/config/_shell.py +84 -67
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +18 -0
- meerschaum/connectors/Connector.py +13 -7
- meerschaum/connectors/__init__.py +28 -15
- meerschaum/connectors/api/APIConnector.py +27 -1
- meerschaum/connectors/api/_actions.py +71 -6
- meerschaum/connectors/api/_jobs.py +368 -0
- meerschaum/connectors/api/_misc.py +1 -1
- meerschaum/connectors/api/_pipes.py +85 -84
- meerschaum/connectors/api/_request.py +13 -9
- meerschaum/connectors/parse.py +27 -15
- meerschaum/core/Pipe/_bootstrap.py +16 -8
- meerschaum/core/Pipe/_sync.py +3 -0
- meerschaum/jobs/_Executor.py +69 -0
- meerschaum/jobs/_Job.py +899 -0
- meerschaum/jobs/__init__.py +396 -0
- meerschaum/jobs/systemd.py +694 -0
- meerschaum/plugins/__init__.py +97 -12
- meerschaum/utils/daemon/Daemon.py +352 -147
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
- meerschaum/utils/daemon/RotatingFile.py +22 -8
- meerschaum/utils/daemon/StdinFile.py +121 -0
- meerschaum/utils/daemon/__init__.py +42 -27
- meerschaum/utils/daemon/_names.py +15 -13
- meerschaum/utils/formatting/__init__.py +83 -37
- meerschaum/utils/formatting/_jobs.py +146 -55
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/misc.py +41 -22
- meerschaum/utils/packages/__init__.py +21 -15
- meerschaum/utils/packages/_packages.py +9 -6
- meerschaum/utils/process.py +9 -9
- meerschaum/utils/prompt.py +20 -7
- meerschaum/utils/schedule.py +21 -15
- meerschaum/utils/venv/__init__.py +2 -2
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/RECORD +80 -70
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
meerschaum/api/_events.py
CHANGED
@@ -19,14 +19,24 @@ from meerschaum.utils.debug import dprint
|
|
19
19
|
from meerschaum.connectors.poll import retry_connect
|
20
20
|
from meerschaum.utils.warnings import warn
|
21
21
|
from meerschaum._internal.term.tools import is_webterm_running
|
22
|
+
from meerschaum.jobs import (
|
23
|
+
start_check_jobs_thread,
|
24
|
+
stop_check_jobs_thread,
|
25
|
+
get_executor_keys_from_context,
|
26
|
+
)
|
27
|
+
|
28
|
+
_check_jobs_thread = None
|
22
29
|
|
23
30
|
@app.on_event("startup")
|
24
31
|
async def startup():
|
25
|
-
|
32
|
+
"""
|
33
|
+
Connect to the instance database and begin monitoring jobs.
|
34
|
+
"""
|
26
35
|
try:
|
27
36
|
if not no_dash:
|
28
37
|
from meerschaum.api.dash.webterm import start_webterm
|
29
38
|
start_webterm()
|
39
|
+
|
30
40
|
connected = retry_connect(
|
31
41
|
get_api_connector(),
|
32
42
|
workers = get_uvicorn_config().get('workers', None),
|
@@ -36,18 +46,32 @@ async def startup():
|
|
36
46
|
import traceback
|
37
47
|
traceback.print_exc()
|
38
48
|
connected = False
|
49
|
+
|
39
50
|
if not connected:
|
40
51
|
await shutdown()
|
41
52
|
os._exit(1)
|
42
53
|
|
54
|
+
if get_executor_keys_from_context() == 'local':
|
55
|
+
start_check_jobs_thread()
|
56
|
+
|
43
57
|
|
44
58
|
@app.on_event("shutdown")
|
45
59
|
async def shutdown():
|
60
|
+
"""
|
61
|
+
Close the database connection and stop monitoring jobs.
|
62
|
+
"""
|
46
63
|
if debug:
|
47
64
|
dprint("Closing connection...")
|
48
65
|
if get_api_connector().type == 'sql':
|
49
66
|
get_api_connector().engine.dispose()
|
50
67
|
|
68
|
+
if get_executor_keys_from_context() == 'local':
|
69
|
+
stop_check_jobs_thread()
|
70
|
+
|
71
|
+
from meerschaum.api.routes._actions import _temp_jobs
|
72
|
+
for name, job in _temp_jobs.items():
|
73
|
+
job.delete()
|
74
|
+
|
51
75
|
### Terminate any running jobs left over.
|
52
76
|
if 'meerschaum.api.dash' in sys.modules:
|
53
77
|
from meerschaum.api.dash.actions import running_jobs, stop_action
|
meerschaum/api/_oauth2.py
CHANGED
@@ -30,6 +30,7 @@ class CustomOAuth2PasswordRequestForm:
|
|
30
30
|
self.client_id = client_id
|
31
31
|
self.client_secret = client_secret
|
32
32
|
|
33
|
+
|
33
34
|
LoginManager = fastapi_login.LoginManager
|
34
35
|
def generate_secret_key() -> str:
|
35
36
|
"""
|
@@ -46,5 +47,6 @@ def generate_secret_key() -> str:
|
|
46
47
|
|
47
48
|
return secret_key.encode('utf-8')
|
48
49
|
|
50
|
+
|
49
51
|
SECRET = generate_secret_key()
|
50
52
|
manager = LoginManager(SECRET, token_url=endpoints['login'])
|
meerschaum/api/_websockets.py
CHANGED
@@ -23,8 +23,8 @@ sessions = {}
|
|
23
23
|
@app.websocket('/dash/ws')
|
24
24
|
@app.websocket(_websocket_endpoint)
|
25
25
|
async def websocket_endpoint(
|
26
|
-
|
27
|
-
|
26
|
+
websocket: fastapi.WebSocket,
|
27
|
+
):
|
28
28
|
"""
|
29
29
|
Communicate with the Web Interface over a websocket.
|
30
30
|
"""
|
@@ -24,13 +24,13 @@ from dash import Patch
|
|
24
24
|
html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
|
25
25
|
import dash_bootstrap_components as dbc
|
26
26
|
from meerschaum.api.dash.components import alert_from_success_tuple, build_cards_grid
|
27
|
-
from meerschaum.utils.daemon import Daemon
|
28
27
|
from dash.exceptions import PreventUpdate
|
29
28
|
from meerschaum.api.dash.jobs import (
|
30
29
|
build_manage_job_buttons_div_children,
|
31
30
|
build_status_children,
|
32
31
|
build_process_timestamps_children,
|
33
32
|
)
|
33
|
+
from meerschaum.jobs import Job
|
34
34
|
from meerschaum.api.dash.users import is_session_authenticated
|
35
35
|
|
36
36
|
@dash_app.callback(
|
@@ -53,15 +53,11 @@ def download_job_logs(n_clicks):
|
|
53
53
|
raise PreventUpdate
|
54
54
|
|
55
55
|
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
56
|
-
|
57
|
-
daemon = Daemon(daemon_id=daemon_id)
|
56
|
+
job_name = component_dict['index']
|
58
57
|
now = datetime.now(timezone.utc)
|
59
|
-
filename = (
|
60
|
-
daemon.rotating_log.file_path.name[:(-1 * len('.log'))]
|
61
|
-
+ '_' + str(int(now.timestamp())) + '.log'
|
62
|
-
)
|
58
|
+
filename = job_name + '_' + str(int(now.timestamp())) + '.log'
|
63
59
|
return {
|
64
|
-
'content':
|
60
|
+
'content': job.get_logs(),
|
65
61
|
'filename': filename,
|
66
62
|
}
|
67
63
|
|
@@ -73,12 +69,14 @@ def download_job_logs(n_clicks):
|
|
73
69
|
Output({'type': 'process-timestamps-div', 'index': MATCH}, 'children'),
|
74
70
|
Input({'type': 'manage-job-button', 'action': ALL, 'index': MATCH}, 'n_clicks'),
|
75
71
|
State('session-store', 'data'),
|
72
|
+
State({'type': 'job-label-p', 'index': MATCH}, 'children'),
|
76
73
|
prevent_initial_call = True,
|
77
74
|
)
|
78
75
|
def manage_job_button_click(
|
79
|
-
|
80
|
-
|
81
|
-
|
76
|
+
n_clicks: Optional[int] = None,
|
77
|
+
session_data: Optional[Dict[str, Any]] = None,
|
78
|
+
job_label: Optional[str] = None,
|
79
|
+
):
|
82
80
|
"""
|
83
81
|
Start, stop, pause, or delete the given job.
|
84
82
|
"""
|
@@ -102,20 +100,20 @@ def manage_job_button_click(
|
|
102
100
|
raise PreventUpdate
|
103
101
|
|
104
102
|
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
105
|
-
|
103
|
+
job_name = component_dict['index']
|
106
104
|
manage_job_action = component_dict['action']
|
107
105
|
try:
|
108
|
-
|
106
|
+
job = Job(job_name, job_label.replace('\n', ' ') if job_label else None)
|
109
107
|
except Exception as e:
|
110
|
-
|
111
|
-
if
|
108
|
+
job = None
|
109
|
+
if job is None:
|
112
110
|
raise PreventUpdate
|
113
111
|
|
114
112
|
manage_functions = {
|
115
|
-
'start':
|
116
|
-
'stop':
|
117
|
-
'pause':
|
118
|
-
'delete':
|
113
|
+
'start': job.start,
|
114
|
+
'stop': job.stop,
|
115
|
+
'pause': job.pause,
|
116
|
+
'delete': job.delete,
|
119
117
|
}
|
120
118
|
if manage_job_action not in manage_functions:
|
121
119
|
return (
|
@@ -125,7 +123,7 @@ def manage_job_button_click(
|
|
125
123
|
dash.no_update,
|
126
124
|
)
|
127
125
|
|
128
|
-
old_status =
|
126
|
+
old_status = job.status
|
129
127
|
try:
|
130
128
|
success, msg = manage_functions[manage_job_action]()
|
131
129
|
except Exception as e:
|
@@ -136,15 +134,15 @@ def manage_job_button_click(
|
|
136
134
|
check_interval_seconds = 0.01
|
137
135
|
begin = time.perf_counter()
|
138
136
|
while (time.perf_counter() - begin) < timeout_seconds:
|
139
|
-
if
|
137
|
+
if job.status != old_status:
|
140
138
|
break
|
141
139
|
time.sleep(check_interval_seconds)
|
142
140
|
|
143
141
|
return (
|
144
142
|
alert_from_success_tuple((success, msg)),
|
145
|
-
build_manage_job_buttons_div_children(
|
146
|
-
build_status_children(
|
147
|
-
build_process_timestamps_children(
|
143
|
+
build_manage_job_buttons_div_children(job),
|
144
|
+
build_status_children(job),
|
145
|
+
build_process_timestamps_children(job),
|
148
146
|
)
|
149
147
|
|
150
148
|
dash_app.clientside_callback(
|
@@ -165,7 +163,7 @@ dash_app.clientside_callback(
|
|
165
163
|
}
|
166
164
|
|
167
165
|
const triggered_id = dash_clientside.callback_context.triggered_id;
|
168
|
-
const
|
166
|
+
const job_name = triggered_id["index"];
|
169
167
|
|
170
168
|
iframe = document.getElementById('webterm-iframe');
|
171
169
|
if (!iframe){ return dash_clientside.no_update; }
|
@@ -174,7 +172,7 @@ dash_app.clientside_callback(
|
|
174
172
|
{
|
175
173
|
action: "show",
|
176
174
|
subaction: "logs",
|
177
|
-
subaction_text:
|
175
|
+
subaction_text: job_name,
|
178
176
|
},
|
179
177
|
url
|
180
178
|
);
|
@@ -197,44 +195,38 @@ dash_app.clientside_callback(
|
|
197
195
|
prevent_initial_call = True,
|
198
196
|
)
|
199
197
|
def refresh_jobs_on_interval(
|
200
|
-
|
201
|
-
|
202
|
-
|
198
|
+
n_intervals: Optional[int] = None,
|
199
|
+
session_data: Optional[Dict[str, Any]] = None,
|
200
|
+
):
|
203
201
|
"""
|
204
202
|
When the jobs refresh interval fires, rebuild the jobs' onscreen components.
|
205
203
|
"""
|
206
204
|
session_id = session_data.get('session-id', None)
|
207
205
|
is_authenticated = is_session_authenticated(session_id)
|
208
206
|
|
209
|
-
|
207
|
+
job_names = [
|
210
208
|
component_dict['id']['index']
|
211
209
|
for component_dict in dash.callback_context.outputs_grouping[0]
|
212
210
|
]
|
213
211
|
|
214
|
-
### NOTE: The
|
215
|
-
|
216
|
-
for daemon_id in daemon_ids:
|
217
|
-
try:
|
218
|
-
daemon = Daemon(daemon_id=daemon_id)
|
219
|
-
except Exception as e:
|
220
|
-
daemon = None
|
221
|
-
daemons.append(daemon)
|
212
|
+
### NOTE: The job may have been deleted, but the card may still exist.
|
213
|
+
jobs = [Job(name) for name in job_names]
|
222
214
|
|
223
215
|
return (
|
224
216
|
[
|
225
217
|
(
|
226
|
-
build_manage_job_buttons_div_children(
|
218
|
+
build_manage_job_buttons_div_children(job)
|
227
219
|
if is_authenticated
|
228
220
|
else []
|
229
221
|
)
|
230
|
-
for
|
222
|
+
for job in jobs
|
231
223
|
],
|
232
224
|
[
|
233
|
-
build_status_children(
|
234
|
-
for
|
225
|
+
build_status_children(job)
|
226
|
+
for job in jobs
|
235
227
|
],
|
236
228
|
[
|
237
|
-
build_process_timestamps_children(
|
238
|
-
for
|
229
|
+
build_process_timestamps_children(job)
|
230
|
+
for job in jobs
|
239
231
|
],
|
240
232
|
)
|
meerschaum/api/dash/jobs.py
CHANGED
@@ -15,13 +15,13 @@ from meerschaum.api.dash.users import is_session_authenticated
|
|
15
15
|
from meerschaum.api import CHECK_UPDATE
|
16
16
|
dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
|
17
17
|
html, dcc = import_html(), import_dcc()
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
from meerschaum.jobs import (
|
19
|
+
get_jobs,
|
20
|
+
get_running_jobs,
|
21
|
+
get_paused_jobs,
|
22
|
+
get_stopped_jobs,
|
23
|
+
get_executor_keys_from_context,
|
24
|
+
Job,
|
25
25
|
)
|
26
26
|
from meerschaum.config import get_config
|
27
27
|
from meerschaum.utils.misc import sorted_dict
|
@@ -33,71 +33,73 @@ STATUS_EMOJI: Dict[str, str] = {
|
|
33
33
|
'dne': get_config('formatting', 'emoji', 'failure')
|
34
34
|
}
|
35
35
|
|
36
|
+
EXECUTOR_KEYS: str = get_executor_keys_from_context()
|
37
|
+
|
36
38
|
def get_jobs_cards(state: WebState):
|
37
39
|
"""
|
38
40
|
Build cards and alerts lists for jobs.
|
39
41
|
"""
|
40
|
-
|
42
|
+
jobs = get_jobs(executor_keys=EXECUTOR_KEYS, include_hidden=False)
|
41
43
|
session_id = state['session-store.data'].get('session-id', None)
|
42
44
|
is_authenticated = is_session_authenticated(session_id)
|
43
45
|
|
44
|
-
alert = alert_from_success_tuple(daemons)
|
45
46
|
cards = []
|
46
47
|
|
47
|
-
for
|
48
|
+
for name, job in jobs.items():
|
48
49
|
footer_children = html.Div(
|
49
|
-
build_process_timestamps_children(
|
50
|
-
id = {'type': 'process-timestamps-div', 'index':
|
50
|
+
build_process_timestamps_children(job),
|
51
|
+
id = {'type': 'process-timestamps-div', 'index': name},
|
51
52
|
)
|
52
53
|
follow_logs_button = dbc.DropdownMenuItem(
|
53
54
|
"Follow logs",
|
54
|
-
id = {'type': 'follow-logs-button', 'index':
|
55
|
+
id = {'type': 'follow-logs-button', 'index': name},
|
55
56
|
)
|
56
57
|
download_logs_button = dbc.DropdownMenuItem(
|
57
58
|
"Download logs",
|
58
|
-
id = {'type': 'job-download-logs-button', 'index':
|
59
|
+
id = {'type': 'job-download-logs-button', 'index': name},
|
59
60
|
)
|
60
61
|
logs_menu_children = (
|
61
62
|
([follow_logs_button] if is_authenticated else []) + [download_logs_button]
|
62
63
|
)
|
63
64
|
header_children = [
|
64
65
|
html.Div(
|
65
|
-
build_status_children(
|
66
|
-
id
|
67
|
-
style
|
66
|
+
build_status_children(job),
|
67
|
+
id={'type': 'manage-job-status-div', 'index': name},
|
68
|
+
style={'float': 'left'},
|
68
69
|
),
|
69
70
|
html.Div(
|
70
71
|
dbc.DropdownMenu(
|
71
72
|
logs_menu_children,
|
72
|
-
label
|
73
|
-
size
|
74
|
-
align_end
|
75
|
-
color
|
76
|
-
menu_variant
|
73
|
+
label="Logs",
|
74
|
+
size="sm",
|
75
|
+
align_end=True,
|
76
|
+
color="secondary",
|
77
|
+
menu_variant='dark',
|
77
78
|
),
|
78
|
-
style
|
79
|
+
style={'float': 'right'},
|
79
80
|
),
|
80
81
|
]
|
81
82
|
|
82
83
|
body_children = [
|
83
|
-
html.H4(html.B(
|
84
|
+
html.H4(html.B(name), className="card-title"),
|
84
85
|
html.Div(
|
85
86
|
html.P(
|
86
|
-
|
87
|
-
className
|
88
|
-
style
|
87
|
+
job.label,
|
88
|
+
className="card-text job-card-text",
|
89
|
+
style={"word-wrap": "break-word"},
|
90
|
+
id={'type': 'job-label-p', 'index': name},
|
89
91
|
),
|
90
|
-
style
|
92
|
+
style={"white-space": "pre-wrap"},
|
91
93
|
),
|
92
94
|
html.Div(
|
93
95
|
(
|
94
|
-
build_manage_job_buttons_div_children(
|
96
|
+
build_manage_job_buttons_div_children(job)
|
95
97
|
if is_authenticated
|
96
98
|
else []
|
97
99
|
),
|
98
|
-
id
|
100
|
+
id={'type': 'manage-job-buttons-div', 'index': name},
|
99
101
|
),
|
100
|
-
html.Div(id={'type': 'manage-job-alert-div', 'index':
|
102
|
+
html.Div(id={'type': 'manage-job-alert-div', 'index': name}),
|
101
103
|
]
|
102
104
|
|
103
105
|
cards.append(
|
@@ -111,11 +113,11 @@ def get_jobs_cards(state: WebState):
|
|
111
113
|
return cards, []
|
112
114
|
|
113
115
|
|
114
|
-
def build_manage_job_buttons_div_children(
|
116
|
+
def build_manage_job_buttons_div_children(job: Job):
|
115
117
|
"""
|
116
118
|
Return the children for the manage job buttons div.
|
117
119
|
"""
|
118
|
-
buttons = build_manage_job_buttons(
|
120
|
+
buttons = build_manage_job_buttons(job)
|
119
121
|
if not buttons:
|
120
122
|
return []
|
121
123
|
return [
|
@@ -127,98 +129,107 @@ def build_manage_job_buttons_div_children(daemon: Daemon):
|
|
127
129
|
]
|
128
130
|
|
129
131
|
|
130
|
-
def build_manage_job_buttons(
|
132
|
+
def build_manage_job_buttons(job: Job):
|
131
133
|
"""
|
132
|
-
Return the currently available job management buttons for a given
|
134
|
+
Return the currently available job management buttons for a given Job.
|
133
135
|
"""
|
134
|
-
if
|
136
|
+
if job is None:
|
135
137
|
return []
|
138
|
+
|
136
139
|
start_button = dbc.Button(
|
137
140
|
'Start',
|
138
|
-
size
|
139
|
-
color
|
140
|
-
style
|
141
|
-
id
|
141
|
+
size='sm',
|
142
|
+
color='success',
|
143
|
+
style={'width': '100%'},
|
144
|
+
id={
|
142
145
|
'type': 'manage-job-button',
|
143
146
|
'action': 'start',
|
144
|
-
'index':
|
147
|
+
'index': job.name,
|
145
148
|
},
|
146
149
|
)
|
147
150
|
pause_button = dbc.Button(
|
148
151
|
'Pause',
|
149
|
-
size
|
150
|
-
color
|
151
|
-
style
|
152
|
-
id
|
152
|
+
size='sm',
|
153
|
+
color='warning',
|
154
|
+
style={'width': '100%'},
|
155
|
+
id={
|
153
156
|
'type': 'manage-job-button',
|
154
157
|
'action': 'pause',
|
155
|
-
'index':
|
158
|
+
'index': job.name,
|
156
159
|
},
|
157
160
|
)
|
158
161
|
stop_button = dbc.Button(
|
159
162
|
'Stop',
|
160
|
-
size
|
161
|
-
color
|
162
|
-
style
|
163
|
-
id
|
163
|
+
size='sm',
|
164
|
+
color='danger',
|
165
|
+
style={'width': '100%'},
|
166
|
+
id={
|
164
167
|
'type': 'manage-job-button',
|
165
168
|
'action': 'stop',
|
166
|
-
'index':
|
169
|
+
'index': job.name,
|
167
170
|
},
|
168
171
|
)
|
169
172
|
delete_button = dbc.Button(
|
170
173
|
'Delete',
|
171
|
-
size
|
172
|
-
color
|
173
|
-
style
|
174
|
-
id
|
174
|
+
size='sm',
|
175
|
+
color='danger',
|
176
|
+
style={'width': '100%'},
|
177
|
+
id={
|
175
178
|
'type': 'manage-job-button',
|
176
179
|
'action': 'delete',
|
177
|
-
'index':
|
180
|
+
'index': job.name,
|
178
181
|
},
|
179
182
|
)
|
180
183
|
buttons = []
|
181
|
-
|
184
|
+
status = job.status
|
185
|
+
if status in ('stopped', 'paused'):
|
182
186
|
buttons.append(start_button)
|
183
|
-
if
|
187
|
+
if status == 'stopped':
|
184
188
|
buttons.append(delete_button)
|
185
|
-
if
|
189
|
+
if status in ('running',):
|
186
190
|
buttons.append(pause_button)
|
187
|
-
if
|
191
|
+
if status in ('running', 'paused'):
|
188
192
|
buttons.append(stop_button)
|
189
193
|
|
190
194
|
return buttons
|
191
195
|
|
192
196
|
|
193
|
-
def build_status_children(
|
197
|
+
def build_status_children(job: Job) -> List[html.P]:
|
194
198
|
"""
|
195
|
-
Return the status HTML component for this
|
199
|
+
Return the status HTML component for this Job.
|
196
200
|
"""
|
197
|
-
if
|
201
|
+
if job is None:
|
198
202
|
return STATUS_EMOJI['dne']
|
199
203
|
|
200
204
|
status_str = (
|
201
|
-
STATUS_EMOJI.get(
|
205
|
+
STATUS_EMOJI.get(job.status, STATUS_EMOJI['stopped'])
|
202
206
|
+ ' '
|
203
|
-
+
|
207
|
+
+ job.status.capitalize()
|
204
208
|
)
|
205
209
|
return html.P(
|
206
210
|
html.B(status_str),
|
207
|
-
className
|
211
|
+
className=f"{job.status}-job",
|
208
212
|
)
|
209
213
|
|
210
214
|
|
211
|
-
def build_process_timestamps_children(
|
215
|
+
def build_process_timestamps_children(job: Job) -> List[dbc.Row]:
|
212
216
|
"""
|
213
217
|
Return the children to the process timestamps in the footer of the job card.
|
214
218
|
"""
|
215
|
-
if
|
219
|
+
if job is None:
|
216
220
|
return []
|
221
|
+
|
217
222
|
children = []
|
218
|
-
for timestamp_key,
|
219
|
-
|
223
|
+
for timestamp_key, timestamp in sorted_dict(
|
224
|
+
{
|
225
|
+
'began': job.began,
|
226
|
+
'paused': job.paused,
|
227
|
+
'ended': job.ended,
|
228
|
+
}
|
220
229
|
).items():
|
221
|
-
timestamp
|
230
|
+
if timestamp is None:
|
231
|
+
continue
|
232
|
+
|
222
233
|
timestamp_str = timestamp.strftime('%Y-%m-%d %H:%M UTC')
|
223
234
|
children.append(
|
224
235
|
dbc.Row(
|
@@ -226,21 +237,21 @@ def build_process_timestamps_children(daemon: Daemon) -> List[dbc.Row]:
|
|
226
237
|
dbc.Col(
|
227
238
|
html.P(
|
228
239
|
timestamp_key.capitalize(),
|
229
|
-
style
|
230
|
-
className
|
240
|
+
style={'font-size': 'small'},
|
241
|
+
className='text-muted mb-0',
|
231
242
|
),
|
232
|
-
width
|
243
|
+
width=4,
|
233
244
|
),
|
234
245
|
dbc.Col(
|
235
246
|
html.P(
|
236
247
|
timestamp_str,
|
237
|
-
style
|
238
|
-
className
|
248
|
+
style={'font-size': 'small', 'text-align': 'right'},
|
249
|
+
className='text-muted mb-0',
|
239
250
|
),
|
240
|
-
width
|
251
|
+
width=8,
|
241
252
|
),
|
242
253
|
],
|
243
|
-
justify
|
254
|
+
justify='between',
|
244
255
|
)
|
245
256
|
)
|
246
257
|
return children
|
@@ -8,6 +8,7 @@ Import all routes from other modules in package
|
|
8
8
|
|
9
9
|
from meerschaum.api.routes._login import *
|
10
10
|
from meerschaum.api.routes._actions import *
|
11
|
+
from meerschaum.api.routes._jobs import *
|
11
12
|
from meerschaum.api.routes._connectors import *
|
12
13
|
from meerschaum.api.routes._index import *
|
13
14
|
from meerschaum.api.routes._misc import *
|