aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--sb_builder/dashboard.py7
-rw-r--r--sb_builder/repository.py34
-rw-r--r--tests/test_gui.py13
4 files changed, 37 insertions, 21 deletions
diff --git a/README.md b/README.md
index 652bcae..10fd7a5 100644
--- a/README.md
+++ b/README.md
@@ -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)