Compare commits

..

5 Commits

Author SHA1 Message Date
Bas Nijholt
772b0518ed web: Improve terminal scroll behavior on mobile
- Add native scroll momentum with -webkit-overflow-scrolling: touch
- Add overscroll-behavior: contain to prevent pull-to-refresh interference
- Add mobile-specific max-height (50vh) and touch-action: pan-y
- Detect touch devices and enable xterm.js smoothScrollDuration
- Increase scrollSensitivity for more responsive touch scrolling
2026-01-18 03:44:42 -08:00
Bas Nijholt
9c72e0937a web: Add clear button to sidebar filter (#168) 2026-01-18 00:11:58 +01:00
Bas Nijholt
74cc2f3245 fix: add COLUMNS and _TYPER_FORCE_DISABLE_TERMINAL for consistent output (#167) 2026-01-16 22:07:41 +01:00
Bas Nijholt
940bd9585a fix: Detect stale NFS mounts in path existence check (#166) 2026-01-15 12:53:21 +01:00
Bas Nijholt
dd60af61a8 docs: Add Plausible analytics (#165) 2026-01-12 17:23:08 +01:00
12 changed files with 301 additions and 233 deletions

View File

@@ -26,7 +26,9 @@ jobs:
env:
TERM: dumb
NO_COLOR: 1
TERMINAL_WIDTH: 90
COLUMNS: 90 # POSIX terminal width for Rich
TERMINAL_WIDTH: 90 # Typer MAX_WIDTH for help panels
_TYPER_FORCE_DISABLE_TERMINAL: 1 # Prevent Typer forcing terminal mode in CI
run: |
uvx --with . markdown-code-runner README.md
sed -i 's/[[:space:]]*$//' README.md

431
README.md
View File

@@ -478,46 +478,41 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Compose Farm - run docker compose commands across multiple hosts
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --version -v Show version and exit │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to │
copy it or customize the installation. │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Configuration ──────────────────────────────────────────────────────────────╮
│ traefik-file Generate a Traefik file-provider fragment from compose
Traefik labels.
refresh Update local state from running stacks.
check Validate configuration, traefik labels, mounts, and networks.
init-network Create Docker network on hosts with consistent settings.
config Manage compose-farm configuration files.
│ ssh Manage SSH keys for passwordless authentication. │
──────────────────────────────────────────────────────────────────────────────╯
╭─ Lifecycle ──────────────────────────────────────────────────────────────────╮
up Start stacks (docker compose up -d). Auto-migrates if host
changed.
down Stop stacks (docker compose down).
stop Stop services without removing containers (docker compose │
stop).
pull Pull latest images (docker compose pull).
restart Restart running containers (docker compose restart).
update Update stacks (pull + build + up). Shorthand for 'up --pull
│ --build'. │
│ apply Make reality match config (start, migrate, stop │
strays/orphans as needed).
compose Run any docker compose command on a stack.
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Monitoring ─────────────────────────────────────────────────────────────────╮
│ logs Show stack logs. With --service, shows logs for just that │
│ service. │
ps Show status of stacks.
│ stats Show overview statistics for hosts and stacks. │
│ list List all stacks and their assigned hosts. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Server ─────────────────────────────────────────────────────────────────────╮
│ web Start the web UI server. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --version -v Show version and exit
│ --install-completion Install completion for the current shell.
│ --show-completion Show completion for the current shell, to copy it or
│ customize the installation.
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Configuration ────────────────────────────────────────────────────────────────────────
│ traefik-file Generate a Traefik file-provider fragment from compose Traefik labels.
refresh Update local state from running stacks.
check Validate configuration, traefik labels, mounts, and networks.
init-network Create Docker network on hosts with consistent settings.
config Manage compose-farm configuration files.
ssh Manage SSH keys for passwordless authentication.
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Lifecycle ────────────────────────────────────────────────────────────────────────────
│ up Start stacks (docker compose up -d). Auto-migrates if host changed. │
down Stop stacks (docker compose down).
stop Stop services without removing containers (docker compose stop).
pull Pull latest images (docker compose pull).
restart Restart running containers (docker compose restart).
update Update stacks (pull + build + up). Shorthand for 'up --pull --build'.
apply Make reality match config (start, migrate, stop strays/orphans as
needed).
compose Run any docker compose command on a stack.
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Monitoring ───────────────────────────────────────────────────────────────────────────╮
logs Show stack logs. With --service, shows logs for just that service.
ps Show status of stacks.
│ stats Show overview statistics for hosts and stacks. │
│ list List all stacks and their assigned hosts. │
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Server ───────────────────────────────────────────────────────────────────────────────╮
web Start the web UI server.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -546,18 +541,18 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Start stacks (docker compose up -d). Auto-migrates if host changed.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --host -H TEXT Filter to stacks on this host │
│ --service -s TEXT Target a specific service within the stack │
│ --pull Pull images before starting (--pull always) │
│ --build Build images before starting │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --host -H TEXT Filter to stacks on this host
│ --service -s TEXT Target a specific service within the stack
│ --pull Pull images before starting (--pull always)
│ --build Build images before starting
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -584,17 +579,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Stop stacks (docker compose down).
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --orphaned Stop orphaned stacks (in state but removed from │
config)
│ --host -H TEXT Filter to stacks on this host
│ --config -c PATH Path to config file
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --orphaned Stop orphaned stacks (in state but removed from config)
--host -H TEXT Filter to stacks on this host
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -621,15 +615,15 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Stop services without removing containers (docker compose stop).
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --service -s TEXT Target a specific service within the stack │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --service -s TEXT Target a specific service within the stack
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -656,15 +650,15 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Pull latest images (docker compose pull).
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --service -s TEXT Target a specific service within the stack │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --service -s TEXT Target a specific service within the stack
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -691,15 +685,15 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Restart running containers (docker compose restart).
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --service -s TEXT Target a specific service within the stack │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --service -s TEXT Target a specific service within the stack
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -726,15 +720,15 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Update stacks (pull + build + up). Shorthand for 'up --pull --build'.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --service -s TEXT Target a specific service within the stack │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --service -s TEXT Target a specific service within the stack
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -774,15 +768,14 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Use --no-strays to skip stopping stray stacks.
Use --full to also run 'up' on all stacks (picks up compose/env changes).
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --dry-run -n Show what would change without executing │
│ --no-orphans Only migrate, don't stop orphaned stacks │
│ --no-strays Don't stop stray stacks (running on wrong host) │
│ --full -f Also run up on all stacks to apply config │
changes
│ --config -c PATH Path to config file
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --dry-run -n Show what would change without executing
│ --no-orphans Only migrate, don't stop orphaned stacks
│ --no-strays Don't stop stray stacks (running on wrong host)
│ --full -f Also run up on all stacks to apply config changes
--config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -819,17 +812,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
cf compose mystack exec web bash - interactive shell
cf compose mystack config - view parsed config
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ * stack TEXT Stack to operate on (use '.' for current dir) │
[required]
* command TEXT Docker compose command [required]
│ args [ARGS]... Additional arguments │
──────────────────────────────────────────────────────────────────────────────
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --host -H TEXT Filter to stacks on this host
│ --config -c PATH Path to config file
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ * stack TEXT Stack to operate on (use '.' for current dir) [required]
* command TEXT Docker compose command [required]
args [ARGS]... Additional arguments
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --host -H TEXT Filter to stacks on this host │
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -858,16 +850,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Generate a Traefik file-provider fragment from compose Traefik labels.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --output -o PATH Write Traefik file-provider YAML to this path │
(stdout if omitted)
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --output -o PATH Write Traefik file-provider YAML to this path (stdout if
omitted)
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -903,16 +895,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Use 'cf apply' to make reality match your config (stop orphans, migrate).
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --config -c PATH Path to config file │
│ --log-path -l PATH Path to Dockerfarm TOML log │
│ --dry-run -n Show what would change without writing │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --config -c PATH Path to config file
│ --log-path -l PATH Path to Dockerfarm TOML log
│ --dry-run -n Show what would change without writing
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -945,14 +937,14 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Use --local to skip SSH-based checks for faster validation.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --local Skip SSH-based checks (faster) │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --local Skip SSH-based checks (faster)
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -984,16 +976,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
communication. Uses the same subnet/gateway on all hosts to ensure
consistent networking.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ hosts [HOSTS]... Hosts to create network on (default: all) │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --network -n TEXT Network name [default: mynetwork] │
│ --subnet -s TEXT Network subnet [default: 172.20.0.0/16] │
│ --gateway -g TEXT Network gateway [default: 172.20.0.1] │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ hosts [HOSTS]... Hosts to create network on (default: all)
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --network -n TEXT Network name [default: mynetwork]
│ --subnet -s TEXT Network subnet [default: 172.20.0.0/16]
│ --gateway -g TEXT Network gateway [default: 172.20.0.1]
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -1021,19 +1013,18 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Manage compose-farm configuration files.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ init Create a new config file with documented example. │
│ edit Open the config file in your default editor. │
│ show Display the config file location and contents. │
│ path Print the config file path (useful for scripting). │
│ validate Validate the config file syntax and schema. │
│ symlink Create a symlink from the default config location to a config │
file.
│ init-env Generate a .env file for Docker deployment. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Commands ─────────────────────────────────────────────────────────────────────────────
│ init Create a new config file with documented example.
│ edit Open the config file in your default editor.
│ show Display the config file location and contents.
│ path Print the config file path (useful for scripting).
│ validate Validate the config file syntax and schema.
│ symlink Create a symlink from the default config location to a config file.
init-env Generate a .env file for Docker deployment.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -1061,14 +1052,14 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Manage SSH keys for passwordless authentication.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ keygen Generate SSH key (does not distribute to hosts). │
│ setup Generate SSH key and distribute to all configured hosts. │
│ status Show SSH key status and host connectivity. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Commands ─────────────────────────────────────────────────────────────────────────────
│ keygen Generate SSH key (does not distribute to hosts).
│ setup Generate SSH key and distribute to all configured hosts.
│ status Show SSH key status and host connectivity.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -1097,19 +1088,18 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Show stack logs. With --service, shows logs for just that service.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --host -H TEXT Filter to stacks on this host │
│ --service -s TEXT Target a specific service within the stack │
│ --follow -f Follow logs │
│ --tail -n INTEGER Number of lines (default: 20 for --all, 100 │
otherwise)
│ --config -c PATH Path to config file
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --host -H TEXT Filter to stacks on this host
│ --service -s TEXT Target a specific service within the stack
│ --follow -f Follow logs
│ --tail -n INTEGER Number of lines (default: 20 for --all, 100 otherwise)
--config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -1142,16 +1132,16 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
With --host: shows stacks on that host.
With --service: filters to a specific service within the stack.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ stacks [STACKS]... Stacks to operate on │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --all -a Run on all stacks │
│ --host -H TEXT Filter to stacks on this host │
│ --service -s TEXT Target a specific service within the stack │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Arguments ────────────────────────────────────────────────────────────────────────────
│ stacks [STACKS]... Stacks to operate on
╰────────────────────────────────────────────────────────────────────────────────────────
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --all -a Run on all stacks
│ --host -H TEXT Filter to stacks on this host
│ --service -s TEXT Target a specific service within the stack
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -1183,14 +1173,13 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
With --live: Also queries Docker on each host for container counts.
With --containers: Shows per-container resource stats (requires Glances).
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --live -l Query Docker for live container stats │
│ --containers -C Show per-container resource stats (requires │
Glances)
│ --host -H TEXT Filter to stacks on this host
│ --config -c PATH Path to config file
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --live -l Query Docker for live container stats
│ --containers -C Show per-container resource stats (requires Glances)
--host -H TEXT Filter to stacks on this host
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -1217,12 +1206,12 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
List all stacks and their assigned hosts.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --host -H TEXT Filter to stacks on this host │
│ --simple -s Plain output (one stack per line, for scripting) │
│ --config -c PATH Path to config file │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --host -H TEXT Filter to stacks on this host
│ --simple -s Plain output (one stack per line, for scripting)
│ --config -c PATH Path to config file
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```
@@ -1251,12 +1240,12 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a
Start the web UI server.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --host -H TEXT Host to bind to [default: 0.0.0.0] │
│ --port -p INTEGER Port to listen on [default: 8000] │
│ --reload -r Enable auto-reload for development │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ──────────────────────────────────────────────────────────────────────────────
│ --host -H TEXT Host to bind to [default: 0.0.0.0]
│ --port -p INTEGER Port to listen on [default: 8000]
│ --reload -r Enable auto-reload for development
│ --help -h Show this message and exit.
╰────────────────────────────────────────────────────────────────────────────────────────
```

View File

@@ -0,0 +1,6 @@
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://plausible.nijho.lt/js/pa-NRX7MolONWKTUREJpAjkB.js"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>

View File

@@ -631,18 +631,28 @@ async def check_paths_exist(
host_name: str,
paths: list[str],
) -> dict[str, bool]:
"""Check if multiple paths exist on a specific host.
"""Check if multiple paths exist and are accessible on a specific host.
Returns a dict mapping path -> exists.
Handles permission denied as "exists" (path is there, just not accessible).
Uses timeout to detect stale NFS mounts that would hang.
"""
# Only report missing if stat says "No such file", otherwise assume exists
# (handles permission denied correctly - path exists, just not accessible)
# Use timeout to detect stale NFS mounts (which hang on access)
# - First try ls with timeout to check accessibility
# - If ls succeeds: path exists and is accessible
# - If ls fails/times out: use stat (also with timeout) to distinguish
# "no such file" from "permission denied" or stale NFS
# - Timeout (exit code 124) is treated as inaccessible (stale NFS mount)
return await _batch_check_existence(
config,
host_name,
paths,
lambda esc: f"stat '{esc}' 2>&1 | grep -q 'No such file' && echo 'N:{esc}' || echo 'Y:{esc}'",
lambda esc: (
f"OUT=$(timeout 2 stat '{esc}' 2>&1); RC=$?; "
f"if [ $RC -eq 124 ]; then echo 'N:{esc}'; "
f"elif echo \"$OUT\" | grep -q 'No such file'; then echo 'N:{esc}'; "
f"else echo 'Y:{esc}'; fi"
),
"mount-check",
)

View File

@@ -31,7 +31,29 @@
.editor-wrapper.env-wrapper { height: 250px; }
.editor-wrapper.viewer-wrapper { height: 300px; }
/* Terminal - no custom CSS needed, using h-full class in HTML */
/* Terminal - mobile touch scrolling improvements */
.xterm-viewport {
/* Allow native scroll momentum on mobile */
-webkit-overflow-scrolling: touch;
/* Prevent pull-to-refresh from interfering */
overscroll-behavior: contain;
}
/* Mobile-specific terminal adjustments */
@media (max-width: 1023px) {
#terminal-container,
#console-terminal,
#exec-terminal {
/* Use viewport height on mobile for better sizing */
min-height: 200px;
max-height: 50vh;
}
/* Ensure terminal content is scrollable within container */
.xterm-screen {
touch-action: pan-y;
}
}
/* Prevent save button resize when text changes */
#save-btn, #save-config-btn {

View File

@@ -14,6 +14,9 @@ const ANSI = {
CRLF: '\r\n'
};
// Detect mobile/touch device
const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// Terminal color theme (dark mode matching PicoCSS)
const TERMINAL_THEME = {
background: '#1a1a2e',
@@ -136,12 +139,19 @@ function whenXtermReady(callback, maxAttempts = 20) {
function createTerminal(container, extraOptions = {}, onResize = null) {
container.innerHTML = '';
// Mobile-optimized terminal options
const mobileOptions = IS_TOUCH_DEVICE ? {
smoothScrollDuration: 125, // Smoother scroll animation on touch
scrollSensitivity: 3, // Increased sensitivity for touch scrolling
} : {};
const term = new Terminal({
convertEol: true,
theme: TERMINAL_THEME,
fontSize: 13,
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
scrollback: 5000,
...mobileOptions,
...extraOptions
});
@@ -494,7 +504,9 @@ function refreshDashboard() {
* Filter sidebar stacks by name and host
*/
function sidebarFilter() {
const q = (document.getElementById('sidebar-filter')?.value || '').toLowerCase();
const input = document.getElementById('sidebar-filter');
const clearBtn = document.getElementById('sidebar-filter-clear');
const q = (input?.value || '').toLowerCase();
const h = document.getElementById('sidebar-host-select')?.value || '';
let n = 0;
document.querySelectorAll('#sidebar-stacks li').forEach(li => {
@@ -503,9 +515,26 @@ function sidebarFilter() {
if (show) n++;
});
document.getElementById('sidebar-count').textContent = '(' + n + ')';
// Show/hide clear button based on input value
if (clearBtn) {
clearBtn.classList.toggle('hidden', !q);
}
}
window.sidebarFilter = sidebarFilter;
/**
* Clear sidebar filter input and refresh list
*/
function clearSidebarFilter() {
const input = document.getElementById('sidebar-filter');
if (input) {
input.value = '';
input.focus();
}
sidebarFilter();
}
window.clearSidebarFilter = clearSidebarFilter;
// Play intro animation on command palette button
function playFabIntro() {
const fab = document.getElementById('cmd-fab');

View File

@@ -21,7 +21,7 @@
<!-- Terminal -->
{% call collapse("Terminal", checked=True, icon=terminal(), subtitle="Full shell access to selected host") %}
<div id="console-terminal" class="w-full bg-base-300 rounded-lg overflow-hidden resize-y" style="height: 384px; min-height: 200px;"></div>
<div id="console-terminal" class="w-full bg-base-300 rounded-lg overflow-hidden resize-y" style="height: 384px; min-height: 200px; touch-action: pan-y;"></div>
{% endcall %}
<!-- Editor -->

View File

@@ -159,6 +159,12 @@
</svg>
{% endmacro %}
{% macro x(size=16) %}
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
</svg>
{% endmacro %}
{% macro alert_triangle(size=16) %}
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>

View File

@@ -1,4 +1,4 @@
{% from "partials/icons.html" import home, search, terminal, box %}
{% from "partials/icons.html" import home, search, terminal, box, x %}
<!-- Navigation Links -->
<div class="mb-4">
<ul class="menu" hx-boost="true" hx-target="#main-content" hx-select="#main-content" hx-swap="outerHTML">
@@ -13,7 +13,7 @@
<h4 class="text-xs uppercase tracking-wide text-base-content/60 px-3 py-1">Stacks <span class="opacity-50" id="sidebar-count">({{ stacks | length }})</span></h4>
<div class="px-2 mb-2 flex flex-col gap-1">
<label class="input input-xs flex items-center gap-2 bg-base-200">
{{ search(14) }}<input type="text" id="sidebar-filter" placeholder="Filter..." onkeyup="sidebarFilter()" />
{{ search(14) }}<input type="text" id="sidebar-filter" placeholder="Filter..." onkeyup="sidebarFilter()" /><button type="button" id="sidebar-filter-clear" class="hidden opacity-50 hover:opacity-100 cursor-pointer" onclick="clearSidebarFilter()">{{ x(12) }}</button>
</label>
<select id="sidebar-host-select" class="select select-xs bg-base-200 w-full" onchange="sidebarFilter()">
<option value="">All hosts</option>

View File

@@ -7,7 +7,7 @@
<span id="terminal-spinner" class="loading loading-spinner loading-sm hidden"></span>
</div>
<div class="collapse-content">
<div id="terminal-container" class="bg-[#1a1a2e] rounded-lg h-[300px] border border-white/10 resize-y overflow-hidden">
<div id="terminal-container" class="bg-[#1a1a2e] rounded-lg h-[300px] lg:h-[300px] border border-white/10 resize-y overflow-hidden" style="touch-action: pan-y;">
<div id="terminal-output" class="h-full"></div>
</div>
</div>

View File

@@ -71,7 +71,7 @@
hx-swap="innerHTML">
<span class="loading loading-spinner loading-sm"></span> Loading containers...
</div>
<div id="exec-terminal-container" class="bg-[#1a1a2e] rounded-lg h-[400px] border border-white/10 hidden">
<div id="exec-terminal-container" class="bg-[#1a1a2e] rounded-lg h-[400px] border border-white/10 hidden" style="touch-action: pan-y;">
<div id="exec-terminal" class="h-full"></div>
</div>
{% endcall %}

View File

@@ -26,6 +26,7 @@ nav = [
]
[project.theme]
custom_dir = "docs/overrides"
language = "en"
features = [
@@ -81,6 +82,9 @@ repo = "lucide/github"
[project.extra]
generator = false
[project.extra.analytics]
provider = "custom"
[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/basnijholt/compose-farm"