OpenOrchestrator 1.2.0__py3-none-any.whl → 1.3.1__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.
- OpenOrchestrator/common/crypto_util.py +21 -9
- OpenOrchestrator/common/datetime_util.py +1 -1
- OpenOrchestrator/database/db_util.py +167 -129
- OpenOrchestrator/database/queues.py +1 -0
- OpenOrchestrator/database/triggers.py +1 -0
- OpenOrchestrator/database/truncated_string.py +18 -0
- OpenOrchestrator/orchestrator/datetime_input.py +16 -2
- OpenOrchestrator/orchestrator/popups/constant_popup.py +8 -7
- OpenOrchestrator/orchestrator/popups/credential_popup.py +8 -7
- OpenOrchestrator/orchestrator/popups/trigger_popup.py +31 -19
- OpenOrchestrator/orchestrator/tabs/constants_tab.py +1 -1
- OpenOrchestrator/orchestrator/tabs/logging_tab.py +4 -3
- OpenOrchestrator/orchestrator/tabs/queue_tab.py +19 -11
- OpenOrchestrator/orchestrator/tabs/settings_tab.py +2 -2
- OpenOrchestrator/orchestrator/tabs/trigger_tab.py +4 -7
- OpenOrchestrator/orchestrator_connection/connection.py +11 -9
- OpenOrchestrator/scheduler/connection_frame.py +1 -1
- OpenOrchestrator/scheduler/runner.py +8 -7
- {OpenOrchestrator-1.2.0.dist-info → openorchestrator-1.3.1.dist-info}/METADATA +8 -8
- openorchestrator-1.3.1.dist-info/RECORD +39 -0
- {OpenOrchestrator-1.2.0.dist-info → openorchestrator-1.3.1.dist-info}/WHEEL +1 -1
- OpenOrchestrator-1.2.0.dist-info/RECORD +0 -38
- {OpenOrchestrator-1.2.0.dist-info → openorchestrator-1.3.1.dist-info}/LICENSE +0 -0
- {OpenOrchestrator-1.2.0.dist-info → openorchestrator-1.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
"""Module for truncating a string by removing the middle part."""
|
2
|
+
|
3
|
+
import math
|
4
|
+
|
5
|
+
|
6
|
+
def truncate_message(message: str, max_length: int = 8000) -> str:
|
7
|
+
"""Truncate a message to max_length characters
|
8
|
+
|
9
|
+
Args:
|
10
|
+
message: The string to truncate
|
11
|
+
max_length: Maximum allowed length, defaults to 8000
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
A string with length set to max_length
|
15
|
+
"""
|
16
|
+
if len(message) <= max_length:
|
17
|
+
return message
|
18
|
+
return message[:math.ceil(max_length/2)] + message[-math.floor(max_length/2):]
|
@@ -19,8 +19,9 @@ class DatetimeInput(ui.input):
|
|
19
19
|
on_change: A callable to execute on change. Defaults to None.
|
20
20
|
allow_empty: Whether to allow an empty input on validation. Defaults to False.
|
21
21
|
"""
|
22
|
-
super().__init__(label,
|
22
|
+
super().__init__(label, on_change=lambda: self._on_change(on_change))
|
23
23
|
self.props("clearable")
|
24
|
+
self.complete = False
|
24
25
|
|
25
26
|
# Define dialog
|
26
27
|
with ui.dialog() as self._dialog, ui.card():
|
@@ -38,6 +39,8 @@ class DatetimeInput(ui.input):
|
|
38
39
|
|
39
40
|
self._define_validation(allow_empty)
|
40
41
|
|
42
|
+
self.complete = True
|
43
|
+
|
41
44
|
def _define_validation(self, allow_empty: bool):
|
42
45
|
if not allow_empty:
|
43
46
|
self.validation = {
|
@@ -47,7 +50,7 @@ class DatetimeInput(ui.input):
|
|
47
50
|
|
48
51
|
else:
|
49
52
|
def validate(value: str):
|
50
|
-
if value
|
53
|
+
if not value:
|
51
54
|
return True
|
52
55
|
|
53
56
|
return self.get_datetime() is not None
|
@@ -73,3 +76,14 @@ class DatetimeInput(ui.input):
|
|
73
76
|
value: The new datetime value.
|
74
77
|
"""
|
75
78
|
self.value = value.strftime(self.PY_FORMAT)
|
79
|
+
|
80
|
+
def _on_change(self, func: Optional[Callable[..., Any]]) -> None:
|
81
|
+
"""Wrapper for the input's on_change function.
|
82
|
+
This avoids that the on_change function is called
|
83
|
+
before the element is fully initialized.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
func: The on_change function of the element.
|
87
|
+
"""
|
88
|
+
if self.complete and func:
|
89
|
+
func()
|
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|
16
16
|
# pylint: disable-next=too-few-public-methods
|
17
17
|
class ConstantPopup():
|
18
18
|
"""A popup for creating/updating queue triggers."""
|
19
|
-
def __init__(self, constant_tab: ConstantTab, constant: Constant = None):
|
19
|
+
def __init__(self, constant_tab: ConstantTab, constant: Constant | None = None):
|
20
20
|
"""Create a new popup.
|
21
21
|
If a constant is given it will be updated instead of creating a new constant.
|
22
22
|
|
@@ -41,9 +41,7 @@ class ConstantPopup():
|
|
41
41
|
ui.button("Delete", color='red', on_click=self._delete_constant)
|
42
42
|
|
43
43
|
self._define_validation()
|
44
|
-
|
45
|
-
if constant:
|
46
|
-
self._pre_populate()
|
44
|
+
self._pre_populate()
|
47
45
|
|
48
46
|
def _define_validation(self):
|
49
47
|
"""Define validation rules for input elements."""
|
@@ -52,9 +50,10 @@ class ConstantPopup():
|
|
52
50
|
|
53
51
|
def _pre_populate(self):
|
54
52
|
"""Pre populate the inputs with an existing constant."""
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
if self.constant:
|
54
|
+
self.name_input.value = self.constant.name
|
55
|
+
self.name_input.disable()
|
56
|
+
self.value_input.value = self.constant.value
|
58
57
|
|
59
58
|
def _create_constant(self):
|
60
59
|
"""Creates a new constant in the database using the data from the
|
@@ -89,6 +88,8 @@ class ConstantPopup():
|
|
89
88
|
self.constant_tab.update()
|
90
89
|
|
91
90
|
async def _delete_constant(self):
|
91
|
+
if not self.constant:
|
92
|
+
return
|
92
93
|
if await question_popup(f"Delete constant '{self.constant.name}?", "Delete", "Cancel", color1='red'):
|
93
94
|
db_util.delete_constant(self.constant.name)
|
94
95
|
self.dialog.close()
|
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|
16
16
|
# pylint: disable-next=too-few-public-methods
|
17
17
|
class CredentialPopup():
|
18
18
|
"""A popup for creating/updating queue triggers."""
|
19
|
-
def __init__(self, constant_tab: ConstantTab, credential: Credential = None):
|
19
|
+
def __init__(self, constant_tab: ConstantTab, credential: Credential | None = None):
|
20
20
|
"""Create a new popup.
|
21
21
|
If a credential is given it will be updated instead of creating a new credential.
|
22
22
|
|
@@ -43,9 +43,7 @@ class CredentialPopup():
|
|
43
43
|
ui.button("Delete", color='red', on_click=self._delete_credential)
|
44
44
|
|
45
45
|
self._define_validation()
|
46
|
-
|
47
|
-
if credential:
|
48
|
-
self._pre_populate()
|
46
|
+
self._pre_populate()
|
49
47
|
|
50
48
|
def _define_validation(self):
|
51
49
|
"""Define validation functions for ui elements."""
|
@@ -55,9 +53,10 @@ class CredentialPopup():
|
|
55
53
|
|
56
54
|
def _pre_populate(self):
|
57
55
|
"""Pre populate the inputs with an existing credential."""
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
if self.credential:
|
57
|
+
self.name_input.value = self.credential.name
|
58
|
+
self.name_input.disable()
|
59
|
+
self.username_input.value = self.credential.username
|
61
60
|
|
62
61
|
def _save_credential(self):
|
63
62
|
"""Create or update a credential in the database using the data from the UI."""
|
@@ -93,6 +92,8 @@ class CredentialPopup():
|
|
93
92
|
|
94
93
|
async def _delete_credential(self):
|
95
94
|
"""Delete the selected credential."""
|
95
|
+
if not self.credential:
|
96
|
+
return
|
96
97
|
if await question_popup(f"Delete credential '{self.credential.name}'?", "Delete", "Cancel", color1='red'):
|
97
98
|
db_util.delete_credential(self.credential.name)
|
98
99
|
self.dialog.close()
|
@@ -5,11 +5,11 @@ from typing import TYPE_CHECKING
|
|
5
5
|
from datetime import datetime
|
6
6
|
|
7
7
|
from nicegui import ui
|
8
|
-
from
|
8
|
+
from cronsim import CronSim, CronSimError
|
9
9
|
|
10
10
|
from OpenOrchestrator.orchestrator.datetime_input import DatetimeInput
|
11
11
|
from OpenOrchestrator.database import db_util
|
12
|
-
from OpenOrchestrator.database.triggers import Trigger, TriggerStatus, TriggerType
|
12
|
+
from OpenOrchestrator.database.triggers import Trigger, TriggerStatus, TriggerType, ScheduledTrigger, SingleTrigger, QueueTrigger
|
13
13
|
from OpenOrchestrator.orchestrator.popups import generic_popups
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|
19
19
|
# pylint: disable-next=(too-many-instance-attributes, too-few-public-methods)
|
20
20
|
class TriggerPopup():
|
21
21
|
"""A popup for creating/updating triggers."""
|
22
|
-
def __init__(self, trigger_tab: TriggerTab, trigger_type: TriggerType, trigger: Trigger = None):
|
22
|
+
def __init__(self, trigger_tab: TriggerTab, trigger_type: TriggerType, trigger: Trigger | None = None):
|
23
23
|
"""Create a new popup.
|
24
24
|
If a trigger is given it will be updated instead of creating a new trigger.
|
25
25
|
|
@@ -65,9 +65,7 @@ class TriggerPopup():
|
|
65
65
|
|
66
66
|
self._disable_unused()
|
67
67
|
self._define_validation()
|
68
|
-
|
69
|
-
if trigger:
|
70
|
-
self._pre_populate()
|
68
|
+
self._pre_populate()
|
71
69
|
|
72
70
|
def _define_validation(self):
|
73
71
|
self.trigger_input.validation = {"Please enter a trigger name": bool}
|
@@ -77,15 +75,18 @@ class TriggerPopup():
|
|
77
75
|
|
78
76
|
def validate_cron(value: str):
|
79
77
|
try:
|
80
|
-
|
78
|
+
CronSim(value, datetime.now())
|
81
79
|
return True
|
82
|
-
except
|
80
|
+
except CronSimError:
|
83
81
|
return False
|
84
82
|
|
85
83
|
self.cron_input.validation = {"Invalid cron expression": validate_cron}
|
86
84
|
|
87
85
|
def _pre_populate(self):
|
88
86
|
"""Populate the form with values from an existing trigger"""
|
87
|
+
if not self.trigger:
|
88
|
+
return
|
89
|
+
|
89
90
|
self.trigger_input.value = self.trigger.trigger_name
|
90
91
|
self.name_input.value = self.trigger.process_name
|
91
92
|
self.path_input.value = self.trigger.process_path
|
@@ -93,13 +94,13 @@ class TriggerPopup():
|
|
93
94
|
self.git_check.value = self.trigger.is_git_repo
|
94
95
|
self.blocking_check.value = self.trigger.is_blocking
|
95
96
|
|
96
|
-
if self.
|
97
|
+
if isinstance(self.trigger, ScheduledTrigger):
|
97
98
|
self.cron_input.value = self.trigger.cron_expr
|
98
99
|
|
99
|
-
if self.
|
100
|
+
if isinstance(self.trigger, (SingleTrigger, ScheduledTrigger)):
|
100
101
|
self.time_input.set_datetime(self.trigger.next_run)
|
101
102
|
|
102
|
-
if self.
|
103
|
+
if isinstance(self.trigger, QueueTrigger):
|
103
104
|
self.queue_input.value = self.trigger.queue_name
|
104
105
|
self.batch_input.value = self.trigger.min_batch_size
|
105
106
|
|
@@ -117,8 +118,8 @@ class TriggerPopup():
|
|
117
118
|
|
118
119
|
def _cron_change(self):
|
119
120
|
if self.cron_input.validate():
|
120
|
-
cron_iter =
|
121
|
-
self.time_input.set_datetime(
|
121
|
+
cron_iter = CronSim(self.cron_input.value, datetime.now())
|
122
|
+
self.time_input.set_datetime(next(cron_iter))
|
122
123
|
|
123
124
|
async def _validate(self) -> bool:
|
124
125
|
result = True
|
@@ -154,7 +155,7 @@ class TriggerPopup():
|
|
154
155
|
|
155
156
|
trigger_name = self.trigger_input.value
|
156
157
|
process_name = self.name_input.value
|
157
|
-
next_run = self.time_input.get_datetime()
|
158
|
+
next_run: datetime = self.time_input.get_datetime() # type: ignore
|
158
159
|
cron_expr = self.cron_input.value
|
159
160
|
queue_name = self.queue_input.value
|
160
161
|
min_batch_size = self.batch_input.value
|
@@ -177,18 +178,17 @@ class TriggerPopup():
|
|
177
178
|
# Update existing trigger
|
178
179
|
self.trigger.trigger_name = trigger_name
|
179
180
|
self.trigger.process_name = process_name
|
180
|
-
self.trigger.next_run = next_run
|
181
181
|
self.trigger.process_path = path
|
182
182
|
self.trigger.process_args = args
|
183
183
|
self.trigger.is_git_repo = is_git
|
184
184
|
self.trigger.is_blocking = is_blocking
|
185
185
|
|
186
|
-
if self.
|
186
|
+
if isinstance(self.trigger, SingleTrigger):
|
187
187
|
self.trigger.next_run = next_run
|
188
|
-
elif self.
|
188
|
+
elif isinstance(self.trigger, ScheduledTrigger):
|
189
189
|
self.trigger.cron_expr = cron_expr
|
190
190
|
self.trigger.next_run = next_run
|
191
|
-
elif self.
|
191
|
+
elif isinstance(self.trigger, QueueTrigger):
|
192
192
|
self.trigger.queue_name = queue_name
|
193
193
|
self.trigger.min_batch_size = min_batch_size
|
194
194
|
|
@@ -199,6 +199,9 @@ class TriggerPopup():
|
|
199
199
|
self.trigger_tab.update()
|
200
200
|
|
201
201
|
async def _delete_trigger(self):
|
202
|
+
if not self.trigger:
|
203
|
+
return
|
204
|
+
|
202
205
|
if await generic_popups.question_popup(f"Delete trigger '{self.trigger.trigger_name}'?", "Delete", "Cancel", color1='red'):
|
203
206
|
db_util.delete_trigger(self.trigger.id)
|
204
207
|
ui.notify("Trigger deleted", type='positive')
|
@@ -206,11 +209,20 @@ class TriggerPopup():
|
|
206
209
|
self.trigger_tab.update()
|
207
210
|
|
208
211
|
def _disable_trigger(self):
|
209
|
-
|
212
|
+
if not self.trigger:
|
213
|
+
return
|
214
|
+
|
215
|
+
if self.trigger.process_status == TriggerStatus.RUNNING:
|
216
|
+
db_util.set_trigger_status(self.trigger.id, TriggerStatus.PAUSING)
|
217
|
+
else:
|
218
|
+
db_util.set_trigger_status(self.trigger.id, TriggerStatus.PAUSED)
|
210
219
|
ui.notify("Trigger status set to 'Paused'.", type='positive')
|
211
220
|
self.trigger_tab.update()
|
212
221
|
|
213
222
|
def _enable_trigger(self):
|
223
|
+
if not self.trigger:
|
224
|
+
return
|
225
|
+
|
214
226
|
db_util.set_trigger_status(self.trigger.id, TriggerStatus.IDLE)
|
215
227
|
ui.notify("Trigger status set to 'Idle'.", type='positive')
|
216
228
|
self.trigger_tab.update()
|
@@ -38,7 +38,7 @@ class ConstantTab():
|
|
38
38
|
"""Callback for when a row is clicked in the table."""
|
39
39
|
row = event.args[1]
|
40
40
|
name = row['Credential Name']
|
41
|
-
credential = db_util.get_credential(name)
|
41
|
+
credential = db_util.get_credential(name, False)
|
42
42
|
CredentialPopup(self, credential)
|
43
43
|
|
44
44
|
def update(self):
|
@@ -4,16 +4,17 @@ in Orchestrator."""
|
|
4
4
|
from nicegui import ui
|
5
5
|
|
6
6
|
from OpenOrchestrator.database import db_util
|
7
|
+
from OpenOrchestrator.database.logs import LogLevel
|
7
8
|
from OpenOrchestrator.orchestrator.datetime_input import DatetimeInput
|
8
9
|
|
9
10
|
|
10
|
-
COLUMNS =
|
11
|
+
COLUMNS = [
|
11
12
|
{'name': "Log Time", 'label': "Log Time", 'field': "Log Time", 'align': 'left', 'sortable': True},
|
12
13
|
{'name': "Process Name", 'label': "Process Name", 'field': "Process Name", 'align': 'left'},
|
13
14
|
{'name': "Level", 'label': "Level", 'field': "Level", 'align': 'left'},
|
14
15
|
{'name': "Message", 'label': "Message", 'field': "Message", 'align': 'left', ':format': 'value => value.length < 100 ? value : value.substring(0, 100)+"..."'},
|
15
16
|
{'name': "ID", 'label': "ID", 'field': "ID", 'headerClasses': 'hidden', 'classes': 'hidden'}
|
16
|
-
|
17
|
+
]
|
17
18
|
|
18
19
|
|
19
20
|
# pylint: disable-next=too-few-public-methods
|
@@ -41,7 +42,7 @@ class LoggingTab():
|
|
41
42
|
from_date = self.from_input.get_datetime()
|
42
43
|
to_date = self.to_input.get_datetime()
|
43
44
|
process_name = self.process_input.value if self.process_input.value != 'All' else None
|
44
|
-
level = self.level_input.value if self.level_input.value != "All" else None
|
45
|
+
level = LogLevel(self.level_input.value) if self.level_input.value != "All" else None
|
45
46
|
limit = self.limit_input.value
|
46
47
|
|
47
48
|
logs = db_util.get_logs(0, limit=limit, from_date=from_date, to_date=to_date, log_level=level, process_name=process_name)
|
@@ -5,17 +5,19 @@ from nicegui import ui
|
|
5
5
|
|
6
6
|
from OpenOrchestrator.database import db_util
|
7
7
|
from OpenOrchestrator.database.queues import QueueStatus
|
8
|
+
from OpenOrchestrator.orchestrator.datetime_input import DatetimeInput
|
8
9
|
|
9
10
|
|
10
|
-
QUEUE_COLUMNS =
|
11
|
+
QUEUE_COLUMNS = [
|
11
12
|
{'name': "Queue Name", 'label': "Queue Name", 'field': "Queue Name", 'align': 'left', 'sortable': True},
|
12
13
|
{'name': "New", 'label': "New", 'field': "New", 'align': 'left', 'sortable': True},
|
13
14
|
{'name': "In Progress", 'label': "In Progress", 'field': "In Progress", 'align': 'left', 'sortable': True},
|
14
15
|
{'name': "Done", 'label': "Done", 'field': "Done", 'align': 'left', 'sortable': True},
|
15
|
-
{'name': "Failed", 'label': "Failed", 'field': "Failed", 'align': 'left', 'sortable': True}
|
16
|
-
|
16
|
+
{'name': "Failed", 'label': "Failed", 'field': "Failed", 'align': 'left', 'sortable': True},
|
17
|
+
{'name': "Abandoned", 'label': "Abandoned", 'field': "Abandoned", 'align': 'left', 'sortable': True}
|
18
|
+
]
|
17
19
|
|
18
|
-
ELEMENT_COLUMNS =
|
20
|
+
ELEMENT_COLUMNS = [
|
19
21
|
{'name': "Reference", 'label': "Reference", 'field': "Reference", 'align': 'left', 'sortable': True},
|
20
22
|
{'name': "Status", 'label': "Status", 'field': "Status", 'align': 'left', 'sortable': True},
|
21
23
|
{'name': "Data", 'label': "Data", 'field': "Data", 'align': 'left', 'sortable': True},
|
@@ -25,7 +27,7 @@ ELEMENT_COLUMNS = (
|
|
25
27
|
{'name': "End Date", 'label': "End Date", 'field': "End Date", 'align': 'left', 'sortable': True},
|
26
28
|
{'name': "Created By", 'label': "Created By", 'field': "Created By", 'align': 'left', 'sortable': True},
|
27
29
|
{'name': "ID", 'label': "ID", 'field': "ID", 'align': 'left', 'sortable': True}
|
28
|
-
|
30
|
+
]
|
29
31
|
|
30
32
|
|
31
33
|
# pylint: disable-next=too-few-public-methods
|
@@ -33,7 +35,7 @@ class QueueTab():
|
|
33
35
|
"""The 'Queues' tab object. It contains tables and buttons for dealing with queues."""
|
34
36
|
def __init__(self, tab_name: str) -> None:
|
35
37
|
with ui.tab_panel(tab_name):
|
36
|
-
self.queue_table = ui.table(title="Queues", columns=QUEUE_COLUMNS, rows=[], row_key='Queue Name', pagination=50).classes("w-full")
|
38
|
+
self.queue_table = ui.table(title="Queues", columns=QUEUE_COLUMNS, rows=[], row_key='Queue Name', pagination={'rowsPerPage': 50, 'sortBy': 'Queue Name'}).classes("w-full")
|
37
39
|
self.queue_table.on("rowClick", self._row_click)
|
38
40
|
|
39
41
|
def update(self):
|
@@ -48,7 +50,8 @@ class QueueTab():
|
|
48
50
|
"New": count.get(QueueStatus.NEW, 0),
|
49
51
|
"In Progress": count.get(QueueStatus.IN_PROGRESS, 0),
|
50
52
|
"Done": count.get(QueueStatus.DONE, 0),
|
51
|
-
"Failed": count.get(QueueStatus.FAILED, 0)
|
53
|
+
"Failed": count.get(QueueStatus.FAILED, 0),
|
54
|
+
"Abandoned": count.get(QueueStatus.ABANDONED, 0),
|
52
55
|
}
|
53
56
|
rows.append(row)
|
54
57
|
|
@@ -68,7 +71,8 @@ class QueuePopup():
|
|
68
71
|
|
69
72
|
with ui.dialog(value=True).props('full-width full-height') as dialog, ui.card():
|
70
73
|
with ui.row().classes("w-full"):
|
71
|
-
self.
|
74
|
+
self.from_input = DatetimeInput("From Date", on_change=self._update, allow_empty=True).style('margin-left: 1rem')
|
75
|
+
self.to_input = DatetimeInput("To Date", on_change=self._update, allow_empty=True)
|
72
76
|
|
73
77
|
self.limit_select = ui.select(
|
74
78
|
options=[100, 200, 500, 1000, "All"],
|
@@ -76,9 +80,10 @@ class QueuePopup():
|
|
76
80
|
value=100,
|
77
81
|
on_change=self._update).classes("w-24")
|
78
82
|
|
79
|
-
ui.switch("Dense", on_change=lambda e: self._dense_table(e.value))
|
80
|
-
|
81
83
|
ui.space()
|
84
|
+
|
85
|
+
ui.switch("Dense", on_change=lambda e: self._dense_table(e.value))
|
86
|
+
self._create_column_filter()
|
82
87
|
ui.button(icon='refresh', on_click=self._update)
|
83
88
|
ui.button(icon="close", on_click=dialog.close)
|
84
89
|
with ui.scroll_area().classes("h-full"):
|
@@ -111,6 +116,9 @@ class QueuePopup():
|
|
111
116
|
if limit == 'All':
|
112
117
|
limit = 1_000_000_000
|
113
118
|
|
114
|
-
|
119
|
+
from_date = self.from_input.get_datetime()
|
120
|
+
to_date = self.to_input.get_datetime()
|
121
|
+
|
122
|
+
queue_elements = db_util.get_queue_elements(self.queue_name, limit=limit, from_date=from_date, to_date=to_date)
|
115
123
|
rows = [element.to_row_dict() for element in queue_elements]
|
116
124
|
self.table.update_rows(rows)
|
@@ -10,8 +10,8 @@ from OpenOrchestrator.common.connection_frame import ConnectionFrame
|
|
10
10
|
# pylint: disable-next=too-few-public-methods
|
11
11
|
class SettingsTab():
|
12
12
|
"""The settings tab object for Orchestrator."""
|
13
|
-
def __init__(self,
|
14
|
-
with ui.tab_panel(
|
13
|
+
def __init__(self, tab_name: str) -> None:
|
14
|
+
with ui.tab_panel(tab_name):
|
15
15
|
conn_frame = ConnectionFrame()
|
16
16
|
with ui.row().classes("w-full"):
|
17
17
|
self.key_button = ui.button("Generate Key", on_click=conn_frame.new_key)
|
@@ -7,9 +7,7 @@ from OpenOrchestrator.database import db_util
|
|
7
7
|
from OpenOrchestrator.database.triggers import SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerType
|
8
8
|
from OpenOrchestrator.orchestrator.popups.trigger_popup import TriggerPopup
|
9
9
|
|
10
|
-
COLUMNS =
|
11
|
-
|
12
|
-
COLUMNS = (
|
10
|
+
COLUMNS = [
|
13
11
|
{'name': "Trigger Name", 'label': "Trigger Name", 'field': "Trigger Name", 'align': 'left', 'sortable': True},
|
14
12
|
{'name': "Type", 'label': "Type", 'field': "Type", 'align': 'left', 'sortable': True},
|
15
13
|
{'name': "Status", 'label': "Status", 'field': "Status", 'align': 'left', 'sortable': True},
|
@@ -17,7 +15,7 @@ COLUMNS = (
|
|
17
15
|
{'name': "Last Run", 'label': "Last Run", 'field': "Last Run", 'align': 'left', 'sortable': True},
|
18
16
|
{'name': "Next_Run", 'label': "Next Run", 'field': "Next Run", 'align': 'left', 'sortable': True},
|
19
17
|
{'name': "ID", 'label': "ID", 'field': "ID", 'align': 'left', 'sortable': True}
|
20
|
-
|
18
|
+
]
|
21
19
|
|
22
20
|
|
23
21
|
# pylint disable-next=too-few-public-methods
|
@@ -30,9 +28,8 @@ class TriggerTab():
|
|
30
28
|
ui.button("New Scheduled Trigger", icon="add", on_click=lambda e: TriggerPopup(self, TriggerType.SCHEDULED))
|
31
29
|
ui.button("New Queue Trigger", icon="add", on_click=lambda e: TriggerPopup(self, TriggerType.QUEUE))
|
32
30
|
|
33
|
-
self.trigger_table = ui.table(COLUMNS, [], title="Triggers", pagination=50, row_key='ID').classes("w-full")
|
31
|
+
self.trigger_table = ui.table(COLUMNS, [], title="Triggers", pagination={'rowsPerPage': 50, 'sortBy': 'Trigger Name'}, row_key='ID').classes("w-full")
|
34
32
|
self.trigger_table.on('rowClick', self._row_click)
|
35
|
-
|
36
33
|
self.add_column_colors()
|
37
34
|
|
38
35
|
def _row_click(self, event):
|
@@ -61,7 +58,7 @@ class TriggerTab():
|
|
61
58
|
"body-cell-Status",
|
62
59
|
'''
|
63
60
|
<q-td key="Status" :props="props">
|
64
|
-
<q-badge v-if="{Running: 'green', Failed: 'red'}[props.value]" :color="{Running: 'green', Failed: 'red'}[props.value]">
|
61
|
+
<q-badge v-if="{Running: 'green', Pausing: 'orange', Failed: 'red'}[props.value]" :color="{Running: 'green', Pausing: 'orange', Failed: 'red'}[props.value]">
|
65
62
|
{{props.value}}
|
66
63
|
</q-badge>
|
67
64
|
<p v-else>
|
@@ -3,6 +3,8 @@ The easiest way to create an OrchestratorConnection object is to call the
|
|
3
3
|
class method create_connection_from_args."""
|
4
4
|
|
5
5
|
import sys
|
6
|
+
from datetime import datetime
|
7
|
+
|
6
8
|
from OpenOrchestrator.common import crypto_util
|
7
9
|
from OpenOrchestrator.database import db_util
|
8
10
|
from OpenOrchestrator.database.queues import QueueElement, QueueStatus
|
@@ -105,7 +107,7 @@ class OrchestratorConnection:
|
|
105
107
|
"""
|
106
108
|
db_util.update_credential(credential_name, new_username, new_password)
|
107
109
|
|
108
|
-
def create_queue_element(self, queue_name: str, reference: str = None, data: str = None, created_by: str = None) -> QueueElement:
|
110
|
+
def create_queue_element(self, queue_name: str, reference: str | None = None, data: str | None = None, created_by: str | None = None) -> QueueElement:
|
109
111
|
"""Adds a queue element to the given queue.
|
110
112
|
|
111
113
|
Args:
|
@@ -119,8 +121,8 @@ class OrchestratorConnection:
|
|
119
121
|
"""
|
120
122
|
return db_util.create_queue_element(queue_name, reference, data, created_by)
|
121
123
|
|
122
|
-
def bulk_create_queue_elements(self, queue_name: str, references: tuple[str], data: tuple[str],
|
123
|
-
created_by: str = None) -> None:
|
124
|
+
def bulk_create_queue_elements(self, queue_name: str, references: tuple[str | None, ...], data: tuple[str | None, ...],
|
125
|
+
created_by: str | None = None) -> None:
|
124
126
|
"""Insert multiple queue elements into a queue in an optimized manner.
|
125
127
|
The lengths of both 'references' and 'data' must be equal to the number of elements to insert.
|
126
128
|
|
@@ -135,7 +137,7 @@ class OrchestratorConnection:
|
|
135
137
|
"""
|
136
138
|
db_util.bulk_create_queue_elements(queue_name, references, data, created_by)
|
137
139
|
|
138
|
-
def get_next_queue_element(self, queue_name: str, reference: str = None,
|
140
|
+
def get_next_queue_element(self, queue_name: str, reference: str | None = None,
|
139
141
|
set_status: bool = True) -> QueueElement | None:
|
140
142
|
"""Gets the next queue element from the given queue that has the status 'new'.
|
141
143
|
|
@@ -150,8 +152,8 @@ class OrchestratorConnection:
|
|
150
152
|
"""
|
151
153
|
return db_util.get_next_queue_element(queue_name, reference, set_status)
|
152
154
|
|
153
|
-
def get_queue_elements(self, queue_name: str, reference: str = None, status: QueueStatus = None,
|
154
|
-
offset: int = 0, limit: int = 100) -> tuple[QueueElement]:
|
155
|
+
def get_queue_elements(self, queue_name: str, reference: str | None = None, status: QueueStatus | None = None,
|
156
|
+
offset: int = 0, limit: int = 100, from_date: datetime | None = None, to_date: datetime | None = None) -> tuple[QueueElement, ...]:
|
155
157
|
"""Get multiple queue elements from a queue. The elements are ordered by created_date.
|
156
158
|
|
157
159
|
Args:
|
@@ -164,12 +166,12 @@ class OrchestratorConnection:
|
|
164
166
|
Returns:
|
165
167
|
tuple[QueueElement]: A tuple of queue elements.
|
166
168
|
"""
|
167
|
-
return db_util.get_queue_elements(queue_name, reference, status, offset, limit)
|
169
|
+
return db_util.get_queue_elements(queue_name, reference, status, from_date, to_date, offset, limit)
|
168
170
|
|
169
|
-
def set_queue_element_status(self, element_id: str, status: QueueStatus, message: str = None) -> None:
|
171
|
+
def set_queue_element_status(self, element_id: str, status: QueueStatus, message: str | None = None) -> None:
|
170
172
|
"""Set the status of a queue element.
|
171
173
|
If the new status is 'in progress' the start date is noted.
|
172
|
-
If the new status is 'Done' or '
|
174
|
+
If the new status is 'Done', 'Failed' or 'Abandoned' the end date is noted.
|
173
175
|
|
174
176
|
Args:
|
175
177
|
element_id: The id of the queue element to change status on.
|
@@ -16,7 +16,7 @@ class Job():
|
|
16
16
|
"""An object that holds information about a running job."""
|
17
17
|
process: subprocess.Popen
|
18
18
|
trigger: Trigger
|
19
|
-
process_folder: str
|
19
|
+
process_folder: str | None
|
20
20
|
|
21
21
|
|
22
22
|
def poll_triggers(app) -> Job | None:
|
@@ -180,7 +180,7 @@ def end_job(job: Job) -> None:
|
|
180
180
|
"""Mark a job as ended in the triggers table
|
181
181
|
in the database.
|
182
182
|
If it's a single trigger it's marked as 'Done'
|
183
|
-
else it's marked as 'Idle'.
|
183
|
+
else it's marked as 'Idle' or 'Paused'.
|
184
184
|
|
185
185
|
Args:
|
186
186
|
job: The job whose trigger to mark as ended.
|
@@ -188,11 +188,12 @@ def end_job(job: Job) -> None:
|
|
188
188
|
if isinstance(job.trigger, SingleTrigger):
|
189
189
|
db_util.set_trigger_status(job.trigger.id, TriggerStatus.DONE)
|
190
190
|
|
191
|
-
elif isinstance(job.trigger, ScheduledTrigger):
|
192
|
-
db_util.
|
193
|
-
|
194
|
-
|
195
|
-
|
191
|
+
elif isinstance(job.trigger, (ScheduledTrigger, QueueTrigger)):
|
192
|
+
current_status = db_util.get_trigger(job.trigger.id).process_status
|
193
|
+
if current_status == TriggerStatus.PAUSING:
|
194
|
+
db_util.set_trigger_status(job.trigger.id, TriggerStatus.PAUSED)
|
195
|
+
elif current_status == TriggerStatus.RUNNING:
|
196
|
+
db_util.set_trigger_status(job.trigger.id, TriggerStatus.IDLE)
|
196
197
|
|
197
198
|
if job.process_folder:
|
198
199
|
clear_folder(job.process_folder)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: OpenOrchestrator
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.3.1
|
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,13 +11,13 @@ 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: cryptography
|
15
|
-
Requires-Dist:
|
16
|
-
Requires-Dist: SQLAlchemy
|
17
|
-
Requires-Dist: pyodbc
|
18
|
-
Requires-Dist: nicegui
|
14
|
+
Requires-Dist: cryptography>=41.0.3
|
15
|
+
Requires-Dist: cronsim>=2.6
|
16
|
+
Requires-Dist: SQLAlchemy>=2.0.22
|
17
|
+
Requires-Dist: pyodbc>=4.0.39
|
18
|
+
Requires-Dist: nicegui==1.4.12
|
19
19
|
Provides-Extra: dev
|
20
|
-
Requires-Dist: pylint
|
20
|
+
Requires-Dist: pylint; extra == "dev"
|
21
21
|
|
22
22
|
# OpenOrchestrator
|
23
23
|
|