From b8400f38411818aa54a773dffb2c3a0c56b06dba Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 19 Oct 2026 20:53:43 +0200 Subject: Add Packages tab, hint view, Run it and signing key unlock Packages tab over the helper inventory: query bar with cat:/maint:/ req:/status: prefixes, overlay and status chips, check column, and a detail pane with versions, dependencies after hints and a structured read-only view of the hint (hints.py mirrors slackrepo's parser). Actions start slackrepo previews; the Jobs tab offers Run it with a publish box (default from [builds] publish), asking for the signing key passphrase first when the key is locked. Status bar shows the key state; Dashboard tiles open their tabs; inventory reloads after slackrepo and publish jobs. Co-Authored-By: Claude Opus 5.5 --- sb_builder/jobs.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) (limited to 'sb_builder/jobs.py') diff --git a/sb_builder/jobs.py b/sb_builder/jobs.py index bcbd812..b413597 100644 --- a/sb_builder/jobs.py +++ b/sb_builder/jobs.py @@ -21,9 +21,9 @@ import re from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QFontDatabase, QTextCursor -from PyQt6.QtWidgets import (QHBoxLayout, QListWidget, QListWidgetItem, QMenu, - QMessageBox, QPlainTextEdit, QPushButton, QSplitter, - QToolButton, QVBoxLayout, QWidget) +from PyQt6.QtWidgets import (QCheckBox, QHBoxLayout, QInputDialog, QLineEdit, QListWidget, + QListWidgetItem, QMenu, QMessageBox, QPlainTextEdit, QPushButton, + QSplitter, QToolButton, QVBoxLayout, QWidget) from .fmt import describe @@ -36,9 +36,10 @@ BAD = ("failed", "killed", "lost") class JobsTab(QWidget): refresh_requested = pyqtSignal() - def __init__(self, remote, parent=None): + def __init__(self, remote, publish=True, parent=None): super().__init__(parent) self.remote = remote + self.pending = () self.jobs = {} self.current = None self.offset = 0 @@ -52,10 +53,15 @@ class JobsTab(QWidget): self.apply_btn.clicked.connect(lambda: self.confirm_start( "Apply the shadowing plan to the repo (sbshadow --yes)?", "sbshadow", "--yes")) self.apply_btn.hide() + self.publish_box = QCheckBox("publish", checked=publish) + self.publish_box.setToolTip("Push to the mirror when the build ends; " + "unticked, use Publish later") + self.run_btn = QPushButton("Run it") + self.run_btn.clicked.connect(self.run_preview) self.kill_btn = QPushButton("Kill") self.kill_btn.clicked.connect(self.kill) bar = QHBoxLayout() - for w in (start, self.apply_btn): + for w in (start, self.apply_btn, self.run_btn, self.publish_box): bar.addWidget(w) bar.addStretch() bar.addWidget(self.kill_btn) @@ -131,6 +137,7 @@ class JobsTab(QWidget): p.deleteLater() def start(self, *args): + self.pending = args self.remote.call(self._started, "job-start", *args) def confirm_start(self, question, *args): @@ -143,7 +150,49 @@ class JobsTab(QWidget): self.remote.call(lambda _d, err: err and QMessageBox.warning(self, "Kill failed", err), "job-kill", self.current) + def run_preview(self): + """Start the previewed slackrepo run for real.""" + args = [a for a in self.jobs[self.current]["args"] if a != "--preview"] + if not self.publish_box.isChecked(): + args.insert(1, "--no-publish") + self.start_signed("slackrepo", *args) + + def ask_passphrase(self, retry): + text, ok = QInputDialog.getText( + self, "Signing key", ("Wrong passphrase, try again." if retry else + "Passphrase of the package signing key:"), + QLineEdit.EchoMode.Password) + return text if ok else None + + def start_signed(self, *args, retry=False): + """Unlock the signing key first when it is locked, then start the job.""" + def status(data, err): + if err: + QMessageBox.warning(self, "Signing key", err) + elif data["unlocked"] is False: + self._unlock(args, retry) + else: + self.start(*args) + self.remote.call(status, "gpg-status") + + def _unlock(self, args, retry): + passphrase = self.ask_passphrase(retry) + if passphrase is None: + return + + def unlocked(_data, err): + if err == "bad passphrase": + self._unlock(args, True) + elif err: + QMessageBox.warning(self, "Signing key", err) + else: + self.start(*args) + self.remote.call(unlocked, "gpg-unlock", stdin=passphrase.encode() + b"\n") + def _started(self, data, err): + if err == "signing key locked": + self.start_signed(*self.pending) # locked since the last check: ask again + return if err: QMessageBox.warning(self, "Start failed", err) return @@ -198,3 +247,7 @@ class JobsTab(QWidget): self.apply_btn.setVisible( bool(j) and j["status"] == "ok" and "--no" in j["args"] and bool(SHADOW_PLAN.search(self.log.toPlainText()))) + preview = bool(j) and j["kind"] == "slackrepo" and j["status"] == "ok" \ + and "--preview" in j["args"] + self.run_btn.setVisible(preview) + self.publish_box.setVisible(preview) -- cgit v1.2.3