Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions src/cronboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
from crontab import CronTab
import tomlkit
from pathlib import Path
from textual import events
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Footer, Label, Tabs, Tab
from textual.widgets import Footer, Label, Tabs, Tab, Button, Input, Checkbox, MaskedInput, RadioButton, Select, Switch, TextArea
from cronboard_widgets.CronTable import CronTable
from textual.containers import Container
from cronboard_widgets.CronTabs import CronTabs
from cronboard_widgets.CronCreator import CronCreator
from cronboard_widgets.CronDeleteConfirmation import CronDeleteConfirmation
from cronboard_widgets.CronServers import CronServers

def is_form_element(element):
return isinstance(element, (Input, Checkbox, Button, MaskedInput, RadioButton, Select, Switch, TextArea))

class CronBoard(App):
"""A Textual App to manage cron jobs."""
Expand All @@ -22,7 +25,7 @@ class CronBoard(App):

BINDINGS = [
Binding("q,ctrl+q", "quit", "Quit", priority=True),
Binding("Tab", "focus_next", "Change Panel"),
Binding("Tab", "next_tab_and_focus", "Change Panel"),
]

def compose(self) -> ComposeResult:
Expand All @@ -46,6 +49,7 @@ def on_mount(self) -> None:
self.local_table = CronTable(id="local-crontable")
self.content_container.mount(self.local_table)
self.local_table.display = True
self.set_focus(self.local_table)

def load_config(self):
if self.config_path.exists():
Expand Down Expand Up @@ -83,6 +87,40 @@ def show_tab_content(self, index: int) -> None:
self.local_table.display = False
self.servers.display = True

def on_key(self, event: events.Key) -> None:
if event.key != "tab":
return

if is_form_element(self.focused):
return

event.prevent_default()
self.action_next_tab_and_focus()

def action_next_tab_and_focus(self) -> None:
tabs = self.tabs
tab_widgets = list(tabs.query(Tab))
tab_ids = [tab.id for tab in tab_widgets]
current = tabs.active
index = tab_ids.index(current)

next_index = (index + 1) % len(tab_ids)
next_tab_id = tab_ids[next_index]

tabs.active = next_tab_id

self.show_tab_content(next_index)
self._focus_active_panel()

def _focus_active_panel(self) -> None:
if self.tabs.active == "local":
if self.local_table:
self.set_focus(self.local_table)

elif self.tabs.active == "servers":
if self.servers:
self.servers.focus_tree()

def action_create_cronjob(
self, cron: CronTab, remote=False, ssh_client=None, crontab_user=None
) -> None:
Expand Down
19 changes: 19 additions & 0 deletions src/cronboard_widgets/CronServers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CronServers(Widget):
Binding("D", "delete_server", "Delete Server"),
Binding("c", "connect_server", "Connect"),
Binding("d", "disconnect_server", "Disconnect Server"),
Binding("J", "jump", "Jump"),
]

def __init__(self) -> None:
Expand Down Expand Up @@ -300,3 +301,21 @@ def on_delete_confirmed(confirmed: bool) -> None:
message=f"Are you sure you want to delete the server '{server_info['name']}' ?",
)
self.app.push_screen(confirmation_modal, on_delete_confirmed)

def focus_tree(self):
try:
self._focus_tree()
except:
self.call_after_refresh(self._focus_tree)

def _focus_tree(self):
tree = self.query_one("#servers-tree", Tree)
if tree:
tree.focus()

def action_jump(self) -> None:
servers_tree = self.query_one("#servers-tree", Tree)
if servers_tree.has_focus and self.current_cron_table:
self.current_cron_table.focus()
else:
servers_tree.focus()
1 change: 0 additions & 1 deletion tests/CronCreator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
@pytest.mark.asyncio
async def test_open_create_cronjob_modal(app: CronBoard):
async with app.run_test() as pilot:
await pilot.press("tab")
await pilot.press("c")
assert isinstance(app.screen, CronCreator)

Expand Down
3 changes: 0 additions & 3 deletions tests/CronDeleteConfirmation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
@pytest.mark.asyncio
async def test_open_delete_cronjob_modal(app: CronBoard):
async with app.run_test() as pilot:
await pilot.press("tab")
await pilot.press("D")
assert isinstance(app.screen, CronDeleteConfirmation)


@pytest.mark.asyncio
async def test_delete_cronjob_cancel(app: CronBoard):
async with app.run_test() as pilot:
await pilot.press("tab")
await pilot.press("D")
await pilot.press("tab")
await pilot.press("enter")
Expand All @@ -26,7 +24,6 @@ async def test_delete_cronjob_cancel(app: CronBoard):
@pytest.mark.asyncio
async def test_delete_cronjob_confirm(app: CronBoard):
async with app.run_test() as pilot:
await pilot.press("tab")
await pilot.press("D")
await pilot.press("enter")
assert not isinstance(app.screen, CronDeleteConfirmation)
Expand Down
3 changes: 0 additions & 3 deletions tests/CronInputSearch_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


async def search_input(pilot: Pilot):
await pilot.press("tab")
await pilot.press("/")
await pilot.press("c")
await pilot.press("r")
Expand All @@ -15,14 +14,12 @@ async def search_input(pilot: Pilot):

@pytest.mark.asyncio
async def test_open_search_modal(pilot: Pilot):
await pilot.press("tab")
await pilot.press("/")
assert isinstance(pilot.app.screen, CronInputSearch)


@pytest.mark.asyncio
async def test_close_search_modal(pilot: Pilot):
await pilot.press("tab")
await pilot.press("/")
await pilot.press("escape")
assert not isinstance(pilot.app.screen, CronInputSearch)
Expand Down
1 change: 1 addition & 0 deletions tests/CronServers_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from cronboard_widgets.CronServers import CronServers


Expand Down
11 changes: 9 additions & 2 deletions tests/app_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import pytest
from cronboard.app import CronBoard

from textual.widgets import Tree

@pytest.mark.asyncio
async def test_change_tab(app: CronBoard):
async with app.run_test() as pilot:
assert app.tabs.active == "local"

await pilot.press("tab")
assert app.local_table.has_focus
await pilot.pause()

assert app.tabs.active == "servers"
assert app.servers is not None

server_tree = app.servers.query_one("#servers-tree", Tree)
assert server_tree.has_focus

@pytest.mark.asyncio
async def test_refresh_data(app: CronBoard):
Expand Down