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 +0 -0
- cronboard/app.py +263 -0
- cronboard/config.py +10 -0
- cronboard/screens/CronCreator.py +444 -0
- cronboard/screens/CronDeleteConfirmation.py +124 -0
- cronboard/screens/CronInputSearch.py +19 -0
- cronboard/screens/CronSSHModal.py +109 -0
- cronboard/screens/CronServers.py +321 -0
- cronboard/screens/__init__.py +0 -0
- cronboard/services/__init__.py +0 -0
- cronboard/services/encryption/CronEncrypt.py +32 -0
- cronboard/services/encryption/__init__.py +0 -0
- cronboard/services/logging/__init__.py +0 -0
- cronboard/services/logging/cron_wrapper.py +225 -0
- cronboard/services/logging/logger.py +84 -0
- cronboard/services/messages.py +19 -0
- cronboard/static/css/cronboard.tcss +237 -0
- cronboard/widgets/CronTable.py +450 -0
- cronboard/widgets/CronTabs.py +16 -0
- cronboard/widgets/CronTree.py +9 -0
- cronboard/widgets/LogView.py +368 -0
- cronboard/widgets/VimKeysRadioSet.py +43 -0
- cronboard/widgets/__init__.py +0 -0
- cronboard-0.7.0.dist-info/METADATA +102 -0
- cronboard-0.7.0.dist-info/RECORD +29 -0
- cronboard-0.7.0.dist-info/WHEEL +5 -0
- cronboard-0.7.0.dist-info/entry_points.txt +2 -0
- cronboard-0.7.0.dist-info/licenses/LICENSE +202 -0
- cronboard-0.7.0.dist-info/top_level.txt +1 -0
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"
|