aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-20 20:21:34 +0200
committerDanilo M. <danix@danix.xyz>2026-10-20 20:21:34 +0200
commit91f882df97adea262625a45be0257039f144ae49 (patch)
treed7aee7910f79b63ac049579dcf56ce7b8b1b7b7b
parent552d8e5bb6ba6537b7031318bc5a8d7e92c971b4 (diff)
downloadsb-builder-91f882df97adea262625a45be0257039f144ae49.tar.gz
sb-builder-91f882df97adea262625a45be0257039f144ae49.zip
sbb-remote 0.9.0: SBo orphans tile on the Dashboard
stats gains "orphans", the packages whose .info says MAINTAINER="orphaned - no maintainer" (0.14s over the tree on the VM). The placeholder tile shows the count and opens Packages with only the orphaned chip on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--README.md5
-rwxr-xr-xremote/sbb-remote11
-rw-r--r--sb_builder/__init__.py2
-rw-r--r--sb_builder/dashboard.py5
-rw-r--r--sb_builder/mainwindow.py1
-rw-r--r--sb_builder/packages.py8
-rw-r--r--tests/test_gui.py8
-rw-r--r--tests/test_remote.py7
8 files changed, 40 insertions, 7 deletions
diff --git a/README.md b/README.md
index bb1ff56..9356f8e 100644
--- a/README.md
+++ b/README.md
@@ -52,8 +52,9 @@ now** loses nothing: **Run it** in the Jobs tab, on a finished preview,
reopens it.
- **Dashboard**: VM load, uptime and free disk, package counts, slackrepo
- build results, last overlay pull, recent jobs. Refreshes while visible.
- The tiles open Packages, Results (one result kind) and Jobs.
+ build results, last overlay pull, SBo orphans, recent jobs. Refreshes while
+ visible. The tiles open Packages (the orphans tile with only the orphaned
+ filter on), Results (one result kind) and Jobs.
- **Packages**: every package of the repository, with built version and
status (`ok`, `failed`, `never`, `outdated`). Search by name or summary, or
with `cat:`, `maint:`, `req:`, `status:`; toggle the overlay and status
diff --git a/remote/sbb-remote b/remote/sbb-remote
index 5f01186..1c87f80 100755
--- a/remote/sbb-remote
+++ b/remote/sbb-remote
@@ -40,7 +40,7 @@ import time
import tomllib
import urllib.request
-VERSION = "0.8.0"
+VERSION = "0.9.0"
DEFAULTS = {
"state_dir": "/var/lib/sb-builder",
"repo_dir": "/var/lib/sbopkg/SBo-danix",
@@ -416,6 +416,14 @@ def _packages(cfg):
"shadowed": len(shadowed)}
+def _orphans(cfg):
+ """Packages SBo marks MAINTAINER="orphaned - no maintainer"; None without a repo."""
+ if not os.path.isdir(cfg["repo_dir"]):
+ return None
+ return sum(re.search(r'^MAINTAINER="orphaned', _read(os.path.join(d, os.path.basename(d) + ".info")) or "",
+ re.M) is not None for d in _pkgdirs(cfg["repo_dir"], "*/*"))
+
+
def _slackrepo(cfg):
db = sqlite3.connect(f"file:{cfg['slackrepo_db']}?mode=ro", uri=True)
try:
@@ -478,6 +486,7 @@ def cmd_stats(cfg):
"disk": {"total": root and root.total, "free": root and root.free},
"slackpkg": _slackpkg(),
"packages": _try(lambda: _packages(cfg)),
+ "orphans": _try(lambda: _orphans(cfg)),
"sync": {o: _try(lambda o=o: int(_git(cfg, "log", "-1", "--format=%ct", "--", o + "/")))
for o in cfg["overlays"]},
"slackrepo": _try(lambda: _slackrepo(cfg)),
diff --git a/sb_builder/__init__.py b/sb_builder/__init__.py
index e4a5697..c845154 100644
--- a/sb_builder/__init__.py
+++ b/sb_builder/__init__.py
@@ -16,4 +16,4 @@
"""sb-builder: Qt6 GUI driving the SlackBuild tooling on a build VM."""
-VERSION = "0.8.0" # must match remote/sbb-remote VERSION
+VERSION = "0.9.0" # must match remote/sbb-remote VERSION
diff --git a/sb_builder/dashboard.py b/sb_builder/dashboard.py
index 3096fb8..9b04183 100644
--- a/sb_builder/dashboard.py
+++ b/sb_builder/dashboard.py
@@ -278,9 +278,7 @@ class Dashboard(QWidget):
self.results.body.addWidget(self.legend)
self.results.body.addStretch()
- self.orphans = Card("SBo orphans")
- self.orphans.body.addWidget(muted(QLabel("coming in phase 5")))
- self.orphans.body.addStretch()
+ self.orphans = Tile("SBo orphans")
self.jobs = Card("Recent jobs")
self.jobs_list = QLabel("no jobs yet")
@@ -320,6 +318,7 @@ class Dashboard(QWidget):
else:
extra = " · ".join(f"{num(v)}\u00a0{k}" for k, v in p.items() if k != "total")
t["Packages"].set(num(p["total"]), extra)
+ self.orphans.set(num(s.get("orphans")), "packages with no maintainer on SBo")
if sr is None:
t["Built"].set(NA, "no slackrepo database")
else:
diff --git a/sb_builder/mainwindow.py b/sb_builder/mainwindow.py
index ec2b87a..3860079 100644
--- a/sb_builder/mainwindow.py
+++ b/sb_builder/mainwindow.py
@@ -108,6 +108,7 @@ class MainWindow(QMainWindow):
self.dashboard = Dashboard()
self.dashboard.refresh.triggered.connect(self.poll_stats)
self.dashboard.tiles["Packages"].link(self.open_packages)
+ self.dashboard.orphans.link(lambda: self.open_packages().filter_to("orphaned"))
self.dashboard.jobs.link(self.open_jobs)
self.dashboard.result_clicked.connect(self.open_results)
self.jobs_tab = JobsTab(self.remote)
diff --git a/sb_builder/packages.py b/sb_builder/packages.py
index 68de7c2..f1a7224 100644
--- a/sb_builder/packages.py
+++ b/sb_builder/packages.py
@@ -319,6 +319,14 @@ class PackagesTab(QWidget):
self.model.set_rows(load_rows(data))
self.update_count()
+ def filter_to(self, name):
+ """Show only one chip's packages (from a Dashboard click)."""
+ for k, c in self.chips.items():
+ c.blockSignals(True)
+ c.setChecked(k == name)
+ c.blockSignals(False)
+ self.refilter()
+
def refilter(self):
on = {n for n, b in self.chips.items() if b.isChecked()}
self.proxy.update(self.query.text(), on - set(STATUSES) - {"orphaned"}, on & set(STATUSES),
diff --git a/tests/test_gui.py b/tests/test_gui.py
index beec1fe..d005447 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -358,6 +358,14 @@ class SmokeTest(unittest.TestCase):
self.assertEqual(w.conn.text(), "● VM off")
self.assertFalse(w.vm_btn.isHidden())
+ def test_orphans_tile_opens_filtered_packages(self):
+ w, _ = self.window(stats=(dict(STATS, orphans=1936), None))
+ w.poll_jobs()
+ self.assertEqual(w.dashboard.orphans.value.text(), "1,936")
+ w.dashboard.orphans.clicked.emit()
+ self.assertIs(w.tabs.currentWidget(), w.packages_tab)
+ self.assertEqual([n for n, c in w.packages_tab.chips.items() if c.isChecked()], ["orphaned"])
+
def test_slackpkg_dot(self):
old = dict(STATS, slackpkg={"update": None, "upgrade": 1})
w, _ = self.window(stats=(old, None))
diff --git a/tests/test_remote.py b/tests/test_remote.py
index 30bdc73..7288828 100644
--- a/tests/test_remote.py
+++ b/tests/test_remote.py
@@ -779,12 +779,19 @@ class JobsTest(Base):
class StatsTest(Base):
+ def test_orphans(self):
+ self.make_pkg("net/a", 'MAINTAINER="orphaned - no maintainer"\nEMAIL="NOEMAIL"\n')
+ self.make_pkg("net/b", 'MAINTAINER="Someone"\n')
+ self.make_pkg("personal/c", 'PRGNAM="c"\nMAINTAINER="orphaned - no maintainer"\n')
+ self.assertEqual(sbb._orphans(self.cfg), 2)
+
def test_missing_sources_are_null(self):
s = sbb.cmd_stats(self.cfg)
self.assertIsNotNone(s["load"])
self.assertEqual(set(s["disk"]), {"total", "free"})
self.assertIn("slackpkg", s)
self.assertIsNone(s["packages"])
+ self.assertIsNone(s["orphans"])
self.assertIsNone(s["slackrepo"])
self.assertEqual(s["sync"], {"personal": None, "pentesting": None})
json.dumps(s)