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.
Files changed (80) hide show
  1. meerschaum/__init__.py +6 -1
  2. meerschaum/__main__.py +9 -9
  3. meerschaum/_internal/arguments/__init__.py +1 -1
  4. meerschaum/_internal/arguments/_parse_arguments.py +72 -6
  5. meerschaum/_internal/arguments/_parser.py +45 -15
  6. meerschaum/_internal/docs/index.py +265 -8
  7. meerschaum/_internal/entry.py +167 -37
  8. meerschaum/_internal/shell/Shell.py +290 -99
  9. meerschaum/_internal/shell/updates.py +175 -0
  10. meerschaum/actions/__init__.py +29 -17
  11. meerschaum/actions/api.py +12 -12
  12. meerschaum/actions/attach.py +113 -0
  13. meerschaum/actions/copy.py +68 -41
  14. meerschaum/actions/delete.py +112 -50
  15. meerschaum/actions/edit.py +3 -3
  16. meerschaum/actions/install.py +40 -32
  17. meerschaum/actions/pause.py +44 -27
  18. meerschaum/actions/register.py +19 -5
  19. meerschaum/actions/restart.py +107 -0
  20. meerschaum/actions/show.py +130 -159
  21. meerschaum/actions/start.py +161 -100
  22. meerschaum/actions/stop.py +78 -42
  23. meerschaum/actions/sync.py +3 -3
  24. meerschaum/actions/upgrade.py +28 -36
  25. meerschaum/api/_events.py +25 -1
  26. meerschaum/api/_oauth2.py +2 -0
  27. meerschaum/api/_websockets.py +2 -2
  28. meerschaum/api/dash/callbacks/jobs.py +36 -44
  29. meerschaum/api/dash/jobs.py +89 -78
  30. meerschaum/api/routes/__init__.py +1 -0
  31. meerschaum/api/routes/_actions.py +148 -17
  32. meerschaum/api/routes/_jobs.py +407 -0
  33. meerschaum/api/routes/_pipes.py +25 -25
  34. meerschaum/config/_default.py +1 -0
  35. meerschaum/config/_formatting.py +1 -0
  36. meerschaum/config/_jobs.py +1 -1
  37. meerschaum/config/_paths.py +11 -0
  38. meerschaum/config/_shell.py +84 -67
  39. meerschaum/config/_version.py +1 -1
  40. meerschaum/config/static/__init__.py +18 -0
  41. meerschaum/connectors/Connector.py +13 -7
  42. meerschaum/connectors/__init__.py +28 -15
  43. meerschaum/connectors/api/APIConnector.py +27 -1
  44. meerschaum/connectors/api/_actions.py +71 -6
  45. meerschaum/connectors/api/_jobs.py +368 -0
  46. meerschaum/connectors/api/_misc.py +1 -1
  47. meerschaum/connectors/api/_pipes.py +85 -84
  48. meerschaum/connectors/api/_request.py +13 -9
  49. meerschaum/connectors/parse.py +27 -15
  50. meerschaum/core/Pipe/_bootstrap.py +16 -8
  51. meerschaum/core/Pipe/_sync.py +3 -0
  52. meerschaum/jobs/_Executor.py +69 -0
  53. meerschaum/jobs/_Job.py +899 -0
  54. meerschaum/jobs/__init__.py +396 -0
  55. meerschaum/jobs/systemd.py +694 -0
  56. meerschaum/plugins/__init__.py +97 -12
  57. meerschaum/utils/daemon/Daemon.py +352 -147
  58. meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
  59. meerschaum/utils/daemon/RotatingFile.py +22 -8
  60. meerschaum/utils/daemon/StdinFile.py +121 -0
  61. meerschaum/utils/daemon/__init__.py +42 -27
  62. meerschaum/utils/daemon/_names.py +15 -13
  63. meerschaum/utils/formatting/__init__.py +83 -37
  64. meerschaum/utils/formatting/_jobs.py +146 -55
  65. meerschaum/utils/formatting/_shell.py +6 -0
  66. meerschaum/utils/misc.py +41 -22
  67. meerschaum/utils/packages/__init__.py +21 -15
  68. meerschaum/utils/packages/_packages.py +9 -6
  69. meerschaum/utils/process.py +9 -9
  70. meerschaum/utils/prompt.py +20 -7
  71. meerschaum/utils/schedule.py +21 -15
  72. meerschaum/utils/venv/__init__.py +2 -2
  73. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
  74. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/RECORD +80 -70
  75. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
  76. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
  77. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
  78. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
  79. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
  80. {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
- conn = get_api_connector()
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'])
@@ -23,8 +23,8 @@ sessions = {}
23
23
  @app.websocket('/dash/ws')
24
24
  @app.websocket(_websocket_endpoint)
25
25
  async def websocket_endpoint(
26
- websocket: fastapi.WebSocket,
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
- daemon_id = component_dict['index']
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': daemon.log_text,
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
- n_clicks: Optional[int] = None,
80
- session_data: Optional[Dict[str, Any]] = None,
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
- daemon_id = component_dict['index']
103
+ job_name = component_dict['index']
106
104
  manage_job_action = component_dict['action']
107
105
  try:
108
- daemon = Daemon(daemon_id=daemon_id)
106
+ job = Job(job_name, job_label.replace('\n', ' ') if job_label else None)
109
107
  except Exception as e:
110
- daemon = None
111
- if daemon is None:
108
+ job = None
109
+ if job is None:
112
110
  raise PreventUpdate
113
111
 
114
112
  manage_functions = {
115
- 'start': functools.partial(daemon.run, allow_dirty_run=True),
116
- 'stop': daemon.quit,
117
- 'pause': daemon.pause,
118
- 'delete': daemon.cleanup,
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 = daemon.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 daemon.status != old_status:
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(daemon),
146
- build_status_children(daemon),
147
- build_process_timestamps_children(daemon),
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 job_daemon_id = triggered_id["index"];
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: job_daemon_id,
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
- n_intervals: Optional[int] = None,
201
- session_data: Optional[Dict[str, Any]] = None,
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
- daemon_ids = [
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 daemon may have been deleted, but the card may still exist.
215
- daemons = []
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(daemon)
218
+ build_manage_job_buttons_div_children(job)
227
219
  if is_authenticated
228
220
  else []
229
221
  )
230
- for daemon in daemons
222
+ for job in jobs
231
223
  ],
232
224
  [
233
- build_status_children(daemon)
234
- for daemon in daemons
225
+ build_status_children(job)
226
+ for job in jobs
235
227
  ],
236
228
  [
237
- build_process_timestamps_children(daemon)
238
- for daemon in daemons
229
+ build_process_timestamps_children(job)
230
+ for job in jobs
239
231
  ],
240
232
  )
@@ -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
- dateutil_parser = attempt_import('dateutil.parser', check_update=CHECK_UPDATE)
19
- from meerschaum.utils.daemon import (
20
- get_daemons,
21
- get_running_daemons,
22
- get_paused_daemons,
23
- get_stopped_daemons,
24
- Daemon,
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
- daemons = get_daemons()
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 d in daemons:
48
+ for name, job in jobs.items():
48
49
  footer_children = html.Div(
49
- build_process_timestamps_children(d),
50
- id = {'type': 'process-timestamps-div', 'index': d.daemon_id},
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': d.daemon_id},
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': d.daemon_id},
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(d),
66
- id = {'type': 'manage-job-status-div', 'index': d.daemon_id},
67
- style = {'float': 'left'},
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 = "Logs",
73
- size = "sm",
74
- align_end = True,
75
- color = "secondary",
76
- menu_variant = 'dark',
73
+ label="Logs",
74
+ size="sm",
75
+ align_end=True,
76
+ color="secondary",
77
+ menu_variant='dark',
77
78
  ),
78
- style = {'float': 'right'},
79
+ style={'float': 'right'},
79
80
  ),
80
81
  ]
81
82
 
82
83
  body_children = [
83
- html.H4(html.B(d.daemon_id), className="card-title"),
84
+ html.H4(html.B(name), className="card-title"),
84
85
  html.Div(
85
86
  html.P(
86
- d.label,
87
- className = "card-text job-card-text",
88
- style = {"word-wrap": "break-word"}
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 = {"white-space": "pre-wrap"},
92
+ style={"white-space": "pre-wrap"},
91
93
  ),
92
94
  html.Div(
93
95
  (
94
- build_manage_job_buttons_div_children(d)
96
+ build_manage_job_buttons_div_children(job)
95
97
  if is_authenticated
96
98
  else []
97
99
  ),
98
- id = {'type': 'manage-job-buttons-div', 'index': d.daemon_id},
100
+ id={'type': 'manage-job-buttons-div', 'index': name},
99
101
  ),
100
- html.Div(id={'type': 'manage-job-alert-div', 'index': d.daemon_id}),
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(daemon: Daemon):
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(daemon)
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(daemon: Daemon):
132
+ def build_manage_job_buttons(job: Job):
131
133
  """
132
- Return the currently available job management buttons for a given Daemon.
134
+ Return the currently available job management buttons for a given Job.
133
135
  """
134
- if daemon is None:
136
+ if job is None:
135
137
  return []
138
+
136
139
  start_button = dbc.Button(
137
140
  'Start',
138
- size = 'sm',
139
- color = 'success',
140
- style = {'width': '100%'},
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': daemon.daemon_id,
147
+ 'index': job.name,
145
148
  },
146
149
  )
147
150
  pause_button = dbc.Button(
148
151
  'Pause',
149
- size = 'sm',
150
- color = 'warning',
151
- style = {'width': '100%'},
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': daemon.daemon_id,
158
+ 'index': job.name,
156
159
  },
157
160
  )
158
161
  stop_button = dbc.Button(
159
162
  'Stop',
160
- size = 'sm',
161
- color = 'danger',
162
- style = {'width': '100%'},
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': daemon.daemon_id,
169
+ 'index': job.name,
167
170
  },
168
171
  )
169
172
  delete_button = dbc.Button(
170
173
  'Delete',
171
- size = 'sm',
172
- color = 'danger',
173
- style = {'width': '100%'},
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': daemon.daemon_id,
180
+ 'index': job.name,
178
181
  },
179
182
  )
180
183
  buttons = []
181
- if daemon.status in ('stopped', 'paused'):
184
+ status = job.status
185
+ if status in ('stopped', 'paused'):
182
186
  buttons.append(start_button)
183
- if daemon.status == 'stopped':
187
+ if status == 'stopped':
184
188
  buttons.append(delete_button)
185
- if daemon.status in ('running',):
189
+ if status in ('running',):
186
190
  buttons.append(pause_button)
187
- if daemon.status in ('running', 'paused'):
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(daemon: Daemon) -> List[html.P]:
197
+ def build_status_children(job: Job) -> List[html.P]:
194
198
  """
195
- Return the status HTML component for this daemon.
199
+ Return the status HTML component for this Job.
196
200
  """
197
- if daemon is None:
201
+ if job is None:
198
202
  return STATUS_EMOJI['dne']
199
203
 
200
204
  status_str = (
201
- STATUS_EMOJI.get(daemon.status, STATUS_EMOJI['stopped'])
205
+ STATUS_EMOJI.get(job.status, STATUS_EMOJI['stopped'])
202
206
  + ' '
203
- + daemon.status.capitalize()
207
+ + job.status.capitalize()
204
208
  )
205
209
  return html.P(
206
210
  html.B(status_str),
207
- className = f"{daemon.status}-job",
211
+ className=f"{job.status}-job",
208
212
  )
209
213
 
210
214
 
211
- def build_process_timestamps_children(daemon: Daemon) -> List[dbc.Row]:
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 daemon is None:
219
+ if job is None:
216
220
  return []
221
+
217
222
  children = []
218
- for timestamp_key, timestamp_val in sorted_dict(
219
- daemon.properties.get('process', {})
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 = dateutil_parser.parse(timestamp_val)
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 = {'font-size': 'small'},
230
- className = 'text-muted mb-0',
240
+ style={'font-size': 'small'},
241
+ className='text-muted mb-0',
231
242
  ),
232
- width = 4,
243
+ width=4,
233
244
  ),
234
245
  dbc.Col(
235
246
  html.P(
236
247
  timestamp_str,
237
- style = {'font-size': 'small', 'text-align': 'right'},
238
- className = 'text-muted mb-0',
248
+ style={'font-size': 'small', 'text-align': 'right'},
249
+ className='text-muted mb-0',
239
250
  ),
240
- width = 8,
251
+ width=8,
241
252
  ),
242
253
  ],
243
- justify = 'between',
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 *