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 <noreply@anthropic.com>
This commit is contained in:
shuki
2026-03-07 06:00:30 +02:00
parent a0c891b093
commit 280b80c956
2 changed files with 98 additions and 53 deletions

View File

@@ -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}"
;;

View File

@@ -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,6 +22,8 @@ class SettingsScreen(Screen):
with Horizontal(classes="screen-with-docs"):
with Vertical(id="settings-screen"):
yield Static("Settings", id="screen-title")
with TabbedContent():
with TabPane("General", id="tab-general"):
yield Static("Log Level:")
yield Select(
[("Debug", "debug"), ("Info", "info"), ("Warning", "warn"), ("Error", "error")],
@@ -34,6 +38,9 @@ class SettingsScreen(Screen):
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:")
@@ -58,13 +65,12 @@ class SettingsScreen(Screen):
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")
yield Static("Extra rsync options:")
yield Input(value=settings.rsync_extra_opts, id="set-rsyncopts")
yield Static("Web Dashboard", classes="section-label")
with TabPane("Web Dashboard", id="tab-web"):
yield Static("Port:")
yield Input(value=settings.web_port, id="set-web-port")
yield Static("Host:")
@@ -73,6 +79,7 @@ class SettingsScreen(Screen):
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()