function cmd::rule::show() { local name="" show_peers=true color=false show_resolved=false while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1 name="$2"; shift 2 ;; --no-peers) show_peers=false; shift ;; --color) color=true; shift ;; --resolved) show_resolved=true; shift ;; --help) cmd::rule::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 rule::require_exists "$name" || return 1 local rule_file rule_file="$(rule::path "$name")" local dns_redirect dns_redirect=$(rule::get_own "$name" "dns_redirect") dns_redirect="${dns_redirect:-false}" local resolved_dns resolved_dns=$(rule::get "$name" "dns_redirect") resolved_dns="${resolved_dns:-false}" local dns_display if [[ "${resolved_dns,,}" == "true" && "${dns_redirect,,}" != "true" ]]; then dns_display="true (inherited)" elif [[ "${dns_redirect,,}" == "true" ]]; then dns_display="true" else dns_display="false" fi log::section "Rule: ${name}" printf "\n" # ── Header info ────────────────────────────── local desc group dns_redirect desc=$(json::get "$rule_file" "desc") group=$(json::get "$rule_file" "group") ui::row "Description" "${desc:-—}" ui::row "Group" "${group:-—}" ui::row "DNS" "$dns_display" # ── Extends section ────────────────────────── local extends_raw=() mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then cmd::rule::_show_section "Extends" for base_name in "${extends_raw[@]}"; do [[ -z "$base_name" ]] && continue printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name" # Show resolved entries for this base local base_allow_ports base_allow_ips base_block_ips base_block_ports base_dns base_allow_ports=$(rule::get "$base_name" "allow_ports" 2>/dev/null || true) base_allow_ips=$(rule::get "$base_name" "allow_ips" 2>/dev/null || true) base_block_ips=$(rule::get "$base_name" "block_ips" 2>/dev/null || true) base_block_ports=$(rule::get "$base_name" "block_ports" 2>/dev/null || true) base_dns=$(json::get "$(rule::path "$base_name")" "dns_redirect" 2>/dev/null || true) while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "+" "$e" done <<< "$base_allow_ports" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "-" "$e" done <<< "$base_allow_ips" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "-" "$e" done <<< "$base_block_ips" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "-" "$e" done <<< "$base_block_ports" [[ "$base_dns" == "true" ]] && \ printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)" done fi # ── Own Rules section ───────────────────────── local own_allow_ports own_allow_ips own_block_ips own_block_ports own_allow_ports=$(json::get "$rule_file" "allow_ports") own_allow_ips=$(json::get "$rule_file" "allow_ips") own_block_ips=$(json::get "$rule_file" "block_ips") own_block_ports=$(json::get "$rule_file" "block_ports") local has_own=false [[ -n "$own_allow_ports" || -n "$own_allow_ips" || \ -n "$own_block_ips" || -n "$own_block_ports" ]] && has_own=true if $has_own; then if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]}" ]]; then cmd::rule::_show_section "Own Rules" printf "\n" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "+" "$e" done <<< "$own_allow_ports" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "+" "$e" done <<< "$own_allow_ips" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "-" "$e" done <<< "$own_block_ips" while IFS= read -r e; do [[ -z "$e" ]] && continue net::print_entry "-" "$e" done <<< "$own_block_ports" [[ "$dns_redirect" == "true" ]] && \ printf " \033[0;36m↺\033[0m DNS → %s\n" "$(config::dns)" else # No inheritance — use Allow/Block sections if [[ -n "$own_allow_ports" || -n "$own_allow_ips" ]]; then cmd::rule::_show_section "Allow" printf "\n" while IFS= read -r e; do [[ -z "$e" ]] && continue printf " \033[0;32m+\033[0m %s\n" "$e" done <<< "$own_allow_ports" while IFS= read -r e; do [[ -z "$e" ]] && continue printf " \033[0;32m+\033[0m %s\n" "$e" done <<< "$own_allow_ips" fi if [[ -n "$own_block_ips" || -n "$own_block_ports" ]]; then cmd::rule::_show_section "Block" printf "\n" while IFS= read -r e; do [[ -z "$e" ]] && continue printf " \033[0;31m-\033[0m %s\n" "$e" done <<< "$own_block_ips" while IFS= read -r e; do [[ -z "$e" ]] && continue printf " \033[0;31m-\033[0m %s\n" "$e" done <<< "$own_block_ports" fi if [[ "$dns_redirect" == "true" ]]; then cmd::rule::_show_section "DNS" printf "\n \033[0;36m↺\033[0m Redirect all DNS → %s\n" "$(config::dns)" fi fi elif [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]}" ]]; then printf "\n" ui::row "Access" "full (no restrictions)" fi # ── Resolved section (optional) ────────────── if $show_resolved; then cmd::rule::_show_section "Resolved (applied to peers)" local res_allow_ports res_allow_ips res_block_ips res_block_ports res_allow_ports=$(rule::get "$name" "allow_ports") res_allow_ips=$(rule::get "$name" "allow_ips") res_block_ips=$(rule::get "$name" "block_ips") res_block_ports=$(rule::get "$name" "block_ports") while IFS= read -r e; do [[ -n "$e" ]] && \ printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ports" while IFS= read -r e; do [[ -n "$e" ]] && \ printf " \033[0;32m+\033[0m %s\n" "$e"; done <<< "$res_allow_ips" while IFS= read -r e; do [[ -n "$e" ]] && \ printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ips" while IFS= read -r e; do [[ -n "$e" ]] && \ printf " \033[0;31m-\033[0m %s\n" "$e"; done <<< "$res_block_ports" fi # ── Peers section ───────────────────────────── cmd::rule::_show_section "Peers" local peer_list=() mapfile -t peer_list < <(peers::with_rule "$name") local peer_count=${#peer_list[@]} ui::row "Assigned" "$peer_count" if $show_peers && [[ $peer_count -gt 0 ]]; then printf "\n" for peer_name in "${peer_list[@]}"; do local ip ip=$(peers::get_ip "$peer_name") printf " %-28s %s\n" "$peer_name" "$ip" done fi printf "\n" }