Add ability to query databases

This commit is contained in:
Peter Stockings
2025-12-24 10:13:25 +11:00
parent 40cb631975
commit 08840b3bc2
2 changed files with 182 additions and 1 deletions

75
app.py
View File

@@ -215,6 +215,56 @@ def get_container_detail(container_name: str) -> dict:
except Exception as e:
return {"error": str(e)}
def run_sql_query(container_name: str, query: str) -> dict:
"""
Execute a SQL query inside a database container using docker exec.
Supports Postgres and MySQL/MariaDB.
"""
try:
if "postgres" in container_name:
# Postgres: use psql with CSV-like output
# -A: unaligned, -F: separator, -t: tuples only (no headers) -> let's keep headers for now
cmd = [DOCKER, "exec", container_name, "psql", "-U", "postgres", "-c", query, "-A", "-F", ","]
elif "mysql" in container_name or "mariadb" in container_name:
# MySQL: use mysql with tab-separated output
cmd = [DOCKER, "exec", container_name, "mysql", "-u", "root", "-e", query, "-B"]
else:
return {"error": "Unsupported database type"}
out = sh(cmd)
# Parse output into rows and columns
lines = out.splitlines()
if not lines:
return {"columns": [], "rows": [], "message": "Query executed successfully (no results)"}
if "postgres" in container_name:
import csv
from io import StringIO
reader = csv.reader(StringIO(out))
rows = list(reader)
if not rows: return {"columns": [], "rows": []}
# psql -A can include a footer like "(1 row)", filter that out
if rows[-1] and rows[-1][0].startswith("(") and rows[-1][0].endswith(")"):
rows = rows[:-1]
columns = rows[0]
data = rows[1:]
else:
# MySQL is tab-separated by default with -B
rows = [line.split("\t") for line in lines]
columns = rows[0]
data = rows[1:]
return {
"columns": columns,
"rows": data,
"count": len(data)
}
except subprocess.CalledProcessError as e:
return {"error": e.output or str(e)}
except Exception as e:
return {"error": str(e)}
def is_app_web_container(name: str) -> bool:
# Dokku apps typically have containers like "<app>.web.1"
return name.endswith(".web.1") and not name.startswith("dokku.")
@@ -349,9 +399,11 @@ def collect():
def collect_admin_data():
"""
Collects logs and detailed container info for the admin dashboard.
Also identifies database containers for the SQL interface.
"""
ps_rows = docker_ps_all()
apps = []
databases = []
for r in ps_rows:
name = r["name"]
@@ -364,12 +416,19 @@ def collect_admin_data():
"logs": get_container_logs(name, lines=50),
"detail": get_container_detail(name)
})
elif classify_infra(name) and ("postgres" in name or "mysql" in name):
databases.append({
"name": name,
"type": "postgres" if "postgres" in name else "mysql"
})
# Sort by app name
# Sort
apps.sort(key=lambda x: x["app"])
databases.sort(key=lambda x: x["name"])
return {
"apps": apps,
"databases": databases,
}
def parse_human_bytes(s: str) -> int:
@@ -443,3 +502,17 @@ def admin():
def api_container_detail(container_name):
detail = get_container_detail(container_name)
return jsonify(detail)
# API endpoint for SQL queries
@app.post("/api/sql/query")
@login_required
def api_sql_query():
data = request.json
container = data.get("container")
query = data.get("query")
if not container or not query:
return jsonify({"error": "Missing container or query"}), 400
result = run_sql_query(container, query)
return jsonify(result)