diff --git a/commands/list.command.sh b/commands/list.command.sh index 956ad91..c21ed06 100644 --- a/commands/list.command.sh +++ b/commands/list.command.sh @@ -230,30 +230,28 @@ function cmd::list::run() { # ============================================ function cmd::list::_collect_all_rows() { - # Outputs pipe-delimited rows for peers that pass all filters - # Fields: name|ip|type|rule|group|status|last_seen|is_blocked|is_restricted local dir dir="$(ctx::clients)" - + local _verbose_status="${LIST_VERBOSE_STATUS:-true}" + for conf in "${dir}"/*.conf; do [[ -f "$conf" ]] || continue local client_name client_name=$(basename "$conf" .conf) [[ -z "$client_name" ]] && continue - - # Identity filter + if [[ ${#p_identity_filter[@]} -gt 0 && \ -z "${p_identity_filter[$client_name]:-}" ]]; then continue fi - + local ip="${p_ips[$client_name]:-}" [[ -z "$ip" ]] && ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1) [[ -z "$ip" ]] && continue - + local type="${p_types[$client_name]:-unknown}" [[ -n "$filter_type" && "$type" != "$filter_type" ]] && continue - + local pubkey="${p_pubkeys[$client_name]:-}" local handshake_ts="${wg_handshakes[$pubkey]:-0}" local is_blocked="${p_blocked[$client_name]:-false}" @@ -261,27 +259,31 @@ function cmd::list::_collect_all_rows() { local last_ts="${p_last_ts[$client_name]:-}" local rule="${p_rules[$client_name]:-}" local group="${p_main_groups[$client_name]:-}" - - # Apply status filters + if $online_only; then peers::is_online "$client_name" "$handshake_ts" "$last_ts" || continue; fi if $offline_only; then peers::is_offline "$client_name" "$handshake_ts" "$last_ts" || continue; fi if $restricted_only && [[ "$is_restricted" != "true" ]]; then continue; fi if $blocked_only && [[ "$is_blocked" != "true" ]]; then continue; fi if $allowed_only && { [[ "$is_blocked" == "true" ]] || \ [[ "$is_restricted" == "true" ]]; }; then continue; fi - - # Apply rule/group filters if [[ -n "$filter_rule" && "$rule" != "$filter_rule" ]]; then continue; fi if [[ -n "$filter_group" ]]; then local all_groups="${peer_group_map[$client_name]:-}" [[ "$all_groups" != *"$filter_group"* ]] && continue fi - - # Resolve status - local state - state=$(peers::connection_state "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts") - local status="${state%%|*}" - + + # Resolve status — verbose or simple + local status + if [[ "$_verbose_status" == "true" ]]; then + status=$(peers::format_status_verbose "$client_name" "$pubkey" \ + "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts" | \ + sed 's/\x1b\[[0-9;]*m//g') + else + local state + state=$(peers::connection_state "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts") + status="${state%%|*}" + fi + # Resolve last seen local last_seen="-" if [[ "$is_blocked" == "true" && -n "$last_ts" && "$last_ts" != "0" ]]; then @@ -291,21 +293,20 @@ function cmd::list::_collect_all_rows() { elif [[ -n "$handshake_ts" && "$handshake_ts" != "0" ]]; then local ts_display ts_display=$(fmt::datetime_short "$handshake_ts") - if [[ "$status" == "online" ]]; then + if [[ "$status" == "online"* ]]; then last_seen="${ts_display} (handshake)" else last_seen="$ts_display" fi fi - - printf "%s|%s|%s|%s|%s|%s|%s|%s|%s\n" \ - "$client_name" "$ip" "$type" \ - "${rule:--}" "${group:--}" \ - "$status" "$last_seen" \ - "$is_blocked" "$is_restricted" + + printf "%s|%s|%s|%s|%s|%s|%s|%s|%s\n" \ + "$client_name" "$ip" "$type" \ + "${rule:--}" "${group:--}" \ + "$status" "$last_seen" \ + "$is_blocked" "$is_restricted" done } - # ============================================ # Compact render # ============================================ @@ -464,22 +465,34 @@ function cmd::list::_render_detailed() { function cmd::list::_render_summary_from_rows() { local rows="${1:-}" - declare -A rule_counts=() + declare -A rule_counts=() group_counts=() local total=0 - - while IFS='|' read -r name ip type rule rest; do + + while IFS='|' read -r name ip type rule group rest; do [[ -z "$name" ]] && continue (( total++ )) || true - rule_counts["${rule:-—}"]=$(( ${rule_counts[${rule:-—}]:-0} + 1 )) || true + rule_counts["${rule:--}"]=$(( ${rule_counts[${rule:--}]:-0} + 1 )) || true + [[ "$group" != "-" && -n "$group" ]] && \ + group_counts["$group"]=$(( ${group_counts[$group]:-0} + 1 )) || true done <<< "$rows" - - local summary="" - for r in "${!rule_counts[@]}"; do - summary+="${rule_counts[$r]} ${r}, " + + local rule_summary="" + for r in $(echo "${!rule_counts[@]}" | tr ' ' '\n' | sort); do + rule_summary+="${rule_counts[$r]} ${r}, " done - summary="${summary%, }" - - printf " Showing %s peers [%s]\n\n" "$total" "$summary" + rule_summary="${rule_summary%, }" + + local group_summary="" + for g in $(echo "${!group_counts[@]}" | tr ' ' '\n' | sort); do + group_summary+="${group_counts[$g]} in ${g}, " + done + group_summary="${group_summary%, }" + + if [[ -n "$group_summary" ]]; then + printf " Showing %s peers [%s] — %s\n\n" "$total" "$rule_summary" "$group_summary" + else + printf " Showing %s peers [%s]\n\n" "$total" "$rule_summary" + fi } # ============================================ diff --git a/modules/ui/peer.module.sh b/modules/ui/peer.module.sh index c16dae2..34d378d 100644 --- a/modules/ui/peer.module.sh +++ b/modules/ui/peer.module.sh @@ -8,6 +8,58 @@ function ui::peer::list_style() { echo "$_LIST_STYLE" } +# ====================================================== +# Row state helpers +# ====================================================== + +function ui::peer::_is_inactive() { + local status="${1:-}" + [[ "$status" == "online"* ]] && return 1 + return 0 +} + +function ui::peer::status_color() { + local is_blocked="${1:-false}" is_restricted="${2:-false}" status="${3:-}" + if [[ "$is_blocked" == "true" && "$status" == "online"* ]]; then + echo "\033[1;31m" + elif [[ "$is_blocked" == "true" ]]; then + echo "\033[2;31m" + elif [[ "$is_restricted" == "true" && "$status" == "online"* ]]; then + echo "\033[1;33m" + elif [[ "$is_restricted" == "true" ]]; then + echo "\033[2;33m" + elif [[ "$status" == "online"* ]]; then + echo "\033[1;32m" + else + echo "\033[2;37m" + fi +} + +# Row color — wraps entire row +function ui::peer::_row_color() { + local is_blocked="${1:-false}" is_restricted="${2:-false}" status="${3:-}" + local inactive=false + ui::peer::_is_inactive "$status" && inactive=true + + if $inactive; then + if [[ "$is_blocked" == "true" ]]; then + echo "\033[2;31m" # dim red — blocked offline + elif [[ "$is_restricted" == "true" ]]; then + echo "\033[2;33m" # dim yellow — restricted offline + else + echo "\033[2m" # dim gray — plain offline + fi + else + if [[ "$is_blocked" == "true" ]]; then + echo "\033[1;31m" # bold red — blocked active + elif [[ "$is_restricted" == "true" ]]; then + echo "\033[1;33m" # bold yellow — restricted active + else + echo "" # no row color — normal online + fi + fi +} + # ====================================================== # Compact layout (tableless) # ====================================================== @@ -19,40 +71,47 @@ function ui::peer::list_row_compact() { local name="${1:-}" ip="${2:-}" type="${3:-}" rule="${4:-}" \ group="${5:-}" status="${6:-}" last_seen="${7:-}" \ is_blocked="${8:-false}" is_restricted="${9:-false}" - - local status_color="\033[0;37m" - if [[ "$is_blocked" == "true" ]]; then - status_color="\033[1;31m" - elif [[ "$is_restricted" == "true" ]]; then - status_color="\033[1;33m" - elif [[ "$status" == "online" ]]; then - status_color="\033[1;32m" - fi - - # Last seen mirrors status color - local ls_color="$status_color" - + + local row_color + row_color=$(ui::peer::_row_color "$is_blocked" "$is_restricted" "$status") + + local status_color + status_color=$(ui::peer::status_color "$is_blocked" "$is_restricted" "$status") + local rule_val="${rule:--}" local group_val="${group:--}" - - local name_pad ip_pad type_pad status_pad + + local name_pad ip_pad type_pad ts_pad status_pad name_pad=$(printf "%-${w_name}s" "$name") ip_pad=$(printf "%-${w_ip}s" "$ip") type_pad=$(printf "%-${w_type}s" "$type") - status_pad=$(printf "%-8s" "$status") - + ts_pad=$(printf "%-11s" "$last_seen") + status_pad=$(printf "%-18s" "$status") + local rule_pad_n group_pad_n rule_pad_n=$(( w_rule - ${#rule_val} )) group_pad_n=$(( w_group - ${#group_val} )) [[ $rule_pad_n -lt 0 ]] && rule_pad_n=0 [[ $group_pad_n -lt 0 ]] && group_pad_n=0 - - printf " %s %s %s \033[2mrule:\033[0m %s%*s \033[2mgroup:\033[0m %s%*s %b%s\033[0m %b%s\033[0m\n" \ - "$name_pad" "$ip_pad" "$type_pad" \ - "$rule_val" "$rule_pad_n" "" \ - "$group_val" "$group_pad_n" "" \ - "$status_color" "$status_pad" \ - "$ls_color" "$last_seen" + + if [[ -n "$row_color" ]]; then + # Colored row — entire row in row_color, status uses status_color + printf " %b%s %s %s \033[2mrule:\033[0m%b %s%*s \033[2mgroup:\033[0m%b %s%*s\033[0m %b%s\033[0m %b%s\033[0m\n" \ + "$row_color" \ + "$name_pad" "$ip_pad" "$type_pad" \ + "$row_color" "$rule_val" "$rule_pad_n" "" \ + "$row_color" "$group_val" "$group_pad_n" "" \ + "$status_color" "$status_pad" \ + "$status_color" "$ts_pad" + else + # Normal online row — white fields, colored status/last_seen + printf " %s %s %s \033[2mrule:\033[0m %s%*s \033[2mgroup:\033[0m %s%*s %b%s\033[0m %b%s\033[0m\n" \ + "$name_pad" "$ip_pad" "$type_pad" \ + "$rule_val" "$rule_pad_n" "" \ + "$group_val" "$group_pad_n" "" \ + "$status_color" "$status_pad" \ + "$status_color" "$ts_pad" + fi } # ====================================================== @@ -117,29 +176,24 @@ function ui::peer::list_row_detailed() { local name="${1:-}" ip="${2:-}" type="${3:-}" rule="${4:-}" \ group="${5:-}" subnet="${6:-}" status="${7:-}" last_seen="${8:-}" \ is_blocked="${9:-false}" is_restricted="${10:-false}" - - local status_color="\033[0;37m" - if [[ "$is_blocked" == "true" ]]; then - status_color="\033[1;31m" - elif [[ "$is_restricted" == "true" ]]; then - status_color="\033[1;33m" - elif [[ "$status" == "online" ]]; then - status_color="\033[1;32m" - fi - - # Last seen mirrors status color - local ls_color="$status_color" - + + local row_color + row_color=$(ui::peer::_row_color "$is_blocked" "$is_restricted" "$status") + + local status_color + status_color=$(ui::peer::status_color "$is_blocked" "$is_restricted" "$status") + local rule_val="${rule:--}" local group_val="${group:--}" local subnet_val="${subnet:--}" - - local name_pad ip_pad type_pad status_pad - name_pad=$(printf "%-${w_name}s" "$name") - ip_pad=$(printf "%-${w_ip}s" "$ip") - type_pad=$(printf "%-${w_type}s" "$type") - status_pad=$(printf "%-8s" "$status") - + + local name_pad ip_pad type_pad ts_pad status_pad + name_pad=$(printf "%-${w_name}s" "$name") + ip_pad=$(printf "%-${w_ip}s" "$ip") + type_pad=$(printf "%-${w_type}s" "$type") + ts_pad=$(printf "%-11s" "$last_seen") + status_pad=$(printf "%-18s" "$status") + local rule_pad_n group_pad_n subnet_pad_n rule_pad_n=$(( w_rule - ${#rule_val} )) group_pad_n=$(( w_group - ${#group_val} )) @@ -147,12 +201,23 @@ function ui::peer::list_row_detailed() { [[ $rule_pad_n -lt 0 ]] && rule_pad_n=0 [[ $group_pad_n -lt 0 ]] && group_pad_n=0 [[ $subnet_pad_n -lt 0 ]] && subnet_pad_n=0 - - printf " · %s %s %s \033[2mrule:\033[0m %s%*s \033[2mgroup:\033[0m %s%*s \033[2msubnet:\033[0m %s%*s %b%s\033[0m %b%s\033[0m\n" \ - "$name_pad" "$ip_pad" "$type_pad" \ - "$rule_val" "$rule_pad_n" "" \ - "$group_val" "$group_pad_n" "" \ - "$subnet_val" "$subnet_pad_n" "" \ - "$status_color" "$status_pad" \ - "$ls_color" "$last_seen" + + if [[ -n "$row_color" ]]; then + printf " · %b%s %s %s \033[2mrule:\033[0m%b %s%*s \033[2mgroup:\033[0m%b %s%*s \033[2msubnet:\033[0m%b %s%*s\033[0m %b%s\033[0m %b%s\033[0m\n" \ + "$row_color" \ + "$name_pad" "$ip_pad" "$type_pad" \ + "$row_color" "$rule_val" "$rule_pad_n" "" \ + "$row_color" "$group_val" "$group_pad_n" "" \ + "$row_color" "$subnet_val" "$subnet_pad_n" "" \ + "$status_color" "$status_pad" \ + "$status_color" "$ts_pad" + else + printf " · %s %s %s \033[2mrule:\033[0m %s%*s \033[2mgroup:\033[0m %s%*s \033[2msubnet:\033[0m %s%*s %b%s\033[0m %b%s\033[0m\n" \ + "$name_pad" "$ip_pad" "$type_pad" \ + "$rule_val" "$rule_pad_n" "" \ + "$group_val" "$group_pad_n" "" \ + "$subnet_val" "$subnet_pad_n" "" \ + "$status_color" "$status_pad" \ + "$status_color" "$ts_pad" + fi } \ No newline at end of file diff --git a/modules/ui/rule.module.sh b/modules/ui/rule.module.sh index 83fde5c..73ddf53 100644 --- a/modules/ui/rule.module.sh +++ b/modules/ui/rule.module.sh @@ -292,29 +292,35 @@ function ui::rule::list_row() { extends_indicator=" \033[2m↳ ${extends_display}\033[0m" fi - # Build allows and blocks — pad to fixed visible width of 5 - local allows_str blocks_str - if [[ "$n_allows" -eq 0 && "$n_blocks" -eq 0 ]]; then - allows_str=$(ui::pad_mb "\033[1;32m+all\033[0m" 5) - blocks_str=$(printf "%5s" "") + # Allows column — green +N if >0, dim +0 if zero + local allows_str + if [[ "$n_allows" -gt 0 ]]; then + allows_str=$(ui::pad_mb "\033[1;32m+${n_allows}\033[0m" 5) else - if [[ "$n_allows" -gt 0 ]]; then - allows_str=$(ui::pad_mb "\033[1;32m+${n_allows}\033[0m" 5) - else - allows_str=$(printf "%5s" "") - fi - if [[ "$n_blocks" -gt 0 ]]; then - blocks_str=$(ui::pad_mb "\033[1;31m-${n_blocks}\033[0m" 5) - else - blocks_str=$(printf "%5s" "") - fi + allows_str=$(ui::pad_mb "\033[2m+0\033[0m" 5) fi - printf " %s %b%b %s%*s%b\n" \ - "$name_pad" "$allows_str" "$blocks_str" \ - "$peers_display" "$peers_pad_n" "" "$extends_indicator" -} + # Blocks column — red -N if >0, dim -0 if zero + local blocks_str + if [[ "$n_blocks" -gt 0 ]]; then + blocks_str=$(ui::pad_mb "\033[1;31m-${n_blocks}\033[0m" 5) + else + blocks_str=$(ui::pad_mb "\033[2m-0\033[0m" 5) + fi + # Peers — dim if zero + local peers_colored + if [[ "$peer_count" -eq 0 ]]; then + peers_colored="\033[2m${peers_display}\033[0m" + else + peers_colored="$peers_display" + fi + + printf " %s %b%b %b%*s%b\n" \ + "$name_pad" "$allows_str" "$blocks_str" \ + "$peers_colored" "$peers_pad_n" "" "$extends_indicator" +} + # ui::rule::list_extends # Renders the extends tree for a rule in list view (compact, one level) function ui::rule::list_extends() {