feat: rule list tableless layout with inline extends and +all/-N indicators
This commit is contained in:
parent
abf4cd7e1c
commit
57e08e88c4
5 changed files with 300 additions and 291 deletions
|
|
@ -23,10 +23,10 @@ function cmd::rule::on_load() {
|
||||||
flag::register --peer
|
flag::register --peer
|
||||||
flag::register --peers
|
flag::register --peers
|
||||||
flag::register --dns-redirect
|
flag::register --dns-redirect
|
||||||
flag::register --color
|
|
||||||
flag::register --base
|
flag::register --base
|
||||||
flag::register --no-base
|
flag::register --no-base
|
||||||
flag::register --tree
|
flag::register --tree
|
||||||
|
flag::register --detailed
|
||||||
flag::register --resolved
|
flag::register --resolved
|
||||||
flag::register --force
|
flag::register --force
|
||||||
flag::register --type
|
flag::register --type
|
||||||
|
|
@ -60,7 +60,7 @@ Options for list:
|
||||||
--base Show only base rules
|
--base Show only base rules
|
||||||
--no-base Hide base rules section
|
--no-base Hide base rules section
|
||||||
--group <name> Filter by group (case insensitive)
|
--group <name> Filter by group (case insensitive)
|
||||||
--tree Show full inheritance tree inline
|
--detailed Show rule entries inline
|
||||||
|
|
||||||
Options for add:
|
Options for add:
|
||||||
--name <name> Rule name
|
--name <name> Rule name
|
||||||
|
|
@ -72,8 +72,8 @@ Options for add:
|
||||||
--allow-port <ip:port:proto> Allow specific port (repeatable)
|
--allow-port <ip:port:proto> Allow specific port (repeatable)
|
||||||
--block-ip <ip/cidr> Block IP or subnet (repeatable)
|
--block-ip <ip/cidr> Block IP or subnet (repeatable)
|
||||||
--block-port <ip:port:proto> Block specific port (repeatable)
|
--block-port <ip:port:proto> Block specific port (repeatable)
|
||||||
--block-service <name> Block named service — resolved to IP/port at creation (repeatable)
|
--block-service <name> Block named service (repeatable)
|
||||||
--allow-service <name> Allow named service — resolved to IP/port at creation (repeatable)
|
--allow-service <name> Allow named service (repeatable)
|
||||||
--dns-redirect Force DNS through Pi-hole
|
--dns-redirect Force DNS through Pi-hole
|
||||||
|
|
||||||
Options for update:
|
Options for update:
|
||||||
|
|
@ -85,7 +85,7 @@ Options for update:
|
||||||
--remove-block-ip <ip> Remove block IP entry
|
--remove-block-ip <ip> Remove block IP entry
|
||||||
--remove-block-port <entry> Remove block port entry
|
--remove-block-port <entry> Remove block port entry
|
||||||
|
|
||||||
Options for show/inspect:
|
Options for show:
|
||||||
--name <name> Rule name
|
--name <name> Rule name
|
||||||
--resolved Show resolved/merged entries
|
--resolved Show resolved/merged entries
|
||||||
--no-peers Hide assigned peers
|
--no-peers Hide assigned peers
|
||||||
|
|
@ -96,14 +96,12 @@ Options for reapply:
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
wgctl rule list
|
wgctl rule list
|
||||||
wgctl rule list --tree
|
wgctl rule list --detailed
|
||||||
wgctl rule list --group "VM Rules"
|
wgctl rule list --group "VM Rules"
|
||||||
wgctl rule show --name guest
|
wgctl rule show --name guest
|
||||||
wgctl rule show --name moonlight-02 --resolved
|
wgctl rule show --name moonlight-02 --resolved
|
||||||
wgctl rule add --name no-proxmox --base --block-service proxmox
|
wgctl rule add --name no-proxmox --base --block-service proxmox
|
||||||
wgctl rule add --name dev-01 --desc "Dev access" --group "Dev" --extends no-lan
|
wgctl rule add --name dev-01 --desc "Dev access" --extends no-lan
|
||||||
wgctl rule add --name restricted-dns --allow-service pihole:dns --block-service pihole
|
|
||||||
wgctl rule update --name user --add-extends no-nginx
|
|
||||||
wgctl rule assign --name dev-01 --peer laptop-nuno
|
wgctl rule assign --name dev-01 --peer laptop-nuno
|
||||||
wgctl rule reapply --all
|
wgctl rule reapply --all
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -118,17 +116,16 @@ function cmd::rule::run() {
|
||||||
shift || true
|
shift || true
|
||||||
|
|
||||||
case "$subcmd" in
|
case "$subcmd" in
|
||||||
list|ls) cmd::rule::list "$@" ;;
|
list|ls) cmd::rule::list "$@" ;;
|
||||||
show|inspect) cmd::rule::show "$@" ;;
|
show|inspect) cmd::rule::show "$@" ;;
|
||||||
inspect) cmd::rule::inspect "$@" ;;
|
add|new|create) cmd::rule::add "$@" ;;
|
||||||
add|new|create) cmd::rule::add "$@" ;;
|
update|edit) cmd::rule::update "$@" ;;
|
||||||
update|edit) cmd::rule::update "$@" ;;
|
remove|rm|del|delete) cmd::rule::remove "$@" ;;
|
||||||
remove|rm|del|delete) cmd::rule::remove "$@" ;;
|
assign) cmd::rule::assign "$@" ;;
|
||||||
assign) cmd::rule::assign "$@" ;;
|
|
||||||
unassign) cmd::rule::unassign "$@" ;;
|
unassign) cmd::rule::unassign "$@" ;;
|
||||||
migrate) cmd::rule::migrate "$@" ;;
|
migrate) cmd::rule::migrate "$@" ;;
|
||||||
reapply) cmd::rule::reapply "$@" ;;
|
reapply) cmd::rule::reapply "$@" ;;
|
||||||
help) cmd::rule::help ;;
|
help) cmd::rule::help ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown subcommand: '${subcmd}'"
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
cmd::rule::help
|
cmd::rule::help
|
||||||
|
|
@ -141,172 +138,98 @@ function cmd::rule::run() {
|
||||||
# List
|
# List
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::rule::_pad() {
|
|
||||||
local text="$1" width="$2"
|
|
||||||
local visible
|
|
||||||
visible=$(printf "%s" "$text" | sed 's/\x1b\[[0-9;]*m//g')
|
|
||||||
local visible_len=${#visible}
|
|
||||||
local byte_len=${#text}
|
|
||||||
local extra=$(( byte_len - visible_len ))
|
|
||||||
printf "%-$(( width + extra ))s" "$text"
|
|
||||||
}
|
|
||||||
|
|
||||||
function cmd::rule::_print_extends_tree() {
|
|
||||||
local extends="$1" indent="${2:-2}" rules_dir="$3"
|
|
||||||
[[ -z "$extends" ]] && return 0
|
|
||||||
|
|
||||||
local extend_list=()
|
|
||||||
IFS=',' read -ra extend_list <<< "$extends"
|
|
||||||
|
|
||||||
for base in "${extend_list[@]}"; do
|
|
||||||
[[ -z "$base" ]] && continue
|
|
||||||
local spaces
|
|
||||||
spaces=$(printf '%*s' "$indent" '')
|
|
||||||
printf " \033[0;37m%s↳ %s\033[0m\n" "$spaces" "$base"
|
|
||||||
|
|
||||||
if [[ "$indent" -lt 12 ]]; then
|
|
||||||
local sub_file=""
|
|
||||||
if sub_file=$(json::find_rule_file "$rules_dir" "$base" 2>/dev/null); then
|
|
||||||
local sub_extends=""
|
|
||||||
sub_extends=$(json::get "$sub_file" "extends" 2>/dev/null \
|
|
||||||
| tr '\n' ',' | sed 's/,$//' || true)
|
|
||||||
if [[ -n "$sub_extends" ]]; then
|
|
||||||
cmd::rule::_print_extends_tree \
|
|
||||||
"$sub_extends" $(( indent + 4 )) "$rules_dir"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
function cmd::rule::list() {
|
function cmd::rule::list() {
|
||||||
local rules_dir
|
local rules_dir
|
||||||
rules_dir="$(ctx::rules)"
|
rules_dir="$(ctx::rules)"
|
||||||
|
|
||||||
local show_base_only=false
|
local show_base_only=false show_base=true
|
||||||
local show_base=true
|
local filter_group="" detailed=false
|
||||||
local filter_group=""
|
|
||||||
local show_tree=false
|
|
||||||
local found_any=false
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--base) show_base_only=true; shift ;;
|
--base) show_base_only=true; shift ;;
|
||||||
--no-base) show_base=false; shift ;;
|
--no-base) show_base=false; shift ;;
|
||||||
--group) util::require_flag "--group" "${2:-}" || return 1
|
--group) filter_group="${2,,}"; shift 2 ;;
|
||||||
filter_group="${2,,}"; shift 2 ;;
|
--detailed) detailed=true; shift ;;
|
||||||
--tree) show_tree=true; shift ;;
|
--help) cmd::rule::help; return ;;
|
||||||
--help) cmd::rule::help; return ;;
|
|
||||||
*)
|
*)
|
||||||
log::error "Unknown flag: $1"
|
log::error "Unknown flag: $1"
|
||||||
return 1 ;;
|
return 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
local rules=("${rules_dir}"/*.rule)
|
local data
|
||||||
if [[ ! -f "${rules[0]}" ]]; then
|
data=$(json::rule_list_data "$rules_dir" "$(ctx::meta)")
|
||||||
log::wg "No rules configured"
|
[[ -z "$data" ]] && log::wg "No rules configured" && return 0
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local header_printed=false
|
# Measure max name width
|
||||||
local printing_base=false
|
local w_name=12
|
||||||
local current_group=""
|
while IFS='|' read -r name rest; do
|
||||||
|
[[ -z "$name" ]] && continue
|
||||||
|
(( ${#name} > w_name )) && w_name=${#name}
|
||||||
|
done <<< "$data"
|
||||||
|
(( w_name += 2 ))
|
||||||
|
|
||||||
|
log::section "Firewall Rules"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local current_group="" printing_base=false found_any=false
|
||||||
|
|
||||||
while IFS="|" read -r name desc n_allows n_blocks \
|
while IFS="|" read -r name desc n_allows n_blocks \
|
||||||
peer_count extends is_base group; do
|
peer_count extends is_base group; do
|
||||||
[[ -z "$name" ]] && continue
|
[[ -z "$name" ]] && continue
|
||||||
|
|
||||||
# --base: show ONLY base rules
|
$show_base_only && [[ "$is_base" == "False" ]] && continue
|
||||||
if $show_base_only && [[ "$is_base" == "False" ]]; then
|
! $show_base && [[ "$is_base" == "True" ]] && continue
|
||||||
continue
|
[[ -n "$filter_group" && "${group,,}" != "$filter_group" ]] && continue
|
||||||
fi
|
|
||||||
|
|
||||||
# --no-base: hide base rules
|
found_any=true
|
||||||
if ! $show_base && [[ "$is_base" == "True" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --group filter (case insensitive)
|
|
||||||
if [[ -n "$filter_group" && "${group,,}" != "$filter_group" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Print header on first match
|
|
||||||
if ! $header_printed; then
|
|
||||||
log::section "Firewall Rules"
|
|
||||||
printf "\n %-20s %-40s %-8s %-8s %s\n" \
|
|
||||||
"NAME" "DESCRIPTION" \
|
|
||||||
"$(ui::center "ALLOWS" 8)" \
|
|
||||||
"$(ui::center "BLOCKS" 8)" \
|
|
||||||
"PEERS"
|
|
||||||
local divider
|
|
||||||
divider=$(printf '─%.0s' {1..88})
|
|
||||||
printf " %s\n" "$divider"
|
|
||||||
header_printed=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Base rules section header
|
# Base rules section header
|
||||||
if [[ "$is_base" == "True" && "$printing_base" == "false" ]]; then
|
if [[ "$is_base" == "True" && "$printing_base" == "false" ]]; then
|
||||||
if ! $show_base_only; then
|
if ! $show_base_only; then
|
||||||
local bdashes
|
ui::rule::list_base_header
|
||||||
bdashes=$(printf '─%.0s' {1..74})
|
|
||||||
printf "\n \033[0;37m── Base Rules \033[0m%s\n" "$bdashes"
|
|
||||||
fi
|
fi
|
||||||
printing_base=true
|
printing_base=true
|
||||||
current_group=""
|
current_group=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Group header — only for non-base rules
|
# Group header — non-base rules only
|
||||||
if [[ "$is_base" == "False" && "${group,,}" != "${current_group,,}" ]]; then
|
if [[ "$is_base" == "False" && "${group,,}" != "${current_group,,}" ]]; then
|
||||||
if [[ -n "$group" ]]; then
|
if [[ -n "$group" ]]; then
|
||||||
printf "\n \033[0;36m▸ %s\033[0m\n" "$group"
|
ui::rule::list_group_header "$group"
|
||||||
elif [[ -n "$current_group" ]]; then
|
elif [[ -n "$current_group" ]]; then
|
||||||
printf "\n"
|
echo ""
|
||||||
fi
|
fi
|
||||||
current_group="$group"
|
current_group="$group"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local short_desc="${desc:0:35}"
|
# Rule row
|
||||||
[[ ${#desc} -gt 35 ]] && short_desc="${short_desc}..."
|
# ui::rule::list_row "$name" "$n_allows" "$n_blocks" "$peer_count" "$w_name"
|
||||||
|
|
||||||
local desc_col_width=40
|
# Extends
|
||||||
[[ "${short_desc:-—}" == "—" ]] && desc_col_width=42
|
# Rule row — pass extends_csv for compact inline display
|
||||||
|
local compact_extends=""
|
||||||
found_any=true
|
if [[ -z "$detailed" ]] || ! $detailed; then
|
||||||
|
compact_extends="$extends"
|
||||||
printf " %-20s %-${desc_col_width}s %-8s %-8s %s\n" \
|
fi
|
||||||
"$name" "${short_desc:-—}" \
|
ui::rule::list_row "$name" "$n_allows" "$n_blocks" "$peer_count" "$w_name" "$compact_extends"
|
||||||
"$(ui::center "$n_allows" 8)" \
|
|
||||||
"$(ui::center "$n_blocks" 8)" \
|
# Detailed mode — show expanded entries
|
||||||
"${peer_count} peers"
|
if $detailed && [[ -n "$extends" ]]; then
|
||||||
|
ui::rule::list_extends_detailed "$extends" "$rules_dir"
|
||||||
# Print extends
|
echo ""
|
||||||
if [[ -n "$extends" ]]; then
|
|
||||||
if $show_tree; then
|
|
||||||
cmd::rule::_print_extends_tree "$extends" 2 "$rules_dir"
|
|
||||||
else
|
|
||||||
local extend_list=()
|
|
||||||
IFS=',' read -ra extend_list <<< "$extends"
|
|
||||||
for base in "${extend_list[@]}"; do
|
|
||||||
[[ -z "$base" ]] && continue
|
|
||||||
printf " \033[0;37m ↳ %s\033[0m\n" "$base"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
printf "\n"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done < <(json::rule_list_data "$rules_dir" "$(ctx::meta)")
|
done <<< "$data"
|
||||||
|
|
||||||
if ! $found_any; then
|
$found_any || {
|
||||||
if [[ -n "$filter_group" ]]; then
|
[[ -n "$filter_group" ]] && \
|
||||||
log::wg_warning "No rules found in group: ${filter_group}"
|
log::wg_warning "No rules found in group: ${filter_group}" || \
|
||||||
else
|
|
||||||
log::wg_warning "No rules found"
|
log::wg_warning "No rules found"
|
||||||
fi
|
}
|
||||||
fi
|
|
||||||
|
|
||||||
printf "\n"
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
@ -318,11 +241,10 @@ function cmd::rule::show() {
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) util::require_flag "--name" "${2:-}" || return 1
|
--name) name="$2"; shift 2 ;;
|
||||||
name="$2"; shift 2 ;;
|
--no-peers) show_peers=false; shift ;;
|
||||||
--no-peers) show_peers=false; shift ;;
|
--resolved) show_resolved=true; shift ;;
|
||||||
--resolved) show_resolved=true; shift ;;
|
--help) cmd::rule::help; return ;;
|
||||||
--help) cmd::rule::help; return ;;
|
|
||||||
*) log::error "Unknown flag: $1"; return 1 ;;
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
@ -333,7 +255,7 @@ function cmd::rule::show() {
|
||||||
local rule_file
|
local rule_file
|
||||||
rule_file="$(rule::path "$name")"
|
rule_file="$(rule::path "$name")"
|
||||||
|
|
||||||
# ── DNS display ───────────────────────────────
|
# DNS display
|
||||||
local dns_redirect resolved_dns dns_display
|
local dns_redirect resolved_dns dns_display
|
||||||
dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
dns_redirect=$(rule::get_own "$name" "dns_redirect")
|
||||||
dns_redirect="${dns_redirect:-false}"
|
dns_redirect="${dns_redirect:-false}"
|
||||||
|
|
@ -359,24 +281,21 @@ function cmd::rule::show() {
|
||||||
ui::row "DNS" "$dns_display"
|
ui::row "DNS" "$dns_display"
|
||||||
|
|
||||||
printf "\n"
|
printf "\n"
|
||||||
# ── Extends + own rules ────────────────────────
|
if ui::rule::tree "$name"; then
|
||||||
if rule::render_extends_tree "$name"; then
|
|
||||||
# Has inheritance — tree already rendered
|
|
||||||
:
|
:
|
||||||
else
|
else
|
||||||
# No inheritance — flat view
|
ui::rule::flat "$name"
|
||||||
rule::render_flat "$name"
|
|
||||||
printf "\n"
|
printf "\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Resolved ──────────────────────────────────
|
# Resolved view
|
||||||
if $show_resolved; then
|
if $show_resolved; then
|
||||||
cmd::rule::_show_section "Resolved (applied to peers)"
|
ui::rule::section_header "Resolved (applied to peers)"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
local res_allow_ports res_allow_ips res_block_ips res_block_ports
|
||||||
res_allow_ports=$(rule::get "$name" "allow_ports")
|
res_allow_ports=$(rule::get "$name" "allow_ports")
|
||||||
res_allow_ips=$(rule::get "$name" "allow_ips")
|
res_allow_ips=$(rule::get "$name" "allow_ips")
|
||||||
res_block_ips=$(rule::get "$name" "block_ips")
|
res_block_ips=$(rule::get "$name" "block_ips")
|
||||||
res_block_ports=$(rule::get "$name" "block_ports")
|
res_block_ports=$(rule::get "$name" "block_ports")
|
||||||
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "+" "$e"; done \
|
while IFS= read -r e; do [[ -n "$e" ]] && net::print_entry "+" "$e"; done \
|
||||||
<<< "$res_allow_ports"$'\n'"$res_allow_ips"
|
<<< "$res_allow_ports"$'\n'"$res_allow_ips"
|
||||||
|
|
@ -385,26 +304,24 @@ function cmd::rule::show() {
|
||||||
printf "\n"
|
printf "\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Peers ─────────────────────────────────────
|
# Peers
|
||||||
local peer_list=()
|
$show_peers || return 0
|
||||||
mapfile -t peer_list < <(peers::with_rule "$name")
|
|
||||||
|
|
||||||
|
local peer_list=()
|
||||||
|
mapfile -t peer_list < <(peers::with_rule "$name") || true
|
||||||
local peer_count=${#peer_list[@]}
|
local peer_count=${#peer_list[@]}
|
||||||
|
|
||||||
ui::empty "$peer_count" && return 0
|
ui::empty "$peer_count" && return 0
|
||||||
|
|
||||||
printf "\n"
|
local peer_word="peers"
|
||||||
printf "\033[0;37m── Peers (%s) \033[0m%s\n\n" \
|
[[ "$peer_count" -eq 1 ]] && peer_word="peer"
|
||||||
"$(color::gray "${peer_count}")" \
|
printf "\n \033[0;37m── Peers (%s %s) \033[0m%s\n\n" \
|
||||||
"$(printf '\033[0;37m─%.0s' {1..35})"
|
"$peer_count" "$peer_word" "$(printf '\033[0;37m─%.0s' {1..30})"
|
||||||
|
|
||||||
if $show_peers && [[ $peer_count -gt 0 ]]; then
|
for peer_name in "${peer_list[@]}"; do
|
||||||
for peer_name in "${peer_list[@]}"; do
|
local ip
|
||||||
local ip
|
ip=$(peers::get_ip "$peer_name")
|
||||||
ip=$(peers::get_ip "$peer_name")
|
printf " %-28s %s\n" "$peer_name" "$ip"
|
||||||
printf " %-28s %s\n" "$peer_name" "$ip"
|
done
|
||||||
done
|
|
||||||
fi
|
|
||||||
printf "\n"
|
printf "\n"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -418,27 +335,26 @@ function cmd::rule::add() {
|
||||||
local extends=()
|
local extends=()
|
||||||
local allow_ips=() block_ips=() block_ports=() allow_ports=()
|
local allow_ips=() block_ips=() block_ports=() allow_ports=()
|
||||||
local block_services=() allow_services=()
|
local block_services=() allow_services=()
|
||||||
local dns_redirect=false
|
local dns_redirect=false is_base=false
|
||||||
local is_base=false
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) name="$2"; shift 2 ;;
|
--name) name="$2"; shift 2 ;;
|
||||||
--desc) desc="$2"; shift 2 ;;
|
--desc) desc="$2"; shift 2 ;;
|
||||||
--group) group="$2"; shift 2 ;;
|
--group) group="$2"; shift 2 ;;
|
||||||
--extends)
|
--extends)
|
||||||
IFS=',' read -ra ext <<< "$2"
|
IFS=',' read -ra ext <<< "$2"
|
||||||
extends+=("${ext[@]}")
|
extends+=("${ext[@]}")
|
||||||
shift 2 ;;
|
shift 2 ;;
|
||||||
--base) is_base=true; shift ;;
|
--base) is_base=true; shift ;;
|
||||||
--allow-ip) allow_ips+=("$2"); shift 2 ;;
|
--allow-ip) allow_ips+=("$2"); shift 2 ;;
|
||||||
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
||||||
--block-ip) block_ips+=("$2"); shift 2 ;;
|
--block-ip) block_ips+=("$2"); shift 2 ;;
|
||||||
--block-port) block_ports+=("$2"); shift 2 ;;
|
--block-port) block_ports+=("$2"); shift 2 ;;
|
||||||
--block-service) block_services+=("$2"); shift 2 ;;
|
--block-service) block_services+=("$2"); shift 2 ;;
|
||||||
--allow-service) allow_services+=("$2"); shift 2 ;;
|
--allow-service) allow_services+=("$2"); shift 2 ;;
|
||||||
--dns-redirect) dns_redirect=true; shift ;;
|
--dns-redirect) dns_redirect=true; shift ;;
|
||||||
--help) cmd::rule::help; return ;;
|
--help) cmd::rule::help; return ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown flag: $1"
|
log::error "Unknown flag: $1"
|
||||||
return 1 ;;
|
return 1 ;;
|
||||||
|
|
@ -452,12 +368,10 @@ function cmd::rule::add() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate extends
|
|
||||||
for ext in "${extends[@]}"; do
|
for ext in "${extends[@]}"; do
|
||||||
rule::require_exists "$ext" || return 1
|
rule::require_exists "$ext" || return 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# Determine target directory
|
|
||||||
local rule_dir
|
local rule_dir
|
||||||
if $is_base; then
|
if $is_base; then
|
||||||
rule_dir="$(ctx::rules)/base"
|
rule_dir="$(ctx::rules)/base"
|
||||||
|
|
@ -487,7 +401,6 @@ function cmd::rule::add() {
|
||||||
done
|
done
|
||||||
|
|
||||||
local rule_file="${rule_dir}/${name}.rule"
|
local rule_file="${rule_dir}/${name}.rule"
|
||||||
|
|
||||||
local allow_str block_str port_str allow_port_str extends_str
|
local allow_str block_str port_str allow_port_str extends_str
|
||||||
allow_str=$(IFS=','; echo "${allow_ips[*]}")
|
allow_str=$(IFS=','; echo "${allow_ips[*]}")
|
||||||
block_str=$(IFS=','; echo "${block_ips[*]}")
|
block_str=$(IFS=','; echo "${block_ips[*]}")
|
||||||
|
|
@ -502,7 +415,6 @@ function cmd::rule::add() {
|
||||||
|
|
||||||
local base_label=""
|
local base_label=""
|
||||||
$is_base && base_label=" (base)"
|
$is_base && base_label=" (base)"
|
||||||
|
|
||||||
log::wg_success "Rule created: ${name}${base_label}"
|
log::wg_success "Rule created: ${name}${base_label}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,9 +431,9 @@ function cmd::rule::update() {
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) name="$2"; shift 2 ;;
|
--name) name="$2"; shift 2 ;;
|
||||||
--desc) desc="$2"; shift 2 ;;
|
--desc) desc="$2"; shift 2 ;;
|
||||||
--group) group="$2"; shift 2 ;;
|
--group) group="$2"; shift 2 ;;
|
||||||
--add-extends)
|
--add-extends)
|
||||||
IFS=',' read -ra ext <<< "$2"
|
IFS=',' read -ra ext <<< "$2"
|
||||||
add_extends+=("${ext[@]}")
|
add_extends+=("${ext[@]}")
|
||||||
|
|
@ -530,16 +442,16 @@ function cmd::rule::update() {
|
||||||
IFS=',' read -ra ext <<< "$2"
|
IFS=',' read -ra ext <<< "$2"
|
||||||
rm_extends+=("${ext[@]}")
|
rm_extends+=("${ext[@]}")
|
||||||
shift 2 ;;
|
shift 2 ;;
|
||||||
--allow-ip) allow_ips+=("$2"); shift 2 ;;
|
--allow-ip) allow_ips+=("$2"); shift 2 ;;
|
||||||
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
--allow-port) allow_ports+=("$2"); shift 2 ;;
|
||||||
--block-ip) block_ips+=("$2"); shift 2 ;;
|
--block-ip) block_ips+=("$2"); shift 2 ;;
|
||||||
--block-port) block_ports+=("$2"); shift 2 ;;
|
--block-port) block_ports+=("$2"); shift 2 ;;
|
||||||
--remove-allow-ip) rm_allow_ips+=("$2"); shift 2 ;;
|
--remove-allow-ip) rm_allow_ips+=("$2"); shift 2 ;;
|
||||||
--remove-block-ip) rm_block_ips+=("$2"); shift 2 ;;
|
--remove-block-ip) rm_block_ips+=("$2"); shift 2 ;;
|
||||||
--remove-block-port) rm_block_ports+=("$2"); shift 2 ;;
|
--remove-block-port) rm_block_ports+=("$2"); shift 2 ;;
|
||||||
--remove-allow-port) rm_allow_ports+=("$2"); shift 2 ;;
|
--remove-allow-port) rm_allow_ports+=("$2"); shift 2 ;;
|
||||||
--dns-redirect) dns_redirect=true; shift ;;
|
--dns-redirect) dns_redirect=true; shift ;;
|
||||||
--help) cmd::rule::help; return ;;
|
--help) cmd::rule::help; return ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown flag: $1"
|
log::error "Unknown flag: $1"
|
||||||
return 1 ;;
|
return 1 ;;
|
||||||
|
|
@ -552,18 +464,15 @@ function cmd::rule::update() {
|
||||||
local rule_file
|
local rule_file
|
||||||
rule_file="$(rule::path "$name")"
|
rule_file="$(rule::path "$name")"
|
||||||
|
|
||||||
# Update simple fields
|
|
||||||
[[ -n "$desc" ]] && json::set "$rule_file" "desc" "\"$desc\""
|
[[ -n "$desc" ]] && json::set "$rule_file" "desc" "\"$desc\""
|
||||||
[[ -n "$group" ]] && json::set "$rule_file" "group" "\"$group\""
|
[[ -n "$group" ]] && json::set "$rule_file" "group" "\"$group\""
|
||||||
[[ -n "$dns_redirect" ]] && json::set "$rule_file" "dns_redirect" "true"
|
[[ -n "$dns_redirect" ]] && json::set "$rule_file" "dns_redirect" "true"
|
||||||
|
|
||||||
# Add entries
|
|
||||||
for ip in "${allow_ips[@]}"; do json::append "$rule_file" "allow_ips" "$ip"; done
|
for ip in "${allow_ips[@]}"; do json::append "$rule_file" "allow_ips" "$ip"; done
|
||||||
for ip in "${block_ips[@]}"; do json::append "$rule_file" "block_ips" "$ip"; done
|
for ip in "${block_ips[@]}"; do json::append "$rule_file" "block_ips" "$ip"; done
|
||||||
for p in "${block_ports[@]}"; do json::append "$rule_file" "block_ports" "$p"; done
|
for p in "${block_ports[@]}"; do json::append "$rule_file" "block_ports" "$p"; done
|
||||||
for p in "${allow_ports[@]}"; do json::append "$rule_file" "allow_ports" "$p"; done
|
for p in "${allow_ports[@]}"; do json::append "$rule_file" "allow_ports" "$p"; done
|
||||||
|
|
||||||
# Add/remove extends
|
|
||||||
for ext in "${add_extends[@]}"; do
|
for ext in "${add_extends[@]}"; do
|
||||||
rule::require_exists "$ext" || return 1
|
rule::require_exists "$ext" || return 1
|
||||||
json::append "$rule_file" "extends" "$ext"
|
json::append "$rule_file" "extends" "$ext"
|
||||||
|
|
@ -572,7 +481,6 @@ function cmd::rule::update() {
|
||||||
json::remove "$rule_file" "extends" "$ext"
|
json::remove "$rule_file" "extends" "$ext"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Remove entries
|
|
||||||
for ip in "${rm_allow_ips[@]}"; do json::remove "$rule_file" "allow_ips" "$ip"; done
|
for ip in "${rm_allow_ips[@]}"; do json::remove "$rule_file" "allow_ips" "$ip"; done
|
||||||
for ip in "${rm_block_ips[@]}"; do json::remove "$rule_file" "block_ips" "$ip"; done
|
for ip in "${rm_block_ips[@]}"; do json::remove "$rule_file" "block_ips" "$ip"; done
|
||||||
for p in "${rm_block_ports[@]}"; do json::remove "$rule_file" "block_ports" "$p"; done
|
for p in "${rm_block_ports[@]}"; do json::remove "$rule_file" "block_ports" "$p"; done
|
||||||
|
|
@ -583,8 +491,7 @@ function cmd::rule::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Remove / Assign / Unassign / Migrate / Reapply
|
# Remove
|
||||||
# (unchanged from original)
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function cmd::rule::remove() {
|
function cmd::rule::remove() {
|
||||||
|
|
@ -603,7 +510,7 @@ function cmd::rule::remove() {
|
||||||
rule::require_exists "$name" || return 1
|
rule::require_exists "$name" || return 1
|
||||||
|
|
||||||
local peer_list=()
|
local peer_list=()
|
||||||
mapfile -t peer_list < <(peers::with_rule "$name")
|
mapfile -t peer_list < <(peers::with_rule "$name") || true
|
||||||
local peer_count=${#peer_list[@]}
|
local peer_count=${#peer_list[@]}
|
||||||
|
|
||||||
if [[ "$peer_count" -gt 0 ]]; then
|
if [[ "$peer_count" -gt 0 ]]; then
|
||||||
|
|
@ -620,6 +527,10 @@ function cmd::rule::remove() {
|
||||||
log::wg_success "Rule removed: ${name}"
|
log::wg_success "Rule removed: ${name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Assign / Unassign
|
||||||
|
# ============================================
|
||||||
|
|
||||||
function cmd::rule::assign() {
|
function cmd::rule::assign() {
|
||||||
local name="" peer="" type=""
|
local name="" peer="" type=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
|
|
@ -635,15 +546,13 @@ function cmd::rule::assign() {
|
||||||
[[ -z "$name" || -z "$peer" ]] && \
|
[[ -z "$name" || -z "$peer" ]] && \
|
||||||
log::error "Missing required flags: --name and --peer" && return 1
|
log::error "Missing required flags: --name and --peer" && return 1
|
||||||
|
|
||||||
rule::require_exists "$name" || return 1
|
rule::require_exists "$name" || return 1
|
||||||
rule::require_assignable "$name" || return 1
|
rule::require_assignable "$name" || return 1
|
||||||
|
|
||||||
peer=$(peers::resolve_and_require "$peer" "$type") || return 1
|
peer=$(peers::resolve_and_require "$peer" "$type") || return 1
|
||||||
|
|
||||||
local existing_rule
|
local existing_rule ip
|
||||||
existing_rule=$(peers::get_meta "$peer" "rule")
|
existing_rule=$(peers::get_meta "$peer" "rule")
|
||||||
|
|
||||||
local ip
|
|
||||||
ip=$(peers::get_ip "$peer")
|
ip=$(peers::get_ip "$peer")
|
||||||
[[ -z "$ip" ]] && log::error "Could not resolve IP for: $peer" && return 1
|
[[ -z "$ip" ]] && log::error "Could not resolve IP for: $peer" && return 1
|
||||||
|
|
||||||
|
|
@ -684,37 +593,41 @@ function cmd::rule::unassign() {
|
||||||
log::wg_success "Unassigned rule from: ${peer}"
|
log::wg_success "Unassigned rule from: ${peer}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Migrate
|
||||||
|
# ============================================
|
||||||
|
|
||||||
function cmd::rule::migrate() {
|
function cmd::rule::migrate() {
|
||||||
log::section "Migrating peers to default rules"
|
log::section "Migrating peers to default rules"
|
||||||
local tmp
|
local count=0
|
||||||
tmp=$(mktemp)
|
|
||||||
|
|
||||||
while IFS= read -r peer_name; do
|
while IFS= read -r peer_name; do
|
||||||
local existing
|
local existing
|
||||||
existing=$(peers::get_meta "$peer_name" "rule")
|
existing=$(peers::get_meta "$peer_name" "rule")
|
||||||
[[ -n "$existing" ]] && continue
|
[[ -n "$existing" ]] && continue
|
||||||
local default_rule
|
|
||||||
default_rule=$(peers::default_rule "$peer_name")
|
# Try to get default rule from subnet policy
|
||||||
|
local peer_type subnet_name default_rule
|
||||||
|
peer_type=$(peers::get_meta "$peer_name" "type")
|
||||||
|
subnet_name=$(peers::get_meta "$peer_name" "subnet")
|
||||||
|
default_rule=$(subnet::default_rule "$subnet_name" "$peer_type")
|
||||||
|
[[ -z "$default_rule" ]] && continue
|
||||||
|
|
||||||
rule::exists "$default_rule" || continue
|
rule::exists "$default_rule" || continue
|
||||||
|
|
||||||
local ip
|
local ip
|
||||||
ip=$(peers::get_ip "$peer_name")
|
ip=$(peers::get_ip "$peer_name")
|
||||||
echo "${peer_name} ${default_rule} ${ip}" >> "$tmp"
|
rule::apply "$default_rule" "$ip" "$peer_name"
|
||||||
done < <(peers::all)
|
|
||||||
|
|
||||||
local count=0
|
|
||||||
local lines=()
|
|
||||||
mapfile -t lines < "$tmp"
|
|
||||||
rm -f "$tmp"
|
|
||||||
|
|
||||||
for line in "${lines[@]}"; do
|
|
||||||
IFS=" " read -r peer_name default_rule ip <<< "$line"
|
|
||||||
rule::apply "$default_rule" "$ip" "$peer_name" </dev/null
|
|
||||||
(( count++ )) || true
|
(( count++ )) || true
|
||||||
done
|
done < <(peers::all)
|
||||||
|
|
||||||
log::wg_success "Migrated ${count} peers"
|
log::wg_success "Migrated ${count} peers"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Reapply
|
||||||
|
# ============================================
|
||||||
|
|
||||||
function cmd::rule::reapply() {
|
function cmd::rule::reapply() {
|
||||||
local name="" all=false
|
local name="" all=false
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
|
|
@ -731,9 +644,8 @@ function cmd::rule::reapply() {
|
||||||
while IFS= read -r rule_file; do
|
while IFS= read -r rule_file; do
|
||||||
local rname
|
local rname
|
||||||
rname=$(basename "$rule_file" .rule)
|
rname=$(basename "$rule_file" .rule)
|
||||||
# Skip if no peers assigned
|
|
||||||
local peer_list=()
|
local peer_list=()
|
||||||
mapfile -t peer_list < <(peers::with_rule "$rname")
|
mapfile -t peer_list < <(peers::with_rule "$rname") || true
|
||||||
[[ ${#peer_list[@]} -eq 0 ]] && continue
|
[[ ${#peer_list[@]} -eq 0 ]] && continue
|
||||||
rule::reapply_all "$rname"
|
rule::reapply_all "$rname"
|
||||||
(( count++ )) || true
|
(( count++ )) || true
|
||||||
|
|
@ -746,20 +658,4 @@ function cmd::rule::reapply() {
|
||||||
rule::require_exists "$name" || return 1
|
rule::require_exists "$name" || return 1
|
||||||
rule::reapply_all "$name"
|
rule::reapply_all "$name"
|
||||||
log::wg_success "Rule '${name}' reapplied"
|
log::wg_success "Rule '${name}' reapplied"
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Show helpers
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
function cmd::rule::_show_section() {
|
|
||||||
local title="${1:-}" color="${2:-white}" use_color="${3:-false}"
|
|
||||||
local color_code=""
|
|
||||||
if $use_color; then
|
|
||||||
case "$color" in
|
|
||||||
green) color_code="\033[0;32m" ;;
|
|
||||||
red) color_code="\033[0;31m" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
printf "\n ${color_code}── %s ──────────────────────────────────\033[0m\n" "$title"
|
|
||||||
}
|
}
|
||||||
23
core/ui.sh
23
core/ui.sh
|
|
@ -64,6 +64,29 @@ for s in sys.argv[1:]:
|
||||||
" "$@"
|
" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ui::vis_len <string>
|
||||||
|
# Returns the visible (character) length of a string,
|
||||||
|
# stripping ANSI codes and accounting for multi-byte UTF-8.
|
||||||
|
function ui::vis_len() {
|
||||||
|
local str="${1:-}"
|
||||||
|
# Strip ANSI codes first
|
||||||
|
local clean
|
||||||
|
clean=$(echo "$str" | sed 's/\x1b\[[0-9;]*m//g')
|
||||||
|
# Use Python for accurate Unicode character count
|
||||||
|
python3 -c "import sys; print(len('${clean//\'/\'\\\'\'}'))" 2>/dev/null || echo "${#clean}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ui::pad_to_col <string_bytes_printed> <target_visible_col>
|
||||||
|
# Returns padding needed accounting for UTF-8 byte/char difference.
|
||||||
|
# extra_bytes = bytes_printed - visible_chars_printed
|
||||||
|
function ui::utf8_extra_bytes() {
|
||||||
|
local str="${1:-}"
|
||||||
|
local byte_len=${#str}
|
||||||
|
local vis_len
|
||||||
|
vis_len=$(ui::vis_len "$str")
|
||||||
|
echo $(( byte_len - vis_len ))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function ui::pad_status() {
|
function ui::pad_status() {
|
||||||
ui::pad "${1:-}" "${2:-25}"
|
ui::pad "${1:-}" "${2:-25}"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@
|
||||||
"desktop-roboclean": "46.189.215.231",
|
"desktop-roboclean": "46.189.215.231",
|
||||||
"laptop-nuno": "94.63.0.129",
|
"laptop-nuno": "94.63.0.129",
|
||||||
"phone-luis": "176.223.61.15",
|
"phone-luis": "176.223.61.15",
|
||||||
"phone-helena-2": "148.69.192.130",
|
"phone-helena-2": "148.69.202.127",
|
||||||
"desktop-zephyr": "86.120.152.74"
|
"desktop-zephyr": "86.120.152.74"
|
||||||
}
|
}
|
||||||
|
|
@ -333,21 +333,14 @@ function rule::restore_all() {
|
||||||
# Rendering
|
# Rendering
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
function rule::render_flat() {
|
# ======================================================
|
||||||
ui::rule::flat "$1"
|
# Aliases (for backward compat — remove in cleanup pass)
|
||||||
}
|
# ======================================================
|
||||||
|
|
||||||
function rule::render_entries() {
|
function rule::render_flat() { ui::rule::flat "$1"; }
|
||||||
ui::rule::entries "$1"
|
function rule::render_entries() { ui::rule::entries "$1"; }
|
||||||
}
|
function rule::render_own_entries() { ui::rule::own_entries "$1"; }
|
||||||
|
function rule::render_extends_tree() { ui::rule::tree "$1"; }
|
||||||
function rule::render_own_entries() {
|
|
||||||
ui::rule::own_entries "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
function rule::render_extends_tree() {
|
|
||||||
ui::rule::tree "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# DNS Redirect
|
# DNS Redirect
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,24 @@
|
||||||
# Renders the fully resolved entries for a rule (allow + block).
|
# Renders the fully resolved entries for a rule (allow + block).
|
||||||
function ui::rule::entries() {
|
function ui::rule::entries() {
|
||||||
local rule_name="${1:-}" indent="${2:-4}"
|
local rule_name="${1:-}" indent="${2:-4}"
|
||||||
|
|
||||||
local allow_ports allow_ips block_ips block_ports dns
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
|
allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
|
||||||
allow_ips=$(rule::get "$rule_name" "allow_ips" 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_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
|
||||||
block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
|
block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
|
||||||
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||||
|
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
[[ -z "$e" ]] && continue
|
[[ -z "$e" ]] && continue
|
||||||
net::print_entry "+" "$e" "$indent"
|
net::print_entry "+" "$e" "$indent"
|
||||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
[[ -z "$e" ]] && continue
|
[[ -z "$e" ]] && continue
|
||||||
net::print_entry "-" "$e" "$indent"
|
net::print_entry "-" "$e" "$indent"
|
||||||
done <<< "$block_ips"$'\n'"$block_ports"
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
|
||||||
[[ "${dns,,}" == "true" ]] && \
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
}
|
}
|
||||||
|
|
@ -40,27 +40,27 @@ function ui::rule::own_entries() {
|
||||||
local rule_file
|
local rule_file
|
||||||
rule_file="$(rule::path "$rule_name")" || return 0
|
rule_file="$(rule::path "$rule_name")" || return 0
|
||||||
[[ -z "$rule_file" ]] && return 0
|
[[ -z "$rule_file" ]] && return 0
|
||||||
|
|
||||||
local allow_ports allow_ips block_ips block_ports dns
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
|
allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
|
||||||
allow_ips=$(json::get "$rule_file" "allow_ips" 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_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
|
||||||
block_ports=$(json::get "$rule_file" "block_ports" 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)
|
dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
|
||||||
|
|
||||||
local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
|
local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
|
||||||
[[ -z "${combined//[$'\n']/}" ]] && return 0
|
[[ -z "${combined//[$'\n']/}" ]] && return 0
|
||||||
|
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
[[ -z "$e" ]] && continue
|
[[ -z "$e" ]] && continue
|
||||||
net::print_entry "+" "$e" "$indent"
|
net::print_entry "+" "$e" "$indent"
|
||||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
|
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
[[ -z "$e" ]] && continue
|
[[ -z "$e" ]] && continue
|
||||||
net::print_entry "-" "$e" "$indent"
|
net::print_entry "-" "$e" "$indent"
|
||||||
done <<< "$block_ips"$'\n'"$block_ports"
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
|
|
||||||
[[ "${dns,,}" == "true" ]] && \
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
}
|
}
|
||||||
|
|
@ -69,22 +69,22 @@ function ui::rule::own_entries() {
|
||||||
# Renders the full resolved entries as a flat list.
|
# Renders the full resolved entries as a flat list.
|
||||||
function ui::rule::flat() {
|
function ui::rule::flat() {
|
||||||
local rule_name="${1:-}"
|
local rule_name="${1:-}"
|
||||||
|
|
||||||
local allow_ports allow_ips block_ips block_ports dns
|
local allow_ports allow_ips block_ips block_ports dns
|
||||||
allow_ports=$(rule::get "$rule_name" "allow_ports")
|
allow_ports=$(rule::get "$rule_name" "allow_ports")
|
||||||
allow_ips=$(rule::get "$rule_name" "allow_ips")
|
allow_ips=$(rule::get "$rule_name" "allow_ips")
|
||||||
block_ips=$(rule::get "$rule_name" "block_ips")
|
block_ips=$(rule::get "$rule_name" "block_ips")
|
||||||
block_ports=$(rule::get "$rule_name" "block_ports")
|
block_ports=$(rule::get "$rule_name" "block_ports")
|
||||||
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
dns=$(rule::get_own "$rule_name" "dns_redirect")
|
||||||
|
|
||||||
local has_content=false
|
local has_content=false
|
||||||
[[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && has_content=true
|
[[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && has_content=true
|
||||||
|
|
||||||
if ! $has_content; then
|
if ! $has_content; then
|
||||||
printf "\n full access (no restrictions)\n"
|
printf "\n full access (no restrictions)\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
|
||||||
printf "\n"
|
printf "\n"
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
|
|
@ -92,7 +92,7 @@ function ui::rule::flat() {
|
||||||
net::print_entry "+" "$e" 2
|
net::print_entry "+" "$e" 2
|
||||||
done <<< "$allow_ports"$'\n'"$allow_ips"
|
done <<< "$allow_ports"$'\n'"$allow_ips"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
if [[ -n "$block_ips" || -n "$block_ports" ]]; then
|
||||||
printf "\n"
|
printf "\n"
|
||||||
while IFS= read -r e; do
|
while IFS= read -r e; do
|
||||||
|
|
@ -100,7 +100,7 @@ function ui::rule::flat() {
|
||||||
net::print_entry "-" "$e" 2
|
net::print_entry "-" "$e" 2
|
||||||
done <<< "$block_ips"$'\n'"$block_ports"
|
done <<< "$block_ips"$'\n'"$block_ports"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ "${dns,,}" == "true" ]] && \
|
[[ "${dns,,}" == "true" ]] && \
|
||||||
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
net::print_dns_redirect "$(config::dns)" 6 "DNS"
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ function ui::rule::_render_bases() {
|
||||||
local entry_indent="${2:-6}" label_indent="${3:-4}"
|
local entry_indent="${2:-6}" label_indent="${3:-4}"
|
||||||
local label_pad
|
local label_pad
|
||||||
label_pad=$(printf '%*s' "$label_indent" '')
|
label_pad=$(printf '%*s' "$label_indent" '')
|
||||||
|
|
||||||
local first=true
|
local first=true
|
||||||
for base_name in "${_bases[@]}"; do
|
for base_name in "${_bases[@]}"; do
|
||||||
[[ -z "$base_name" ]] && continue
|
[[ -z "$base_name" ]] && continue
|
||||||
|
|
@ -140,23 +140,23 @@ function ui::rule::tree() {
|
||||||
local rule_file
|
local rule_file
|
||||||
rule_file="$(rule::path "$rule_name")" || return 1
|
rule_file="$(rule::path "$rule_name")" || return 1
|
||||||
[[ -z "$rule_file" ]] && return 1
|
[[ -z "$rule_file" ]] && return 1
|
||||||
|
|
||||||
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[@]} -eq 0 || -z "${extends_raw[0]:-}" ]]; then
|
if [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ui::rule::_render_bases extends_raw 6 4
|
ui::rule::_render_bases extends_raw 6 4
|
||||||
|
|
||||||
local own_output
|
local own_output
|
||||||
own_output=$(ui::rule::own_entries "$rule_name" 6)
|
own_output=$(ui::rule::own_entries "$rule_name" 6)
|
||||||
if [[ -n "$own_output" ]]; then
|
if [[ -n "$own_output" ]]; then
|
||||||
printf "\n \033[0;37mOwn:\033[0m\n"
|
printf "\n \033[0;37mOwn:\033[0m\n"
|
||||||
printf "%s\n" "$own_output"
|
printf "%s\n" "$own_output"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,16 +194,15 @@ function ui::rule::_identity_rule_entry() {
|
||||||
local rule_name="${1:-}"
|
local rule_name="${1:-}"
|
||||||
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"
|
printf " \033[0;37m↳ %s\033[0m\n" "$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
|
||||||
# Rule has extends — render one level deep using shared helper
|
|
||||||
ui::rule::_render_bases extends_raw 10 8
|
ui::rule::_render_bases extends_raw 10 8
|
||||||
|
|
||||||
local own_output
|
local own_output
|
||||||
own_output=$(ui::rule::own_entries "$rule_name" 10)
|
own_output=$(ui::rule::own_entries "$rule_name" 10)
|
||||||
if [[ -n "$own_output" ]]; then
|
if [[ -n "$own_output" ]]; then
|
||||||
|
|
@ -211,7 +210,6 @@ function ui::rule::_identity_rule_entry() {
|
||||||
printf "%s\n" "$own_output"
|
printf "%s\n" "$own_output"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Leaf rule — show own entries or note full access
|
|
||||||
local own_output
|
local own_output
|
||||||
own_output=$(ui::rule::own_entries "$rule_name" 8)
|
own_output=$(ui::rule::own_entries "$rule_name" 8)
|
||||||
if [[ -n "$own_output" ]]; then
|
if [[ -n "$own_output" ]]; then
|
||||||
|
|
@ -254,4 +252,103 @@ function ui::rule::_peer_rule_entry() {
|
||||||
printf " \033[2mfull access (no restrictions)\033[0m\n"
|
printf " \033[2mfull access (no restrictions)\033[0m\n"
|
||||||
fi
|
fi
|
||||||
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
|
||||||
|
|
||||||
|
# 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" "")
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " %s %b%b %s%*s%b\n" \
|
||||||
|
"$name_pad" "$allows_str" "$blocks_str" \
|
||||||
|
"$peers_display" "$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"
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue