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
This commit is contained in:
Nuno Duque Nunes 2026-05-25 18:45:23 +00:00
parent 86220850c1
commit a003e3b753
12 changed files with 145 additions and 94 deletions

View file

@ -104,7 +104,7 @@ function cmd::activity::run() {
return 0 return 0
fi fi
# Measure w_peer and w_drops from data # Measure column widths
local w_peer=16 w_drops=1 local w_peer=16 w_drops=1
while IFS='|' read -r type rest; do while IFS='|' read -r type rest; do
case "$type" in case "$type" in
@ -125,9 +125,9 @@ function cmd::activity::run() {
(( w_peer += 2 )) (( 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) # " " (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 # Visible: 2 + w_peer + 2+1 + 10 + 2+1 + 10 + 2 = w_peer + 30
local drops_col=$(( w_peer + 30 )) local drops_col=$(( w_peer + 30 ))
@ -155,8 +155,8 @@ function cmd::activity::run() {
first_peer=false first_peer=false
local rx_fmt tx_fmt local rx_fmt tx_fmt
rx_fmt=$(cmd::activity::_fmt_bytes "$rx") rx_fmt=$(fmt::bytes "$rx")
tx_fmt=$(cmd::activity::_fmt_bytes "$tx") tx_fmt=$(fmt::bytes "$tx")
local name_pad rx_pad tx_pad local name_pad rx_pad tx_pad
name_pad=$(printf "%-${w_peer}s" "$name") name_pad=$(printf "%-${w_peer}s" "$name")
@ -165,8 +165,9 @@ function cmd::activity::run() {
local drop_word="drops" local drop_word="drops"
[[ "$drops" -eq 1 ]] && drop_word="drop" [[ "$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) service)
@ -175,19 +176,11 @@ function cmd::activity::run() {
local peer dest_display drop_count local peer dest_display drop_count
IFS='|' read -r peer dest_display drop_count <<< "$rest" 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" local svc_drop_word="drops"
[[ "$drop_count" -eq 1 ]] && svc_drop_word="drop" [[ "$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 esac
done <<< "$data" done <<< "$data"
@ -195,21 +188,3 @@ function cmd::activity::run() {
echo "" 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
}

View file

@ -169,6 +169,7 @@ function cmd::group::show() {
desc=$(json::get "$group_file" "desc") desc=$(json::get "$group_file" "desc")
ui::row "Description" "${desc:-}" ui::row "Description" "${desc:-}"
# Load and filter peers
local peers_list=() local peers_list=()
mapfile -t peers_list < <(json::get "$group_file" "peers") || true mapfile -t peers_list < <(json::get "$group_file" "peers") || true
local filtered=() local filtered=()
@ -179,8 +180,7 @@ function cmd::group::show() {
local peer_count=${#peers_list[@]} local peer_count=${#peers_list[@]}
[[ -z "${peers_list[0]:-}" ]] && peer_count=0 [[ -z "${peers_list[0]:-}" ]] && peer_count=0
local peer_word="peers" # Count valid peers (data logic stays in command)
[[ "$peer_count" -eq 1 ]] && peer_word="peer"
local valid_count=0 local valid_count=0
for p in "${peers_list[@]}"; do for p in "${peers_list[@]}"; do
[[ -z "$p" ]] && continue [[ -z "$p" ]] && continue
@ -192,7 +192,7 @@ function cmd::group::show() {
printf "\n" printf "\n"
if [[ "$peer_count" -gt 0 ]]; then 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 local w_name=16 w_ip=13
for peer_name in "${peers_list[@]}"; do for peer_name in "${peers_list[@]}"; do
[[ -z "$peer_name" ]] && continue [[ -z "$peer_name" ]] && continue
@ -200,22 +200,8 @@ function cmd::group::show() {
done done
(( w_name += 2 )) (( w_name += 2 ))
for peer_name in "${peers_list[@]}"; do # Delegate rendering to ui::
[[ -z "$peer_name" ]] && continue ui::group::show_peers peers_list "$w_name" "$w_ip"
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
else else
printf " \033[2m—\033[0m\n" printf " \033[2m—\033[0m\n"
fi fi

View file

@ -100,7 +100,7 @@ function cmd::identity::run() {
function cmd::identity::_list() { function cmd::identity::_list() {
local data local data
data=$(identity::list_data) data=$(identity::list_data | ui::sort_rows 1)
if [[ -z "$data" ]]; then if [[ -z "$data" ]]; then
log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers." log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers."

View file

@ -99,7 +99,7 @@ function cmd::policy::run() {
function cmd::policy::_list() { function cmd::policy::_list() {
local data local data
data=$(policy::list_data) data=$(policy::list_data | ui::sort_rows 1)
if [[ -z "$data" ]]; then if [[ -z "$data" ]]; then
log::info "No policies defined." log::info "No policies defined."

View file

@ -85,7 +85,7 @@ function cmd::subnet::run() {
function cmd::subnet::_list() { function cmd::subnet::_list() {
local data local data
data=$(subnet::list_data) data=$(subnet::list_data | ui::sort_rows 1)
if [[ -z "$data" ]]; then if [[ -z "$data" ]]; then
log::info "No subnets defined." log::info "No subnets defined."

View file

@ -45,6 +45,7 @@ function ctx::meta() { echo "$_CTX_META"; }
function ctx::daemon() { echo "$_CTX_DAEMON"; } function ctx::daemon() { echo "$_CTX_DAEMON"; }
function ctx::net() { echo "$_CTX_NET"; } function ctx::net() { echo "$_CTX_NET"; }
function ctx::identities() { echo "${_CTX_IDENTITY}"; } function ctx::identities() { echo "${_CTX_IDENTITY}"; }
function ctx::policies() { echo "${_CTX_DATA}/policies.json"; }
function ctx::subnets() { echo "${_CTX_DATA}/subnets.json"; } function ctx::subnets() { echo "${_CTX_DATA}/subnets.json"; }
function ctx::hosts() { echo "${_CTX_DATA}/hosts.json"; } function ctx::hosts() { echo "${_CTX_DATA}/hosts.json"; }
function ctx::events_log() { echo "$(ctx::daemon)/events.log"; } function ctx::events_log() { echo "$(ctx::daemon)/events.log"; }

View file

@ -79,3 +79,17 @@ function fmt::set_date_format() {
esac 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
}

View file

@ -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') 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) ────────────────────── # ── Lazy imports (only load what's needed per command) ──────────────────────
def _events(): def _events():
@ -1220,21 +1227,16 @@ def identity_exists(file):
# ====================================================== # ======================================================
def _policy_read(file): def _policy_read(file):
"""Read policies.json, fall back to hardcoded defaults if missing."""
try: try:
if not os.path.exists(file): if not os.path.exists(file):
return dict(_POLICY_DEFAULTS) return {}
with open(file) as f: with open(file) as f:
content = f.read().strip() content = f.read().strip()
if not content: if not content:
return dict(_POLICY_DEFAULTS) return {}
data = json.loads(content) return json.loads(content)
# Merge with defaults so hardcoded policies always exist
merged = dict(_POLICY_DEFAULTS)
merged.update(data)
return merged
except Exception: except Exception:
return dict(_POLICY_DEFAULTS) return {}
def _policy_write(file, data): def _policy_write(file, data):
"""Write policies.json.""" """Write policies.json."""

View file

@ -56,8 +56,6 @@ function policy::_hardcoded_field() {
# Core Accessors # Core Accessors
# ====================================================== # ======================================================
function ctx::policies() { echo "${_CTX_DATA}/policies.json"; }
function policy::exists() { function policy::exists() {
local name="${1:-}" local name="${1:-}"
json::policy_exists "$(ctx::policies)" "$name" 2>/dev/null json::policy_exists "$(ctx::policies)" "$name" 2>/dev/null

View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
# ui/activity.module.sh — rendering for wgctl activity
# ui::activity::peer_row <name_pad> <rx_pad> <tx_pad> <drops> <drop_word> <w_drops>
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 <dest_display> <drop_count> <drop_word> <drops_col> <w_drops>
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"
}

View file

@ -112,6 +112,34 @@ function ui::group::show_member_row_table() {
"$name" "$ip" "${rule:--}" "${status_color}${status_str}\033[0m" "$name" "$ip" "${rule:--}" "${status_color}${status_str}\033[0m"
} }
# ui::group::show_peers <peers_list_nameref> <w_name> <w_ip>
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 # Helpers