// 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.fetch(false); } 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: "" Item { width: col.width height: Math.max(status.implicitHeight, reload.height) Text { id: status anchors { left: parent.left; right: reload.left; rightMargin: 8; verticalCenter: parent.verticalCenter } 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 Reload." color: Sbb.reachable || Sbb.busy ? Theme.subtext : Theme.yellow font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } Button { id: reload anchors { right: parent.right; verticalCenter: parent.verticalCenter } text: "Reload" enabled: !Sbb.busy onClicked: Sbb.fetch(false) } } Column { width: col.width spacing: 6 // Read-only while a save runs too: typing then would be lost or mis-recorded. enabled: Sbb.reachable && !Sbb.busy 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." + (col.st.manifestsDefault ? " These are the helper's defaults, not in the file: the first change writes them all, then adds or removes its own." : "") 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 placeholder: "a URL; Remove deletes the row" } 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 = ""; } } } } } } } }