feat: display config, table layouts for all commands

- display.module.sh: style toggle per view (compact/table)
- display.json: default config with all views set to compact
- ctx::display: points to .wgctl/config/display.json
- list: _render_table with dynamic widths, colors, shared row_color/status_color
- group/identity/net/hosts/activity: _render_table added
- rule/subnet/policy: table UI functions + _render_table
- ui::peer::status_color: \033[2m for offline (dimmer, more readable)
- note: individual table layout refinements pending cleanup pass
- note: configurable colors per field deferred to display config v2
This commit is contained in:
Nuno Duque Nunes 2026-05-27 03:32:31 +00:00
parent 7a544f9019
commit 1a78dcf5da
14 changed files with 241 additions and 47 deletions

View file

@ -143,7 +143,12 @@ function cmd::activity::run() {
log::section "Activity Monitor (last ${hours_display})" log::section "Activity Monitor (last ${hours_display})"
echo "" echo ""
if display::is_table "activity"; then
cmd::activity::_render_table "$data"
return 0
fi
local first_peer=true skip_peer=false local first_peer=true skip_peer=false
while IFS='|' read -r record_type rest; do while IFS='|' read -r record_type rest; do
@ -195,6 +200,34 @@ function cmd::activity::run() {
echo "" echo ""
} }
function cmd::activity::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::activity::header_table
local skip_peer=false
while IFS='|' read -r record_type rest; do
case "$record_type" in
peer)
local name rx tx drops
IFS='|' read -r name rx tx drops <<< "$rest"
skip_peer=false
local rx_fmt tx_fmt
rx_fmt=$(fmt::bytes "$rx")
tx_fmt=$(fmt::bytes "$tx")
ui::activity::peer_row_table "$name" "$rx_fmt" "$tx_fmt" "$drops" ""
;;
service)
$skip_peer && continue
local peer dest count
IFS='|' read -r peer dest count <<< "$rest"
ui::activity::service_row_table "$dest" "$count" "drops"
;;
esac
done <<< "$data"
}
function cmd::activity::_output_json() { function cmd::activity::_output_json() {
local hours="${1:-24}" local hours="${1:-24}"
local data local data

View file

@ -147,6 +147,11 @@ function cmd::group::list() {
log::section "Groups" log::section "Groups"
echo "" echo ""
if display::is_table "group_list"; then
cmd::group::_render_table "$data" "$w_name" "$w_desc"
return 0
fi
while IFS="|" read -r name desc total blocked; do while IFS="|" read -r name desc total blocked; do
[[ -z "$name" ]] && continue [[ -z "$name" ]] && continue
ui::group::list_row "$name" "$desc" "$total" "$blocked" "$w_name" "$w_desc" ui::group::list_row "$name" "$desc" "$total" "$blocked" "$w_name" "$w_desc"
@ -223,6 +228,17 @@ function cmd::group::show() {
printf "\n" printf "\n"
} }
function cmd::group::_render_table() {
local data="${1:-}" w_name="${2:-20}" w_desc="${3:-20}"
[[ -z "$data" ]] && return 0
ui::group::list_header_table
while IFS='|' read -r name desc total blocked; do
[[ -z "$name" ]] && continue
ui::group::list_row_table "$name" "$desc" "$total" "$blocked"
done <<< "$data"
}
# ============================================ # ============================================
# Add # Add
# ============================================ # ============================================

View file

@ -146,6 +146,11 @@ function cmd::hosts::list() {
log::section "Host Mappings" log::section "Host Mappings"
echo "" echo ""
if display::is_table "hosts_list"; then
cmd::hosts::_render_table "$data"
return 0
fi
local last_type="" found=false local last_type="" found=false
while IFS='|' read -r type key name desc tags; do while IFS='|' read -r type key name desc tags; do
[[ -z "$type" ]] && continue [[ -z "$type" ]] && continue
@ -167,19 +172,15 @@ function cmd::hosts::list() {
echo "" echo ""
} }
# Table version (kept for future display config) function cmd::hosts::_render_table() {
function cmd::hosts::_list_table() { local data="${1:-}"
local hosts_file="${1:-}" [[ -z "$data" ]] && return 0
printf "\n %-6s %-18s %-16s %-30s %s\n" \
"TYPE" "KEY" "NAME" "DESCRIPTION" "TAGS" ui::hosts::list_header_table
printf " %s\n" "$(printf '─%.0s' {1..80})"
while IFS='|' read -r type key name desc tags; do while IFS='|' read -r type key name desc tags; do
[[ -z "$type" ]] && continue [[ -z "$type" ]] && continue
printf " %-6s %-18s %-16s %-30s %s\n" \ ui::hosts::list_row_table "$type" "$key" "$name" "$desc" "$tags"
"$type" "$key" "$name" "${desc:-}" "${tags:-}" done <<< "$data"
done < <(json::hosts_list "$hosts_file" 2>/dev/null)
printf "\n"
} }
# ============================================ # ============================================

View file

@ -111,7 +111,12 @@ function cmd::identity::_list() {
log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers." log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers."
return 0 return 0
fi fi
if display::is_table "identity_list"; then
cmd::identity::_render_table "$data"
return 0
fi
echo "" echo ""
while IFS='|' read -r name peer_count types rules policy; do while IFS='|' read -r name peer_count types rules policy; do
local rules_display local rules_display
@ -185,6 +190,21 @@ function cmd::identity::_show() {
echo "" echo ""
} }
function cmd::identity::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
printf "\n %-20s %-8s %-20s %s\n" "NAME" "PEERS" "RULES" "POLICY"
printf " %s\n" "$(printf '─%.0s' {1..65})"
while IFS='|' read -r name peer_count types rules policy; do
[[ -z "$name" ]] && continue
local rules_display
rules_display=$(echo "$rules" | sed 's/,/, /g')
ui::identity::list_row_table "$name" "$peer_count" "$rules_display" "$policy"
done <<< "$data"
printf " %s\n\n" "$(printf '─%.0s' {1..65})"
}
function cmd::identity::_device_status() { function cmd::identity::_device_status() {
local peer_name="${1:-}" local peer_name="${1:-}"
local -n _handshakes="${2:-__empty_map}" local -n _handshakes="${2:-__empty_map}"

View file

@ -363,9 +363,8 @@ function cmd::list::_render_table() {
(( ${#group} > w_group )) && w_group=${#group} (( ${#group} > w_group )) && w_group=${#group}
(( ${#last_seen} > w_last )) && w_last=${#last_seen} (( ${#last_seen} > w_last )) && w_last=${#last_seen}
local cs local cs
cs=$(printf "%s" "$status" | sed 's/\x1b\[[0-9;]*m//g') cs=$(printf "%s" "$status" | sed 's/\x1b\[[0-9;]*m//g')
(( ${#cs} > w_status )) && w_status=${#cs} (( ${#cs} > w_status )) && w_status=${#cs}
echo "DEBUG cs='$cs' name='$name'" >&2
done <<< "$rows" done <<< "$rows"
(( w_name += 2 )); (( w_ip += 2 )) (( w_name += 2 )); (( w_ip += 2 ))
(( w_type += 2 )); (( w_rule += 2 )) (( w_type += 2 )); (( w_rule += 2 ))
@ -390,13 +389,18 @@ cs=$(printf "%s" "$status" | sed 's/\x1b\[[0-9;]*m//g')
local status_colored="${status_color}${clean_status}\033[0m" local status_colored="${status_color}${clean_status}\033[0m"
local last_seen_colored="$last_seen"
[[ -n "$row_color" ]] && last_seen_colored="${row_color}${last_seen}\033[0m" \
|| last_seen_colored="${status_color}${last_seen}\033[0m"
if [[ -n "$row_color" ]]; then if [[ -n "$row_color" ]]; then
printf " %b%-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %-${w_status}s %s\033[0m\n" \ printf " %b%-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %-${w_status}s %s\033[0m\n" \
"$row_color" "$name" "$ip" "$type" "$rule" "$group" "$clean_status" "$last_seen" "$row_color" "$name" "$ip" "$type" "$rule" "$group" "$clean_status" "$last_seen"
else else
printf " %-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %b%*s\033[0m %s\n" \ printf " %-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %b%*s\033[0m %b\n" \
"$name" "$ip" "$type" "$rule" "$group" \ "$name" "$ip" "$type" "$rule" "$group" \
"$status_color${clean_status}" "$status_pad_n" "" "$last_seen" "$status_color${clean_status}" "$status_pad_n" "" \
"$last_seen_colored"
fi fi
done <<< "$rows" done <<< "$rows"

View file

@ -129,7 +129,7 @@ function cmd::net::list() {
log::wg_warning "No services configured" log::wg_warning "No services configured"
return 0 return 0
} }
# Measure column widths # Measure column widths
local w_name=12 w_ip=13 w_ports=16 local w_name=12 w_ip=13 w_ports=16
while IFS="|" read -r name ip desc tags ports; do while IFS="|" read -r name ip desc tags ports; do
@ -144,6 +144,11 @@ function cmd::net::list() {
log::section "Network Services" log::section "Network Services"
echo "" echo ""
if display::is_table "net_list"; then
cmd::net::_render_table "$filtered_data"
return 0
fi
while IFS="|" read -r name ip desc tags ports; do while IFS="|" read -r name ip desc tags ports; do
[[ -z "$name" ]] && continue [[ -z "$name" ]] && continue
@ -162,6 +167,17 @@ function cmd::net::list() {
echo "" echo ""
} }
function cmd::net::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::net::list_header_table
while IFS='|' read -r name ip desc tags port_count; do
[[ -z "$name" ]] && continue
ui::net::list_row_table "$name" "$ip" "$desc" "$tags" "$port_count"
done <<< "$data"
}
# ============================================ # ============================================
# Show # Show
# ============================================ # ============================================

View file

@ -113,6 +113,11 @@ function cmd::policy::_list() {
return 0 return 0
fi fi
if display::is_table "policy_list"; then
cmd::policy::_render_table "$data"
return 0
fi
echo "" echo ""
while IFS='|' read -r name tunnel default_rule strict auto desc; do while IFS='|' read -r name tunnel default_rule strict auto desc; do
ui::policy::list_row "$name" "$default_rule" "$strict" "$auto" ui::policy::list_row "$name" "$default_rule" "$strict" "$auto"
@ -120,6 +125,19 @@ function cmd::policy::_list() {
echo "" echo ""
} }
function cmd::policy::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::policy::list_header_table
while IFS='|' read -r name tunnel default_rule strict auto desc; do
[[ -z "$name" ]] && continue
ui::policy::list_row_table "$name" "$tunnel" "$default_rule" "$strict" "$auto"
done <<< "$data"
printf "\n"
}
function cmd::policy::_show() { function cmd::policy::_show() {
local name="" local name=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do

View file

@ -181,6 +181,11 @@ function cmd::rule::list() {
log::section "Firewall Rules" log::section "Firewall Rules"
echo "" echo ""
if display::is_table "rule_list"; then
cmd::rule::_render_table "$data"
return 0
fi
local current_group="" printing_base=false found_any=false local current_group="" printing_base=false found_any=false
while IFS="|" read -r name desc n_allows n_blocks \ while IFS="|" read -r name desc n_allows n_blocks \
@ -240,6 +245,18 @@ function cmd::rule::list() {
echo "" echo ""
} }
function cmd::rule::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::rule::list_header_table
while IFS='|' read -r name desc n_allows n_blocks peer_count extends is_base group; do
[[ -z "$name" ]] && continue
ui::rule::list_row_table "$name" "$n_allows" "$n_blocks" "$peer_count" "$extends" "$group"
done <<< "$data"
printf "\n"
}
# ============================================ # ============================================
# Show # Show
# ============================================ # ============================================

View file

@ -99,6 +99,11 @@ function cmd::subnet::_list() {
return 0 return 0
fi fi
if display::is_table "subnet_list"; then
cmd::subnet::_render_table "$data"
return 0
fi
echo "" echo ""
local prev_group="" local prev_group=""
while IFS='|' read -r display_name subnet type_key tunnel_mode desc is_group group_parent; do while IFS='|' read -r display_name subnet type_key tunnel_mode desc is_group group_parent; do
@ -120,6 +125,18 @@ function cmd::subnet::_list() {
echo "" echo ""
} }
function cmd::subnet::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::subnet::list_header_table
while IFS='|' read -r type cidr display_name tunnel desc is_group group_parent; do
[[ -z "$type" ]] && continue
ui::subnet::list_row_table "$type" "$cidr" "$tunnel" "$desc"
done <<< "$data"
printf "\n"
}
function cmd::subnet::_maybe_group_separator() { function cmd::subnet::_maybe_group_separator() {
local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}" local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
if [[ "$is_group" == "true" && "$group_parent" != "$prev_group" && -n "$prev_group" ]]; then if [[ "$is_group" == "true" && "$group_parent" != "$prev_group" && -n "$prev_group" ]]; then

View file

@ -31,7 +31,7 @@ function ui::peer::status_color() {
elif [[ "$status" == "online"* ]]; then elif [[ "$status" == "online"* ]]; then
echo "\033[1;32m" echo "\033[1;32m"
else else
echo "\033[2;37m" echo "\033[2m"
fi fi
} }

View file

@ -0,0 +1,45 @@
#!/usr/bin/env bash
function ui::policy::list_row() {
local name="${1:-}" default_rule="${2:-}" strict="${3:-}" auto="${4:-}"
local rule_val="-"
[[ -n "$default_rule" ]] && rule_val="$default_rule"
local rule_padded
rule_padded=$(printf "%-16s" "$rule_val")
local strict_display
[[ "$strict" == "true" ]] && strict_display="yes" || strict_display="no"
local strict_padded
strict_padded=$(printf "%-4s" "$strict_display")
local auto_display=""
[[ "$auto" == "false" ]] && auto_display=" \033[2mauto:\033[0m no"
printf " %-14s \033[2mrule:\033[0m %s \033[2mstrict:\033[0m %s%s\n" \
"$name" "$rule_padded" "$strict_padded" "$auto_display"
}
function ui::policy::detail_field() {
local key="${1:-}" value="${2:-}"
ui::row "$key" "$value"
}
# ======================================================
# Table view
# ======================================================
function ui::policy::list_header_table() {
printf "\n %-16s %-8s %-14s %-8s %s\n" \
"NAME" "TUNNEL" "DEFAULT RULE" "STRICT" "AUTO"
printf " %s\n" "$(printf '─%.0s' {1..60})"
}
function ui::policy::list_row_table() {
local name="${1:-}" tunnel="${2:-}" default_rule="${3:-}" \
strict="${4:-}" auto="${5:-}"
printf " %-16s %-8s %-14s %-8s %s\n" \
"$name" "$tunnel" "${default_rule:--}" "$strict" "$auto"
}

View file

@ -392,6 +392,24 @@ function ui::rule::list_extends_detailed() {
done done
} }
# ======================================================
# Table view
# ======================================================
function ui::rule::list_header_table() {
printf "\n %-20s %-6s %-6s %-8s %-20s %s\n" \
"NAME" "ALLOW" "BLOCK" "PEERS" "EXTENDS" "GROUP"
printf " %s\n" "$(printf '─%.0s' {1..75})"
}
function ui::rule::list_row_table() {
local name="${1:-}" n_allows="${2:-0}" n_blocks="${3:-0}" \
peer_count="${4:-0}" extends="${5:-}" group="${6:-}"
printf " %-20s %-6s %-6s %-8s %-20s %s\n" \
"$name" "+${n_allows}" "-${n_blocks}" "$peer_count" \
"${extends:--}" "${group:--}"
}
# ====================================================== # ======================================================
# Show helpers # Show helpers
# ====================================================== # ======================================================

View file

@ -8,33 +8,6 @@ function ui::subnet::header() {
ui::divider 70 ui::divider 70
} }
function ui::policy::list_row() {
local name="${1:-}" default_rule="${2:-}" strict="${3:-}" auto="${4:-}"
local rule_val="-"
[[ -n "$default_rule" ]] && rule_val="$default_rule"
local rule_padded
rule_padded=$(printf "%-16s" "$rule_val")
local strict_display
[[ "$strict" == "true" ]] && strict_display="yes" || strict_display="no"
local strict_padded
strict_padded=$(printf "%-4s" "$strict_display")
local auto_display=""
[[ "$auto" == "false" ]] && auto_display=" \033[2mauto:\033[0m no"
printf " %-14s \033[2mrule:\033[0m %s \033[2mstrict:\033[0m %s%s\n" \
"$name" "$rule_padded" "$strict_padded" "$auto_display"
}
function ui::policy::detail_field() {
local key="${1:-}" value="${2:-}"
ui::row "$key" "$value"
}
function ui::subnet::row() { function ui::subnet::row() {
local display_name="${1:-}" subnet="${2:-}" type_key="${3:-}" \ local display_name="${1:-}" subnet="${2:-}" type_key="${3:-}" \
tunnel_mode="${4:-}" desc="${5:-}" is_group="${6:-false}" tunnel_mode="${4:-}" desc="${5:-}" is_group="${6:-false}"
@ -210,4 +183,20 @@ function ui::subnet::show_peers_annotated() {
for peer in "${no_identity[@]}"; do for peer in "${no_identity[@]}"; do
printf " · %b\n" "$peer" printf " · %b\n" "$peer"
done done
}
# ======================================================
# Table view
# ======================================================
function ui::subnet::list_header_table() {
printf "\n %-14s %-20s %-8s %s\n" \
"TYPE" "CIDR" "TUNNEL" "DESCRIPTION"
printf " %s\n" "$(printf '─%.0s' {1..65})"
}
function ui::subnet::list_row_table() {
local type="${1:-}" cidr="${2:-}" tunnel="${3:-}" desc="${4:-}"
printf " %-14s %-20s %-8s %s\n" \
"$type" "$cidr" "$tunnel" "${desc:--}"
} }