From 82ddf40282e7dc913a241a8f31cb2855b1bb74da Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Wed, 21 Oct 2026 10:13:32 +0200 Subject: sbbuilder: Desktop and Build VM settings pages Under SB Builder in the settings window. The desktop page edits sb-builder's config.toml in place; VM control is a switch because an emptied field means the default domain, not "". The VM page is read-only until the helper answers config-get. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/sbbuilder/SbbDesktopModule.qml | 147 +++++++++++++++++++++ desktop/modules/sbbuilder/SbbVmModule.qml | 175 +++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 desktop/modules/sbbuilder/SbbDesktopModule.qml create mode 100644 desktop/modules/sbbuilder/SbbVmModule.qml (limited to 'desktop/modules') diff --git a/desktop/modules/sbbuilder/SbbDesktopModule.qml b/desktop/modules/sbbuilder/SbbDesktopModule.qml new file mode 100644 index 0000000..be4ad22 --- /dev/null +++ b/desktop/modules/sbbuilder/SbbDesktopModule.qml @@ -0,0 +1,147 @@ +// Copyright (C) 2026 Danilo M. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import QtQuick +import "../.." +import "../qtmaildir" + +// sb-builder's GUI config on this desktop. The GUI watches the file, so a +// save applies to a running sb-builder at once. +Module { + name: "sbb-desktop" + label: "Desktop" + // nf-fa-desktop, present in Inconsolata Nerd Font. + icon: "\uf108" + settings: true + group: "SB Builder" + dirty: Sbb.desktop.dirty + keywords: ["sb-builder", "slackbuild", "ssh", "refresh", "notifications", "libvirt", "publish", + "nvchecker", "repository"] + readonly property var fields: [ + ["remote", "target", "Target"], ["remote", "ssh_options", "ssh options"], + ["refresh", "dashboard", "Dashboard refresh"], ["refresh", "jobs", "Jobs refresh"], + ["notify", "enabled", "Notifications"], ["vm", "libvirt_uri", "libvirt URI"], + ["vm", "control", "VM control"], ["vm", "domain", "Domain"], + ["vm", "autostart", "Start the VM"], + ["builds", "publish", "Publish"], ["hints", "nvchecker_keyfile", "nvchecker keyfile"], + ["repository", "path", "Repository"]] + searchItems: fields.map(f => ({ text: f[2], key: f[0] + "/" + f[1] })) + function refresh() { Sbb.refresh(); } + + page: Component { + Page { + title: "SB Builder: desktop" + actions: Component { + SaveBar { + dirty: Sbb.desktop.dirty; valid: Sbb.desktop.valid; error: Sbb.desktop.error + onSave: Sbb.saveDesktop(); onDiscard: Sbb.desktop.discard() + } + } + Column { + id: col + width: parent.width + spacing: 6 + readonly property var st: Sbb.desktop + Text { + width: col.width + wrapMode: Text.Wrap + text: Sbb.path + ". A running sb-builder rereads it on save." + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + + Heading { text: "Build VM connection" } + Setting { store: col.st; section: "remote"; key: "target"; label: "Target" + placeholder: col.st.placeholder("remote", "target"); hint: "Any ssh destination: a host alias or user@host." } + Setting { store: col.st; section: "remote"; key: "ssh_options"; label: "ssh options" + hint: "Extra ssh arguments, separated by spaces: -p 2222." } + + Heading { text: "Refresh" } + Setting { store: col.st; section: "refresh"; key: "dashboard"; label: "Dashboard refresh" + placeholder: col.st.placeholder("refresh", "dashboard"); kind: "int"; min: 1; max: 3600 + hint: "Seconds, while the Dashboard tab is visible." } + Setting { store: col.st; section: "refresh"; key: "jobs"; label: "Jobs refresh" + placeholder: col.st.placeholder("refresh", "jobs"); kind: "int"; min: 1; max: 3600 + hint: "Seconds; doubles up to 30 while the VM is down." } + + Heading { text: "Notifications" } + BoolSetting { store: col.st; section: "notify"; key: "enabled"; label: "Notifications"; fallback: true } + Repeater { + model: col.st.fallback("notify", "events") + delegate: Item { + id: ev + required property string modelData + objectName: "notify/events/" + modelData + width: col.width + height: 34 + Text { + id: evLabel + anchors { left: parent.left; leftMargin: 16; verticalCenter: parent.verticalCenter } + width: 144 + text: ev.modelData + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + Switch { + anchors { left: evLabel.right; leftMargin: 8; verticalCenter: parent.verticalCenter } + checked: col.st.value("notify", "events").includes(ev.modelData) + onToggled: { + const all = col.st.fallback("notify", "events"); + const on = col.st.value("notify", "events").filter(e => e !== ev.modelData); + if (checked) on.push(ev.modelData); + col.st.put("notify", "events", all.filter(e => on.includes(e))); + } + } + } + } + + Heading { text: "VM control" } + Setting { store: col.st; section: "vm"; key: "libvirt_uri"; label: "libvirt URI" + placeholder: col.st.placeholder("vm", "libvirt_uri") } + Item { + objectName: "vm/control" + width: col.width + height: 34 + Text { + id: ctlLabel + anchors { left: parent.left; verticalCenter: parent.verticalCenter } + width: 160 + text: "VM control" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + Switch { + anchors { left: ctlLabel.right; leftMargin: 8; verticalCenter: parent.verticalCenter } + // "" is how sb-builder spells no VM control; an + // emptied field means the default instead. + checked: col.st.value("vm", "domain") !== "" + onToggled: col.st.put("vm", "domain", checked ? null : "") + } + } + Setting { store: col.st; section: "vm"; key: "domain"; label: "Domain" + visible: col.st.value("vm", "domain") !== "" + placeholder: col.st.placeholder("vm", "domain"); hint: "The VM's libvirt domain." } + BoolSetting { store: col.st; section: "vm"; key: "autostart"; label: "Start the VM"; fallback: false + hint: "without asking when it is off" } + + Heading { text: "Builds and hints" } + BoolSetting { store: col.st; section: "builds"; key: "publish"; label: "Publish"; fallback: true + hint: "the initial state of the publish box next to Run it" } + Setting { store: col.st; section: "hints"; key: "nvchecker_keyfile"; label: "nvchecker keyfile" + placeholder: col.st.placeholder("hints", "nvchecker_keyfile") + hint: "A GitHub token for nvchecker; optional." } + Setting { store: col.st; section: "repository"; key: "path"; label: "Repository" + placeholder: col.st.placeholder("repository", "path") + hint: "Local mount of slackrepo's package repository, for the Repository tab." } + } + } + } +} diff --git a/desktop/modules/sbbuilder/SbbVmModule.qml b/desktop/modules/sbbuilder/SbbVmModule.qml new file mode 100644 index 0000000..8b9cebc --- /dev/null +++ b/desktop/modules/sbbuilder/SbbVmModule.qml @@ -0,0 +1,175 @@ +// Copyright (C) 2026 Danilo M. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import QtQuick +import "../.." +import "../qtmaildir" + +// sbb-remote's config on the build VM, through the helper's config-get and +// config-set. The helper reads it on every call, so a save applies to the +// next one. +Module { + name: "sbb-vm" + label: "Build VM" + // nf-fa-server, present in Inconsolata Nerd Font. + icon: "\uf233" + settings: true + group: "SB Builder" + dirty: Sbb.vm.dirty + keywords: ["sb-builder", "sbb-remote", "slackrepo", "hints", "overlays", "manifests", "phantom", "gpg"] + readonly property var fields: [ + ["state_dir", "State"], ["repo_dir", "SlackBuilds repo"], ["upstream_branch", "Upstream branch"], + ["slackrepo_db", "slackrepo database"], ["sbpull_script", "sbpull script"], ["overlays", "Overlays"], + ["slackrepo_repo", "slackrepo repo"], ["hintdir", "Hints"], ["default_hintdir", "Default hints"], + ["genreprc", "genreprc"], ["slackrepo_logdir", "slackrepo logs"], ["phantom_deps_file", "Phantom deps"]] + searchItems: fields.map(f => ({ text: f[1], key: "/" + f[0] })) + .concat(Sbb.vm.manifests.map(p => ({ text: p + " manifest", key: "manifests/" + p }))) + function refresh() { Sbb.refresh(); } + + page: Component { + Page { + title: "SB Builder: build VM" + actions: Component { + SaveBar { + dirty: Sbb.vm.dirty; valid: Sbb.vm.valid && Sbb.reachable; error: Sbb.vm.error + busy: Sbb.busy + onSave: Sbb.saveVm(); onDiscard: Sbb.vm.discard() + } + } + Column { + id: col + width: parent.width + spacing: 6 + readonly property var st: Sbb.vm + property string confirming: "" + Text { + width: col.width + wrapMode: Text.Wrap + text: Sbb.busy ? "Asking the VM..." + : Sbb.reachable ? Sbb.vmPath + " on the VM. The helper checks a save and keeps the old file as .bak." + : "The VM did not answer, so nothing here can change. Start it, then reopen this page." + color: Sbb.reachable || Sbb.busy ? Theme.subtext : Theme.yellow + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + + Column { + width: col.width + spacing: 6 + enabled: Sbb.reachable + opacity: Sbb.reachable ? 1 : 0.5 + Repeater { + model: [ + ["Paths"], + ["state_dir", "State", "Job logs and the lock."], + ["repo_dir", "SlackBuilds repo", ""], + ["slackrepo_db", "slackrepo database", ""], + ["slackrepo_logdir", "slackrepo logs", ""], + ["sbpull_script", "sbpull script", ""], + ["genreprc", "genreprc", "Its REPOSOWNERGPG names the signing key."], + ["Repository"], + ["upstream_branch", "Upstream branch", ""], + ["overlays", "Overlays", "Separated by spaces, in search order."], + ["slackrepo_repo", "slackrepo repo", ""], + ["Hints"], + ["hintdir", "Hints", ""], + ["default_hintdir", "Default hints", "Searched after Hints when set."], + ["phantom_deps_file", "Phantom deps", "Deps -current ships, one per line."]] + delegate: Loader { + id: row + required property var modelData + width: col.width + sourceComponent: row.modelData.length === 1 ? heading : field + Component { id: heading; Heading { text: row.modelData[0] } } + Component { + id: field + Setting { store: col.st; section: ""; key: row.modelData[0]; label: row.modelData[1] + hint: row.modelData[2]; placeholder: col.st.placeholder("", row.modelData[0]) } + } + } + } + + Heading { text: "Bundled deps" } + Text { + width: col.width + wrapMode: Text.Wrap + text: "A package's upstream deps list; {VERSION} is the new version. A hint update moves the package's extra downloads to the versions listed there." + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + Repeater { + model: col.st.manifests + delegate: Item { + id: mrow + required property string modelData + objectName: "manifests/" + modelData + width: col.width + height: url.height + Setting { + id: url + width: parent.width - del.width - 8 + store: col.st; section: "manifests"; key: mrow.modelData; label: mrow.modelData + } + Button { + id: del + anchors { right: parent.right; top: parent.top; topMargin: 4 } + danger: true + text: col.confirming === mrow.modelData ? "Confirm" : "Remove" + onClicked: { + if (col.confirming === mrow.modelData) { col.confirming = ""; col.st.remove("manifests", mrow.modelData); } + else col.confirming = mrow.modelData; + } + } + } + } + Item { + width: col.width + height: 34 + Rectangle { + id: pkgBox + anchors { left: parent.left; right: add.left; rightMargin: 8; top: parent.top; bottom: parent.bottom } + radius: 8 + color: Theme.surface + TextInput { + id: pkg + activeFocusOnTab: true + anchors { fill: parent; leftMargin: 10; rightMargin: 10 } + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + onAccepted: add.clicked(null) + Text { + anchors.verticalCenter: parent.verticalCenter + visible: parent.text === "" + text: "package name, then its URL in the new row" + color: Theme.overlay + font: parent.font + } + } + } + Button { + id: add + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + text: "Add" + enabled: /^[A-Za-z0-9_.+-]+$/.test(pkg.text) && !col.st.manifests.includes(pkg.text) + onClicked: { + if (!add.enabled) return; + col.st.put("manifests", pkg.text, "https://"); + pkg.text = ""; + } + } + } + } + } + } + } +} -- cgit v1.2.3