Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Docs

on: push

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod

- name: Install tools
run: |
go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0
go install github.com/gohugoio/hugo@v0.161.1

- name: Generate API reference
run: |
mkdir -p docs/content/docs/reference
gomarkdoc --output docs/content/docs/reference/config.md ./internal/config/
gomarkdoc --output docs/content/docs/reference/db.md ./internal/db/
gomarkdoc --output docs/content/docs/reference/rules.md ./internal/rules/
gomarkdoc --output docs/content/docs/reference/tui.md ./internal/tui/
gomarkdoc --output docs/content/docs/reference/theme.md ./internal/tui/theme/
gomarkdoc --output docs/content/docs/reference/views.md ./internal/tui/views/

- name: Add front matter to generated docs
run: |
for f in docs/content/docs/reference/*.md; do
name=$(basename "$f" .md)
if [ "$name" = "_index" ]; then continue; fi
tmp=$(mktemp)
printf -- '---\ntitle: %s\n---\n\n' "$name" > "$tmp"
cat "$f" >> "$tmp"
mv "$tmp" "$f"
done

- name: Build site
run: cd docs && hugo --minify

- name: Upload artifact
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: docs/public/

deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ dist/
# env file
.env

# Hugo
docs/public/
docs/resources/
docs/.hugo_build.lock

# Generated API reference docs
docs/content/docs/reference/config.md
docs/content/docs/reference/db.md
docs/content/docs/reference/rules.md
docs/content/docs/reference/tui.md
docs/content/docs/reference/theme.md
docs/content/docs/reference/views.md

# Editor/IDE
# .idea/
# .vscode/
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[![CI](https://github.com/Fraser-Isbester/tusk/actions/workflows/ci.yml/badge.svg)](https://github.com/Fraser-Isbester/tusk/actions/workflows/ci.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/fraser-isbester/tusk)](https://goreportcard.com/report/github.com/fraser-isbester/tusk)

# Tusk

A k9s-style terminal UI for real-time PostgreSQL monitoring and management. Not a database client — a tool for observing and acting on live database activity.
Expand Down
28 changes: 28 additions & 0 deletions docs/content/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: Tusk
layout: hextra-home
---

<div class="hx-mt-6 hx-mb-6">
{{< hextra/hero-headline >}}
Tusk
{{< /hextra/hero-headline >}}
</div>

<div class="hx-mb-12">
{{< hextra/hero-subtitle >}}
A k9s-style terminal UI for real-time PostgreSQL monitoring and management
{{< /hextra/hero-subtitle >}}
</div>

<div class="hx-mb-6">
{{< hextra/hero-button text="Get Started" link="docs/getting-started" >}}
</div>

## Features

{{< cards >}}
{{< card link="docs/views" title="Live Views" subtitle="Queries, transactions, sessions, locks, tables, indexes with 2s auto-refresh" >}}
{{< card link="docs/rules" title="Rules Engine" subtitle="CEL-based policy rules evaluated against live database state" >}}
{{< card link="docs/keybindings" title="Keyboard-Driven" subtitle="k9s-inspired navigation with column sorting, filtering, and drill-down" >}}
{{< /cards >}}
8 changes: 8 additions & 0 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Documentation
next: getting-started
sidebar:
open: true
---

Tusk is a terminal UI for real-time PostgreSQL monitoring. It connects to live Postgres instances and provides interactive views of database activity, with a rules engine for policy enforcement.
59 changes: 59 additions & 0 deletions docs/content/docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Development
weight: 5
---

## Architecture

```
cmd/tusk/main.go Entry point, CLI flags, profile resolution, engine setup
internal/config/ YAML config with profiles, connection strings, rule definitions
internal/db/ PostgreSQL connectivity (pgx pool), query methods, data types
internal/rules/ CEL rules engine, violation store, action executors
internal/tui/ TUI application (tview), views, detail panes, navigation
internal/tui/theme/ Color palette and styles
internal/tui/views/ Individual view implementations (queries, transactions, etc.)
scripts/ Load testing and seed data
```

## Key types

- `db.ResourceBase` -- shared fields for all `pg_stat_activity` resources (PID, User, App, Database, State, BackendStart)
- `db.Query` -- active query with duration, wait events, SQLcommenter tags, BlockedBy, QueryID, QueryStart
- `db.Transaction` -- active transaction with XactStart, XactDuration, QueryDuration, LockCount
- `rules.Violation` -- rule violation with timestamped event log (detected, action, cooldown, closed)
- `rules.Engine` -- evaluates CEL rules against snapshots every 2s, manages violation store

## Conventions

- Views implement the `tui.View` interface: `Table()`, `Start()`, `Stop()`, `ItemCount()`, `SetFilter()`
- Detail views return `*tview.Flex` with split panes (Info, Query/Queries, Activity)
- Navigation uses a view stack with `Esc` to go back; `Navigator` callback for drill-down
- `Tab` / `Shift+Tab` cycles focus between panes in detail views
- All table views save/restore selection across refresh cycles
- `Stop()` methods guard against double-close with `v.done = nil` after close
- Query identity: `(PID, BackendStart)` for sessions, `(PID, XactStart)` for transactions
- SQL formatting is display-only via `formatSQL()` before `highlightSQL()`

## Build and test

```bash
task build # go build -o tusk ./cmd/tusk
task run # build + run with dev profile
task db:up # start local Postgres in Docker
task loadtest # generate realistic load
task test # run all tests with Ginkgo
task check # run vet + fmt check + tests
./tusk -P <profile> # run with a named profile
./tusk '<dsn>' # run with a direct connection string
```

## Dependencies

| Package | Purpose |
|---------|---------|
| `rivo/tview` + `gdamore/tcell` | TUI framework |
| `jackc/pgx/v5` | PostgreSQL driver and connection pool |
| `google/cel-go` | Common Expression Language for rule evaluation |
| `spf13/cobra` | CLI framework |
| `gopkg.in/yaml.v3` | Configuration parsing |
98 changes: 98 additions & 0 deletions docs/content/docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Getting Started
weight: 1
---

## Install

### From source

```bash
go install github.com/fraser-isbester/tusk/cmd/tusk@latest
```

### Pre-built binaries

Download from [GitHub Releases](https://github.com/fraser-isbester/tusk/releases).

## Usage

```bash
# Direct connection
tusk 'postgres://user:pass@localhost:5432/mydb'

# Using a profile
tusk -P production
```

## Configuration

Tusk reads its configuration from `~/.config/tusk/config.yaml`. A configuration file defines one or more connection profiles, each with optional rules.

```yaml
default_profile: dev

profiles:
dev:
url: "postgres://postgres:postgres@localhost:5432/mydb?sslmode=disable"
rules:
- name: kill-idle-in-txn
resource: transaction
when: "state == 'idle in transaction' && xact_duration > duration('5m')"
action: terminate
cooldown: 5m
dry_run: true

- name: long-queries
resource: query
when: "state == 'active' && duration > duration('30s')"
action: cancel
cooldown: 1m
dry_run: true

production:
host: db.example.com
port: 5432
user: monitor
password: 'secret'
database: prod
readonly: true
rules:
- name: idle-in-txn-5m
resource: transaction
when: "state == 'idle in transaction' && xact_duration > duration('5m')"
action: terminate
cooldown: 5m
dry_run: true
```

### Profile fields

Profiles can specify a connection either as a full `url` or with individual fields (`host`, `port`, `user`, `password`, `database`, `sslmode`). Additional profile-level options:

| Field | Description |
|-------|-------------|
| `readonly` | Forces all rules in this profile to dry-run mode |
| `color` | Profile accent color for the header |
| `refresh_interval` | Polling interval (default `2s`) |

### Rule fields

| Field | Description |
|-------|-------------|
| `name` | Human-readable identifier for the rule |
| `resource` | `query`, `transaction`, or `lock` |
| `when` | CEL expression evaluated against the resource fields |
| `action` | `terminate`, `cancel`, or `log` |
| `cooldown` | Minimum interval between action firings per PID |
| `dry_run` | Record violations but don't execute the action |

### CEL expression fields

Each resource type exposes different fields to CEL expressions:

**Query**: `pid`, `user`, `app`, `database`, `state`, `duration`, `wait_event_type`, `wait_event`, `query`, `blocked_by`, `query_id`, `route`, `controller`, `action_name`, `framework`

**Transaction**: `pid`, `user`, `app`, `database`, `state`, `xact_duration`, `query_duration`, `query`, `lock_count`

**Lock**: `blocked_pid`, `blocking_pid`, `blocked_user`, `blocking_user`, `blocked_app`, `blocking_app`, `lock_type`, `mode`, `wait_duration`
58 changes: 58 additions & 0 deletions docs/content/docs/keybindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Key Bindings
weight: 4
---

Tusk uses keyboard-driven navigation inspired by k9s and vim.

## Global keys

| Key | Action |
|-----|--------|
| `Left` / `Right` | Switch between views |
| `:` | Open command prompt (type a view name, e.g. `:queries`) |
| `/` | Open filter input |
| `h` | Show help overlay |
| `q` | Quit |
| `Ctrl+C` | Quit |

## Table views

| Key | Action |
|-----|--------|
| `Enter` | Drill into detail view for the selected row |
| `Esc` | Go back to the previous view / clear filter |
| `Shift+letter` | Sort by column (e.g. `Shift+D` for duration) |

## Query and transaction views

| Key | Action |
|-----|--------|
| `c` | Cancel query (`pg_cancel_backend`) |
| `t` | Terminate backend (`pg_terminate_backend`) |

## Lock view

| Key | Action |
|-----|--------|
| `t` | Terminate the blocking backend |

## Detail views

| Key | Action |
|-----|--------|
| `Tab` | Cycle focus to the next pane |
| `Shift+Tab` | Cycle focus to the previous pane |
| `Esc` | Return to the parent list view |
| `c` | Cancel query (in query/transaction detail) |
| `t` | Terminate backend (in query/transaction detail) |

## Filter

| Key | Action |
|-----|--------|
| `/` | Open filter input |
| `Enter` | Apply filter and return focus to the table |
| `Esc` | Clear filter and close filter input |

Filter text is matched case-insensitively across all visible columns. The filter persists until cleared with `Esc`.
6 changes: 6 additions & 0 deletions docs/content/docs/reference/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: API Reference
weight: 10
---

These pages are auto-generated from Go source using [gomarkdoc](https://github.com/princjef/gomarkdoc). They are rebuilt on every push by the docs CI workflow and should not be edited by hand.
Loading