diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-21 10:07:37 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-21 10:07:42 +0200 |
| commit | 28b7d44e2d29d14ce2b9b30adecdf1bff3a71bf6 (patch) | |
| tree | 5f339562dc04f207c59c70151d7379d594e40dd8 | |
| parent | 6b0f67655b40fef221f9c84cfb6c0bcedf34e3bb (diff) | |
| download | sb-builder-28b7d44e2d29d14ce2b9b30adecdf1bff3a71bf6.tar.gz sb-builder-28b7d44e2d29d14ce2b9b30adecdf1bff3a71bf6.zip | |
Repository: stats as Dashboard tiles
Packages, Size, Unpublished (opens the unpublished filter), Last publish,
in place of the one-line summary. CARD_STYLE moves to dashboard.py so
both tabs share it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | sb_builder/dashboard.py | 7 | ||||
| -rw-r--r-- | sb_builder/repository.py | 34 | ||||
| -rw-r--r-- | tests/test_gui.py | 13 |
4 files changed, 37 insertions, 21 deletions
@@ -126,8 +126,8 @@ reopens it. the failure, with its `resource.log` (load, memory, tmp over the build). - **Repository**: the packages slackrepo built, read from the local mount of its package repository (`[repository] path`, default `/repo`; no VM - needed). A stats line (count, size, per overlay, unpublished, last - publish), search by name or description, overlay and `unpublished` chips; + needed). Tiles for count (per overlay), size, unpublished (opens that + filter) and last publish; search by name or description, overlay and `unpublished` chips; the detail pane shows the description, sizes and the package's ChangeLog entries, with a link to it in Packages. - **Jobs**: every job with its live log. **Pull overlay** (`sbpull <overlay>`) diff --git a/sb_builder/dashboard.py b/sb_builder/dashboard.py index 9b04183..3280168 100644 --- a/sb_builder/dashboard.py +++ b/sb_builder/dashboard.py @@ -61,6 +61,10 @@ def muted(label): return label +CARD_STYLE = ("QFrame#card { background: palette(base);" + " border: 1px solid palette(mid); border-radius: 10px; }") + + class Card(QFrame): """Rounded panel with a muted title; content goes into self.body.""" @@ -254,8 +258,7 @@ class Dashboard(QWidget): def __init__(self, parent=None): super().__init__(parent) - self.setStyleSheet("QFrame#card { background: palette(base);" - " border: 1px solid palette(mid); border-radius: 10px; }") + self.setStyleSheet(CARD_STYLE) self.tiles = {name: Tile(name) for name in ("Packages", "Built", "Last pull", "slackpkg")} self.vm = Card("VM") diff --git a/sb_builder/repository.py b/sb_builder/repository.py index 38c6ecc..1eae22e 100644 --- a/sb_builder/repository.py +++ b/sb_builder/repository.py @@ -27,7 +27,8 @@ from collections import Counter from PyQt6.QtCore import QAbstractTableModel, QModelIndex, QSortFilterProxyModel, Qt, pyqtSignal from PyQt6.QtWidgets import QHBoxLayout, QLabel, QScrollArea, QSplitter, QTableView, QVBoxLayout, QWidget -from .fmt import NA, fmt_bytes, fmt_time +from .dashboard import CARD_STYLE, Tile +from .fmt import NA, fmt_age, fmt_bytes, fmt_time from .packages import OVERLAYS, table from .ui import SP, action, chip, group_label, search_field @@ -124,16 +125,15 @@ def overlay(row): def summary(data): - """The stats line above the table.""" - rows = data["rows"] + """{tile title: (value, detail)} for the tiles above the table.""" + rows, published = data["rows"], data["published"] by = Counter(overlay(r) for r in rows) usize = sum(r["usize"] or 0 for r in rows) - return " · ".join([ - f"{len(rows):,} packages", f"{fmt_bytes(sum(r['size'] for r in rows))} on disk", - f"{fmt_bytes(usize)} uncompressed", - *(f"{by[o]} {o}" for o in (*OVERLAYS, "upstream")), - f"{sum(not r['published'] for r in rows)} unpublished", - f"published {fmt_time(data['published']) if data['published'] else NA}"]) + return { + "Packages": (f"{len(rows):,}", " · ".join(f"{by[o]} {o}" for o in (*OVERLAYS, "upstream"))), + "Size": (fmt_bytes(sum(r["size"] for r in rows)), f"{fmt_bytes(usize)} uncompressed"), + "Unpublished": (str(sum(not r["published"] for r in rows)), "built, not in PACKAGES.TXT"), + "Last publish": (fmt_age(published), fmt_time(published) if published else "")} COLUMNS = ("name", "version", "build", "category", "size", "date", "published") @@ -236,7 +236,7 @@ class RepositoryTab(QWidget): self.table.setSelectionMode(QTableView.SelectionMode.SingleSelection) self.table.verticalHeader().hide() self.table.horizontalHeader().setStretchLastSection(True) - for col, width in enumerate((220, 120, 80, 110, 90, 130)): + for col, width in enumerate((220, 120, 80, 110, 90, 150)): self.table.setColumnWidth(col, width) self.table.selectionModel().currentRowChanged.connect(self.show_detail) self.detail = QLabel(textFormat=Qt.TextFormat.RichText, wordWrap=True, @@ -264,12 +264,19 @@ class RepositoryTab(QWidget): self.loaded = False self.generation = 0 - self.stats = QLabel(wordWrap=True) + self.stats = QLabel(wordWrap=True) # loading and errors; the tiles hold the numbers + self.setStyleSheet(CARD_STYLE) + self.tiles = {name: Tile(name) for name in ("Packages", "Size", "Unpublished", "Last publish")} + self.tiles["Unpublished"].link(lambda: self.chips["unpublished"].setChecked(True)) + tiles = QHBoxLayout() + for t in self.tiles.values(): + tiles.addWidget(t) self.count = QLabel() search = QHBoxLayout() search.addWidget(self.query) search.addWidget(self.count) layout = QVBoxLayout(self) + layout.addLayout(tiles) layout.addWidget(self.stats) layout.addLayout(search) layout.addLayout(chips) @@ -294,7 +301,10 @@ class RepositoryTab(QWidget): if gen != self.generation: return # an older Refresh, overtaken self.model.set_rows(data["rows"] if data else []) - self.stats.setText(err or summary(data)) + self.stats.setText(err) + stats = summary(data) if data else {} + for name, tile in self.tiles.items(): + tile.set(*stats.get(name, (NA, ""))) self.refilter() def refilter(self): diff --git a/tests/test_gui.py b/tests/test_gui.py index a12efdd..08df339 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -381,10 +381,10 @@ class RepositoryTest(unittest.TestCase): def test_summary(self): s = repo.summary(repo.scan(self.root)) - self.assertIn("2 packages", s) - self.assertIn("3.0 KiB on disk", s) - self.assertIn("1 personal · 0 pentesting · 1 upstream", s) - self.assertIn("1 unpublished", s) + self.assertEqual(s["Packages"], ("2", "1 personal · 0 pentesting · 1 upstream")) + self.assertEqual(s["Size"], ("3.0 KiB", "40.0 KiB uncompressed")) + self.assertEqual(s["Unpublished"][0], "1") + self.assertTrue(s["Last publish"][0].endswith("ago")) def test_missing(self): with self.assertRaises(OSError): @@ -643,7 +643,10 @@ class SmokeTest(unittest.TestCase): tab = w.repository_tab w.tabs.setCurrentWidget(tab) self.wait(lambda: tab.model.rows) - self.assertIn("2 packages", tab.stats.text()) + self.assertEqual(tab.tiles["Packages"].value.text(), "2") + tab.tiles["Unpublished"].clicked.emit() # opens the unpublished filter + self.assertTrue(tab.chips["unpublished"].isChecked()) + tab.chips["unpublished"].setChecked(False) self.assertEqual(tab.proxy.rowCount(), 2) tab.chips["unpublished"].setChecked(True) self.assertEqual(tab.proxy.rowCount(), 1) |
