diff --git a/lib/schedule.sh b/lib/schedule.sh index 1a1ba73..893dd6a 100644 --- a/lib/schedule.sh +++ b/lib/schedule.sh @@ -56,6 +56,7 @@ load_schedule() { SCHEDULE_TIME="" SCHEDULE_DAY="" SCHEDULE_CRON="" + SCHEDULE_ACTIVE="yes" SCHEDULE_REMOTES="" SCHEDULE_TARGETS="" @@ -191,6 +192,11 @@ install_schedules() { continue fi + if [[ "${SCHEDULE_ACTIVE:-yes}" != "yes" ]]; then + log_debug "Schedule '$sname' is inactive, skipping" + continue + fi + local cron_line cron_line=$(build_cron_line "$sname") || { log_error "Skipping schedule '$sname': invalid schedule"; continue; } diff --git a/tui/gniza.tcss b/tui/gniza.tcss index 99b8a17..f433bcf 100644 --- a/tui/gniza.tcss +++ b/tui/gniza.tcss @@ -256,17 +256,6 @@ Switch { margin: 0 1; } -#sched-active-row { - height: auto; - align: left middle; - margin: 1 0; -} - -#sched-active-label { - width: auto; - margin: 0 1 0 0; -} - .section-label { text-style: bold; color: #00cc00; diff --git a/tui/models.py b/tui/models.py index 9e46568..596d453 100644 --- a/tui/models.py +++ b/tui/models.py @@ -157,6 +157,7 @@ class Schedule: cron: str = "" targets: str = "" remotes: str = "" + active: str = "yes" def to_conf(self) -> dict[str, str]: return { @@ -164,6 +165,7 @@ class Schedule: "SCHEDULE_TIME": self.time, "SCHEDULE_DAY": self.day, "SCHEDULE_CRON": self.cron, + "SCHEDULE_ACTIVE": self.active, "TARGETS": self.targets, "REMOTES": self.remotes, } @@ -178,6 +180,7 @@ class Schedule: cron=data.get("SCHEDULE_CRON", ""), targets=data.get("TARGETS", ""), remotes=data.get("REMOTES", ""), + active=data.get("SCHEDULE_ACTIVE", "yes"), ) diff --git a/tui/screens/schedule.py b/tui/screens/schedule.py index cfb7154..cc952f4 100644 --- a/tui/screens/schedule.py +++ b/tui/screens/schedule.py @@ -1,11 +1,11 @@ import re from textual.app import ComposeResult from textual.screen import Screen -from textual.widgets import Header, Footer, Static, Button, DataTable, Input, Select, SelectionList, Switch +from textual.widgets import Header, Footer, Static, Button, DataTable, Input, Select, SelectionList from textual.containers import Vertical, Horizontal from textual import work -from tui.config import list_conf_dir, parse_conf, write_conf, CONFIG_DIR +from tui.config import list_conf_dir, parse_conf, write_conf, update_conf_key, CONFIG_DIR from tui.models import Schedule from tui.backend import run_cli from tui.widgets import ConfirmDialog, OperationLog @@ -40,12 +40,10 @@ class ScheduleScreen(Screen): with Vertical(id="schedule-screen"): yield Static("Schedules", id="screen-title") yield DataTable(id="sched-table") - with Horizontal(id="sched-active-row"): - yield Static("Active (crontab):", id="sched-active-label") - yield Switch(id="sched-active") with Horizontal(id="sched-buttons"): yield Button("Add", variant="primary", id="btn-add") yield Button("Edit", id="btn-edit") + yield Button("Toggle Active", variant="warning", id="btn-toggle") yield Button("Delete", variant="error", id="btn-delete") yield Button("Show crontab", id="btn-show") yield Button("Back", id="btn-back") @@ -111,20 +109,6 @@ class ScheduleScreen(Screen): def on_mount(self) -> None: self._refresh_table() self._update_type_visibility() - self._check_crontab_status() - - @work - async def _check_crontab_status(self) -> None: - rc, stdout, stderr = await run_cli("schedule", "show") - has_entries = bool(stdout.strip()) and "no gniza" not in stdout.lower() - self.query_one("#sched-active", Switch).value = has_entries - - def on_switch_changed(self, event: Switch.Changed) -> None: - if event.switch.id == "sched-active": - if event.value: - self._install_schedules() - else: - self._remove_schedules() def on_select_changed(self, event: Select.Changed) -> None: if event.select.id == "sched-type": @@ -149,12 +133,13 @@ class ScheduleScreen(Screen): def _refresh_table(self) -> None: table = self.query_one("#sched-table", DataTable) table.clear(columns=True) - table.add_columns("Name", "Type", "Time", "Targets", "Remotes") + table.add_columns("Name", "Active", "Type", "Time", "Targets", "Remotes") schedules = list_conf_dir("schedules.d") for name in schedules: data = parse_conf(CONFIG_DIR / "schedules.d" / f"{name}.conf") s = Schedule.from_conf(name, data) - table.add_row(name, s.schedule, s.time, s.targets or "all", s.remotes or "all", key=name) + active = "✅" if s.active == "yes" else "❌" + table.add_row(name, active, s.schedule, s.time, s.targets or "all", s.remotes or "all", key=name) def _selected_schedule(self) -> str | None: table = self.query_one("#sched-table", DataTable) @@ -182,6 +167,12 @@ class ScheduleScreen(Screen): ) else: self.notify("Select a schedule first", severity="warning") + elif event.button.id == "btn-toggle": + name = self._selected_schedule() + if name: + self._toggle_active(name) + else: + self.notify("Select a schedule first", severity="warning") elif event.button.id == "btn-show": self._show_crontab() @@ -277,6 +268,22 @@ class ScheduleScreen(Screen): self.query_one("#sched-name", Input).value = "" self.query_one("#sched-form-title", Static).update("Add Schedule") + def _toggle_active(self, name: str) -> None: + conf = CONFIG_DIR / "schedules.d" / f"{name}.conf" + data = parse_conf(conf) + current = data.get("SCHEDULE_ACTIVE", "yes") + new_val = "no" if current == "yes" else "yes" + update_conf_key(conf, "SCHEDULE_ACTIVE", new_val) + state = "activated" if new_val == "yes" else "deactivated" + self.notify(f"Schedule '{name}' {state}") + self._refresh_table() + self._sync_crontab() + + @work + async def _sync_crontab(self) -> None: + """Reinstall crontab with only active schedules.""" + await run_cli("schedule", "install") + def _delete_schedule(self, name: str) -> None: conf = CONFIG_DIR / "schedules.d" / f"{name}.conf" if conf.is_file():