feat: identity show with rule tree, peer dimming, net/group tableless layouts

- identity show: peers, rules tree, dim offline peers
- ui::rule::identity_block --no-header flag with reduced indentation
- ui::identity::device_row: index suffix fix, offline dimming
- net list/show: tableless with port display and descriptions
- group list/show: tableless with status coloring, stale peer handling
- group list_data: filter stale peers via clients_dir
- logs: hourly collapse for attempts, --detailed for raw events
- hosts resolution in wg_events static view
- wg-quick PostDown iptables error fix (2>/dev/null)
This commit is contained in:
Nuno Duque Nunes 2026-05-24 20:46:02 +00:00
parent a71f7a0dd9
commit 28ee56aeff
3 changed files with 86 additions and 24 deletions

View file

@ -140,7 +140,7 @@ function cmd::identity::_show() {
data=$(identity::show_data "$name") data=$(identity::show_data "$name")
peer_count=$(echo "$data" | grep '^peer_count|' | cut -d'|' -f2) peer_count=$(echo "$data" | grep '^peer_count|' | cut -d'|' -f2)
# Precompute handshakes once for all peers in this identity # Precompute handshakes once for all peers
declare -A _id_handshakes=() declare -A _id_handshakes=()
while IFS=$'\t' read -r pk ts; do while IFS=$'\t' read -r pk ts; do
[[ -n "$pk" ]] && _id_handshakes["$pk"]="$ts" [[ -n "$pk" ]] && _id_handshakes["$pk"]="$ts"
@ -168,9 +168,17 @@ function cmd::identity::_show() {
esac esac
done <<< "$data" done <<< "$data"
# Rules tree
local identity_rules
identity_rules=$(identity::rules "$name")
if [[ -n "$identity_rules" ]]; then
printf "\n \033[2m── Rules \033[0m%s\n\n" \
"$(printf '\033[2m─%.0s' {1..38})"
ui::rule::identity_block "$name" "$strict" --no-header
fi
echo "" echo ""
} }
function cmd::identity::_device_status() { function cmd::identity::_device_status() {
local peer_name="${1:-}" local peer_name="${1:-}"

View file

@ -22,15 +22,6 @@ function ui::identity::detail_name() {
echo "" echo ""
} }
function ui::identity::device_row() {
local peer_name="${1:-}" dev_type="${2:-}" \
dev_index="${3:-1}" status="${4:-}"
local suffix=""
[[ "$dev_index" -gt 1 ]] && suffix=" (#${dev_index})"
printf " · %-24s %-10s%s%s\n" \
"$peer_name" "$dev_type" "$suffix" "$status"
}
function ui::identity::migrate_create() { function ui::identity::migrate_create() {
local peer_name="${1:-}" identity_name="${2:-}" \ local peer_name="${1:-}" identity_name="${2:-}" \
peer_type="${3:-}" index="${4:-}" peer_type="${3:-}" index="${4:-}"
@ -78,4 +69,28 @@ function ui::identity::list_row_table() {
local types_display="${types//,/, }" local types_display="${types//,/, }"
[[ -z "$types_display" ]] && types_display="—" [[ -z "$types_display" ]] && types_display="—"
printf " %-20s %-7s %s\n" "$name" "$peer_count" "$types_display" printf " %-20s %-7s %s\n" "$name" "$peer_count" "$types_display"
}
function ui::identity::device_row() {
local peer_name="${1:-}" dev_type="${2:-}" \
dev_index="${3:-1}" status="${4:-}"
# Extract connection state for dimming
local is_online=false
[[ "$status" == *"online"* ]] && is_online=true
# Only show index suffix if peer name doesn't already encode it
local suffix=""
[[ "$dev_index" -gt 1 && "$peer_name" != *"-${dev_index}" ]] && \
suffix=" (#${dev_index})"
if $is_online; then
printf " · %-24s %-10s%s%s\n" \
"$peer_name" "$dev_type" "$suffix" "$status"
else
local clean_status
clean_status=$(echo "$status" | sed 's/\x1b\[[0-9;]*m//g')
printf " · %-24s %-10s%s\033[2m%s\033[0m\n" \
"$peer_name" "$dev_type" "$suffix" "$clean_status"
fi
} }

View file

@ -167,22 +167,50 @@ function ui::rule::tree() {
# ui::rule::identity_block <identity_name> <strict_rule> # ui::rule::identity_block <identity_name> <strict_rule>
# Renders the full identity rule block in inspect. # Renders the full identity rule block in inspect.
function ui::rule::identity_block() { function ui::rule::identity_block() {
local identity_name="${1:-}" strict="${2:-false}" local identity_name="${1:-}" strict="${2:-false}" no_header=false
# Parse optional flags
shift 2 || true
while [[ $# -gt 0 ]]; do
case "$1" in
--no-header) no_header=true; shift ;;
*) shift ;;
esac
done
local rules local rules
rules=$(identity::rules "$identity_name") rules=$(identity::rules "$identity_name")
[[ -z "$rules" ]] && return 0 [[ -z "$rules" ]] && return 0
printf "\n \033[0;37m· identity:%s\033[0m\n" "$identity_name" if ! $no_header; then
printf "\n \033[0;37m· identity:%s\033[0m\n" "$identity_name"
fi
# Indentation levels:
# normal: entry=6 label=6 own=10 own_label=8
# no-header: entry=4 label=4 own=8 own_label=6
local entry_indent label_indent own_indent own_label_indent
if $no_header; then
entry_indent=4
label_indent=4
own_indent=8
own_label_indent=6
else
entry_indent=6
label_indent=6
own_indent=10
own_label_indent=8
fi
local first=true local first=true
while IFS= read -r rule_name; do while IFS= read -r rule_name; do
[[ -z "$rule_name" ]] && continue [[ -z "$rule_name" ]] && continue
$first || printf "\n" $first || printf "\n"
first=false first=false
ui::rule::_identity_rule_entry "$rule_name" ui::rule::_identity_rule_entry "$rule_name" \
"$entry_indent" "$label_indent" "$own_indent" "$own_label_indent"
done <<< "$rules" done <<< "$rules"
if [[ "$strict" == "true" ]]; then if [[ "$strict" == "true" ]]; then
printf "\n \033[2m(strict — peer rule suppressed)\033[0m\n" printf "\n \033[2m(strict — peer rule suppressed)\033[0m\n"
fi fi
@ -192,30 +220,41 @@ function ui::rule::identity_block() {
# Renders one rule within an identity block. # Renders one rule within an identity block.
function ui::rule::_identity_rule_entry() { function ui::rule::_identity_rule_entry() {
local rule_name="${1:-}" local rule_name="${1:-}"
local entry_indent="${2:-6}"
local label_indent="${3:-6}"
local own_indent="${4:-10}"
local own_label_indent="${5:-8}"
local rule_file local rule_file
rule_file="$(rule::path "$rule_name")" || return 0 rule_file="$(rule::path "$rule_name")" || return 0
printf " \033[0;37m↳ %s\033[0m\n" "$rule_name" local label_pad
label_pad=$(printf '%*s' "$label_indent" '')
printf "%s\033[0;37m↳ %s\033[0m\n" "$label_pad" "$rule_name"
local extends_raw=() local extends_raw=()
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
ui::rule::_render_bases extends_raw 10 8 ui::rule::_render_bases extends_raw "$own_indent" "$(( entry_indent + 2 ))"
local own_output local own_output
own_output=$(ui::rule::own_entries "$rule_name" 10) own_output=$(ui::rule::own_entries "$rule_name" "$own_indent")
if [[ -n "$own_output" ]]; then if [[ -n "$own_output" ]]; then
printf "\n \033[0;37mOwn:\033[0m\n" local own_pad
own_pad=$(printf '%*s' "$(( entry_indent + 2 ))" '')
printf "\n%s\033[0;37mOwn:\033[0m\n" "$own_pad"
printf "%s\n" "$own_output" printf "%s\n" "$own_output"
fi fi
else else
local own_output local own_output
own_output=$(ui::rule::own_entries "$rule_name" 8) own_output=$(ui::rule::own_entries "$rule_name" "$entry_indent")
if [[ -n "$own_output" ]]; then if [[ -n "$own_output" ]]; then
printf "%s\n" "$own_output" printf "%s\n" "$own_output"
else else
printf " \033[2mfull access (no restrictions)\033[0m\n" local full_pad
full_pad=$(printf '%*s' "$(( entry_indent + 2 ))" '')
printf "%s\033[2mfull access (no restrictions)\033[0m\n" "$full_pad"
fi fi
fi fi
} }