OpenOrchestrator 1.0.2__py3-none-any.whl → 1.2.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 (40) hide show
  1. OpenOrchestrator/__init__.py +7 -0
  2. OpenOrchestrator/__main__.py +2 -0
  3. OpenOrchestrator/common/connection_frame.py +35 -49
  4. OpenOrchestrator/common/crypto_util.py +4 -4
  5. OpenOrchestrator/common/datetime_util.py +20 -0
  6. OpenOrchestrator/database/constants.py +25 -19
  7. OpenOrchestrator/database/db_util.py +77 -30
  8. OpenOrchestrator/database/logs.py +13 -0
  9. OpenOrchestrator/database/queues.py +17 -0
  10. OpenOrchestrator/database/triggers.py +25 -56
  11. OpenOrchestrator/orchestrator/application.py +87 -34
  12. OpenOrchestrator/orchestrator/datetime_input.py +75 -0
  13. OpenOrchestrator/orchestrator/popups/constant_popup.py +87 -69
  14. OpenOrchestrator/orchestrator/popups/credential_popup.py +92 -82
  15. OpenOrchestrator/orchestrator/popups/generic_popups.py +27 -0
  16. OpenOrchestrator/orchestrator/popups/trigger_popup.py +216 -0
  17. OpenOrchestrator/orchestrator/tabs/constants_tab.py +52 -0
  18. OpenOrchestrator/orchestrator/tabs/logging_tab.py +70 -0
  19. OpenOrchestrator/orchestrator/tabs/queue_tab.py +116 -0
  20. OpenOrchestrator/orchestrator/tabs/settings_tab.py +22 -0
  21. OpenOrchestrator/orchestrator/tabs/trigger_tab.py +87 -0
  22. OpenOrchestrator/scheduler/application.py +3 -3
  23. OpenOrchestrator/scheduler/connection_frame.py +96 -0
  24. OpenOrchestrator/scheduler/run_tab.py +87 -80
  25. OpenOrchestrator/scheduler/runner.py +33 -25
  26. OpenOrchestrator/scheduler/settings_tab.py +2 -1
  27. {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/METADATA +2 -2
  28. OpenOrchestrator-1.2.0.dist-info/RECORD +38 -0
  29. OpenOrchestrator/orchestrator/constants_tab.py +0 -169
  30. OpenOrchestrator/orchestrator/logging_tab.py +0 -221
  31. OpenOrchestrator/orchestrator/popups/queue_trigger_popup.py +0 -129
  32. OpenOrchestrator/orchestrator/popups/scheduled_trigger_popup.py +0 -129
  33. OpenOrchestrator/orchestrator/popups/single_trigger_popup.py +0 -134
  34. OpenOrchestrator/orchestrator/settings_tab.py +0 -31
  35. OpenOrchestrator/orchestrator/table_util.py +0 -76
  36. OpenOrchestrator/orchestrator/trigger_tab.py +0 -231
  37. OpenOrchestrator-1.0.2.dist-info/RECORD +0 -36
  38. {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/LICENSE +0 -0
  39. {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/WHEEL +0 -0
  40. {OpenOrchestrator-1.0.2.dist-info → OpenOrchestrator-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,96 @@
1
+ """This module contains a single class: ConnectionFrame."""
2
+
3
+ import os
4
+ import tkinter
5
+ from tkinter import ttk, messagebox
6
+
7
+ from OpenOrchestrator.common import crypto_util
8
+ from OpenOrchestrator.database import db_util
9
+
10
+
11
+ # pylint: disable-next=too-many-ancestors
12
+ class ConnectionFrame(ttk.Frame):
13
+ """A ttk.Frame object that contains two ttk.Entry and
14
+ two ttk.Button used to enter a connection string and
15
+ encryption key and to connect to the database.
16
+ """
17
+ def __init__(self, parent: tkinter.Widget):
18
+ super().__init__(parent)
19
+
20
+ frame = ttk.Frame(self)
21
+ frame.columnconfigure(1, weight=1)
22
+ frame.pack(fill='both')
23
+
24
+ ttk.Label(frame, text="Connection string:").grid(row=0, column=0, sticky='w')
25
+
26
+ self.conn_entry = ttk.Entry(frame)
27
+ self.conn_entry.grid(row=0, column=1, sticky='ew')
28
+
29
+ self.conn_button = ttk.Button(frame, text="Connect", command=self._connect)
30
+ self.conn_button.grid(row=0, column=2, sticky='e')
31
+
32
+ ttk.Label(frame, text="Encryption key:").grid(row=1, column=0, sticky='w')
33
+
34
+ self.key_entry = ttk.Entry(frame)
35
+ self.key_entry.grid(row=1, column=1, sticky='ew')
36
+
37
+ self.disconn_button = ttk.Button(frame, text="Disconnect", command=self._disconnect, state='disabled')
38
+ self.disconn_button.grid(row=1, column=2, sticky='e')
39
+
40
+ self._initial_connect()
41
+
42
+ def _connect(self) -> None:
43
+ """Validate the connection string and encryption key
44
+ and connect to the database.
45
+ """
46
+ conn_string = self.conn_entry.get()
47
+ crypto_key = self.key_entry.get()
48
+
49
+ if not crypto_util.validate_key(crypto_key):
50
+ messagebox.showerror("Invalid encryption key", "The entered encryption key is not a valid AES key.")
51
+ return
52
+
53
+ if db_util.connect(conn_string):
54
+ crypto_util.set_key(crypto_key)
55
+ self._set_state(True)
56
+
57
+ def _disconnect(self) -> None:
58
+ db_util.disconnect()
59
+ crypto_util.set_key(None)
60
+ self._set_state(False)
61
+
62
+ def _set_state(self, connected: bool) -> None:
63
+ if connected:
64
+ self.conn_entry.configure(state='disabled')
65
+ self.key_entry.configure(state='disabled')
66
+ self.conn_button.configure(state='disabled')
67
+ self.disconn_button.configure(state='normal')
68
+ else:
69
+ self.conn_entry.configure(state='normal')
70
+ self.key_entry.configure(state='normal')
71
+ self.conn_button.configure(state='normal')
72
+ self.disconn_button.configure(state='disabled')
73
+
74
+ def _initial_connect(self) -> None:
75
+ """Check the environment for a connection string
76
+ and encryption key and connect to the database if both
77
+ are found.
78
+ """
79
+ conn_string = os.environ.get('OpenOrchestratorConnString', None)
80
+ if conn_string:
81
+ self.conn_entry.insert(0, conn_string)
82
+
83
+ crypto_key = os.environ.get('OpenOrchestratorKey', None)
84
+ if crypto_key:
85
+ self.key_entry.insert(0, crypto_key)
86
+
87
+ if conn_string and crypto_key:
88
+ self._connect()
89
+
90
+ def new_key(self):
91
+ """Creates a new encryption key and inserts it
92
+ into the key entry.
93
+ """
94
+ key = crypto_util.generate_key()
95
+ self.key_entry.delete(0, 'end')
96
+ self.key_entry.insert(0, key)
@@ -1,115 +1,123 @@
1
1
  """This module is responsible for the layout and functionality of the run tab
2
2
  in Scheduler."""
3
3
 
4
+ from __future__ import annotations
5
+ from typing import TYPE_CHECKING
6
+
4
7
  import tkinter
5
8
  from tkinter import ttk
6
9
  import sys
7
10
 
11
+ from sqlalchemy import exc as alc_exc
12
+
8
13
  from OpenOrchestrator.common import crypto_util
9
14
  from OpenOrchestrator.database import db_util
10
15
  from OpenOrchestrator.scheduler import runner
11
16
 
12
- def create_tab(parent: ttk.Notebook, app) -> ttk.Frame:
13
- """Create a new Run tab object.
17
+ if TYPE_CHECKING:
18
+ from OpenOrchestrator.scheduler.application import Application
14
19
 
15
- Args:
16
- parent: The ttk.Notebook object that this tab is a child of.
17
- app: The Scheduler application object.
18
-
19
- Returns:
20
- ttk.Frame: The created tab object as a ttk.Frame.
21
- """
22
- tab = ttk.Frame(parent)
23
- tab.pack(fill='both', expand=True)
24
20
 
25
- status_label = ttk.Label(tab, text="State: Paused")
26
- status_label.pack()
21
+ # pylint: disable-next=too-many-ancestors
22
+ class RunTab(ttk.Frame):
23
+ """A ttk.frame object containing the functionality of the run tab in Scheduler."""
24
+ def __init__(self, parent: ttk.Notebook, app: Application):
25
+ super().__init__(parent)
26
+ self.pack(fill='both', expand=True)
27
27
 
28
- ttk.Button(tab, text="Run", command=lambda: run(app, status_label)).pack()
29
- ttk.Button(tab, text="Pause", command=lambda: pause(app, status_label)).pack()
28
+ self.app = app
30
29
 
31
- # Text area
32
- text_frame = tkinter.Frame(tab)
33
- text_frame.pack()
30
+ s = ttk.Style()
31
+ s.configure('my.TButton', font=('Helvetica Bold', 24))
32
+ self.button = ttk.Button(self, text="Run", command=self.button_click, style='my.TButton')
33
+ self.button.pack()
34
34
 
35
- text_area = tkinter.Text(text_frame, state='disabled', wrap='none')
36
- sys.stdout.write = lambda s: print_text(text_area, s)
35
+ # Text area
36
+ text_frame = tkinter.Frame(self)
37
+ text_frame.pack()
37
38
 
38
- text_yscroll = ttk.Scrollbar(text_frame, orient='vertical', command=text_area.yview)
39
- text_yscroll.pack(side='right', fill='y')
40
- text_area.configure(yscrollcommand=text_yscroll.set)
39
+ self.text_area = tkinter.Text(text_frame, state='disabled', wrap='none')
41
40
 
42
- text_xscroll = ttk.Scrollbar(text_frame, orient='horizontal', command=text_area.xview)
43
- text_xscroll.pack(side='bottom', fill='x')
44
- text_area.configure(xscrollcommand=text_xscroll.set)
41
+ # Redirect stdout to the text area instead of console
42
+ sys.stdout.write = self.print_text
45
43
 
46
- text_area.pack()
44
+ # Add scroll bars to text area
45
+ text_yscroll = ttk.Scrollbar(text_frame, orient='vertical', command=self.text_area.yview)
46
+ text_yscroll.pack(side='right', fill='y')
47
+ self.text_area.configure(yscrollcommand=text_yscroll.set)
47
48
 
48
- return tab
49
+ text_xscroll = ttk.Scrollbar(text_frame, orient='horizontal', command=self.text_area.xview)
50
+ text_xscroll.pack(side='bottom', fill='x')
51
+ self.text_area.configure(xscrollcommand=text_xscroll.set)
49
52
 
53
+ self.text_area.pack()
50
54
 
51
- def run(app, status_label: ttk.Label) -> None:
52
- """Starts the Scheduler and sets the app's status to 'running'.
55
+ def button_click(self):
56
+ """Callback for when the run/pause button is clicked."""
57
+ if self.app.running:
58
+ self.pause()
59
+ else:
60
+ self.run()
53
61
 
54
- Args:
55
- app: The Scheduler application object.
56
- status_label: The label showing the current status.
57
- """
58
- if db_util.get_conn_string() is None:
59
- print("Can't start without a valid connection string. Go to the settings tab to configure the connection string")
60
- return
61
- if crypto_util.get_key() is None:
62
- print("Can't start without a valid encryption key. Go to the settings tab to configure the encryption key")
63
- return
64
-
65
- if not app.running:
66
- status_label.configure(text='State: Running')
62
+ def pause(self):
63
+ """Stops the Scheduler and sets the app's status to 'paused'."""
64
+ self.button.configure(text="Run")
65
+ print('Paused... Please wait for all processes to stop before closing the application\n')
66
+ self.app.running = False
67
+
68
+ def run(self):
69
+ """Starts the Scheduler and sets the app's status to 'running'."""
70
+ if db_util.get_conn_string() is None:
71
+ print("Can't start without a valid connection string. Go to the settings tab to configure the connection string")
72
+ return
73
+ if crypto_util.get_key() is None:
74
+ print("Can't start without a valid encryption key. Go to the settings tab to configure the encryption key")
75
+ return
76
+
77
+ self.button.configure(text="Pause")
67
78
  print('Running...\n')
68
- app.running = True
69
-
70
- # Only start loop if it's not already running
71
- if app.tk.call('after', 'info') == '':
72
- app.after(0, loop, app)
79
+ self.app.running = True
73
80
 
81
+ # Only start a new loop if it's not already running
82
+ if self.app.tk.call('after', 'info') == '':
83
+ self.app.after(0, loop, self.app)
74
84
 
75
- def pause(app, status_label: ttk.Label):
76
- """Stops the Scheduler and sets the app's status to 'paused'.
85
+ def print_text(self, text: str) -> None:
86
+ """Appends text to the text area.
87
+ Is used to replace the functionality of sys.stdout.write (print).
77
88
 
78
- Args:
79
- app: The Scheduler application object.
80
- status_label: The label showing the current status.
81
- """
82
- if app.running:
83
- status_label.configure(text="State: Paused")
84
- print('Paused... Please wait for all processes to stop before closing the application\n')
85
- app.running = False
89
+ Args:
90
+ string: The string to append.
91
+ """
92
+ # Insert text at the end
93
+ self.text_area.configure(state='normal')
94
+ self.text_area.insert('end', text)
86
95
 
96
+ # If the number of lines are above 1000 delete 10 lines from the top
97
+ num_lines = int(self.text_area.index('end').split('.', maxsplit=1)[0])
98
+ if num_lines > 1000:
99
+ self.text_area.delete("1.0", "10.0")
87
100
 
88
- def print_text(text_widget: tkinter.Text, string: str) -> None:
89
- """Appends text to the text area.
90
- Is used to replace the functionality of sys.stdout.write (print).
91
-
92
- Args:
93
- print_text: The text area object.
94
- string: The string to append.
95
- """
96
- text_widget.configure(state='normal')
97
- text_widget.insert('end', string)
98
- text_widget.see('end')
99
- text_widget.configure(state='disabled')
101
+ # Scroll to end
102
+ self.text_area.see('end')
103
+ self.text_area.configure(state='disabled')
100
104
 
101
105
 
102
- def loop(app) -> None:
106
+ def loop(app: Application) -> None:
103
107
  """The main loop function of the Scheduler.
104
108
  Checks heartbeats, check triggers, and schedules the next loop.
105
109
 
106
110
  Args:
107
111
  app: The Scheduler Application object.
108
112
  """
109
- check_heartbeats(app)
113
+ try:
114
+ check_heartbeats(app)
115
+
116
+ if app.running:
117
+ check_triggers(app)
110
118
 
111
- if app.running:
112
- check_triggers(app)
119
+ except alc_exc.OperationalError:
120
+ print("Couldn't connect to database.")
113
121
 
114
122
  if len(app.running_jobs) == 0:
115
123
  print("Doing cleanup...")
@@ -123,7 +131,7 @@ def loop(app) -> None:
123
131
  print("Scheduler is paused and no more processes are running.")
124
132
 
125
133
 
126
- def check_heartbeats(app) -> None:
134
+ def check_heartbeats(app: Application) -> None:
127
135
  """Check if any running jobs are still running, failed or done.
128
136
 
129
137
  Args:
@@ -132,27 +140,26 @@ def check_heartbeats(app) -> None:
132
140
  print('Checking heartbeats...')
133
141
  for job in app.running_jobs:
134
142
  if job.process.poll() is not None:
135
- app.running_jobs.remove(job)
136
-
137
143
  if job.process.returncode == 0:
138
144
  print(f"Process '{job.trigger.process_name}' is done")
139
145
  runner.end_job(job)
140
146
  else:
141
- print(f"Process '{job.trigger.process_name}' failed")
147
+ print(f"Process '{job.trigger.process_name}' failed. Check process log for more info.")
142
148
  runner.fail_job(job)
143
149
 
150
+ app.running_jobs.remove(job)
144
151
  else:
145
152
  print(f"Process '{job.trigger.process_name}' is still running")
146
153
 
147
154
 
148
- def check_triggers(app) -> None:
155
+ def check_triggers(app: Application) -> None:
149
156
  """Checks any process is blocking
150
157
  and if not checks if any trigger should be run.
151
158
 
152
159
  Args:
153
160
  app: The Scheduler Application object.
154
161
  """
155
- #Check if process is blocking
162
+ # Check if process is blocking
156
163
  blocking = False
157
164
  for job in app.running_jobs:
158
165
  if job.trigger.is_blocking:
@@ -1,23 +1,22 @@
1
1
  """This module is responsible for checking triggers and running processes."""
2
2
 
3
3
  import os
4
- from datetime import datetime
5
4
  import subprocess
6
5
  from dataclasses import dataclass
7
6
  import uuid
8
7
 
9
- from croniter import croniter
10
-
11
8
  from OpenOrchestrator.common import crypto_util
12
9
  from OpenOrchestrator.database import db_util
13
10
  from OpenOrchestrator.database.triggers import Trigger, SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerStatus
14
11
  from OpenOrchestrator.database.logs import LogLevel
15
12
 
13
+
16
14
  @dataclass
17
15
  class Job():
18
16
  """An object that holds information about a running job."""
19
17
  process: subprocess.Popen
20
18
  trigger: Trigger
19
+ process_folder: str
21
20
 
22
21
 
23
22
  def poll_triggers(app) -> Job | None:
@@ -68,10 +67,7 @@ def run_single_trigger(trigger: SingleTrigger) -> Job | None:
68
67
  print('Running trigger: ', trigger.trigger_name)
69
68
 
70
69
  if db_util.begin_single_trigger(trigger.id):
71
- process = run_process(trigger)
72
-
73
- if process is not None:
74
- return Job(process, trigger)
70
+ return run_process(trigger)
75
71
 
76
72
  return None
77
73
 
@@ -89,13 +85,8 @@ def run_scheduled_trigger(trigger: ScheduledTrigger) -> Job | None:
89
85
  """
90
86
  print('Running trigger: ', trigger.trigger_name)
91
87
 
92
- next_run = croniter(trigger.cron_expr, trigger.next_run).get_next(datetime)
93
-
94
- if db_util.begin_scheduled_trigger(trigger.id, next_run):
95
- process = run_process(trigger)
96
-
97
- if process is not None:
98
- return Job(process, trigger)
88
+ if db_util.begin_scheduled_trigger(trigger.id):
89
+ return run_process(trigger)
99
90
 
100
91
  return None
101
92
 
@@ -113,10 +104,7 @@ def run_queue_trigger(trigger: QueueTrigger) -> Job | None:
113
104
  print('Running trigger: ', trigger.trigger_name)
114
105
 
115
106
  if db_util.begin_queue_trigger(trigger.id):
116
- process = run_process(trigger)
117
-
118
- if process is not None:
119
- return Job(process, trigger)
107
+ return run_process(trigger)
120
108
 
121
109
  return None
122
110
 
@@ -146,8 +134,7 @@ def clone_git_repo(repo_url: str) -> str:
146
134
  def clear_repo_folder() -> None:
147
135
  """Completely remove the repos folder."""
148
136
  repo_folder = get_repo_folder_path()
149
- subprocess.run(['rmdir', '/s', '/q', repo_folder], check=False, shell=True)
150
-
137
+ clear_folder(repo_folder)
151
138
 
152
139
 
153
140
  def get_repo_folder_path() -> str:
@@ -161,6 +148,15 @@ def get_repo_folder_path() -> str:
161
148
  return repo_path
162
149
 
163
150
 
151
+ def clear_folder(folder_path: str) -> None:
152
+ """Clear a folder on the system.
153
+
154
+ Args:
155
+ folder_path: The folder to remove.
156
+ """
157
+ subprocess.run(['rmdir', '/s', '/q', folder_path], check=False, shell=True, capture_output=True)
158
+
159
+
164
160
  def find_main_file(folder_path: str) -> str:
165
161
  """Finds the file in the given folder with the name 'main.py'.
166
162
  The search checks subfolders recursively.
@@ -198,6 +194,9 @@ def end_job(job: Job) -> None:
198
194
  elif isinstance(job.trigger, QueueTrigger):
199
195
  db_util.set_trigger_status(job.trigger.id, TriggerStatus.IDLE)
200
196
 
197
+ if job.process_folder:
198
+ clear_folder(job.process_folder)
199
+
201
200
 
202
201
  def fail_job(job: Job) -> None:
203
202
  """Mark a job as failed in the triggers table in the database.
@@ -206,9 +205,15 @@ def fail_job(job: Job) -> None:
206
205
  job: The job whose trigger to mark as failed.
207
206
  """
208
207
  db_util.set_trigger_status(job.trigger.id, TriggerStatus.FAILED)
208
+ _, error = job.process.communicate()
209
+ error_msg = f"An uncaught error ocurred during the process:\n{error}"
210
+ db_util.create_log(job.trigger.process_name, LogLevel.ERROR, error_msg)
211
+
212
+ if job.process_folder:
213
+ clear_folder(job.process_folder)
209
214
 
210
215
 
211
- def run_process(trigger: Trigger) -> subprocess.Popen | None:
216
+ def run_process(trigger: Trigger) -> Job | None:
212
217
  """Runs the process of the given trigger with the necessary inputs:
213
218
  Process name
214
219
  Connection string
@@ -226,14 +231,15 @@ def run_process(trigger: Trigger) -> subprocess.Popen | None:
226
231
  trigger: The trigger whose process to run.
227
232
 
228
233
  Returns:
229
- subprocess.Popen: The Popen instance of the process if successful.
234
+ Job: A Job object referencing the process if succesful.
230
235
  """
231
236
  process_path = trigger.process_path
237
+ folder_path = None
232
238
 
233
239
  try:
234
240
  if trigger.is_git_repo:
235
- git_folder_path = clone_git_repo(process_path)
236
- process_path = find_main_file(git_folder_path)
241
+ folder_path = clone_git_repo(process_path)
242
+ process_path = find_main_file(folder_path)
237
243
 
238
244
  if not os.path.isfile(process_path):
239
245
  raise ValueError(f"The process path didn't point to a file on the system. Path: '{process_path}'")
@@ -246,7 +252,9 @@ def run_process(trigger: Trigger) -> subprocess.Popen | None:
246
252
 
247
253
  command_args = ['python', process_path, trigger.process_name, conn_string, crypto_key, trigger.process_args]
248
254
 
249
- return subprocess.Popen(command_args)
255
+ process = subprocess.Popen(command_args, stderr=subprocess.PIPE, text=True) # pylint: disable=consider-using-with
256
+
257
+ return Job(process, trigger, folder_path)
250
258
 
251
259
  # We actually want to catch any exception here
252
260
  # pylint: disable=broad-exception-caught
@@ -3,7 +3,8 @@ in Scheduler."""
3
3
 
4
4
  from tkinter import ttk
5
5
 
6
- from OpenOrchestrator.common.connection_frame import ConnectionFrame
6
+ from OpenOrchestrator.scheduler.connection_frame import ConnectionFrame
7
+
7
8
 
8
9
  def create_tab(parent: ttk.Notebook) -> ttk.Frame:
9
10
  """Creates a new Settings tab object.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: OpenOrchestrator
3
- Version: 1.0.2
3
+ Version: 1.2.0
4
4
  Summary: A package containing OpenOrchestrator and OpenOrchestrator Scheduler
5
5
  Author-email: ITK Development <itk-rpa@mkb.aarhus.dk>
6
6
  Project-URL: Homepage, https://github.com/itk-dev-rpa/OpenOrchestrator
@@ -11,11 +11,11 @@ Classifier: Operating System :: Microsoft :: Windows
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: tkcalendar >=1.6.1
15
14
  Requires-Dist: cryptography >=41.0.3
16
15
  Requires-Dist: croniter >=1.4.1
17
16
  Requires-Dist: SQLAlchemy >=2.0.22
18
17
  Requires-Dist: pyodbc >=4.0.39
18
+ Requires-Dist: nicegui ==1.4.12
19
19
  Provides-Extra: dev
20
20
  Requires-Dist: pylint ; extra == 'dev'
21
21
 
@@ -0,0 +1,38 @@
1
+ OpenOrchestrator/__init__.py,sha256=i4Ir68mBu7rrqhlEE6Qh_uyble599jaEWCEMf8g58tI,179
2
+ OpenOrchestrator/__main__.py,sha256=Oe3SbWqw3zUhpQpspUaS6mMddXxVvvV-pJbuBU_1GgA,518
3
+ OpenOrchestrator/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ OpenOrchestrator/common/connection_frame.py,sha256=NuRwbEZviOExszhspbETvHcMi7pS4cgOnwQBvAqAZc4,2838
5
+ OpenOrchestrator/common/crypto_util.py,sha256=Yo3WvIJnWtBJvlbvkDRu-HaReUUdaW747PcamCJUHzw,2029
6
+ OpenOrchestrator/common/datetime_util.py,sha256=xNf_LORb67ohR3KfWE_owY15RqAOLJ8DfDPQlMBfsGc,527
7
+ OpenOrchestrator/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ OpenOrchestrator/database/constants.py,sha256=ik6lY6IR8EOyKEg-N70X-0U654i3nFb3u-XWhqbUbJU,2404
9
+ OpenOrchestrator/database/db_util.py,sha256=VViN_V7o2zhKJcxsv-jMQmwecvXRavWgLsBKQZYm61o,27568
10
+ OpenOrchestrator/database/logs.py,sha256=h2BztjmDRhj8TACYN3LK5c9hIqn5xCs9m_LDUdYYHEk,1623
11
+ OpenOrchestrator/database/queues.py,sha256=BFCbUUbo_OOCPNCzRRxDcgoxos_LI__Ypg7t9e3qdUk,2264
12
+ OpenOrchestrator/database/triggers.py,sha256=Qq1lsSNrOYBKXnKhaD5s71YVmviaTxiD8j8kMBc76sg,4004
13
+ OpenOrchestrator/orchestrator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ OpenOrchestrator/orchestrator/application.py,sha256=Sb0rMrlFsvwkXuTLvBLqTUNqFSYjvdrAz3keiQYiUvY,3224
15
+ OpenOrchestrator/orchestrator/datetime_input.py,sha256=1EEkmKxYOQSIE6e-anbjACgbo7SL4wyuhqerdiBIg5w,2571
16
+ OpenOrchestrator/orchestrator/popups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ OpenOrchestrator/orchestrator/popups/constant_popup.py,sha256=B6-xfEYRNNkf4dwbPgjGRuDqfKDL5pa6wUoZJ-A2l4g,3367
18
+ OpenOrchestrator/orchestrator/popups/credential_popup.py,sha256=dIDro77ogBCZEZ8jUXMkenpSBANIfYX-PqVgKvVVbvU,3839
19
+ OpenOrchestrator/orchestrator/popups/generic_popups.py,sha256=wV5isJrwFGUBpDhDpuOzzURwU4NTp4l5mzMDg-KebKw,1061
20
+ OpenOrchestrator/orchestrator/popups/trigger_popup.py,sha256=Ir8Hx-DHtvCu3qm5BkG0uSRTObM9G4fpk-VOfeajelA,9684
21
+ OpenOrchestrator/orchestrator/tabs/constants_tab.py,sha256=JcRIyntrzZBh5Ugkb0QrkBrjI-g9xl2VpbeKhdT6bFM,2478
22
+ OpenOrchestrator/orchestrator/tabs/logging_tab.py,sha256=7HdXAnAMDHGC6Au8FaQ58M8i88oalBBtBAvGvdiRD_0,3527
23
+ OpenOrchestrator/orchestrator/tabs/queue_tab.py,sha256=3JwrQUFr796uDmASDESjal1py18OT-Vq5K_VrnPlHCk,5169
24
+ OpenOrchestrator/orchestrator/tabs/settings_tab.py,sha256=xBMBdgrTxehmbuAk-H3QfEP_zdILzJ3sIgMCii7qlQg,845
25
+ OpenOrchestrator/orchestrator/tabs/trigger_tab.py,sha256=gRwLINuf4wQcFieNZL1grlc5qmPOqGRAI1-TS1za_5Q,4047
26
+ OpenOrchestrator/orchestrator_connection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ OpenOrchestrator/orchestrator_connection/connection.py,sha256=DU552EigOu2xzM3SjY4766BXrDEnDC2jC4Jjy0-9OrU,8706
28
+ OpenOrchestrator/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ OpenOrchestrator/scheduler/application.py,sha256=vjDzW8x0MwS9giFI6UOlhtFqUPHKbsRnrb6-HY8jWOI,1620
30
+ OpenOrchestrator/scheduler/connection_frame.py,sha256=I1Qooso_iqkNhvztQvFgjlC89Lvfqx0s5B6olATqa98,3369
31
+ OpenOrchestrator/scheduler/run_tab.py,sha256=QP-hoZEJAI5A-T7kV1lFCftpTm_e_R3T8I2Fg9BHxnA,5779
32
+ OpenOrchestrator/scheduler/runner.py,sha256=0lI-Xpv4Co617ZvTrRm9pCAZP1-aNg0P-Vy1c1oy7cM,8170
33
+ OpenOrchestrator/scheduler/settings_tab.py,sha256=AQxt5HxPn4sLfj-6GwyAQ8ffJ35D0PHLBVoi8W3tu2A,613
34
+ OpenOrchestrator-1.2.0.dist-info/LICENSE,sha256=4-Kjm-gkbiOLCBYMzsVJZEepdsm2vk8QesNOASvi9mg,1068
35
+ OpenOrchestrator-1.2.0.dist-info/METADATA,sha256=Y0hvXWs-UDhtwmJ4X9YTSGeZwqmxykiulkZleWTBwXw,1938
36
+ OpenOrchestrator-1.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
37
+ OpenOrchestrator-1.2.0.dist-info/top_level.txt,sha256=2btKMQESHuRC_ICbCjHTHH_-us2G7CyeskeaSTTL07Y,17
38
+ OpenOrchestrator-1.2.0.dist-info/RECORD,,