cleanup: ui::pad_mb removal, watch alignment fixes, endpoint cache fallback

- ui::rule::list_row: inline padding math replaces ui::pad_mb (major perf gain)
- ui::fw_row/wg_row: drop ui::pad_mb for fw/wg labels (always 2 chars)
- watch: endpoint fallback via monitor::get_cached_endpoint
- watch: _poll_handshakes sorts by ts descending (most recent first)
- watch: empty endpoint uses - not — (avoids multi-byte padding issues)
- ui.sh: UTF-8 extra byte constants (_UI_EMDASH_EXTRA, _UI_ARROW_EXTRA, _UI_BULLET_EXTRA)
This commit is contained in:
Nuno Duque Nunes 2026-05-25 15:09:13 +00:00
parent 5c2e16e358
commit 3058750c3d
9 changed files with 98 additions and 76 deletions

View file

@ -108,6 +108,9 @@ function cmd::watch::_poll_handshakes() {
local filter_name="${1:-}" filter_type="${2:-}" filter_peers="${3:-}" local filter_name="${1:-}" filter_type="${2:-}" filter_peers="${3:-}"
local w_client="${4:-20}" w_dest="${5:-18}" local w_client="${4:-20}" w_dest="${5:-18}"
# Collect rows with sort key before printing
local -a rows=()
while IFS= read -r line; do while IFS= read -r line; do
local public_key ts local public_key ts
public_key=$(echo "$line" | awk '{print $1}') public_key=$(echo "$line" | awk '{print $1}')
@ -142,20 +145,34 @@ function cmd::watch::_poll_handshakes() {
local ts_fmt local ts_fmt
ts_fmt=$(fmt::datetime_short "$ts") ts_fmt=$(fmt::datetime_short "$ts")
# Resolve endpoint — try wg show first, fall back to endpoint cache
local endpoint local endpoint
endpoint=$(monitor::endpoint_for_key "$public_key") endpoint=$(monitor::endpoint_for_key "$public_key")
# Resolve endpoint if [[ -z "$endpoint" ]]; then
endpoint=$(monitor::get_cached_endpoint "$client_name")
fi
local endpoint_display local endpoint_display
endpoint_display=$(resolve::ip "${endpoint:-}") endpoint_display=$(resolve::ip "${endpoint:-}")
[[ -z "$endpoint_display" ]] && endpoint_display="${endpoint:-}" [[ -z "$endpoint_display" ]] && endpoint_display="${endpoint:--}"
ui::watch::wg_row "$ts_fmt" "$client_name" "$endpoint_display" "handshake" \ # Build row with ts prefix for sorting
"$w_client" "$w_dest" local row
row=$(ui::watch::wg_row "$ts_fmt" "$client_name" "$endpoint_display" "handshake" \
"$w_client" "$w_dest")
rows+=("${ts}|${row}")
done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null) done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
}
# Sort by ts descending (most recent first) and print
if [[ ${#rows[@]} -gt 0 ]]; then
printf '%s\n' "${rows[@]}" | sort -t'|' -k1,1rn | while IFS= read -r entry; do
printf "%s\n" "${entry#*|}"
done
fi
}
# ============================================ # ============================================
# Event Tailer # Event Tailer
# ============================================ # ============================================
@ -243,8 +260,6 @@ function cmd::watch::_tail_events() {
local now; now=$(date +%s) local now; now=$(date +%s)
local last="${_WATCH_LAST_WG[$wg_key]:-0}" local last="${_WATCH_LAST_WG[$wg_key]:-0}"
# Handshakes — only show if gap > 5min (new session)
# Attempts — shorter window (30s) since each attempt is meaningful
local window=30 local window=30
[[ "$event" == "handshake" ]] && window="${WG_HANDSHAKE_CHECK_TIME_SEC:-300}" [[ "$event" == "handshake" ]] && window="${WG_HANDSHAKE_CHECK_TIME_SEC:-300}"
@ -254,12 +269,15 @@ function cmd::watch::_tail_events() {
local ts_fmt local ts_fmt
ts_fmt=$(fmt::datetime_short "$(json::iso_to_ts "$ts" 2>/dev/null || echo 0)") ts_fmt=$(fmt::datetime_short "$(json::iso_to_ts "$ts" 2>/dev/null || echo 0)")
# Resolve endpoint # Resolve endpoint — fall back to endpoint cache if empty
local endpoint_display local endpoint_resolved
endpoint_display=$(resolve::ip "${endpoint:-}") endpoint_resolved=$(resolve::ip "${endpoint:-}")
[[ -z "$endpoint_display" ]] && endpoint_display="${endpoint:-}" if [[ -z "$endpoint_resolved" && -n "$endpoint" ]]; then
endpoint_resolved="$endpoint"
fi
[[ -z "$endpoint_resolved" ]] && endpoint_resolved="-"
ui::watch::wg_row "$ts_fmt" "$client" "$endpoint_display" "$event" \ ui::watch::wg_row "$ts_fmt" "$client" "$endpoint_resolved" "$event" \
"$w_client" "$w_dest" "$w_client" "$w_dest"
fi fi
done done

View file

@ -3,6 +3,12 @@
UI_ROW_WIDTH=${UI_ROW_WIDTH:-20} UI_ROW_WIDTH=${UI_ROW_WIDTH:-20}
UI_SECTION_WIDTH=${UI_SECTION_WIDTH:-44} UI_SECTION_WIDTH=${UI_SECTION_WIDTH:-44}
# UTF-8 multi-byte character extras (bash ${#} counts bytes, not chars)
# extra = byte_length - visible_char_length
_UI_EMDASH_EXTRA=2 # — (em dash) 3 bytes, 1 visible
_UI_ARROW_EXTRA=2 # → (right arrow) 3 bytes, 1 visible
_UI_BULLET_EXTRA=1 # · (middle dot) 2 bytes, 1 visible
function ui::row() { function ui::row() {
local label="$1" value="$2" width="${3:-$UI_ROW_WIDTH}" local label="$1" value="$2" width="${3:-$UI_ROW_WIDTH}"
printf " %-${width}s %s\n" "${label}:" "$value" printf " %-${width}s %s\n" "${label}:" "$value"

View file

@ -89,7 +89,7 @@ function monitor::cache_endpoint() {
} }
function monitor::get_cached_endpoint() { function monitor::get_cached_endpoint() {
local client="$1" local client="${1:-}"
json::get "$ENDPOINT_CACHE" "$client" json::get "$ENDPOINT_CACHE" "$client"
} }

View file

@ -95,16 +95,15 @@ function ui::watch::fw_row() {
local ts="${1:-}" client="${2:-}" dest_display="${3:-}" \ local ts="${1:-}" client="${2:-}" dest_display="${3:-}" \
w_client="${4:-20}" w_dest="${5:-18}" w_client="${4:-20}" w_dest="${5:-18}"
local ts_pad # "fw" is always 2 visible chars — no padding needed
ts_pad=$(printf "%-11s" "$ts") local src="${_UI_WATCH_FW_COLOR}fw\033[0m"
local src local ts_pad client_pad dest_pad_n
src=$(ui::pad_mb "${_UI_WATCH_FW_COLOR}fw\033[0m" 2) ts_pad=$(printf "%-11s" "$ts")
local client_pad dest_pad_n
client_pad=$(printf "%-${w_client}s" "$client") client_pad=$(printf "%-${w_client}s" "$client")
dest_pad_n=$(( w_dest - ${#dest_display} )) dest_pad_n=$(( w_dest - ${#dest_display} ))
[[ $dest_pad_n -lt 0 ]] && dest_pad_n=0 [[ $dest_pad_n -lt 0 ]] && dest_pad_n=0
# echo "DEBUG fw: ts_bytes=${#ts} src_bytes=${#src} client='$client'(${#client}) client_pad_bytes=${#client_pad}" >&2
printf " %s %b %s \033[1;31m→\033[0m %s%*s \033[1;31mdrop\033[0m\n" \ printf " %s %b %s \033[1;31m→\033[0m %s%*s \033[1;31mdrop\033[0m\n" \
"$ts_pad" "$src" "$client_pad" "$dest_display" "$dest_pad_n" "" "$ts_pad" "$src" "$client_pad" "$dest_display" "$dest_pad_n" ""
} }
@ -113,35 +112,31 @@ function ui::watch::wg_row() {
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \ local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
w_client="${5:-20}" w_endpoint="${6:-18}" w_client="${5:-20}" w_endpoint="${6:-18}"
local ts_pad
ts_pad=$(printf "%-11s" "$ts")
local event_color local event_color
case "$event" in case "$event" in
handshake) event_color="\033[1;32m" ;; handshake) event_color="\033[1;32m" ;;
attempt) event_color="\033[1;31m" ;; attempt) event_color="\033[1;31m" ;;
*) event_color="\033[0;37m" ;; *) event_color="\033[0;37m" ;;
esac esac
local src
src=$(ui::pad_mb "${_UI_WATCH_WG_COLOR}wg\033[0m" 2)
case "$event" in # "wg" is always 2 visible chars — no padding needed
handshake) src="\033[1;32m" ;; # green local src="${_UI_WATCH_WG_COLOR}wg\033[0m"
attempt) src="\033[1;31m" ;; # red
*) src="\033[0;37m" ;; # gray
esac
local src_colored="${src}wg\033[0m"
local client_pad endpoint_pad_n # Use "-" not "—" to avoid multi-byte padding issues
local endpoint_display="${endpoint:--}"
local ts_pad client_pad endpoint_pad_n
ts_pad=$(printf "%-11s" "$ts")
client_pad=$(printf "%-${w_client}s" "$client") client_pad=$(printf "%-${w_client}s" "$client")
endpoint_pad_n=$(( w_endpoint - ${#endpoint} )) endpoint_pad_n=$(( w_endpoint - ${#endpoint_display} ))
[[ $endpoint_pad_n -lt 0 ]] && endpoint_pad_n=0 [[ $endpoint_pad_n -lt 0 ]] && endpoint_pad_n=0
# echo "DEBUG wg: ts_bytes=${#ts} src_bytes=${#src} client='$client'(${#client}) client_pad_bytes=${#client_pad}" >&2
printf " %s %b %s %s%*s %b%s\033[0m\n" \ printf " %s %b %s %s%*s %b%s\033[0m\n" \
"$ts_pad" "$src_colored" "$client_pad" "$endpoint" "$endpoint_pad_n" "" \ "$ts_pad" "$src" "$client_pad" "$endpoint_display" "$endpoint_pad_n" "" \
"$event_color" "$event" "$event_color" "$event"
} }
function ui::watch::header_table() { function ui::watch::header_table() {
printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \ printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \
"TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS" "TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS"

View file

@ -331,20 +331,23 @@ function ui::rule::list_row() {
extends_indicator=" \033[2m↳ ${extends_display}\033[0m" extends_indicator=" \033[2m↳ ${extends_display}\033[0m"
fi fi
# Allows column — green +N if >0, dim +0 if zero # Allows — green +N, padded to 5 visible chars
# Append spaces after reset code so printf doesn't miscount
local allows_str local allows_str
if [[ "$n_allows" -gt 0 ]]; then if [[ "$n_allows" -gt 0 ]]; then
allows_str=$(ui::pad_mb "\033[1;32m+${n_allows}\033[0m" 5) printf -v allows_str "\033[1;32m+%s\033[0m" "$n_allows"
allows_str="${allows_str}$(printf '%*s' "$(( 4 - ${#n_allows} ))" '')"
else else
allows_str=$(ui::pad_mb "\033[2m+0\033[0m" 5) printf -v allows_str "\033[2m+0\033[0m " # "+0" = 2 visible + 3 spaces = 5
fi fi
# Blocks column — red -N if >0, dim -0 if zero # Blocks — red -N, padded to 5 visible chars
local blocks_str local blocks_str
if [[ "$n_blocks" -gt 0 ]]; then if [[ "$n_blocks" -gt 0 ]]; then
blocks_str=$(ui::pad_mb "\033[1;31m-${n_blocks}\033[0m" 5) printf -v blocks_str "\033[1;31m-%s\033[0m" "$n_blocks"
blocks_str="${blocks_str}$(printf '%*s' "$(( 4 - ${#n_blocks} ))" '')"
else else
blocks_str=$(ui::pad_mb "\033[2m-0\033[0m" 5) printf -v blocks_str "\033[2m-0\033[0m " # "-0" = 2 visible + 3 spaces = 5
fi fi
# Peers — dim if zero # Peers — dim if zero