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
@@ -1,169 +0,0 @@
1
- """This module is responsible for the layout and functionality of the Constants tab
2
- in Orchestrator."""
3
-
4
- import tkinter
5
- from tkinter import ttk, messagebox
6
-
7
- from OpenOrchestrator.database import db_util
8
- from OpenOrchestrator.orchestrator import table_util
9
- from OpenOrchestrator.orchestrator.popups import constant_popup, credential_popup
10
-
11
- def create_tab(parent: ttk.Notebook) -> ttk.Frame:
12
- """Create a new Constants tab object.
13
-
14
- Args:
15
- parent: The ttk.Notebook object that this tab is a child of.
16
-
17
- Returns:
18
- ttk.Frame: The created tab object as a ttk.Frame.
19
- """
20
- tab = ttk.Frame(parent)
21
- tab.pack(fill='both', expand=True)
22
-
23
- tab.columnconfigure(0, weight=1)
24
- tab.rowconfigure((0,2), weight=1, uniform='a')
25
- tab.rowconfigure((1,3), weight=6, uniform='a')
26
- tab.rowconfigure((4, 5), weight=1, uniform='a')
27
-
28
- #Constants table
29
- ttk.Label(tab, text="Constants").grid(row=0, column=0)
30
- const_table_frame = ttk.Frame(tab)
31
- const_table_frame.grid(row=1, column=0, sticky='nsew')
32
- const_table = table_util.create_table(const_table_frame, ('Name', 'Value', 'Changed at'))
33
-
34
- #Credentials table
35
- ttk.Label(tab, text="Credentials").grid(row=2, column=0)
36
- cred_table_frame = ttk.Frame(tab)
37
- cred_table_frame.grid(row=3, column=0, sticky='nsew')
38
- cred_table = table_util.create_table(cred_table_frame, ('Name', 'Username', 'Password', 'Changed at'))
39
-
40
- # Controls 1
41
- controls_frame = ttk.Frame(tab)
42
- controls_frame.grid(row=4, column=0)
43
- def update_command():
44
- update_tables(const_table, cred_table)
45
-
46
- update_button = ttk.Button(controls_frame, text='Update', command=update_command)
47
- update_button.pack(side='left')
48
-
49
- delete_button = ttk.Button(controls_frame, text='Delete', command=lambda: delete_selected(const_table, cred_table))
50
- delete_button.pack(side='left')
51
-
52
- # Controls 2
53
- controls_frame2 = ttk.Frame(tab)
54
- controls_frame2.grid(row=5, column=0)
55
-
56
- ttk.Button(controls_frame2, text='New constant', command=lambda: show_constant_popup(update_command)).pack(side='left')
57
- ttk.Button(controls_frame2, text='New credential', command=lambda: show_credential_popup(update_command)).pack(side='left')
58
-
59
- # Bindings
60
- const_table.bind('<FocusIn>', lambda e: table_util.deselect_tables(cred_table))
61
- const_table.bind('<Double-1>', lambda e: double_click_constant_table(e, const_table, update_command))
62
-
63
- cred_table.bind('<FocusIn>', lambda e: table_util.deselect_tables(const_table))
64
- cred_table.bind('<Double-1>', lambda e: double_click_credential_table(e, cred_table, update_command))
65
-
66
- tab.bind_all('<Delete>', lambda e: delete_selected(const_table, cred_table))
67
-
68
- return tab
69
-
70
-
71
- def update_tables(const_table: ttk.Treeview, cred_table: ttk.Treeview) -> None:
72
- """Update the constant and credential tables with
73
- new values from the database.
74
-
75
- Args:
76
- const_table: The constants table object.
77
- cred_table: The credentials table object.
78
- """
79
-
80
- # Convert ORM objects to lists of values
81
- const_list = [c.to_tuple() for c in db_util.get_constants()]
82
- cred_list = [c.to_tuple() for c in db_util.get_credentials()]
83
-
84
- table_util.update_table(const_table, const_list)
85
- table_util.update_table(cred_table, cred_list)
86
-
87
-
88
- def delete_selected(const_table: ttk.Treeview, cred_table: ttk.Treeview) -> None:
89
- """Deletes the currently selected constant or credential
90
- from the database. Updates the tables afterwards.
91
-
92
- Args:
93
- const_table: The constants table object.
94
- cred_table: The credentials table object.
95
- """
96
- if const_table.selection():
97
- name = const_table.item(const_table.selection()[0], 'values')[0]
98
- if not messagebox.askyesno('Delete constant?', f"Are you sure you want to delete constant '{name}'?"):
99
- return
100
- db_util.delete_constant(name)
101
-
102
- elif cred_table.selection():
103
- name = cred_table.item(cred_table.selection()[0], 'values')[0]
104
- if not messagebox.askyesno('Delete credential?', f"Are you sure you want to delete credential '{name}'?"):
105
- return
106
- db_util.delete_credential(name)
107
-
108
- else:
109
- return
110
-
111
- update_tables(const_table, cred_table)
112
-
113
-
114
- def show_constant_popup(on_close: callable, name: str=None, value: str=None) -> None:
115
- """Shows the new constant popup.
116
- Optionally populates the entry widgets in the popup with 'name' and 'value'.
117
- Binds a callable to the popup's on_close event.
118
-
119
- Args:
120
- on_close: A function to be called when the popup closes.
121
- name (optional): A value to pre-populate the name entry widget with. Defaults to None.
122
- value (optional): A value to pre-populate the value entry widget with. Defaults to None.
123
- """
124
- popup = constant_popup.show_popup(name, value)
125
- popup.bind('<Destroy>', lambda e: on_close() if e.widget == popup else ...)
126
-
127
-
128
- def double_click_constant_table(event: tkinter.Event, const_table:ttk.Treeview, on_close:callable) -> None:
129
- """This function is called whenever the constants table is double clicked.
130
- It opens the new constant popup with pre-populated values.
131
-
132
- Args:
133
- event: The event of the double click.
134
- const_table: The constants table.
135
- on_close: A function to be called when the popup is closed.
136
- """
137
- row = const_table.identify_row(event.y)
138
- if row:
139
- name, value, *_ = const_table.item(row, 'values')
140
- show_constant_popup(on_close, name, value)
141
-
142
-
143
- def double_click_credential_table(event: tkinter.Event, cred_table: ttk.Treeview, on_close: callable) -> None:
144
- """This function is called whenever the credentials table is double clicked.
145
- It opens the new credential popup with pre-populated values.
146
-
147
- Args:
148
- event: The event of the double click.
149
- cred_table: The credentials table.
150
- on_close: A function to be called when the popup is closed.
151
- """
152
- row = cred_table.identify_row(event.y)
153
- if row:
154
- name, value, *_ = cred_table.item(row, 'values')
155
- show_credential_popup(on_close, name, value)
156
-
157
-
158
- def show_credential_popup(on_close: callable, name: str=None, username: str=None) -> None:
159
- """Shows the new credential popup.
160
- Optionally populates the entry widgets in the popup with 'name' and 'username'.
161
- Binds a callable to the popup's on_close event.
162
-
163
- Args:
164
- on_close: A function to be called when the popup closes.
165
- name (optional): A value to pre-populate the name entry widget with. Defaults to None.
166
- username (optional): A value to pre-populate the username entry widget with. Defaults to None.
167
- """
168
- popup = credential_popup.show_popup(name, username)
169
- popup.bind('<Destroy>', lambda e: on_close() if e.widget == popup else ...)
@@ -1,221 +0,0 @@
1
- """This module is responsible for the layout and functionality of the Logging tab
2
- in Orchestrator."""
3
-
4
- import tkinter
5
- from tkinter import ttk, font, messagebox
6
- from datetime import datetime
7
- from ast import literal_eval
8
-
9
- from OpenOrchestrator.database import db_util
10
- from OpenOrchestrator.orchestrator import table_util
11
-
12
- def create_tab(parent):
13
- """Create a new Logging tab object.
14
-
15
- Args:
16
- parent: The ttk.Notebook object that this tab is a child of.
17
-
18
- Returns:
19
- ttk.Frame: The created tab object as a ttk.Frame.
20
- """
21
- tab = ttk.Frame(parent)
22
- tab.pack(fill='both', expand=True)
23
-
24
- tab.columnconfigure(0, weight=1)
25
- tab.rowconfigure(0, weight=2, uniform='a')
26
- tab.rowconfigure(1, weight=1, uniform='a')
27
-
28
- #Table
29
- table_frame = ttk.Frame(tab)
30
- table_frame.grid(row=0, column=0, sticky="nsew")
31
-
32
- table = table_util.create_table(table_frame, ("Time", "Level", "Process", "Message"))
33
- table.column("Time", width=120, stretch=False)
34
- table.column("Level", width=50, stretch=False)
35
- table.column("Process", width=150, stretch=False)
36
- table.bind("<Double-1>", lambda e: double_click_log(table))
37
-
38
- # Filters
39
- filter_frame = ttk.Frame(tab)
40
- filter_frame.grid(row=1, column=0, sticky='nsew')
41
-
42
- # Date filters
43
- ttk.Label(filter_frame, text="Date from:").grid(row=0, column=0, sticky='w')
44
- from_date_entry = ttk.Entry(filter_frame, width=21, validate='key')
45
- reg = tab.register(validate_date(from_date_entry))
46
- from_date_entry.configure(validatecommand=(reg, '%P'))
47
- from_date_entry.insert(0, 'dd-mm-yyyy hh:mm:ss')
48
- from_date_entry.grid(row=0, column=1)
49
-
50
- ttk.Label(filter_frame, text='Date to:').grid(row=0, column=2)
51
- to_date_entry = ttk.Entry(filter_frame, width=21, validate='key')
52
- reg = tab.register(validate_date(to_date_entry))
53
- to_date_entry.configure(validatecommand=(reg, '%P'))
54
- to_date_entry.insert(0, 'dd-mm-yyyy hh:mm:ss')
55
- to_date_entry.grid(row=0, column=3)
56
-
57
- # Process filter
58
- ttk.Label(filter_frame, text="Process name:").grid(row=1, column=0)
59
- ttk.Style().configure("TMenubutton", background='white')
60
- process_options_var = tkinter.StringVar()
61
- process_options = ttk.OptionMenu(filter_frame, process_options_var, "", *("", "hej1", "med2", "dig3","hej4", "med5", "dig6"))
62
- process_options['menu'].delete(0, 'end')
63
- process_options.grid(row=1, column=1, columnspan=3, sticky='ew', pady=2)
64
-
65
- # Level filter
66
- ttk.Label(filter_frame, text="Log level:").grid(row=2, column=0)
67
- log_level = tkinter.StringVar()
68
- ttk.OptionMenu(filter_frame, log_level, "", *("", "Trace", "Info", "Error")).grid(row=2, column=1, sticky='ew', pady=2)
69
-
70
- #Buttons
71
- def update_command():
72
- update(table, from_date_entry, to_date_entry, process_options, process_options_var, log_level)
73
- update_button = ttk.Button(filter_frame, text="Update", command=update_command)
74
- update_button.grid(row=4, column=0)
75
-
76
- return tab
77
-
78
-
79
- def validate_date(entry: ttk.Entry) -> callable:
80
- """Creates a validator function to validate if
81
- an datetime entered in the given entry is valid.
82
- Changes the color of the Entry widget according
83
- to the validity of the datetime.
84
-
85
- Args:
86
- entry: The entry widget to validate on.
87
-
88
- Returns:
89
- callable: The validator function.
90
- """
91
- def inner(text: str):
92
- if parse_date(text) is not None:
93
- entry.configure(foreground='black')
94
- else:
95
- entry.configure(foreground='red')
96
- return True
97
- return inner
98
-
99
-
100
- def resize_table(table: ttk.Treeview) -> None:
101
- """Resizes the 'Message' column of the table to match the longest string
102
- in the column.
103
-
104
- Args:
105
- table (ttk.Treeview): The table object.
106
- """
107
- max_length = 0
108
- default_font = font.nametofont("TkDefaultFont")
109
-
110
- for row in table.get_children():
111
- item = table.item(row)
112
- message = item['values'][-1]
113
- max_length = max(max_length, default_font.measure(message))
114
-
115
- table.column("Message", width=max_length+20, stretch=False)
116
-
117
-
118
- def update(table: ttk.Treeview, from_date_entry: ttk.Entry, to_date_entry: ttk.Entry,
119
- process_options:ttk.OptionMenu, process_options_var:tkinter.StringVar,
120
- log_level: tkinter.StringVar) -> None:
121
- """Updates the logs table with new values from the database
122
- using the filter options given in the UI.
123
- Updates the filter option menus with values from the database.
124
-
125
- Args:
126
- table: The logs table to update.
127
- from_date_entry: The entry with the from date.
128
- to_date_entry: The entry with the to date.
129
- process_options: The options menu with process names.
130
- process_options_var: The StringVar connected to process_options.
131
- log_level: The log level to filter on.
132
- """
133
- offset = 0
134
- limit = 100
135
-
136
- from_date = parse_date(from_date_entry.get()) or datetime(1900, 1, 1)
137
- to_date = parse_date(to_date_entry.get()) or datetime(2100, 12, 31)
138
-
139
- process_name = process_options_var.get()
140
- log_level = log_level.get()
141
-
142
- logs = db_util.get_logs(offset, limit, from_date, to_date, process_name, log_level)
143
-
144
- # Convert log objects to lists of strings
145
- logs_list = [
146
- [
147
- l.log_time.strftime('%d-%m-%Y %H:%M:%S'),
148
- l.log_level.value,
149
- l.process_name,
150
- repr(l.log_message) # Convert message to single line text
151
- ]
152
- for l in logs
153
- ]
154
-
155
- table_util.update_table(table, logs_list)
156
- resize_table(table)
157
-
158
- # Update process_name OptionMenu
159
- process_names = list(db_util.get_unique_log_process_names())
160
- process_names.insert(0, "")
161
- replace_options(process_options, process_options_var, process_names)
162
-
163
-
164
- def parse_date(date_str: str) -> datetime | None:
165
- """Tries to parse a string to a datetime object with
166
- a selection of different formats.
167
-
168
- Args:
169
- date_str: The string to parse.
170
-
171
- Returns:
172
- datetime: The parsed datetime if possible else None.
173
- """
174
- formats = (
175
- '%d-%m-%Y %H:%M:%S',
176
- '%d-%m-%Y %H:%M',
177
- '%d-%m-%Y'
178
- )
179
-
180
- for format_ in formats:
181
- try:
182
- return datetime.strptime(date_str, format_)
183
- except ValueError:
184
- ...
185
-
186
- return None
187
-
188
-
189
- def double_click_log(table: ttk.Treeview) -> None:
190
- """Handles double clicks on the logs table.
191
- Opens a popup with the selected log's text.
192
-
193
- Args:
194
- table (ttk.Treeview): _description_
195
- """
196
- item = table.selection()
197
-
198
- if item:
199
- values = list(table.item(item[0], 'values'))
200
-
201
- # Convert message back to multiline text
202
- values[-1] = literal_eval(values[-1])
203
-
204
- text = "\n".join(values)
205
- messagebox.showinfo("Info", text)
206
-
207
-
208
- def replace_options(option_menu: ttk.OptionMenu, option_menu_var: tkinter.StringVar, new_options: tuple[str]) -> None:
209
- """Replaces the options in a ttk.OptionsMenu.
210
-
211
- Args:
212
- option_menu: The OptionsMenu whose options to replace.
213
- option_menu_var: The StringVar connected to the OptionsMenu.
214
- new_options (tuple[str]): _description_
215
- """
216
- selected = option_menu_var.get()
217
-
218
- option_menu.set_menu(None, *new_options)
219
-
220
- if selected in new_options:
221
- option_menu_var.set(selected)
@@ -1,129 +0,0 @@
1
- """This module is responsible for the layout and functionality of the 'New Queue Trigger' popup."""
2
-
3
- # Disable pylint duplicate code error since it
4
- # mostly reacts to the layout code being similar.
5
- # pylint: disable=R0801
6
-
7
- import tkinter
8
- from tkinter import ttk, messagebox
9
-
10
- from OpenOrchestrator.database import db_util
11
- from OpenOrchestrator.database.triggers import QueueTrigger
12
-
13
- # pylint: disable-next=too-many-instance-attributes
14
- class QueueTriggerPopup(tkinter.Toplevel):
15
- """A popup for creating/updating scheduled triggers."""
16
- def __init__(self, trigger: QueueTrigger = None):
17
- """Create a new popup.
18
- If a trigger is given it will be updated instead of creating a new trigger.
19
-
20
- Args:
21
- trigger: The Scheduled Trigger to update if any.
22
- """
23
- self.trigger = trigger
24
- title = 'Update Queue Trigger' if trigger else 'New Queue Trigger'
25
-
26
- super().__init__()
27
- self.grab_set()
28
- self.title(title)
29
- self.geometry("300x350")
30
-
31
- ttk.Label(self, text="Trigger Name:").pack()
32
- self.trigger_entry = ttk.Entry(self)
33
- self.trigger_entry.pack()
34
-
35
- ttk.Label(self, text="Process Name:").pack()
36
- self.name_entry = ttk.Entry(self)
37
- self.name_entry.pack()
38
-
39
- ttk.Label(self, text="Queue Name:").pack()
40
- self.queue_entry = ttk.Entry(self)
41
- self.queue_entry.pack()
42
-
43
- ttk.Label(self, text="Min batch size:").pack()
44
- self.size_entry = ttk.Entry(self)
45
- self.size_entry.pack()
46
-
47
- ttk.Label(self, text="Process Path:").pack()
48
- self.path_entry = ttk.Entry(self)
49
- self.path_entry.pack()
50
-
51
- ttk.Label(self, text="Arguments:").pack()
52
- self.args_entry = ttk.Entry(self)
53
- self.args_entry.pack()
54
-
55
- self.git_check = tkinter.IntVar()
56
- ttk.Checkbutton(self, text="Is Git Repo?", variable=self.git_check).pack()
57
-
58
- self.blocking_check = tkinter.IntVar()
59
- ttk.Checkbutton(self, text="Is Blocking?", variable=self.blocking_check).pack()
60
-
61
- button_text = 'Update' if trigger else 'Create'
62
-
63
- ttk.Button(self, text=button_text, command=self.create_trigger).pack()
64
- ttk.Button(self, text='Cancel', command=self.destroy).pack()
65
-
66
- if self.trigger is not None:
67
- self.pre_populate()
68
-
69
- def pre_populate(self):
70
- """Populate the form with values from an existing trigger"""
71
- self.trigger_entry.insert(0, self.trigger.trigger_name)
72
- self.name_entry.insert(0, self.trigger.process_name)
73
- self.queue_entry.insert(0, self.trigger.queue_name)
74
- self.size_entry.insert(0, self.trigger.min_batch_size)
75
- self.path_entry.insert(0, self.trigger.process_path)
76
- self.args_entry.insert(0, self.trigger.process_args)
77
- self.git_check.set(self.trigger.is_git_repo)
78
- self.blocking_check.set(self.trigger.is_blocking)
79
-
80
- def create_trigger(self):
81
- """Creates a new scheduled trigger in the database using the data entered in the UI.
82
- If an existing trigger was given when creating the popup it is updated instead.
83
- """
84
- trigger_name = self.trigger_entry.get()
85
- process_name = self.name_entry.get()
86
- queue_name = self.queue_entry.get()
87
- min_batch_size = self.size_entry.get()
88
- path = self.path_entry.get()
89
- args = self.args_entry.get()
90
- is_git = self.git_check.get()
91
- is_blocking = self.blocking_check.get()
92
-
93
- if not trigger_name:
94
- messagebox.showerror('Error', 'Please enter a trigger name')
95
- return
96
-
97
- if not process_name:
98
- messagebox.showerror('Error', 'Please enter a process name')
99
- return
100
-
101
- if not queue_name:
102
- messagebox.showerror("Error", "Please enter a queue name")
103
-
104
- try:
105
- min_batch_size = int(min_batch_size)
106
- except ValueError:
107
- messagebox.showerror("Error", "Please enter a integer value for min batch size")
108
- return
109
-
110
- if not path:
111
- messagebox.showerror('Error', 'Please enter a process path')
112
- return
113
-
114
- if self.trigger is None:
115
- # Create new trigger in database
116
- db_util.create_queue_trigger(trigger_name, process_name, queue_name, path, args, is_git, is_blocking, min_batch_size)
117
- else:
118
- # Update existing trigger
119
- self.trigger.trigger_name = trigger_name
120
- self.trigger.process_name = process_name
121
- self.trigger.queue_name = queue_name
122
- self.trigger.min_batch_size = min_batch_size
123
- self.trigger.process_path = path
124
- self.trigger.process_args = args
125
- self.trigger.is_git_repo = is_git
126
- self.trigger.is_blocking = is_blocking
127
- db_util.update_trigger(self.trigger)
128
-
129
- self.destroy()
@@ -1,129 +0,0 @@
1
- """This module is responsible for the layout and functionality of the 'New Scheduled Trigger' popup."""
2
-
3
- # Disable pylint duplicate code error since it
4
- # mostly reacts to the layout code being similar.
5
- # pylint: disable=duplicate-code
6
-
7
- import tkinter
8
- from tkinter import ttk, messagebox
9
- from datetime import datetime
10
- import webbrowser
11
-
12
- import croniter
13
-
14
- from OpenOrchestrator.database import db_util
15
- from OpenOrchestrator.database.triggers import ScheduledTrigger
16
-
17
- # pylint: disable-next=too-many-instance-attributes
18
- class ScheduledTriggerPopup(tkinter.Toplevel):
19
- """A popup for creating/updating scheduled triggers."""
20
- def __init__(self, trigger: ScheduledTrigger = None):
21
- """Create a new popup.
22
- If a trigger is given it will be updated instead of creating a new trigger.
23
-
24
- Args:
25
- trigger: The Scheduled Trigger to update if any.
26
- """
27
- self.trigger = trigger
28
- title = 'Update Scheduled Trigger' if trigger else 'New Scheduled Trigger'
29
-
30
- super().__init__()
31
- self.grab_set()
32
- self.title(title)
33
- self.geometry("300x350")
34
-
35
- ttk.Label(self, text="Trigger Name:").pack()
36
- self.trigger_entry = ttk.Entry(self)
37
- self.trigger_entry.pack()
38
-
39
- ttk.Label(self, text="Process Name:").pack()
40
- self.name_entry = ttk.Entry(self)
41
- self.name_entry.pack()
42
-
43
- ttk.Label(self, text="Cron Expression:").pack()
44
- self.cron_entry = ttk.Entry(self)
45
- self.cron_entry.pack()
46
-
47
- help_label = ttk.Label(self, text='Cron Help', cursor='hand2', foreground='blue')
48
- help_label.bind('<Button-1>', lambda e: webbrowser.open('crontab.guru'))
49
- help_label.pack()
50
-
51
- ttk.Label(self, text="Process Path:").pack()
52
- self.path_entry = ttk.Entry(self)
53
- self.path_entry.pack()
54
-
55
- ttk.Label(self, text="Arguments:").pack()
56
- self.args_entry = ttk.Entry(self)
57
- self.args_entry.pack()
58
-
59
- self.git_check = tkinter.IntVar()
60
- ttk.Checkbutton(self, text="Is Git Repo?", variable=self.git_check).pack()
61
-
62
- self.blocking_check = tkinter.IntVar()
63
- ttk.Checkbutton(self, text="Is Blocking?", variable=self.blocking_check).pack()
64
-
65
- button_text = 'Update' if trigger else 'Create'
66
-
67
- ttk.Button(self, text=button_text, command=self.create_trigger).pack()
68
- ttk.Button(self, text='Cancel', command=self.destroy).pack()
69
-
70
- if self.trigger is not None:
71
- self.pre_populate()
72
-
73
- def pre_populate(self):
74
- """Populate the form with values from an existing trigger"""
75
- self.trigger_entry.insert(0, self.trigger.trigger_name)
76
- self.name_entry.insert(0, self.trigger.process_name)
77
- self.cron_entry.insert(0, self.trigger.cron_expr)
78
- self.path_entry.insert(0, self.trigger.process_path)
79
- self.args_entry.insert(0, self.trigger.process_args)
80
- self.git_check.set(self.trigger.is_git_repo)
81
- self.blocking_check.set(self.trigger.is_blocking)
82
-
83
- def create_trigger(self):
84
- """Creates a new scheduled trigger in the database using the data entered in the UI.
85
- If an existing trigger was given when creating the popup it is updated instead.
86
- """
87
- trigger_name = self.trigger_entry.get()
88
- process_name = self.name_entry.get()
89
- cron_string = self.cron_entry.get()
90
- path = self.path_entry.get()
91
- args = self.args_entry.get()
92
- is_git = self.git_check.get()
93
- is_blocking = self.blocking_check.get()
94
-
95
- if not trigger_name:
96
- messagebox.showerror('Error', 'Please enter a trigger name')
97
- return
98
-
99
- if not process_name:
100
- messagebox.showerror('Error', 'Please enter a process name')
101
- return
102
-
103
- try:
104
- cron_iter = croniter.croniter(cron_string, datetime.now())
105
- next_run = cron_iter.get_next(datetime)
106
- except croniter.CroniterBadCronError as exc:
107
- messagebox.showerror('Error', 'Please enter a valid cron expression\n'+str(exc))
108
- return
109
-
110
- if not path:
111
- messagebox.showerror('Error', 'Please enter a process path')
112
- return
113
-
114
- if self.trigger is None:
115
- # Create new trigger in database
116
- db_util.create_scheduled_trigger(trigger_name, process_name, cron_string, next_run, path, args, is_git, is_blocking)
117
- else:
118
- # Update existing trigger
119
- self.trigger.trigger_name = trigger_name
120
- self.trigger.process_name = process_name
121
- self.trigger.cron_expr = cron_string
122
- self.trigger.next_run = next_run
123
- self.trigger.process_path = path
124
- self.trigger.process_args = args
125
- self.trigger.is_git_repo = is_git
126
- self.trigger.is_blocking = is_blocking
127
- db_util.update_trigger(self.trigger)
128
-
129
- self.destroy()