- ui::rule::list_row: inline padding math replaces ui::pad_mb (major perf gain)
- ui:⌚:fw_row/wg_row: drop ui::pad_mb for fw/wg labels (always 2 chars)
- watch: endpoint fallback via monitor::get_cached_endpoint
- watch: _poll_handshakes sorts by ts descending (most recent first)
- watch: empty endpoint uses - not — (avoids multi-byte padding issues)
- ui.sh: UTF-8 extra byte constants (_UI_EMDASH_EXTRA, _UI_ARROW_EXTRA, _UI_BULLET_EXTRA)
402 lines
No EOL
13 KiB
Bash
402 lines
No EOL
13 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}" 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
|
|
rules=$(identity::rules "$identity_name")
|
|
[[ -z "$rules" ]] && return 0
|
|
|
|
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
|
|
while IFS= read -r rule_name; do
|
|
[[ -z "$rule_name" ]] && continue
|
|
$first || printf "\n"
|
|
first=false
|
|
ui::rule::_identity_rule_entry "$rule_name" \
|
|
"$entry_indent" "$label_indent" "$own_indent" "$own_label_indent"
|
|
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 entry_indent="${2:-6}"
|
|
local label_indent="${3:-6}"
|
|
local own_indent="${4:-10}"
|
|
local own_label_indent="${5:-8}"
|
|
|
|
local rule_file
|
|
rule_file="$(rule::path "$rule_name")" || return 0
|
|
|
|
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=()
|
|
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 "$own_indent" "$(( entry_indent + 2 ))"
|
|
|
|
local own_output
|
|
own_output=$(ui::rule::own_entries "$rule_name" "$own_indent")
|
|
if [[ -n "$own_output" ]]; then
|
|
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"
|
|
fi
|
|
else
|
|
local own_output
|
|
own_output=$(ui::rule::own_entries "$rule_name" "$entry_indent")
|
|
if [[ -n "$own_output" ]]; then
|
|
printf "%s\n" "$own_output"
|
|
else
|
|
local full_pad
|
|
full_pad=$(printf '%*s' "$(( entry_indent + 2 ))" '')
|
|
printf "%s\033[2mfull access (no restrictions)\033[0m\n" "$full_pad"
|
|
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 — green +N, padded to 5 visible chars
|
|
# Append spaces after reset code so printf doesn't miscount
|
|
local allows_str
|
|
if [[ "$n_allows" -gt 0 ]]; then
|
|
printf -v allows_str "\033[1;32m+%s\033[0m" "$n_allows"
|
|
allows_str="${allows_str}$(printf '%*s' "$(( 4 - ${#n_allows} ))" '')"
|
|
else
|
|
printf -v allows_str "\033[2m+0\033[0m " # "+0" = 2 visible + 3 spaces = 5
|
|
fi
|
|
|
|
# Blocks — red -N, padded to 5 visible chars
|
|
local blocks_str
|
|
if [[ "$n_blocks" -gt 0 ]]; then
|
|
printf -v blocks_str "\033[1;31m-%s\033[0m" "$n_blocks"
|
|
blocks_str="${blocks_str}$(printf '%*s' "$(( 4 - ${#n_blocks} ))" '')"
|
|
else
|
|
printf -v blocks_str "\033[2m-0\033[0m " # "-0" = 2 visible + 3 spaces = 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"
|
|
} |