From a003e3b7539bde164910fb76786ee78e938226c4 Mon Sep 17 00:00:00 2001 From: Nuno Duque Nunes Date: Mon, 25 May 2026 18:45:23 +0000 Subject: [PATCH] fix: policy_read accidental defaults merge from module split - _policy_read: remove erroneous _POLICY_DEFAULTS merge (introduced during split) - fmt.sh: fmt::bytes extracted from cmd::activity::_fmt_bytes - identity/subnet/policy list: ui::sort_rows applied - ctx::policies moved from policy.module.sh to context.sh --- commands/activity.command.sh | 77 ++++++------------ commands/group.command.sh | 44 ++++------ commands/identity.command.sh | 2 +- commands/policy.command.sh | 2 +- commands/subnet.command.sh | 2 +- core/context.sh | 1 + core/fmt.sh | 14 ++++ core/json_helper.py | 20 +++-- core/lib/__pycache__/activity.cpython-311.pyc | Bin 7451 -> 7451 bytes modules/policy.module.sh | 2 - modules/ui/activity.module.sh | 47 +++++++++++ modules/ui/group.module.sh | 28 +++++++ 12 files changed, 145 insertions(+), 94 deletions(-) create mode 100644 modules/ui/activity.module.sh diff --git a/commands/activity.command.sh b/commands/activity.command.sh index c9537e1..a2eb16a 100644 --- a/commands/activity.command.sh +++ b/commands/activity.command.sh @@ -104,7 +104,7 @@ function cmd::activity::run() { return 0 fi - # Measure w_peer and w_drops from data + # Measure column widths local w_peer=16 w_drops=1 while IFS='|' read -r type rest; do case "$type" in @@ -122,94 +122,69 @@ function cmd::activity::run() { ;; esac done <<< "$data" - + (( w_peer += 2 )) - - # Compute exact column where drop count starts on peer row: + + # Compute column where drop count starts on peer row: # " " (2) + name (w_peer) + " ↓" (3) + rx (10) + " ↑" (3) + tx (10) + " " (2) - # Note: ↓ and ↑ are multi-byte (3 bytes) but display as 1 char — account for 2 extra bytes each + # ↓ and ↑ are multi-byte (3 bytes, 1 visible) — 2 extra bytes each # Visible: 2 + w_peer + 2+1 + 10 + 2+1 + 10 + 2 = w_peer + 30 local drops_col=$(( w_peer + 30 )) - + local hours_display="${hours}h" [[ "$hours" == "0" ]] && hours_display="all time" - + log::section "Activity Monitor (last ${hours_display})" echo "" - + local first_peer=true skip_peer=false - + while IFS='|' read -r record_type rest; do case "$record_type" in peer) local name rx tx drops IFS='|' read -r name rx tx drops <<< "$rest" - + skip_peer=false if $dropped_only && [[ "$drops" -eq 0 ]]; then skip_peer=true continue fi - + $first_peer || echo "" first_peer=false - + local rx_fmt tx_fmt - rx_fmt=$(cmd::activity::_fmt_bytes "$rx") - tx_fmt=$(cmd::activity::_fmt_bytes "$tx") - + rx_fmt=$(fmt::bytes "$rx") + tx_fmt=$(fmt::bytes "$tx") + local name_pad rx_pad tx_pad name_pad=$(printf "%-${w_peer}s" "$name") rx_pad=$(printf "%-10s" "$rx_fmt") tx_pad=$(printf "%-10s" "$tx_fmt") - + local drop_word="drops" [[ "$drops" -eq 1 ]] && drop_word="drop" - printf " \033[1m%s\033[0m \033[2m↓\033[0m%s \033[2m↑\033[0m%s %${w_drops}s %s\n" \ - "$name_pad" "$rx_pad" "$tx_pad" "$drops" "$drop_word" + + ui::activity::peer_row \ + "$name_pad" "$rx_pad" "$tx_pad" "$drops" "$drop_word" "$w_drops" ;; - + service) $skip_peer && continue - + local peer dest_display drop_count IFS='|' read -r peer dest_display drop_count <<< "$rest" - - # Compute padding to align drop count with peer drop column - # Service row visible prefix: " → " (6) + ${#dest_display} - local arrow_prefix=" → " - local prefix_bytes=${#arrow_prefix} # = 8 due to → being 3 bytes - local prefix_len=$(( prefix_bytes + ${#dest_display} )) - # local prefix_len=$(( 6 + ${#dest_display} )) - local pad_n=$(( drops_col - prefix_len )) - [[ $pad_n -lt 1 ]] && pad_n=1 - + local svc_drop_word="drops" [[ "$drop_count" -eq 1 ]] && svc_drop_word="drop" - printf " \033[2m→\033[0m %s%*s %${w_drops}s %s\n" \ - "$dest_display" "$pad_n" "" "$drop_count" "$svc_drop_word" + + ui::activity::service_row \ + "$dest_display" "$drop_count" "$svc_drop_word" "$drops_col" "$w_drops" ;; esac done <<< "$data" - + echo "" } -# ============================================ -# Helpers -# ============================================ - -function cmd::activity::_fmt_bytes() { - local bytes="${1:-0}" - if (( bytes == 0 )); then - printf "—" - elif (( bytes >= 1073741824 )); then - printf "%dGB" $(( bytes / 1073741824 )) - elif (( bytes >= 1048576 )); then - printf "%dMB" $(( bytes / 1048576 )) - elif (( bytes >= 1024 )); then - printf "%dKB" $(( bytes / 1024 )) - else - printf "%dB" "$bytes" - fi -} \ No newline at end of file diff --git a/commands/group.command.sh b/commands/group.command.sh index 51499ed..4f45c2e 100644 --- a/commands/group.command.sh +++ b/commands/group.command.sh @@ -147,7 +147,7 @@ function cmd::group::list() { function cmd::group::show() { local name="" - + while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;; @@ -155,20 +155,21 @@ function cmd::group::show() { *) log::error "Unknown flag: $1"; return 1 ;; esac done - + [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 group::require_exists "$name" || return 1 - + local group_file group_file="$(group::path "$name")" - + log::section "Group: ${name}" printf "\n" - + local desc desc=$(json::get "$group_file" "desc") ui::row "Description" "${desc:-—}" - + + # Load and filter peers local peers_list=() mapfile -t peers_list < <(json::get "$group_file" "peers") || true local filtered=() @@ -178,9 +179,8 @@ function cmd::group::show() { peers_list=("${filtered[@]:-}") local peer_count=${#peers_list[@]} [[ -z "${peers_list[0]:-}" ]] && peer_count=0 - - local peer_word="peers" - [[ "$peer_count" -eq 1 ]] && peer_word="peer" + + # Count valid peers (data logic stays in command) local valid_count=0 for p in "${peers_list[@]}"; do [[ -z "$p" ]] && continue @@ -190,36 +190,22 @@ function cmd::group::show() { [[ "$valid_count" -eq 1 ]] && peer_word="peer" ui::row "Peers" "${valid_count} ${peer_word}" printf "\n" - + if [[ "$peer_count" -gt 0 ]]; then - # Measure name and IP widths + # Measure widths (data logic stays in command) local w_name=16 w_ip=13 for peer_name in "${peers_list[@]}"; do [[ -z "$peer_name" ]] && continue (( ${#peer_name} > w_name )) && w_name=${#peer_name} done (( w_name += 2 )) - - for peer_name in "${peers_list[@]}"; do - [[ -z "$peer_name" ]] && continue - - if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then - printf " \033[2m%-${w_name}s (no longer exists)\033[0m\n" "$peer_name" - continue - fi - - local ip rule is_blocked - ip=$(peers::get_ip "$peer_name") - rule=$(peers::get_meta "$peer_name" "rule") - peers::is_blocked "$peer_name" 2>/dev/null && is_blocked="true" || is_blocked="false" - - ui::group::show_member_row "$peer_name" "$ip" "${rule:--}" \ - "$is_blocked" "$w_name" "$w_ip" - done + + # Delegate rendering to ui:: + ui::group::show_peers peers_list "$w_name" "$w_ip" else printf " \033[2m—\033[0m\n" fi - + printf "\n" } diff --git a/commands/identity.command.sh b/commands/identity.command.sh index 9810b4f..58f6487 100644 --- a/commands/identity.command.sh +++ b/commands/identity.command.sh @@ -100,7 +100,7 @@ function cmd::identity::run() { function cmd::identity::_list() { local data - data=$(identity::list_data) + data=$(identity::list_data | ui::sort_rows 1) if [[ -z "$data" ]]; then log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers." diff --git a/commands/policy.command.sh b/commands/policy.command.sh index b9b0149..649a540 100644 --- a/commands/policy.command.sh +++ b/commands/policy.command.sh @@ -99,7 +99,7 @@ function cmd::policy::run() { function cmd::policy::_list() { local data - data=$(policy::list_data) + data=$(policy::list_data | ui::sort_rows 1) if [[ -z "$data" ]]; then log::info "No policies defined." diff --git a/commands/subnet.command.sh b/commands/subnet.command.sh index 04b8c6f..0a41956 100644 --- a/commands/subnet.command.sh +++ b/commands/subnet.command.sh @@ -85,7 +85,7 @@ function cmd::subnet::run() { function cmd::subnet::_list() { local data - data=$(subnet::list_data) + data=$(subnet::list_data | ui::sort_rows 1) if [[ -z "$data" ]]; then log::info "No subnets defined." diff --git a/core/context.sh b/core/context.sh index c1ebff9..bbeada5 100644 --- a/core/context.sh +++ b/core/context.sh @@ -45,6 +45,7 @@ function ctx::meta() { echo "$_CTX_META"; } function ctx::daemon() { echo "$_CTX_DAEMON"; } function ctx::net() { echo "$_CTX_NET"; } function ctx::identities() { echo "${_CTX_IDENTITY}"; } +function ctx::policies() { echo "${_CTX_DATA}/policies.json"; } function ctx::subnets() { echo "${_CTX_DATA}/subnets.json"; } function ctx::hosts() { echo "${_CTX_DATA}/hosts.json"; } function ctx::events_log() { echo "$(ctx::daemon)/events.log"; } diff --git a/core/fmt.sh b/core/fmt.sh index fb97281..6dad26c 100644 --- a/core/fmt.sh +++ b/core/fmt.sh @@ -79,3 +79,17 @@ function fmt::set_date_format() { esac } +function fmt::bytes() { + local bytes="${1:-0}" + if (( bytes == 0 )); then + printf "—" + elif (( bytes >= 1073741824 )); then + printf "%dGB" $(( bytes / 1073741824 )) + elif (( bytes >= 1048576 )); then + printf "%dMB" $(( bytes / 1048576 )) + elif (( bytes >= 1024 )); then + printf "%dKB" $(( bytes / 1024 )) + else + printf "%dB" "$bytes" + fi +} \ No newline at end of file diff --git a/core/json_helper.py b/core/json_helper.py index 2f0c579..01d5982 100644 --- a/core/json_helper.py +++ b/core/json_helper.py @@ -13,6 +13,13 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib')) DATETIME_FMT = os.environ.get('WGCTL_DATETIME_FMT', '%Y-%m-%d %H:%M') +_POLICY_DEFAULTS = { + 'tunnel_mode': 'split', + 'default_rule': None, + 'strict_rule': False, + 'auto_apply': True, +} + # ── Lazy imports (only load what's needed per command) ────────────────────── def _events(): @@ -1220,21 +1227,16 @@ def identity_exists(file): # ====================================================== def _policy_read(file): - """Read policies.json, fall back to hardcoded defaults if missing.""" try: if not os.path.exists(file): - return dict(_POLICY_DEFAULTS) + return {} with open(file) as f: content = f.read().strip() if not content: - return dict(_POLICY_DEFAULTS) - data = json.loads(content) - # Merge with defaults so hardcoded policies always exist - merged = dict(_POLICY_DEFAULTS) - merged.update(data) - return merged + return {} + return json.loads(content) except Exception: - return dict(_POLICY_DEFAULTS) + return {} def _policy_write(file, data): """Write policies.json.""" diff --git a/core/lib/__pycache__/activity.cpython-311.pyc b/core/lib/__pycache__/activity.cpython-311.pyc index 5ccd93cc943e07c7ec2fa7d166b635b9ff6d7b39..847be3c4df7505a05d73df7843d73494f2484f1e 100644 GIT binary patch delta 20 acmbPjHQS1NIWI340}$*87um=yAPWFCCIpQD delta 20 acmbPjHQS1NIWI340}vEt32)>UkOcrVsRUU7 diff --git a/modules/policy.module.sh b/modules/policy.module.sh index 4e71fce..5a80ea5 100644 --- a/modules/policy.module.sh +++ b/modules/policy.module.sh @@ -56,8 +56,6 @@ function policy::_hardcoded_field() { # Core Accessors # ====================================================== -function ctx::policies() { echo "${_CTX_DATA}/policies.json"; } - function policy::exists() { local name="${1:-}" json::policy_exists "$(ctx::policies)" "$name" 2>/dev/null diff --git a/modules/ui/activity.module.sh b/modules/ui/activity.module.sh new file mode 100644 index 0000000..f83d000 --- /dev/null +++ b/modules/ui/activity.module.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# ui/activity.module.sh — rendering for wgctl activity + +# ui::activity::peer_row +function ui::activity::peer_row() { + local name_pad="${1:-}" rx_pad="${2:-}" tx_pad="${3:-}" \ + drops="${4:-0}" drop_word="${5:-drops}" w_drops="${6:-1}" + + printf " \033[1m%s\033[0m \033[2m↓\033[0m%s \033[2m↑\033[0m%s %${w_drops}s %s\n" \ + "$name_pad" "$rx_pad" "$tx_pad" "$drops" "$drop_word" +} + +# ui::activity::service_row +function ui::activity::service_row() { + local dest_display="${1:-}" drop_count="${2:-0}" drop_word="${3:-drops}" \ + drops_col="${4:-30}" w_drops="${5:-1}" + + # Align drop count with peer drop column + # Service row visible prefix: " → " (6 visible) + ${#dest_display} + # But "→" is 3 bytes, 1 visible — arrow_prefix bytes = 8, visible = 6 + local arrow_prefix=" → " + local prefix_bytes=${#arrow_prefix} # 8 bytes due to → being 3 bytes + local prefix_len=$(( prefix_bytes + ${#dest_display} )) + local pad_n=$(( drops_col - prefix_len )) + [[ $pad_n -lt 1 ]] && pad_n=1 + + printf " \033[2m→\033[0m %s%*s %${w_drops}s %s\n" \ + "$dest_display" "$pad_n" "" "$drop_count" "$drop_word" +} + +# Table versions (kept for future display config) +function ui::activity::header_table() { + printf "\n %-24s %-14s %-14s %s\n" "PEER" "↓ RX" "↑ TX" "DROPS" + printf " %s\n" "$(printf '─%.0s' {1..65})" +} + +function ui::activity::peer_row_table() { + local name="${1:-}" rx_fmt="${2:-}" tx_fmt="${3:-}" \ + drops="${4:-0}" drop_word="${5:-drops}" + printf " %-24s %-14s %-14s %s %s\n" \ + "$name" "↓$rx_fmt" "↑$tx_fmt" "$drops" "$drop_word" +} + +function ui::activity::service_row_table() { + local dest_display="${1:-}" drop_count="${2:-0}" drop_word="${3:-drops}" + printf " → %-30s %s %s\n" "$dest_display" "$drop_count" "$drop_word" +} \ No newline at end of file diff --git a/modules/ui/group.module.sh b/modules/ui/group.module.sh index 202a53f..e1e5f4c 100644 --- a/modules/ui/group.module.sh +++ b/modules/ui/group.module.sh @@ -112,6 +112,34 @@ function ui::group::show_member_row_table() { "$name" "$ip" "${rule:--}" "${status_color}${status_str}\033[0m" } +# ui::group::show_peers +function ui::group::show_peers() { + local -n _peers_list="$1" + local w_name="${2:-16}" w_ip="${3:-13}" + + if [[ ${#_peers_list[@]} -eq 0 || -z "${_peers_list[0]:-}" ]]; then + printf " \033[2m—\033[0m\n" + return 0 + fi + + for peer_name in "${_peers_list[@]}"; do + [[ -z "$peer_name" ]] && continue + + if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then + printf " \033[2m%-${w_name}s (no longer exists)\033[0m\n" "$peer_name" + continue + fi + + local ip rule is_blocked + ip=$(peers::get_ip "$peer_name") + rule=$(peers::get_meta "$peer_name" "rule") + peers::is_blocked "$peer_name" 2>/dev/null && is_blocked="true" || is_blocked="false" + + ui::group::show_member_row "$peer_name" "$ip" "${rule:--}" \ + "$is_blocked" "$w_name" "$w_ip" + done +} + # ====================================================== # Helpers