From 280b80c95671cd3925aa54752692c6c55276b8e5 Mon Sep 17 00:00:00 2001 From: shuki Date: Sat, 7 Mar 2026 06:00:30 +0200 Subject: [PATCH] Add tabbed settings screen and Send Test Email button Organize settings into General, Email, SSH, and Web Dashboard tabs using TabbedContent. Add test-email CLI command and button that auto-saves settings then sends a test email via SMTP. Co-Authored-By: Claude Opus 4.6 --- bin/gniza | 23 ++++++++ tui/screens/settings.py | 128 +++++++++++++++++++++++----------------- 2 files changed, 98 insertions(+), 53 deletions(-) diff --git a/bin/gniza b/bin/gniza index 0e6d23f..4d9939a 100755 --- a/bin/gniza +++ b/bin/gniza @@ -594,6 +594,29 @@ run_cli() { exit "$rc" ;; + test-email) + if [[ -z "${NOTIFY_EMAIL:-}" ]]; then + echo "Error: NOTIFY_EMAIL is not set. Configure it in Settings first." + exit 1 + fi + if [[ -z "${SMTP_HOST:-}" ]]; then + echo "Error: SMTP_HOST is not set. Configure SMTP settings first." + exit 1 + fi + local hostname; hostname=$(hostname -f 2>/dev/null || hostname) + local subject="[gniza] [$hostname] Test Email" + local body="This is a test email from gniza on $hostname."$'\n' + body+="Sent at: $(date '+%Y-%m-%d %H:%M:%S')"$'\n' + body+=""$'\n' + body+="If you received this message, your SMTP settings are working correctly." + if _send_via_smtp "$subject" "$body"; then + echo "Test email sent successfully to $NOTIFY_EMAIL" + else + echo "Failed to send test email. Check your SMTP settings." + exit 1 + fi + ;; + version) echo "gniza v${GNIZA4LINUX_VERSION}" ;; diff --git a/tui/screens/settings.py b/tui/screens/settings.py index 1c9f8cd..3da6bb6 100644 --- a/tui/screens/settings.py +++ b/tui/screens/settings.py @@ -1,12 +1,14 @@ from textual.app import ComposeResult from textual.screen import Screen -from textual.widgets import Header, Footer, Static, Button, Input, Select +from textual.widgets import Header, Footer, Static, Button, Input, Select, TabbedContent, TabPane from tui.widgets.header import GnizaHeader as Header # noqa: F811 from textual.containers import Vertical, Horizontal +from textual import work from tui.config import parse_conf, write_conf, CONFIG_DIR from tui.models import AppSettings -from tui.widgets import DocsPanel +from tui.backend import run_cli +from tui.widgets import DocsPanel, OperationLog class SettingsScreen(Screen): @@ -20,59 +22,64 @@ class SettingsScreen(Screen): with Horizontal(classes="screen-with-docs"): with Vertical(id="settings-screen"): yield Static("Settings", id="screen-title") - yield Static("Log Level:") - yield Select( - [("Debug", "debug"), ("Info", "info"), ("Warning", "warn"), ("Error", "error")], - id="set-loglevel", - value=settings.log_level.lower(), - ) - yield Static("Log Retention (days):") - yield Input(value=settings.log_retain, id="set-logretain") - yield Static("Default Retention Count:") - yield Input(value=settings.retention_count, id="set-retention") - yield Static("Default Bandwidth Limit (KB/s, 0=unlimited):") - yield Input(value=settings.bwlimit, id="set-bwlimit") - yield Static("Disk Usage Threshold (%, 0=disable):") - yield Input(value=settings.disk_usage_threshold, id="set-diskthreshold") - yield Static("Notification Email:") - yield Input(value=settings.notify_email, id="set-email") - yield Static("Notify On:") - yield Select( - [("Always", "always"), ("Failure only", "failure"), ("Never", "never")], - id="set-notifyon", - value=settings.notify_on, - ) - yield Static("SMTP Host:") - yield Input(value=settings.smtp_host, id="set-smtphost") - yield Static("SMTP Port:") - yield Input(value=settings.smtp_port, id="set-smtpport") - yield Static("SMTP User:") - yield Input(value=settings.smtp_user, id="set-smtpuser") - yield Static("SMTP Password:") - yield Input(value=settings.smtp_password, password=True, id="set-smtppass") - yield Static("SMTP From:") - yield Input(value=settings.smtp_from, id="set-smtpfrom") - yield Static("SMTP Security:") - yield Select( - [("TLS", "tls"), ("SSL", "ssl"), ("None", "none")], - id="set-smtpsec", - value=settings.smtp_security, - ) - yield Static("SSH Timeout:") - yield Input(value=settings.ssh_timeout, id="set-sshtimeout") - yield Static("SSH Retries:") - yield Input(value=settings.ssh_retries, id="set-sshretries") - yield Static("Extra rsync options:") - yield Input(value=settings.rsync_extra_opts, id="set-rsyncopts") - yield Static("Web Dashboard", classes="section-label") - yield Static("Port:") - yield Input(value=settings.web_port, id="set-web-port") - yield Static("Host:") - yield Input(value=settings.web_host, id="set-web-host") - yield Static("API Key:") - yield Input(value=settings.web_api_key, password=True, id="set-web-key") + with TabbedContent(): + with TabPane("General", id="tab-general"): + yield Static("Log Level:") + yield Select( + [("Debug", "debug"), ("Info", "info"), ("Warning", "warn"), ("Error", "error")], + id="set-loglevel", + value=settings.log_level.lower(), + ) + yield Static("Log Retention (days):") + yield Input(value=settings.log_retain, id="set-logretain") + yield Static("Default Retention Count:") + yield Input(value=settings.retention_count, id="set-retention") + yield Static("Default Bandwidth Limit (KB/s, 0=unlimited):") + yield Input(value=settings.bwlimit, id="set-bwlimit") + yield Static("Disk Usage Threshold (%, 0=disable):") + yield Input(value=settings.disk_usage_threshold, id="set-diskthreshold") + yield Static("Extra rsync options:") + yield Input(value=settings.rsync_extra_opts, id="set-rsyncopts") + with TabPane("Email", id="tab-email"): + yield Static("Notification Email:") + yield Input(value=settings.notify_email, id="set-email") + yield Static("Notify On:") + yield Select( + [("Always", "always"), ("Failure only", "failure"), ("Never", "never")], + id="set-notifyon", + value=settings.notify_on, + ) + yield Static("SMTP Host:") + yield Input(value=settings.smtp_host, id="set-smtphost") + yield Static("SMTP Port:") + yield Input(value=settings.smtp_port, id="set-smtpport") + yield Static("SMTP User:") + yield Input(value=settings.smtp_user, id="set-smtpuser") + yield Static("SMTP Password:") + yield Input(value=settings.smtp_password, password=True, id="set-smtppass") + yield Static("SMTP From:") + yield Input(value=settings.smtp_from, id="set-smtpfrom") + yield Static("SMTP Security:") + yield Select( + [("TLS", "tls"), ("SSL", "ssl"), ("None", "none")], + id="set-smtpsec", + value=settings.smtp_security, + ) + with TabPane("SSH", id="tab-ssh"): + yield Static("SSH Timeout:") + yield Input(value=settings.ssh_timeout, id="set-sshtimeout") + yield Static("SSH Retries:") + yield Input(value=settings.ssh_retries, id="set-sshretries") + with TabPane("Web Dashboard", id="tab-web"): + yield Static("Port:") + yield Input(value=settings.web_port, id="set-web-port") + yield Static("Host:") + yield Input(value=settings.web_host, id="set-web-host") + yield Static("API Key:") + yield Input(value=settings.web_api_key, password=True, id="set-web-key") with Horizontal(id="set-buttons"): yield Button("Save", variant="primary", id="btn-save") + yield Button("Send Test Email", id="btn-test-email") yield Button("Back", id="btn-back") yield DocsPanel.for_screen("settings-screen") yield Footer() @@ -82,6 +89,9 @@ class SettingsScreen(Screen): self.app.pop_screen() elif event.button.id == "btn-save": self._save() + elif event.button.id == "btn-test-email": + self._save() + self._send_test_email() def _get_select_val(self, sel_id: str, default: str) -> str: sel = self.query_one(sel_id, Select) @@ -113,5 +123,17 @@ class SettingsScreen(Screen): write_conf(conf_path, settings.to_conf()) self.notify("Settings saved.") + @work + async def _send_test_email(self) -> None: + log_screen = OperationLog("Send Test Email", show_spinner=True) + self.app.push_screen(log_screen) + log_screen.write("Sending test email...") + rc, stdout, stderr = await run_cli("test-email") + if stdout: + log_screen.write(stdout) + if stderr: + log_screen.write(stderr) + log_screen.finish() + def action_go_back(self) -> None: self.app.pop_screen()