cronboard 0.7.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.
cronboard/__init__.py ADDED
File without changes
cronboard/app.py ADDED
@@ -0,0 +1,263 @@
1
+ import tomllib
2
+ from importlib.metadata import version, PackageNotFoundError
3
+ from crontab import CronTab
4
+ import tomlkit
5
+ from pathlib import Path
6
+ from textual import events, on
7
+ from textual.app import App, ComposeResult
8
+ from textual.binding import Binding
9
+ from textual.widgets import (
10
+ Footer,
11
+ Label,
12
+ Tabs,
13
+ Tab,
14
+ Button,
15
+ Input,
16
+ Checkbox,
17
+ MaskedInput,
18
+ RadioButton,
19
+ RadioSet,
20
+ Select,
21
+ Switch,
22
+ TextArea,
23
+ )
24
+ from cronboard.widgets.CronTable import CronTable
25
+ from textual.containers import Container
26
+ from cronboard.widgets.CronTabs import CronTabs
27
+ from cronboard.screens.CronCreator import CronCreator
28
+ from cronboard.services.messages import CronJobDeleted
29
+ from cronboard.services.logging.logger import delete_logs_for_identificator
30
+ from cronboard.screens.CronDeleteConfirmation import CronDeleteConfirmation
31
+ from cronboard.screens.CronServers import CronServers
32
+
33
+
34
+ def is_form_element(element):
35
+ return isinstance(
36
+ element,
37
+ (
38
+ Input,
39
+ Checkbox,
40
+ Button,
41
+ MaskedInput,
42
+ RadioButton,
43
+ RadioSet,
44
+ Select,
45
+ Switch,
46
+ TextArea,
47
+ ),
48
+ )
49
+
50
+
51
+ class CronBoard(App):
52
+ """A Textual App to manage cron jobs."""
53
+
54
+ BASE_DIR = Path(__file__).resolve().parent
55
+ CSS_PATH = BASE_DIR / "static" / "css" / "cronboard.tcss"
56
+
57
+ BINDINGS = [
58
+ Binding("q,ctrl+q", "quit", "Quit", priority=True),
59
+ Binding("Tab", "next_tab_and_focus", "Change Tab"),
60
+ ]
61
+
62
+ def compose(self) -> ComposeResult:
63
+ version = self.get_version()
64
+ self.config_path = Path.home() / ".config/cronboard/config.toml"
65
+ yield Label(
66
+ f"""▄▖ ▄ ▌
67
+ ▌ ▛▘▛▌▛▌▙▘▛▌▀▌▛▘▛▌
68
+ ▙▖▌ ▙▌▌▌▙▘▙▌█▌▌ ▙▌ v{version}""",
69
+ id="title",
70
+ )
71
+ yield Footer()
72
+ self.tabs = CronTabs(
73
+ Tab("Local", id="local"),
74
+ Tab("Servers", id="servers"),
75
+ )
76
+ yield self.tabs
77
+ self.content_container = Container(id="tab-content")
78
+ yield self.content_container
79
+
80
+ @on(CronJobDeleted)
81
+ def _on_cron_job_deleted(self, event: CronJobDeleted) -> None:
82
+ delete_logs_for_identificator(event.identificator, event.ssh_client)
83
+
84
+ def on_mount(self) -> None:
85
+ config = self.load_config()
86
+ saved_theme = config.get("theme", "catppuccin-mocha")
87
+ self.theme = saved_theme
88
+ self.servers = None
89
+ self.local_table = CronTable(id="local-crontable")
90
+ self.content_container.mount(self.local_table)
91
+ self.local_table.display = True
92
+ self.set_focus(self.local_table)
93
+ self.tab_disabled = False
94
+
95
+ def load_config(self):
96
+ if self.config_path.exists():
97
+ try:
98
+ with self.config_path.open("rb") as f:
99
+ return tomllib.load(f)
100
+ except Exception as e:
101
+ print(f"Warning: Failed to load config: {e}")
102
+ return {}
103
+
104
+ def watch_theme(self, theme: str):
105
+ try:
106
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
107
+ with self.config_path.open("w") as f:
108
+ f.write(tomlkit.dumps({"theme": theme}))
109
+ except Exception as e:
110
+ print(f"Warning: Failed to save theme: {e}")
111
+
112
+ def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
113
+ tab_label = event.tab.label
114
+ if tab_label == "Local":
115
+ self.show_tab_content(0)
116
+ elif tab_label == "Servers":
117
+ self.show_tab_content(1)
118
+
119
+ def show_tab_content(self, index: int) -> None:
120
+ if index == 0:
121
+ self.local_table.display = True
122
+ if self.servers:
123
+ self.servers.display = False
124
+ elif index == 1:
125
+ if not self.servers:
126
+ self.servers = CronServers()
127
+ self.content_container.mount(self.servers)
128
+ self.local_table.display = False
129
+ self.servers.display = True
130
+
131
+ def toggle_tab_enablement(self):
132
+ self.tab_disabled = not self.tab_disabled
133
+
134
+ def on_key(self, event: events.Key) -> None:
135
+ if event.key != "tab":
136
+ return
137
+
138
+ if self.tab_disabled:
139
+ event.prevent_default()
140
+ return
141
+
142
+ if is_form_element(self.focused):
143
+ return
144
+
145
+ event.prevent_default()
146
+ self.action_next_tab_and_focus()
147
+
148
+ def action_next_tab_and_focus(self) -> None:
149
+ tabs = self.tabs
150
+ tab_widgets = list(tabs.query(Tab))
151
+ tab_ids = [tab.id for tab in tab_widgets]
152
+ current = tabs.active
153
+ index = tab_ids.index(current)
154
+
155
+ next_index = (index + 1) % len(tab_ids)
156
+ next_tab_id = tab_ids[next_index]
157
+
158
+ tabs.active = next_tab_id
159
+
160
+ self.show_tab_content(next_index)
161
+ self._focus_active_panel()
162
+
163
+ def _focus_active_panel(self) -> None:
164
+ if self.tabs.active == "local":
165
+ if self.local_table:
166
+ self.set_focus(self.local_table)
167
+
168
+ elif self.tabs.active == "servers":
169
+ if self.servers:
170
+ self.servers.focus_tree()
171
+
172
+ def action_create_cronjob(
173
+ self, cron: CronTab, remote=False, ssh_client=None, crontab_user=None
174
+ ) -> None:
175
+ def check_save(save: bool | None) -> None:
176
+ if save:
177
+ self.local_table.action_refresh()
178
+ if (
179
+ self.servers
180
+ and hasattr(self.servers, "current_cron_table")
181
+ and self.servers.current_cron_table
182
+ ):
183
+ self.servers.current_cron_table.action_refresh()
184
+
185
+ self.push_screen(
186
+ CronCreator(
187
+ cron, remote=remote, ssh_client=ssh_client, crontab_user=crontab_user
188
+ ),
189
+ check_save,
190
+ )
191
+
192
+ def action_delete_cronjob(
193
+ self, job, cron=None, remote=False, ssh_client=None, crontab_user=None
194
+ ) -> None:
195
+ def check_delete(deleted: bool | None) -> None:
196
+ if deleted:
197
+ self.local_table.action_refresh()
198
+ if (
199
+ self.servers
200
+ and hasattr(self.servers, "current_cron_table")
201
+ and self.servers.current_cron_table
202
+ ):
203
+ self.servers.current_cron_table.action_refresh()
204
+
205
+ self.push_screen(
206
+ CronDeleteConfirmation(
207
+ job=job,
208
+ cron=cron,
209
+ remote=remote,
210
+ ssh_client=ssh_client,
211
+ crontab_user=crontab_user,
212
+ ),
213
+ check_delete,
214
+ )
215
+
216
+ def action_edit_cronjob(
217
+ self,
218
+ cron: CronTab,
219
+ identificator: str,
220
+ expression: str,
221
+ command: str,
222
+ remote=False,
223
+ ssh_client=None,
224
+ crontab_user=None,
225
+ ) -> None:
226
+ def check_save(save: bool | None) -> None:
227
+ if save:
228
+ self.local_table.action_refresh()
229
+ if (
230
+ self.servers
231
+ and hasattr(self.servers, "current_cron_table")
232
+ and self.servers.current_cron_table
233
+ ):
234
+ self.servers.current_cron_table.action_refresh()
235
+
236
+ self.push_screen(
237
+ CronCreator(
238
+ cron,
239
+ identificator=identificator,
240
+ expression=expression,
241
+ command=command,
242
+ remote=remote,
243
+ ssh_client=ssh_client,
244
+ crontab_user=crontab_user,
245
+ ),
246
+ check_save,
247
+ )
248
+
249
+ def get_version(self):
250
+ try:
251
+ return version("cronboard")
252
+ except PackageNotFoundError:
253
+ return "Unknown"
254
+
255
+
256
+ def main():
257
+ app = CronBoard()
258
+ app.run()
259
+
260
+
261
+ if __name__ == "__main__":
262
+ app = CronBoard()
263
+ app.run()
cronboard/config.py ADDED
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ CONFIG_REL_PATH = ".config/cronboard"
4
+ WRAPPER_DIST = "cron-wrapper.sh"
5
+ LOG_REL_PATH = f"{CONFIG_REL_PATH}/logs"
6
+ CONFIG_DIR = Path.home() / CONFIG_REL_PATH
7
+ CONFIG_FILE = CONFIG_DIR / "servers.toml"
8
+ KEY_FILE = CONFIG_DIR / "secret.key"
9
+ LOG_DIR = CONFIG_DIR / "logs"
10
+ WRAPPER_SOURCE = Path(__file__).parent / "logging" / "cron-wrapper.sh"