diff --git a/docs/docker.md b/docs/docker.md index af06d648..f9ed5c66 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -14,7 +14,7 @@ This page covers common Docker workflows and troubleshooting. Simons typical workflow. -While making code changes, I do this. +While making code changes, I use `./rebuild.sh`, that does this: ```bash docker compose down --remove-orphans diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index 253ba1ca..6a952715 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1971,6 +1971,41 @@ def _build_plan_telemetry( return telemetry + def _get_database_size_info(self) -> dict[str, Any]: + """Query PostgreSQL for database size and per-table breakdown.""" + from sqlalchemy import text + info: dict[str, Any] = {"error": None, "database_name": None, "total_bytes": 0, "total_mb": 0.0, "tables": []} + try: + with self.db.engine.connect() as conn: + row = conn.execute(text( + "SELECT current_database(), pg_database_size(current_database())" + )).fetchone() + if row: + info["database_name"] = row[0] + info["total_bytes"] = row[1] + info["total_mb"] = round(row[1] / (1024 * 1024), 2) + + tables = conn.execute(text( + "SELECT schemaname, tablename, " + "pg_total_relation_size(schemaname || '.' || tablename) AS total_bytes, " + "pg_relation_size(schemaname || '.' || tablename) AS table_bytes, " + "pg_total_relation_size(schemaname || '.' || tablename) - pg_relation_size(schemaname || '.' || tablename) AS index_bytes " + "FROM pg_tables WHERE schemaname = 'public' " + "ORDER BY total_bytes DESC" + )).fetchall() + for t in tables: + info["tables"].append({ + "name": t[1], + "total_bytes": t[2], + "total_mb": round(t[2] / (1024 * 1024), 2), + "table_mb": round(t[3] / (1024 * 1024), 2), + "index_mb": round(t[4] / (1024 * 1024), 2), + }) + except Exception as e: + logger.exception("Failed to query database size") + info["error"] = str(e) + return info + def _build_reconciliation_report(self, max_tasks: int, tolerance_usd: float) -> tuple[list[dict[str, Any]], dict[str, Any]]: tasks = ( PlanItem.query @@ -2942,6 +2977,15 @@ def admin_reconciliation(): refresh_seconds=refresh_seconds, ) + @self.app.route('/admin/db-size') + @admin_required + def admin_db_size(): + size_info = self._get_database_size_info() + return self.admin.index_view.render( + "admin/db_size.html", + size_info=size_info, + ) + @self.app.route('/ping/stream') @login_required def ping_stream(): diff --git a/frontend_multi_user/templates/admin/db_size.html b/frontend_multi_user/templates/admin/db_size.html new file mode 100644 index 00000000..2a2866c0 --- /dev/null +++ b/frontend_multi_user/templates/admin/db_size.html @@ -0,0 +1,121 @@ +{% extends 'admin/master.html' %} + +{% block head_css %} + {{ super() }} + +{% endblock %} + +{% block body %} +
| Table | +Total (MB) | +Data (MB) | +Indexes (MB) | ++ |
|---|---|---|---|---|
| {{ t.name }} | +{{ t.total_mb }} | +{{ t.table_mb }} | +{{ t.index_mb }} | ++ {% if size_info.tables[0].total_bytes > 0 %} + + {% endif %} + | +