Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

115 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cli2ui

CLI database ops, as buttons. A web UI over the database CLI commands you keep half-remembering. No AI, no magic — just psql operations turned into clicks, running fully on your machine.

⚠️ Local-only by design. cli2ui has no authentication and binds to your machine. Run it on localhost or inside a trusted network — never expose it to the public internet. See SECURITY.md.

psql -c "SELECT * FROM pg_stat_activity"   →  one "running queries" button
pg_dump -t users mydb                       →  one "back up this table" button
SELECT pg_terminate_backend(pid)            →  one "kill this process" button

Built for app developers and solo developers — not DBAs. The pitch is "you're looking at your tables 3 minutes after deciding to" — no install marathon, no connection-wizard maze, no digging through nested trees. And no "now go find a demo database" homework, either: a sample database is bundled and pre-wired into the connection form, with a realistic Airlines dataset one opt-in away.

The UI ships in English and Japanese — a header toggle switches between them, and it auto-detects your browser's language on first visit.

What it looks like

Workspace overview — every panel one click away, with live counts

Table detail SQL runner
Table detail SQL runner

Quick start

docker compose up

Then open http://localhost:8000. The connection form is pre-filled to point at a bundled sample database — just hit Connect and you'll see its tables.

To point at your own PostgreSQL, change the form fields (host, port, db, user, password) and connect.

Port 8000 already taken? It's a common default, so a clash is likely. Just remap the host side in docker-compose.yml"8000:8000""8001:8000" (any free port works) — and open that port instead. Only the number on the left changes; the app keeps listening on 8000 inside the container.

Want more realistic data?

The bundled shop database is deliberately tiny. For something with real volume — enough rows for the health checks, activity views, and jsonb tooling to have something to chew on — there's an opt-in second database: the Postgres Pro "Airlines" demo dataset (flights and bookings, ~250MB restored, jsonb columns included).

seed/demo/fetch.sh                            # downloads the dump (~60MB, not committed)
docker compose --profile demo up -d airlines  # first boot loads it — takes a few minutes

Then connect with host airlines, database demo, user demo, password demo (port 5434 if connecting from your host machine). The loaded data persists in a named volume, so the slow load only happens once. fetch.sh also accepts small or big if you want a different dataset size.

Connecting to a database in another container

cli2ui runs in its own container, so "localhost" in the connection form means the cli2ui container, not your machine. Your database almost always lives somewhere else — another container, another compose project, or a native install.

New to Docker networking? Don't guess — README.NETWORKING.md walks through why localhost fails and gives a copy-paste fix for every setup (native DB, another compose project, a published port), with diagrams and a full worked example.

The short version — three ways to reach it (no code changes, just how you fill the form):

  1. Via the host (simplest). If the database publishes a port on your machine (e.g. -p 5432:5432), set the connection host to host.docker.internal and the port to the published one. Works the same on macOS, Windows, and Linux — the extra_hosts entry in docker-compose.yml makes the name resolve everywhere.

  2. Share a network, connect by container name. Put both on one external network and use the database's container name as the host:

    docker network create cli2ui-net
    # then in BOTH compose files:
    #   networks:
    #     default:
    #       name: cli2ui-net
    #       external: true
  3. Attach at runtime. Join the running cli2ui container to the database's existing network, then use the DB container name as the host:

    docker network connect <db-network> <cli2ui-app-container>

Trying replication locally

The Replication panel reads pg_stat_replication / pg_replication_slots, so its Standbys table stays empty until a replica is actually attached. To watch it populate, spin up a throwaway primary + standby pair and point cli2ui at the primary — see README.REPLICATION.md for a copy-paste compose file and the exact connection details.

Status

v1.2.0 — a multi-DB (PostgreSQL & MySQL) ops console. docker compose up → connect → browse your tables in a DB-client layout (table list in the sidebar, table detail in the main pane).

  • ✅ PostgreSQL: connect + list tables (estimated row counts)
  • ✅ Table detail: column definitions with column & table comments and generated-column expressions (\d+ table), an on-demand JSON shape view on json/jsonb columns (sampled top-level keys, value types, nesting depth, GIN coverage — describes the rows examined, not every row), a row preview, and a filter builder — stack column / operator / value rows (ANDed) into a read-only SELECT … WHERE, no SQL typing
  • ✅ SQL runner: read-only ad-hoc queries by default (SET TRANSACTION READ ONLY
    • statement_timeout + 1000-row cap), with an opt-in write mode that commits — guarded by a whole-database safety snapshot taken before each write
  • ✅ Export to CSV / JSON: download a full query result set (past the display cap) or an entire table's rows, streamed through a server-side cursor (UTF-8 BOM on CSV for Excel)
  • ✅ CSV import: append rows from a CSV into an existing table (COPY, matched by header name) in one all-or-nothing transaction, with a safety snapshot first
  • ✅ EXPLAIN snapshots + diff: save query plans and diff two (before/after an index) instead of copy-pasting plans into a scratch file
  • ✅ Scale simulation: EXPLAIN the same query at 1× / 100× / 10000× the real row counts (a what-if pg_class edit, always rolled back) to see where the plan shape breaks as data grows
  • ✅ Index lab: measure a hypothetical index (EXPLAIN ANALYZE before/after, the index created and rolled back in one transaction) before you commit to creating it
  • ✅ Activity: running queries + connections from pg_stat_activity, with one-click cancel (pg_cancel_backend) / kill (pg_terminate_backend)
  • ✅ Objects browser: databases (\l), schemas (\dn), roles (\du) — a read-only listing, with create / rename / alter / drop (and DB clone via TEMPLATE) behind confirm gates in a side drawer
  • postgresql.conf editor: read/edit parameters via pg_settings + ALTER SYSTEM SET + pg_reload_conf(), with reload-vs-restart badges
  • ✅ Locks: the wait-for chain folded into a tree — the head blocker at the top (cancel / kill it to free the whole chain beneath) with the blocked sessions nested under it (pg_locks + pg_blocking_pids), blocking cycles detected and flagged, one-click cancel / kill on any session
  • ✅ Replication: readiness check (wal_level / max_wal_senders) + WAL position, connected standbys (pg_stat_replication), and slot create / drop
  • ✅ Health: table sizes, unused indexes, unindexed foreign keys (FK columns with no index the planner can use for them — Postgres doesn't auto-create one, and a partial or invalid index doesn't count), redundant indexes (a non-unique index that's a leading prefix of, or identical to, another), orphan rows (foreign keys left NOT VALID, plus *_id columns with no FK that name-match a table's primary key — each with an on-demand, read-only anti-join count that never validates or changes anything), dead-rows / vacuum, and a stats-only bloat estimate — read-only catalog facts, no advice
  • ✅ Dependencies: the foreign-key graph topologically sorted (graphlib) into a safe TRUNCATE/DELETE order and its reverse (the load order), with foreign-key cycles detected and named — read-only, nothing is truncated (PostgreSQL & MySQL)
  • ✅ Extensions: what's installed in this database and what the server could install (the Web equivalent of \dx unioned with pg_available_extensions), with an update-available badge when an installed version lags the server's default — read-only; CREATE EXTENSION stays in the SQL runner
  • ✅ Command history: SQL run through the runner, logged to the management DB — status, row count, timing, and one-click re-open
  • ✅ Backup / restore: automatic table snapshots (pg_dump custom format) before every destructive or structural change (drop / truncate / rename / alter), kept under a per-connection size budget (oldest pruned automatically — tune with CLI2UI_MAX_AUTO_BACKUP_BYTES per snapshot and CLI2UI_MAX_AUTO_BACKUP_TOTAL_BYTES in total), plus restore of an uploaded dump — streamed to the client tool (not buffered in memory) — into a new database or, with a type-gate, an existing one
  • ✅ Workspace overview: a bento dashboard summarizing every panel (live counts for activity, snapshots, backups, replication readiness…), one click away
  • ✅ Internationalization: full English / Japanese UI with a header toggle (cookie-persisted, falling back to the browser's Accept-Language)
  • ✅ MySQL (8.0+): the same console over a MySQL connection — browse, read-only + write query runner, filter builder, CSV import, CSV/JSON export, EXPLAIN (into the shared plan tree, so snapshots/diff work), index + table + column DDL, database create/drop, activity (SHOW PROCESSLIST), locks (performance_schema), health (unused indexes / sizes), backup & restore (mysqldump/mysql), a settings editor (SET PERSIST), and replication status. Features with no MySQL equivalent (vacuum/bloat, schemas separate from databases, the planner what-if lab, replication slots) show as "not applicable" rather than an empty card.

Stack

Layer Choice Why
Frontend htmx + Alpine.js Click → swap in HTML. No SPA build needed.
Backend Django Battle-tested psycopg2; management plumbing for free.
Management DB SQLite Saved connections + (later) history. Zero extra infra.
Infra Docker Local-only, self-contained. No DB creds ever leave your network.

Not doing (on purpose)

  • No AI. API key management + sending your schema to a third party is a non-starter for the people this is for.
  • No SaaS. The moment we'd hold your DB connection info, the liability and encryption story swamps the project. Local-only keeps it honest.

Security

cli2ui is local-only by design — no SaaS, no auth, no outbound calls — so the threat model is narrow: it runs next to your database on your machine. Even so, the destructive surface is taken seriously: identifiers are bound with psycopg2.sql.Identifier, the SQL runner defaults to SET TRANSACTION READ ONLY, what-if features always ROLLBACK, CSRF is enforced, and DEBUG is off by default. The full threat model and static-analysis results live in specs/security-check.md. To report a vulnerability, see SECURITY.md.

Local development (without Docker)

pip install -r requirements.txt
python manage.py migrate
python manage.py runserver

You'll need a PostgreSQL to connect to (the sampledb service in docker-compose.yml is one option, exposed on localhost:5433).

DEBUG is off by default so error pages don't leak tracebacks, settings, or SQL. Set DJANGO_DEBUG=1 while developing if you want Django's rich error pages.

Contributing

Issues and PRs welcome. See CONTRIBUTING.md for how to run the app, the test suite, and the UI conventions (STYLE.md) new panels follow.

License

MIT © TABATA Hitoshi

About

CLI database ops, as buttons — a local-only web UI over psql. No AI, no SaaS.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages