diff --git a/commands/list.command.sh b/commands/list.command.sh index e4779f4..cfee902 100644 --- a/commands/list.command.sh +++ b/commands/list.command.sh @@ -226,8 +226,11 @@ function cmd::list::run() { case "$style" in table) cmd::list::_render_table ;; - compact) cmd::list::_render_compact "$collected_rows" ;; - *) cmd::list::_render_compact "$collected_rows" ;; + compact) display::render "peer_list" "$collected_rows" \ + "cmd::list::_render_compact" "cmd::list::_render_table" ;; + + *) display::render "peer_list" "$collected_rows" \ + "cmd::list::_render_compact" "cmd::list::_render_table" ;; esac } @@ -346,19 +349,59 @@ function cmd::list::_render_compact() { # ============================================ function cmd::list::_render_table() { - declare -A rule_counts=() group_counts=() - _list_header_printed=false + local rows="${1:-}" + [[ -z "$rows" ]] && log::wg_warning "No results found" && return 0 - cmd::list::_iter_confs_table + # Measure column widths from data (same as compact) + local w_name=16 w_ip=13 w_type=8 w_rule=10 w_group=10 w_status=10 w_last=20 + while IFS='|' read -r name ip type rule group status last_seen is_blocked is_restricted; do + [[ -z "$name" ]] && continue + (( ${#name} > w_name )) && w_name=${#name} + (( ${#ip} > w_ip )) && w_ip=${#ip} + (( ${#type} > w_type )) && w_type=${#type} + (( ${#rule} > w_rule )) && w_rule=${#rule} + (( ${#group} > w_group )) && w_group=${#group} + (( ${#last_seen} > w_last )) && w_last=${#last_seen} + local cs +cs=$(printf "%s" "$status" | sed 's/\x1b\[[0-9;]*m//g') +(( ${#cs} > w_status )) && w_status=${#cs} + echo "DEBUG cs='$cs' name='$name'" >&2 + done <<< "$rows" + (( w_name += 2 )); (( w_ip += 2 )) + (( w_type += 2 )); (( w_rule += 2 )) + (( w_group += 2 )); (( w_last += 2 )) - if [[ "$_list_header_printed" == "true" ]]; then - cmd::list::_render_footer $has_groups - local group_summary="" - cmd::list::_build_group_summary - printf "\n Showing peers\n\n" - else - log::wg_warning "No results found" - fi + # Header + printf "\n %-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %-${w_status}s %s\n" \ + "NAME" "IP" "TYPE" "RULE" "GROUP" "STATUS" "LAST SEEN" + printf " %s\n" "$(printf '─%.0s' {1..115})" + + # Rows + while IFS='|' read -r name ip type rule group status last_seen is_blocked is_restricted; do + [[ -z "$name" ]] && continue + local clean_status + clean_status=$(echo "$status" | sed 's/\x1b\[[0-9;]*m//g') + local status_pad_n=$(( w_status - ${#clean_status} )) + [[ $status_pad_n -lt 0 ]] && status_pad_n=0 + + local row_color status_color + row_color=$(ui::peer::_row_color "$is_blocked" "$is_restricted" "$clean_status") + status_color=$(ui::peer::status_color "$is_blocked" "$is_restricted" "$clean_status") + + local status_colored="${status_color}${clean_status}\033[0m" + + if [[ -n "$row_color" ]]; then + printf " %b%-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %-${w_status}s %s\033[0m\n" \ + "$row_color" "$name" "$ip" "$type" "$rule" "$group" "$clean_status" "$last_seen" + else + printf " %-${w_name}s %-${w_ip}s %-${w_type}s %-${w_rule}s %-${w_group}s %b%*s\033[0m %s\n" \ + "$name" "$ip" "$type" "$rule" "$group" \ + "$status_color${clean_status}" "$status_pad_n" "" "$last_seen" + fi + done <<< "$rows" + + printf " %s\n" "$(printf '─%.0s' {1..115})" + cmd::list::_render_summary_from_rows "$rows" } function cmd::list::_iter_confs_table() { diff --git a/core/context.sh b/core/context.sh index f14ea7e..811a9d2 100644 --- a/core/context.sh +++ b/core/context.sh @@ -74,6 +74,7 @@ function ctx::policies() { echo "$_CTX_POLICIES"; } # Config files function ctx::config_file() { echo "$_CTX_CONFIG_FILE"; } +function ctx::display() { echo "${_CTX_CONFIG}/display.json"; } # Daemon files function ctx::events_log() { echo "${_CTX_DAEMON}/events.log"; } diff --git a/core/json_helper.py b/core/json_helper.py index c5a5a21..972f1f5 100644 --- a/core/json_helper.py +++ b/core/json_helper.py @@ -1611,6 +1611,22 @@ def config_load(file): print(f"Error: {e}", file=sys.stderr) sys.exit(1) +def display_load(file): + """ + Load display.json and output view=style pairs. + Output: view_name=style per line + """ + try: + with open(file) as f: + d = json.load(f) + views = d.get('views', {}) + for view_name, view_config in views.items(): + style = view_config.get('style', 'compact') + print(f"{view_name}={style}") + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + # ====================================================== def _net_read(file): @@ -1981,6 +1997,7 @@ commands = { 'batch_resolve': lambda args: batch_resolve(args[0], args[1], *args[2:]), 'peer_history_lookup': lambda args: peer_history_lookup(args[0], args[1]), 'config_load': lambda args: config_load(args[0]), + 'display_load': lambda args: display_load(args[0]), } # ── Main ───────────────────────────────────────────────────────────────────── diff --git a/core/lib/__pycache__/events.cpython-311.pyc b/core/lib/__pycache__/events.cpython-311.pyc index 98fb0ac..42921bd 100644 Binary files a/core/lib/__pycache__/events.cpython-311.pyc and b/core/lib/__pycache__/events.cpython-311.pyc differ diff --git a/daemon/endpoint_cache.json b/daemon/endpoint_cache.json index d1fae4b..4e01a44 100644 --- a/daemon/endpoint_cache.json +++ b/daemon/endpoint_cache.json @@ -1,13 +1,13 @@ { "phone-fred": "176.223.61.130", "phone-helena": "148.69.46.73", - "phone-nuno": "148.69.50.62", + "phone-nuno": "148.69.48.20", "tablet-nuno": "148.69.202.5", "guest-zephyr": "86.120.152.74", "guest-zephyr-test": "94.63.0.129", "desktop-roboclean": "46.189.215.231", "laptop-nuno": "94.63.0.129", "phone-luis": "176.223.61.15", - "phone-helena-2": "148.69.203.225", + "phone-helena-2": "148.69.202.234", "desktop-zephyr": "86.120.152.74" } \ No newline at end of file diff --git a/modules/display.module.sh b/modules/display.module.sh new file mode 100644 index 0000000..7702a4e --- /dev/null +++ b/modules/display.module.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# modules/display.module.sh +# Display configuration — controls layout style per view + +# ============================================ +# State — loaded once on first access +# ============================================ + +_DISPLAY_LOADED=false +declare -gA _DISPLAY_STYLES=() + +# ============================================ +# Load display config +# ============================================ + +function display::_load() { + $_DISPLAY_LOADED && return 0 + _DISPLAY_LOADED=true + + local display_file + display_file="$(ctx::display)" + [[ ! -f "$display_file" ]] && return 0 + + # Load styles per view via json_helper + local view style + while IFS='=' read -r view style; do + [[ -n "$view" && -n "$style" ]] && _DISPLAY_STYLES["$view"]="$style" + done < <(python3 "$(ctx::json_helper)" display_load "$display_file" 2>/dev/null) +} + +# ============================================ +# Accessors +# ============================================ + +# display::style +# Returns: compact | table | minimal (default: compact) +function display::style() { + local view="${1:-}" + display::_load + echo "${_DISPLAY_STYLES[$view]:-compact}" +} + +# display::is_compact +function display::is_compact() { + [[ "$(display::style "$1")" == "compact" ]] +} + +# display::is_table +function display::is_table() { + [[ "$(display::style "$1")" == "table" ]] +} + +# ============================================ +# display::render [extra_args...] +# Generic dispatcher — calls compact or table render function +# ============================================ + +function display::render() { + local view="${1:-}" data="${2:-}" compact_fn="${3:-}" table_fn="${4:-}" + shift 4 || true + + case "$(display::style "$view")" in + table) + declare -f "$table_fn" >/dev/null 2>&1 && \ + "$table_fn" "$data" "$@" || \ + "$compact_fn" "$data" "$@" # fallback to compact if no table fn + ;; + *) + "$compact_fn" "$data" "$@" + ;; + esac +} \ No newline at end of file diff --git a/wgctl b/wgctl index dbfdc82..919f79d 100755 --- a/wgctl +++ b/wgctl @@ -11,6 +11,7 @@ LOG_LEVEL=DEBUG load_module ip load_module ui +load_module display load_module config load_module keys load_module peers