wgctl/modules/ui/rule.module.sh
Nuno Duque Nunes 560e4cbe09 feat: peer list row coloring, verbose status, dim offline rows
- Dim gray for offline peers — lights off visual
- Dim/bold red for blocked peers (offline/online)
- Dim/bold yellow for restricted peers (offline/online)
- online (blocked) / offline (blocked) verbose status
- LIST_VERBOSE_STATUS=false to revert to simple status
- Rule list: +0/-0 dimmed, 0 peers dimmed
- Summary includes group breakdown
2026-05-23 04:51:33 +00:00

360 lines
No EOL
11 KiB
Bash

#!/usr/bin/env bash
# ui/rule.module.sh — rendering for rule data
# Replaces rule::render_* functions from rule.module.sh.
# All functions pure rendering — no writes, no state changes.
# ======================================================
# Entry Rendering (shared primitives)
# ======================================================
# ui::rule::entries <rule_name> [indent]
# Renders the fully resolved entries for a rule (allow + block).
function ui::rule::entries() {
local rule_name="${1:-}" indent="${2:-4}"
local allow_ports allow_ips block_ips block_ports dns
allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
dns=$(rule::get_own "$rule_name" "dns_redirect")
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "+" "$e" "$indent"
done <<< "$allow_ports"$'\n'"$allow_ips"
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "-" "$e" "$indent"
done <<< "$block_ips"$'\n'"$block_ports"
[[ "${dns,,}" == "true" ]] && \
net::print_dns_redirect "$(config::dns)" 6 "DNS"
}
# ui::rule::own_entries <rule_name> [indent]
# Renders only the rule's own (non-inherited) entries.
function ui::rule::own_entries() {
local rule_name="${1:-}" indent="${2:-4}"
local rule_file
rule_file="$(rule::path "$rule_name")" || return 0
[[ -z "$rule_file" ]] && return 0
local allow_ports allow_ips block_ips block_ports dns
allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
[[ -z "${combined//[$'\n']/}" ]] && return 0
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "+" "$e" "$indent"
done <<< "$allow_ports"$'\n'"$allow_ips"
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "-" "$e" "$indent"
done <<< "$block_ips"$'\n'"$block_ports"
[[ "${dns,,}" == "true" ]] && \
net::print_dns_redirect "$(config::dns)" 6 "DNS"
}
# ui::rule::flat <rule_name>
# Renders the full resolved entries as a flat list.
function ui::rule::flat() {
local rule_name="${1:-}"
local allow_ports allow_ips block_ips block_ports dns
allow_ports=$(rule::get "$rule_name" "allow_ports")
allow_ips=$(rule::get "$rule_name" "allow_ips")
block_ips=$(rule::get "$rule_name" "block_ips")
block_ports=$(rule::get "$rule_name" "block_ports")
dns=$(rule::get_own "$rule_name" "dns_redirect")
local has_content=false
[[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && has_content=true
if ! $has_content; then
printf "\n full access (no restrictions)\n"
return 0
fi
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
printf "\n"
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "+" "$e" 2
done <<< "$allow_ports"$'\n'"$allow_ips"
fi
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
printf "\n"
while IFS= read -r e; do
[[ -z "$e" ]] && continue
net::print_entry "-" "$e" 2
done <<< "$block_ips"$'\n'"$block_ports"
fi
[[ "${dns,,}" == "true" ]] && \
net::print_dns_redirect "$(config::dns)" 6 "DNS"
}
# ======================================================
# Shared Base Renderer
# ======================================================
# ui::rule::_render_bases <array_nameref> [entry_indent] [label_indent]
# Renders a list of base rule names with newlines between them.
# Used by both ui::rule::tree and ui::rule::_identity_rule_entry.
function ui::rule::_render_bases() {
local -n _bases="$1"
local entry_indent="${2:-6}" label_indent="${3:-4}"
local label_pad
label_pad=$(printf '%*s' "$label_indent" '')
local first=true
for base_name in "${_bases[@]}"; do
[[ -z "$base_name" ]] && continue
$first || printf "\n"
first=false
printf "%s\033[0;37m↳ %s\033[0m\n" "$label_pad" "$base_name"
ui::rule::entries "$base_name" "$entry_indent"
done
}
# ======================================================
# Tree Rendering
# ======================================================
# ui::rule::tree <rule_name>
# Renders a rule's extends tree — one level deep with own entries.
# Returns 1 if rule has no extends (caller can fall back to flat).
function ui::rule::tree() {
local rule_name="${1:-}"
local rule_file
rule_file="$(rule::path "$rule_name")" || return 1
[[ -z "$rule_file" ]] && return 1
local extends_raw=()
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
if [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]]; then
return 1
fi
ui::rule::_render_bases extends_raw 6 4
local own_output
own_output=$(ui::rule::own_entries "$rule_name" 6)
if [[ -n "$own_output" ]]; then
printf "\n \033[0;37mOwn:\033[0m\n"
printf "%s\n" "$own_output"
fi
return 0
}
# ======================================================
# Identity Rule Block
# ======================================================
# ui::rule::identity_block <identity_name> <strict_rule>
# Renders the full identity rule block in inspect.
function ui::rule::identity_block() {
local identity_name="${1:-}" strict="${2:-false}"
local rules
rules=$(identity::rules "$identity_name")
[[ -z "$rules" ]] && return 0
printf "\n \033[0;37m· identity:%s\033[0m\n" "$identity_name"
local first=true
while IFS= read -r rule_name; do
[[ -z "$rule_name" ]] && continue
$first || printf "\n"
first=false
ui::rule::_identity_rule_entry "$rule_name"
done <<< "$rules"
if [[ "$strict" == "true" ]]; then
printf "\n \033[2m(strict — peer rule suppressed)\033[0m\n"
fi
}
# ui::rule::_identity_rule_entry <rule_name>
# Renders one rule within an identity block.
function ui::rule::_identity_rule_entry() {
local rule_name="${1:-}"
local rule_file
rule_file="$(rule::path "$rule_name")" || return 0
printf " \033[0;37m↳ %s\033[0m\n" "$rule_name"
local extends_raw=()
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
ui::rule::_render_bases extends_raw 10 8
local own_output
own_output=$(ui::rule::own_entries "$rule_name" 10)
if [[ -n "$own_output" ]]; then
printf "\n \033[0;37mOwn:\033[0m\n"
printf "%s\n" "$own_output"
fi
else
local own_output
own_output=$(ui::rule::own_entries "$rule_name" 8)
if [[ -n "$own_output" ]]; then
printf "%s\n" "$own_output"
else
printf " \033[2mfull access (no restrictions)\033[0m\n"
fi
fi
}
# ======================================================
# Peer Entry
# ======================================================
function ui::rule::_peer_rule_entry() {
local rule_name="${1:-}"
local rule_file
rule_file="$(rule::path "$rule_name")" || return 0
printf " \033[0;37m↳ %s\033[0m\n" "$rule_name"
local extends_raw=()
mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
ui::rule::_render_bases extends_raw 10 8
local own_output
own_output=$(ui::rule::own_entries "$rule_name" 10)
if [[ -n "$own_output" ]]; then
printf "\n \033[0;37mOwn:\033[0m\n"
printf "%s\n" "$own_output"
fi
else
local own_output
own_output=$(ui::rule::own_entries "$rule_name" 8)
if [[ -n "$own_output" ]]; then
printf "%s\n" "$own_output"
else
printf " \033[2mfull access (no restrictions)\033[0m\n"
fi
fi
}
# ======================================================
# List Rendering
# ======================================================
# ui::rule::list_group_header <group_name>
function ui::rule::list_group_header() {
local group="${1:-}"
printf "\n \033[0;36m▸ %s\033[0m\n" "$group"
}
# ui::rule::list_base_header
function ui::rule::list_base_header() {
printf "\n \033[2m── Base Rules ──────────────────────\033[0m\n"
}
# ui::rule::list_row <name> <n_allows> <n_blocks> <peer_count> <w_name>
function ui::rule::list_row() {
local name="${1:-}" n_allows="${2:-0}" n_blocks="${3:-0}" \
peer_count="${4:-0}" w_name="${5:-16}" extends_csv="${6:-}"
local name_pad
name_pad=$(printf "%-${w_name}s" "$name")
local peer_word="peers"
[[ "$peer_count" -eq 1 ]] && peer_word="peer"
local peers_display
peers_display=$(printf "%s %s" "$peer_count" "$peer_word")
local peers_pad_n=$(( 10 - ${#peers_display} ))
[[ $peers_pad_n -lt 0 ]] && peers_pad_n=0
local extends_indicator=""
if [[ -n "$extends_csv" ]]; then
local extends_display="${extends_csv//,/, }"
extends_indicator=" \033[2m↳ ${extends_display}\033[0m"
fi
# 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
allows_str=$(ui::pad_mb "\033[2m+0\033[0m" 5)
fi
# 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 <extends_csv>
# Renders the extends tree for a rule in list view (compact, one level)
function ui::rule::list_extends() {
local extends_csv="${1:-}"
[[ -z "$extends_csv" ]] && return 0
local extend_list=()
IFS=',' read -ra extend_list <<< "$extends_csv"
for base in "${extend_list[@]}"; do
[[ -z "$base" ]] && continue
printf " \033[0;37m ↳ %s\033[0m\n" "$base"
done
}
# ui::rule::list_extends_detailed <extends_csv> <rules_dir>
# Renders the extends tree with entries expanded (--detailed mode)
function ui::rule::list_extends_detailed() {
local extends_csv="${1:-}" rules_dir="${2:-}"
[[ -z "$extends_csv" ]] && return 0
local extend_list=()
IFS=',' read -ra extend_list <<< "$extends_csv"
for base in "${extend_list[@]}"; do
[[ -z "$base" ]] && continue
printf " \033[0;37m ↳ %s\033[0m\n" "$base"
ui::rule::entries "$base" 6
done
}
# ======================================================
# Show helpers
# ======================================================
function ui::rule::section_header() {
local title="${1:-}"
printf "\n \033[0;37m── %s ──────────────────────────────────\033[0m\n" "$title"
}