From 035132ded5fb1ed9cf2bc8346cbd55dd07050ed5 Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sun, 24 May 2026 01:24:50 -0700 Subject: [PATCH] release: archivebox 0.9.31rc27 --- Dockerfile | 17 +++++-- archivebox/core/admin_site.py | 46 +++++++++++++++++++ archivebox/templates/admin/index.html | 23 ++++++++++ .../templates/admin/index_model_card.html | 7 ++- pyproject.toml | 2 +- 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index ce73ab6a..f16afed9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -205,7 +205,7 @@ RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-$TARGETARCH$TARGE # Set up uv and main app /venv RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/bin sh -ENV UV_COMPILE_BYTECODE=1 \ +ENV UV_COMPILE_BYTECODE=0 \ UV_PYTHON_PREFERENCE=managed \ UV_PYTHON_INSTALL_DIR=/opt/uv/python \ UV_LINK_MODE=copy \ @@ -268,6 +268,7 @@ RUN --mount=type=bind,source=pyproject.toml,target=/app/pyproject.toml \ && apt-get install -qq -y --no-install-recommends build-essential gcc python3-dev \ && uv sync \ --no-cache \ + --no-dev \ --inexact \ --all-extras \ --no-install-project \ @@ -275,6 +276,8 @@ RUN --mount=type=bind,source=pyproject.toml,target=/app/pyproject.toml \ --no-sources \ && apt-get purge -y python3-dev build-essential gcc \ && apt-get autoremove -y \ + && find /venv -type d -name __pycache__ -prune -exec rm -rf {} + \ + && find /venv -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete \ && rm -rf /var/lib/apt/lists/* # installs the pip packages that archivebox depends on, defined in pyproject.toml dependencies @@ -289,7 +292,9 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked,id=uv-$TARGETARCH$T pip show archivebox \ && which archivebox \ && echo -e '\n\n' \ - ) | tee -a /VERSION.txt + ) | tee -a /VERSION.txt \ + && find /venv "$CODE_DIR" -type d -name __pycache__ -prune -exec rm -rf {} + \ + && find /venv "$CODE_DIR" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete # installs archivebox itself, and any other vendored packages in pkgs/*, defined in pyproject.toml workspaces #################################################### @@ -319,6 +324,9 @@ RUN openssl rand -hex 16 > /etc/machine-id \ # returned to the runtime archivebox user. RUN echo "[+] Initializing image collection and installing plugin runtime dependencies into $LIB_DIR..." \ && PUID=0 PGID=0 archivebox init --install \ + && find /venv "$CODE_DIR" "$LIB_DIR" "$DATA_DIR" -type d -name __pycache__ -prune -exec rm -rf {} + \ + && find /venv "$CODE_DIR" "$LIB_DIR" "$DATA_DIR" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete \ + && rm -rf /root/.cache /var/cache/apt/* /var/lib/apt/lists/* \ && (chown "$DEFAULT_PUID:$DEFAULT_PGID" "$DATA_DIR" "$DATA_DIR"/logs "$DATA_DIR"/sources "$DATA_DIR"/archive "$DATA_DIR"/archive/users "$DATA_DIR"/personas "$DATA_DIR"/index.sqlite3 "$DATA_DIR"/ArchiveBox.conf 2>/dev/null || true) \ && chown -R "$DEFAULT_PUID:$DEFAULT_PGID" "$LIB_DIR" @@ -330,7 +338,10 @@ RUN (echo -e "\n\n[√] Finished Docker build successfully. Saving build summary # Verify ArchiveBox is installed and write full version/dependency info. RUN chmod +x "$CODE_DIR"/bin/*.sh \ - && gosu "$ARCHIVEBOX_USER" archivebox version 2>&1 | tee -a /VERSION.txt + && gosu "$ARCHIVEBOX_USER" archivebox version 2>&1 | tee -a /VERSION.txt \ + && find /venv "$CODE_DIR" "$LIB_DIR" "$DATA_DIR" -type d -name __pycache__ -prune -exec rm -rf {} + \ + && find /venv "$CODE_DIR" "$LIB_DIR" "$DATA_DIR" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete \ + && rm -rf /root/.cache /var/cache/apt/* /var/lib/apt/lists/* #################################################### diff --git a/archivebox/core/admin_site.py b/archivebox/core/admin_site.py index 770a1d2a..0d7cd2b3 100644 --- a/archivebox/core/admin_site.py +++ b/archivebox/core/admin_site.py @@ -3,6 +3,7 @@ __package__ = "archivebox.core" from typing import TYPE_CHECKING, Any from django.contrib import admin +from django.db import DatabaseError, connection from admin_data_views.admin import ( admin_data_index_view as adv_admin_data_index_view, get_admin_data_urls as adv_get_admin_data_urls, @@ -31,6 +32,51 @@ class ArchiveBoxAdmin(admin.AdminSite): def admin_data_index_view(self, request: "HttpRequest", **kwargs: Any) -> "TemplateResponse": return adv_admin_data_index_view(self, request, **kwargs) + def index(self, request: "HttpRequest", extra_context: dict[str, Any] | None = None) -> "TemplateResponse": + response = super().index(request, extra_context) + if connection.vendor != "sqlite": + return response + + models_by_table: dict[str, list[dict[str, Any]]] = {} + for app in response.context_data.get("app_list", []): + for model in app.get("models", []): + model_class = model.get("model") + if not model_class or not model.get("perms", {}).get("view"): + continue + models_by_table.setdefault(model_class._meta.db_table, []).append(model) + + if not models_by_table: + return response + + try: + with connection.cursor() as cursor: + cursor.execute("SELECT tbl, stat FROM sqlite_stat1") + for table, stat in cursor.fetchall(): + models = models_by_table.get(table) + if not models: + continue + try: + count = int(str(stat).split()[0]) + except (IndexError, TypeError, ValueError): + continue + if count >= 1_000_000_000: + count_label = f"~{count / 1_000_000_000:.1f}B" + elif count >= 1_000_000: + count_label = f"~{count / 1_000_000:.1f}M" + elif count >= 1_000: + count_label = f"~{count / 1_000:.1f}K" + else: + count_label = f"~{count:,}" + count_label = count_label.replace(".0", "") + for model in models: + model["object_count"] = count + model["object_count_label"] = count_label + model["object_count_title"] = f"Approximate count from SQLite stats: {count:,}" + models_by_table.pop(table, None) + except DatabaseError: + pass + return response + def get_admin_data_urls(self) -> list["URLResolver | URLPattern"]: return adv_get_admin_data_urls(self) diff --git a/archivebox/templates/admin/index.html b/archivebox/templates/admin/index.html index 0cba31aa..2410f524 100644 --- a/archivebox/templates/admin/index.html +++ b/archivebox/templates/admin/index.html @@ -271,6 +271,13 @@ min-width: 0; } + .abx-admin-card__title-row { + display: flex; + gap: 7px; + align-items: baseline; + min-width: 0; + } + #content .abx-admin-card__title { color: var(--abx-ink) !important; font-size: 15px; @@ -279,6 +286,22 @@ text-decoration: none !important; } + .abx-admin-card__count { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + min-height: 16px; + padding: 0 5px; + color: var(--abx-ink-4); + background: rgba(28, 24, 20, .035); + border: 1px solid rgba(28, 24, 20, .08); + border-radius: 999px; + font-family: var(--abx-font-mono); + font-size: 10px; + font-weight: 650; + line-height: 1; + } + .abx-admin-card__text { color: var(--abx-ink-3); font-size: 12px; diff --git a/archivebox/templates/admin/index_model_card.html b/archivebox/templates/admin/index_model_card.html index 5750d314..1b4e96ae 100644 --- a/archivebox/templates/admin/index_model_card.html +++ b/archivebox/templates/admin/index_model_card.html @@ -50,7 +50,12 @@ - {{ model.name }} + + {{ model.name }} + {% if model.object_count_label %} + {{ model.object_count_label }} + {% endif %} + {% if model.object_name == "Crawl" %} Create and monitor URL collection jobs. diff --git a/pyproject.toml b/pyproject.toml index 57d66298..f668ffe1 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "archivebox" -version = "0.9.31rc26" +version = "0.9.31rc27" requires-python = ">=3.13" description = "Self-hosted internet archiving solution." authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}]