diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py
index 9650df85..cb7f4a18 100644
--- a/aurweb/routers/packages.py
+++ b/aurweb/routers/packages.py
@@ -96,3 +96,20 @@ async def package(request: Request, name: str) -> Response:
context["conflicts"] = conflicts
return render_template(request, "packages/show.html", context)
+
+
+@router.get("/pkgbase/{name}")
+async def package_base(request: Request, name: str) -> Response:
+ # Get the PackageBase.
+ pkgbase = get_pkgbase(name)
+
+ # If this is not a split package, redirect to /packages/{name}.
+ if pkgbase.packages.count() == 1:
+ return RedirectResponse(f"/packages/{name}",
+ status_code=int(HTTPStatus.SEE_OTHER))
+
+ # Add our base information.
+ context = await make_single_context(request, pkgbase)
+ context["packages"] = pkgbase.packages.all() # Doesn't need to be here.
+
+ return render_template(request, "pkgbase.html", context)
diff --git a/templates/partials/packages/pkgbase_metadata.html b/templates/partials/packages/pkgbase_metadata.html
new file mode 100644
index 00000000..ba27fda5
--- /dev/null
+++ b/templates/partials/packages/pkgbase_metadata.html
@@ -0,0 +1,13 @@
+
+
Packages ({{ packages_count }})
+
+
diff --git a/templates/pkgbase.html b/templates/pkgbase.html
new file mode 100644
index 00000000..315cdf67
--- /dev/null
+++ b/templates/pkgbase.html
@@ -0,0 +1,21 @@
+{% extends "partials/layout.html" %}
+
+{% block pageContent %}
+ {% include "partials/packages/search.html" %}
+
+
{{ 'Package Base Details' | tr }}: {{ pkgbase.Name }}
+
+ {% set result = pkgbase %}
+ {% include "partials/packages/actions.html" %}
+ {% include "partials/packages/details.html" %}
+
+
+ {% include "partials/packages/pkgbase_metadata.html" %}
+
+
+
+ {% set pkgname = result.Name %}
+ {% set pkgbase_id = result.ID %}
+ {% set comments = comments %}
+ {% include "partials/packages/comments.html" %}
+{% endblock %}
diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py
index f9592238..44ef7fcd 100644
--- a/test/test_packages_routes.py
+++ b/test/test_packages_routes.py
@@ -281,3 +281,40 @@ def test_package_dependencies(client: TestClient, maintainer: User,
broken_node = root.find('.//ul[@id="pkgdepslist"]/li/span')
assert broken_node.text.strip() == broken_dep.DepName
+
+
+def test_pkgbase_not_found(client: TestClient):
+ with client as request:
+ resp = request.get("/pkgbase/not_found")
+ assert resp.status_code == int(HTTPStatus.NOT_FOUND)
+
+
+def test_pkgbase_redirect(client: TestClient, package: Package):
+ with client as request:
+ resp = request.get(f"/pkgbase/{package.Name}",
+ allow_redirects=False)
+ assert resp.status_code == int(HTTPStatus.SEE_OTHER)
+ assert resp.headers.get("location") == f"/packages/{package.Name}"
+
+
+def test_pkgbase(client: TestClient, package: Package):
+ second = db.create(Package, Name="second-pkg",
+ PackageBase=package.PackageBase)
+
+ expected = [package.Name, second.Name]
+ with client as request:
+ resp = request.get(f"/pkgbase/{package.Name}",
+ allow_redirects=False)
+ assert resp.status_code == int(HTTPStatus.OK)
+
+ root = parse_root(resp.text)
+
+ # Check the details box title.
+ title = root.find('.//div[@id="pkgdetails"]/h2')
+ title, pkgname = title.text.split(": ")
+ assert title == "Package Base Details"
+ assert pkgname == package.Name
+
+ pkgs = root.findall('.//div[@id="pkgs"]/ul/li/a')
+ for i, name in enumerate(expected):
+ assert pkgs[i].text.strip() == name