diff --git a/README.md b/README.md index 63f95da..c059655 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ The GUI opens in your browser and lets you: - change the reminder interval - configure automatic refresh - choose the terminal used for installing updates +- manage settings in a dedicated dialog with tabs for general behavior, notifications, installation, and ignored packages +- disable reminders, keep install terminals open or let them close, and control confirmation for selective installs Selective package installs use `pacman -S --needed` or the configured AUR helper with `-S --needed`. On Arch, full system updates are still the safer default because partial upgrades can cause dependency mismatches. diff --git a/cmd/lazy-update-manager/main.go b/cmd/lazy-update-manager/main.go index c47311a..e3c9fe7 100644 --- a/cmd/lazy-update-manager/main.go +++ b/cmd/lazy-update-manager/main.go @@ -134,6 +134,9 @@ func notifyUpdates(args []string) int { if result.Total() == 0 { return 0 } + if !cfg.NotificationsEnabled { + return 0 + } store, err := state.Load(defaultStatePath()) if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index 977ae18..089bf44 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,22 +8,28 @@ import ( ) type Config struct { - CheckAUR bool `json:"check_aur"` - ReminderIntervalHours int `json:"reminder_interval_hours"` - Terminal string `json:"terminal"` - AutoRefreshMinutes int `json:"auto_refresh_minutes"` - ShowIgnored bool `json:"show_ignored"` - IgnoredPackages []string `json:"ignored_packages"` + CheckAUR bool `json:"check_aur"` + ReminderIntervalHours int `json:"reminder_interval_hours"` + Terminal string `json:"terminal"` + AutoRefreshMinutes int `json:"auto_refresh_minutes"` + ShowIgnored bool `json:"show_ignored"` + NotificationsEnabled bool `json:"notifications_enabled"` + KeepTerminalOpen bool `json:"keep_terminal_open"` + ConfirmSelectedInstalls bool `json:"confirm_selected_installs"` + IgnoredPackages []string `json:"ignored_packages"` } func Default() Config { return Config{ - CheckAUR: true, - ReminderIntervalHours: 168, - Terminal: "auto", - AutoRefreshMinutes: 30, - ShowIgnored: false, - IgnoredPackages: []string{}, + CheckAUR: true, + ReminderIntervalHours: 168, + Terminal: "auto", + AutoRefreshMinutes: 30, + ShowIgnored: false, + NotificationsEnabled: true, + KeepTerminalOpen: true, + ConfirmSelectedInstalls: true, + IgnoredPackages: []string{}, } } @@ -37,10 +43,25 @@ func Load(path string) (Config, error) { if err != nil { return cfg, err } + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return cfg, err + } if err := json.Unmarshal(data, &cfg); err != nil { return cfg, err } + defaults := Default() + if _, ok := raw["notifications_enabled"]; !ok { + cfg.NotificationsEnabled = defaults.NotificationsEnabled + } + if _, ok := raw["keep_terminal_open"]; !ok { + cfg.KeepTerminalOpen = defaults.KeepTerminalOpen + } + if _, ok := raw["confirm_selected_installs"]; !ok { + cfg.ConfirmSelectedInstalls = defaults.ConfirmSelectedInstalls + } + cfg = normalize(cfg) return cfg, nil } diff --git a/internal/gui/assets.go b/internal/gui/assets.go index cadc542..8fd5538 100644 --- a/internal/gui/assets.go +++ b/internal/gui/assets.go @@ -16,6 +16,7 @@ const indexHTML = `

Pruefe Updates...

+ @@ -114,55 +115,89 @@ const indexHTML = `
- -
-
-

Einstellungen

- -
-
- - - - - - - - - - - -
-
+ + +
+ + + + +
+ + + +
+ +
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+
` @@ -498,6 +533,105 @@ form { padding: 16px; } +dialog { + width: min(760px, calc(100vw - 28px)); + max-height: min(760px, calc(100vh - 28px)); + border: 1px solid var(--line); + border-radius: 8px; + padding: 0; + color: var(--text); + background: var(--panel); + box-shadow: 0 28px 80px rgba(0, 0, 0, 0.45); +} + +dialog::backdrop { + background: rgba(0, 0, 0, 0.62); +} + +.settings-modal { + padding: 0; +} + +.modal-head, +.modal-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 16px; +} + +.modal-head { + border-bottom: 1px solid var(--line); +} + +.modal-head p { + margin-top: 4px; + color: var(--muted); +} + +.icon-btn { + min-height: 36px; +} + +.tabs { + display: flex; + gap: 8px; + padding: 12px 16px 0; + border-bottom: 1px solid var(--line); +} + +.tab { + min-height: 38px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + color: var(--muted); + background: transparent; +} + +.tab.active { + border-color: var(--line); + border-bottom-color: var(--panel); + color: var(--text); + background: var(--panel-soft); +} + +.tab-panel { + display: none; + gap: 16px; + padding: 16px; +} + +.tab-panel.active { + display: grid; +} + +.modal-actions { + border-top: 1px solid var(--line); +} + +.ignored-settings-list { + display: grid; + gap: 10px; +} + +.ignored-setting-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--panel-soft); +} + +.empty-inline { + margin: 0; + color: var(--muted); +} + label { display: grid; gap: 8px; @@ -539,6 +673,10 @@ input[type="checkbox"] { display: grid; } + .tabs { + flex-wrap: wrap; + } + #searchInput { width: 100%; } @@ -557,6 +695,9 @@ const ignoredCount = document.querySelector("#ignoredCount"); const diskFree = document.querySelector("#diskFree"); const kernel = document.querySelector("#kernel"); const refreshBtn = document.querySelector("#refreshBtn"); +const settingsBtn = document.querySelector("#settingsBtn"); +const settingsDialog = document.querySelector("#settingsDialog"); +const closeSettingsBtn = document.querySelector("#closeSettingsBtn"); const installBtn = document.querySelector("#installBtn"); const installSelectedBtn = document.querySelector("#installSelectedBtn"); const selectAll = document.querySelector("#selectAll"); @@ -578,10 +719,16 @@ const lockState = document.querySelector("#lockState"); const form = document.querySelector("#settingsForm"); const checkAur = document.querySelector("#checkAur"); const showIgnored = document.querySelector("#showIgnored"); +const notificationsEnabled = document.querySelector("#notificationsEnabled"); +const keepTerminalOpen = document.querySelector("#keepTerminalOpen"); +const confirmSelectedInstalls = document.querySelector("#confirmSelectedInstalls"); const reminderHours = document.querySelector("#reminderHours"); const autoRefreshMinutes = document.querySelector("#autoRefreshMinutes"); const terminal = document.querySelector("#terminal"); const saveState = document.querySelector("#saveState"); +const ignoredSettingsList = document.querySelector("#ignoredSettingsList"); +const tabs = document.querySelectorAll(".tab"); +const tabPanels = document.querySelectorAll(".tab-panel"); let currentData = null; let refreshTimer = null; @@ -718,6 +865,9 @@ function render(data) { checkAur.checked = data.settings.check_aur; showIgnored.checked = data.settings.show_ignored; + notificationsEnabled.checked = data.settings.notifications_enabled; + keepTerminalOpen.checked = data.settings.keep_terminal_open; + confirmSelectedInstalls.checked = data.settings.confirm_selected_installs; reminderHours.value = data.settings.reminder_interval_hours; autoRefreshMinutes.value = data.settings.auto_refresh_minutes; terminal.value = data.settings.terminal || "auto"; @@ -727,9 +877,36 @@ function render(data) { renderTable(); renderIgnored(); + renderIgnoredSettings(); scheduleAutoRefresh(); } +function renderIgnoredSettings() { + ignoredSettingsList.replaceChildren(); + const names = currentData?.settings.ignored_packages || []; + if (names.length === 0) { + const empty = document.createElement("p"); + empty.className = "empty-inline"; + empty.textContent = "Keine ausgeblendeten Pakete."; + ignoredSettingsList.append(empty); + return; + } + + for (const name of names) { + const row = document.createElement("div"); + row.className = "ignored-setting-row"; + const label = document.createElement("span"); + label.textContent = name; + const button = document.createElement("button"); + button.type = "button"; + button.className = "ghost"; + button.textContent = "Einblenden"; + button.addEventListener("click", () => setIgnored(name, false)); + row.append(label, button); + ignoredSettingsList.append(row); + } +} + function updateSelectionUi(packages = visiblePackages()) { const selectedCount = selectedPackages.size; selectionText.textContent = selectedCount === 1 ? "1 Update ausgewaehlt" : selectedCount + " Updates ausgewaehlt"; @@ -768,6 +945,8 @@ async function setIgnored(name, ignored) { } refreshBtn.addEventListener("click", loadStatus); +settingsBtn.addEventListener("click", () => settingsDialog.showModal()); +closeSettingsBtn.addEventListener("click", () => settingsDialog.close()); searchInput.addEventListener("input", renderTable); sourceFilter.addEventListener("change", renderTable); selectAll.addEventListener("change", () => { @@ -799,9 +978,11 @@ installSelectedBtn.addEventListener("click", async () => { if (packages.length === 0) { return; } - const ok = window.confirm("Ausgewaehlte Pakete gezielt installieren? Unter Arch ist ein vollstaendiges Systemupdate meistens sicherer."); - if (!ok) { - return; + if (currentData?.settings.confirm_selected_installs) { + const ok = window.confirm("Ausgewaehlte Pakete gezielt installieren? Unter Arch ist ein vollstaendiges Systemupdate meistens sicherer."); + if (!ok) { + return; + } } installSelectedBtn.disabled = true; @@ -827,6 +1008,9 @@ form.addEventListener("submit", async (event) => { body: JSON.stringify({ check_aur: checkAur.checked, show_ignored: showIgnored.checked, + notifications_enabled: notificationsEnabled.checked, + keep_terminal_open: keepTerminalOpen.checked, + confirm_selected_installs: confirmSelectedInstalls.checked, reminder_interval_hours: Number(reminderHours.value), auto_refresh_minutes: Number(autoRefreshMinutes.value), terminal: terminal.value, @@ -840,5 +1024,17 @@ form.addEventListener("submit", async (event) => { } }); +for (const tab of tabs) { + tab.addEventListener("click", () => { + const target = tab.dataset.tab; + for (const item of tabs) { + item.classList.toggle("active", item === tab); + } + for (const panel of tabPanels) { + panel.classList.toggle("active", panel.dataset.panel === target); + } + }); +} + loadStatus(); ` diff --git a/internal/gui/server.go b/internal/gui/server.go index 5577ac3..0b97663 100644 --- a/internal/gui/server.go +++ b/internal/gui/server.go @@ -312,20 +312,24 @@ func kernelVersion() string { } func terminalCommand(cfg config.Config, packages []string) (string, []string, error) { - updateCommand := "lazy-update-manager update; printf '\\nDone. Press enter to close... '; read _" + hold := "" + if cfg.KeepTerminalOpen { + hold = "; printf '\\nDone. Press enter to close... '; read _" + } + updateCommand := "lazy-update-manager update" + hold if len(packages) > 0 { selected := shellPackageList(packages) if selected == "" { return "", nil, errors.New("no valid packages selected") } - updateCommand = "sudo pacman -S --needed " + selected + "; printf '\\nDone. Press enter to close... '; read _" + updateCommand = "sudo pacman -S --needed " + selected + hold if helper := updater.AURHelper(); helper != "" && cfg.CheckAUR { - updateCommand = helper + " -S --needed " + selected + "; printf '\\nDone. Press enter to close... '; read _" + updateCommand = helper + " -S --needed " + selected + hold } } if helper := updater.AURHelper(); helper != "" && cfg.CheckAUR { if len(packages) == 0 { - updateCommand = helper + " -Syu; printf '\\nDone. Press enter to close... '; read _" + updateCommand = helper + " -Syu" + hold } }