feat: add tabbed settings dialog

This commit is contained in:
2026-05-03 23:12:40 +02:00
parent e8d4d2e400
commit 3df489ba6a
5 changed files with 291 additions and 65 deletions

View File

@@ -16,6 +16,7 @@ const indexHTML = `<!doctype html>
<p id="summary">Pruefe Updates...</p>
</div>
<div class="actions">
<button id="settingsBtn" type="button">Einstellungen</button>
<button id="refreshBtn" type="button" title="Updates pruefen">Refresh</button>
<button id="installSelectedBtn" type="button" disabled>Auswahl installieren</button>
<button id="installBtn" type="button">Alle installieren</button>
@@ -114,55 +115,89 @@ const indexHTML = `<!doctype html>
</div>
</dl>
</section>
<section class="panel settings-panel">
<div class="panel-head">
<h2>Einstellungen</h2>
<span id="saveState"></span>
</div>
<form id="settingsForm">
<label class="toggle">
<input id="checkAur" type="checkbox">
<span>AUR Updates pruefen</span>
</label>
<label class="toggle">
<input id="showIgnored" type="checkbox">
<span>Ausgeblendete anzeigen</span>
</label>
<label>
<span>Erinnerung alle</span>
<input id="reminderHours" type="number" min="1" step="1">
<small>Stunden</small>
</label>
<label>
<span>Auto-Refresh</span>
<input id="autoRefreshMinutes" type="number" min="0" step="1">
<small>Minuten, 0 deaktiviert</small>
</label>
<label>
<span>Terminal</span>
<select id="terminal">
<option value="auto">Automatisch</option>
<option value="foot">foot</option>
<option value="kitty">kitty</option>
<option value="alacritty">alacritty</option>
<option value="wezterm">wezterm</option>
<option value="ghostty">ghostty</option>
<option value="konsole">konsole</option>
<option value="xterm">xterm</option>
</select>
</label>
<button type="submit">Speichern</button>
</form>
</section>
</aside>
</section>
</main>
<dialog id="settingsDialog">
<form id="settingsForm" method="dialog" class="settings-modal">
<header class="modal-head">
<div>
<h2>Einstellungen</h2>
<p>Update-Verhalten, Installation und ausgeblendete Pakete</p>
</div>
<button id="closeSettingsBtn" type="button" class="icon-btn">Schliessen</button>
</header>
<nav class="tabs" aria-label="Einstellungen">
<button class="tab active" type="button" data-tab="general">Allgemein</button>
<button class="tab" type="button" data-tab="notifications">Benachrichtigungen</button>
<button class="tab" type="button" data-tab="install">Installation</button>
<button class="tab" type="button" data-tab="ignored">Ausgeblendet</button>
</nav>
<section class="tab-panel active" data-panel="general">
<label class="toggle">
<input id="checkAur" type="checkbox">
<span>AUR Updates pruefen</span>
</label>
<label class="toggle">
<input id="showIgnored" type="checkbox">
<span>Ausgeblendete Updates in der App anzeigen</span>
</label>
<label>
<span>Auto-Refresh</span>
<input id="autoRefreshMinutes" type="number" min="0" step="1">
<small>Minuten, 0 deaktiviert</small>
</label>
</section>
<section class="tab-panel" data-panel="notifications">
<label class="toggle">
<input id="notificationsEnabled" type="checkbox">
<span>Woechentliche Erinnerungen senden</span>
</label>
<label>
<span>Erinnerung alle</span>
<input id="reminderHours" type="number" min="1" step="1">
<small>Stunden</small>
</label>
</section>
<section class="tab-panel" data-panel="install">
<label>
<span>Terminal</span>
<select id="terminal">
<option value="auto">Automatisch</option>
<option value="foot">foot</option>
<option value="kitty">kitty</option>
<option value="alacritty">alacritty</option>
<option value="wezterm">wezterm</option>
<option value="ghostty">ghostty</option>
<option value="konsole">konsole</option>
<option value="xterm">xterm</option>
</select>
</label>
<label class="toggle">
<input id="keepTerminalOpen" type="checkbox">
<span>Terminal nach Installationen offen lassen</span>
</label>
<label class="toggle">
<input id="confirmSelectedInstalls" type="checkbox">
<span>Selektive Installationen bestaetigen</span>
</label>
</section>
<section class="tab-panel" data-panel="ignored">
<div id="ignoredSettingsList" class="ignored-settings-list"></div>
</section>
<footer class="modal-actions">
<span id="saveState"></span>
<button type="submit">Speichern</button>
</footer>
</form>
</dialog>
<script src="/app.js"></script>
</body>
</html>`
@@ -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();
`

View File

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