Compare commits

..

No commits in common. "92993e64232a9d07594d639649e833ccecdbc70c" and "e54ce9c4177458905736fd9572947bb7532940db" have entirely different histories.

4 changed files with 131 additions and 325 deletions

View file

@ -107,38 +107,47 @@ function cmd::group::run() {
function cmd::group::list() { function cmd::group::list() {
local groups_dir local groups_dir
groups_dir="$(ctx::groups)" groups_dir="$(ctx::groups)"
local groups=("${groups_dir}"/*.group) local groups=("${groups_dir}"/*.group)
if [[ ! -f "${groups[0]}" ]]; then if [[ ! -f "${groups[0]}" ]]; then
log::wg "No groups configured" log::wg "No groups configured"
return 0 return 0
fi fi
local data
data=$(json::group_list_data "$groups_dir" "$(ctx::blocks)")
[[ -z "$data" ]] && log::wg "No groups configured" && return 0
# Measure column widths
local w_name=12 w_desc=16
while IFS="|" read -r name desc total blocked; do
[[ -z "$name" ]] && continue
(( ${#name} > w_name )) && w_name=${#name}
local desc_len=${#desc}
[[ -z "$desc" ]] && desc_len=1
(( desc_len > w_desc )) && w_desc=$desc_len
done <<< "$data"
(( w_name += 2 ))
(( w_desc += 2 ))
log::section "Groups" log::section "Groups"
echo "" printf "\n %-20s %-35s %-8s %s\n" "NAME" "DESCRIPTION" "PEERS" "STATUS"
printf " %s\n" "$(printf '─%.0s' {1..75})"
while IFS="|" read -r name desc total blocked; do while IFS="|" read -r name desc total blocked; do
[[ -z "$name" ]] && continue [[ -z "$name" ]] && continue
ui::group::list_row "$name" "$desc" "$total" "$blocked" "$w_name" "$w_desc"
done <<< "$data" local status_color="" status_str="active"
if [[ "$total" -gt 0 ]]; then
echo "" if [[ "$blocked" -eq "$total" ]]; then
status_color="\033[1;31m"
status_str="blocked"
elif [[ "$blocked" -gt 0 ]]; then
status_color="\033[1;33m"
status_str="blocked (${blocked}/${total})"
else
status_color="\033[1;32m"
status_str="active"
fi
fi
local short_desc="${desc:0:33}"
[[ ${#desc} -gt 33 ]] && short_desc="${short_desc}..."
local desc_col_width=35
[[ "$desc" == "—" || -z "$desc" ]] && desc_col_width=37
printf " %-20s %-${desc_col_width}s %-8s %b\n" \
"$name" "${short_desc:-}" "$total" \
"${status_color}${status_str}\033[0m"
done < <(json::group_list_data "$groups_dir" "$(ctx::blocks)")
printf "\n"
} }
# ============================================ # ============================================
@ -147,7 +156,7 @@ function cmd::group::list() {
function cmd::group::show() { function cmd::group::show() {
local name="" local name=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;; --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
@ -155,72 +164,70 @@ function cmd::group::show() {
*) log::error "Unknown flag: $1"; return 1 ;; *) log::error "Unknown flag: $1"; return 1 ;;
esac esac
done done
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
group::require_exists "$name" || return 1 group::require_exists "$name" || return 1
local group_file local group_file
group_file="$(group::path "$name")" group_file="$(group::path "$name")"
log::section "Group: ${name}" log::section "Group: ${name}"
printf "\n"
local desc local desc
desc=$(json::get "$group_file" "desc") desc=$(json::get "$group_file" "desc")
ui::row "Description" "${desc:-}" printf "\n %-20s %s\n" "Description:" "${desc:-}"
# Load 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")
# Filter empty entries
local filtered=() local filtered=()
for p in "${peers_list[@]:-}"; do for p in "${peers_list[@]:-}"; do
[[ -n "$p" ]] && filtered+=("$p") [[ -n "$p" ]] && filtered+=("$p")
done done
peers_list=("${filtered[@]:-}") peers_list=("${filtered[@]:-}")
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"
[[ "$peer_count" -eq 1 ]] && peer_word="peer" printf " %-20s %s\n" "Peers:" "$peer_count"
local valid_count=0 printf " %s\n" "$(printf '─%.0s' {1..50})"
for p in "${peers_list[@]}"; do
[[ -z "$p" ]] && continue
peers::require_exists "$p" > /dev/null 2>&1 && (( valid_count++ )) || true
done
local peer_word="peers"
[[ "$valid_count" -eq 1 ]] && peer_word="peer"
ui::row "Peers" "${valid_count} ${peer_word}"
printf "\n"
if [[ "$peer_count" -gt 0 ]]; then if [[ "$peer_count" -gt 0 ]]; then
# Measure name and IP widths printf "\n %-28s %-15s %-12s %s\n" "NAME" "IP" "RULE" "STATUS"
local w_name=16 w_ip=13 printf " %s\n" "$(printf '─%.0s' {1..65})"
for peer_name in "${peers_list[@]}"; do for peer_name in "${peers_list[@]}"; do
[[ -z "$peer_name" ]] && continue [[ -z "$peer_name" ]] && continue
(( ${#peer_name} > w_name )) && w_name=${#peer_name}
done # Skip if peer no longer exists
(( 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 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" log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
continue continue
fi fi
local ip rule is_blocked local ip rule status_str status_color
ip=$(peers::get_ip "$peer_name") ip=$(peers::get_ip "$peer_name")
rule=$(peers::get_meta "$peer_name" "rule") rule=$(peers::get_meta "$peer_name" "rule")
peers::is_blocked "$peer_name" 2>/dev/null && is_blocked="true" || is_blocked="false" rule="${rule:-}"
ui::group::show_member_row "$peer_name" "$ip" "${rule:--}" \ if peers::is_blocked "$peer_name" 2>/dev/null; then
"$is_blocked" "$w_name" "$w_ip" status_color="\033[1;31m"
status_str="blocked"
else
status_color="\033[1;32m"
status_str="active"
fi
printf " %-28s %-15s %-12s %b\n" \
"$peer_name" "0" "$rule" \
"${status_str}\033[0m"
done done
else else
printf " \033[2m—\033[0m\n" printf " \n"
fi fi
printf "\n" printf "\n"
return 0
} }
# ============================================ # ============================================

View file

@ -77,7 +77,7 @@ function cmd::net::run() {
function cmd::net::list() { function cmd::net::list() {
local detailed=false filter_tag="" local detailed=false filter_tag=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--detailed) detailed=true; shift ;; --detailed) detailed=true; shift ;;
@ -86,72 +86,62 @@ function cmd::net::list() {
*) log::error "Unknown flag: $1"; return 1 ;; *) log::error "Unknown flag: $1"; return 1 ;;
esac esac
done done
local net_file local net_file
net_file="$(ctx::net)" net_file="$(ctx::net)"
if [[ ! -f "$net_file" ]]; then if [[ ! -f "$net_file" ]]; then
log::wg_warning "No services configured. Use 'wgctl net add' to add one." log::wg_warning "No services configured. Use 'wgctl net add' to add one."
return 0 return 0
fi fi
# Collect filtered data and build ports display per service log::section "Network Services"
local filtered_data="" printf "\n %-20s %-16s %-6s %s\n" "NAME" "IP" "PORTS" "DESCRIPTION"
while IFS="|" read -r name ip desc tags port_count; do local divider
divider=$(printf '─%.0s' {1..72})
printf " %s\n" "$divider"
local found=false
while IFS="|" read -r name ip desc tags ports; do
[[ -z "$name" ]] && continue [[ -z "$name" ]] && continue
[[ -n "$filter_tag" && "$tags" != *"$filter_tag"* ]] && continue
# Tag filter
# Build ports display from json::net_show if [[ -n "$filter_tag" ]]; then
local ports_display="" [[ "$tags" != *"$filter_tag"* ]] && continue
while IFS="|" read -r ptype pname pport pproto pdesc; do fi
[[ "$ptype" != "port" ]] && continue
local port_str=":${pport}" found=true
[[ -n "$pproto" && "$pproto" != "tcp" ]] && port_str="${port_str}/${pproto}" local tag_display=""
ports_display+="${port_str}, " [[ -n "$tags" ]] && tag_display=" \033[0;37m[${tags}]\033[0m"
done < <(json::net_show "$net_file" "$name")
ports_display="${ports_display%, }" printf " %-20s %-16s %-6s %s%b\n" \
[[ -z "$ports_display" ]] && ports_display="-" "$name" "$ip" "${ports}p" "${desc:-}" "$tag_display"
filtered_data+="${name}|${ip}|${desc}|${tags}|${ports_display}"$'\n' if $detailed; then
local has_ports=false
# Show ports inline
while IFS="|" read -r ptype pname pport pproto pdesc; do
[[ "$ptype" != "port" ]] && continue
has_ports=true
local ann
ann=$(net::annotation "$ip" "$pport" "$pproto")
printf " \033[0;37m%-18s %s:%s%s\033[0m\n" \
"${pname}" "$pport" "$pproto" \
"${pdesc:+ # $pdesc}"
done < <(json::net_show "$net_file" "$name")
$has_ports && printf "\n" # newline after each service with ports
fi
done < <(json::net_list "$net_file") done < <(json::net_list "$net_file")
[[ -z "$filtered_data" ]] && { if ! $found; then
[[ -n "$filter_tag" ]] && \ [[ -n "$filter_tag" ]] && \
log::wg_warning "No services with tag: ${filter_tag}" || \ log::wg_warning "No services with tag: ${filter_tag}" || \
log::wg_warning "No services configured" log::wg_warning "No services configured"
return 0 fi
}
printf "\n"
# Measure column widths return 0
local w_name=12 w_ip=13 w_ports=16
while IFS="|" read -r name ip desc tags ports; do
[[ -z "$name" ]] && continue
(( ${#name} > w_name )) && w_name=${#name}
(( ${#ip} > w_ip )) && w_ip=${#ip}
(( ${#ports} > w_ports )) && w_ports=${#ports}
done <<< "$filtered_data"
(( w_name += 2 ))
(( w_ip += 2 ))
(( w_ports += 2 ))
log::section "Network Services"
echo ""
while IFS="|" read -r name ip desc tags ports; do
[[ -z "$name" ]] && continue
ui::net::list_row "$name" "$ip" "$desc" "$tags" "$ports" \
"$w_name" "$w_ip" "$w_ports"
if $detailed; then
while IFS="|" read -r ptype pname pport pproto pdesc; do
[[ "$ptype" != "port" ]] && continue
ui::net::show_port_row "$pname" "$pport" "$pproto" "$pdesc"
done < <(json::net_show "$net_file" "$name")
echo ""
fi
done <<< "$filtered_data"
echo ""
} }
# ============================================ # ============================================
@ -168,31 +158,33 @@ function cmd::net::show() {
*) log::error "Unknown flag: $1"; return 1 ;; *) log::error "Unknown flag: $1"; return 1 ;;
esac esac
done done
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
net::require_exists "$name" || return 1 net::require_exists "$name" || return 1
log::section "Service: ${name}" log::section "Service: ${name}"
printf "\n" printf "\n"
local has_ports=false
while IFS="|" read -r key val1 val2 val3 val4; do while IFS="|" read -r key val1 val2 val3 val4; do
case "$key" in case "$key" in
name) ui::row "Name" "$val1" ;; name) ui::row "Name" "$val1" ;;
ip) ui::row "IP" "$val1" ;;
desc) ui::row "Description" "${val1:-}" ;; desc) ui::row "Description" "${val1:-}" ;;
tags) ui::row "Tags" "${val1:-}" ;; tags) ui::row "Tags" "${val1:-}" ;;
ip) ui::row "IP" "$val1" ;;
port) port)
if ! $has_ports; then # val1=port_name val2=port val3=proto val4=desc
printf " %-20s\n" "Ports:" local ann
has_ports=true ann=$(net::annotation "$(json::net_resolve "$(ctx::net)" "$name")" \
fi "$val2" "$val3" 2>/dev/null || true)
ui::net::show_port_row "$val1" "$val2" "$val3" "$val4" printf " %-20s \033[0;36m%s\033[0m %s:%s%s\n" \
"${val1}:" "" "$val2" "$val3" \
"${val4:+ # $val4}"
;; ;;
esac esac
done < <(json::net_show "$(ctx::net)" "$name") done < <(json::net_show "$(ctx::net)" "$name")
printf "\n" printf "\n"
return 0
} }
# ============================================ # ============================================

View file

@ -1,134 +0,0 @@
#!/usr/bin/env bash
# ui/group.module.sh — rendering for groups
# ======================================================
# List rendering
# ======================================================
# ui::group::list_row <name> <desc> <total> <blocked> <w_name> <w_desc>
function ui::group::list_row() {
local name="${1:-}" desc="${2:-}" total="${3:-0}" blocked="${4:-0}" \
w_name="${5:-16}" w_desc="${6:-30}"
local name_pad desc_val desc_pad_n
name_pad=$(printf "%-${w_name}s" "$name")
desc_val="${desc:--}"
desc_pad_n=$(( w_desc - ${#desc_val} ))
[[ $desc_pad_n -lt 0 ]] && desc_pad_n=0
# Peer count — dim if zero
local peers_word="peers"
[[ "$total" -eq 1 ]] && peers_word="peer"
local peers_display
if [[ "$total" -eq 0 ]]; then
peers_display="\033[2m0 ${peers_word}\033[0m"
else
peers_display="${total} ${peers_word}"
fi
local peers_pad
peers_pad=$(printf "%-10s" "${total} ${peers_word}")
# Status
local status_color status_str
IFS='|' read -r status_color status_str <<< "$(ui::group::status "$total" "$blocked")"
local peers_str="${total} ${peers_word}"
local peers_pad_n=$(( 10 - ${#peers_str} ))
[[ $peers_pad_n -lt 0 ]] && peers_pad_n=0
if [[ "$total" -eq 0 ]]; then
printf " \033[2m%s %s%*s %s%*s %s\033[0m\n" \
"$name_pad" "$desc_val" "$desc_pad_n" "" \
"$peers_str" "$peers_pad_n" "" "inactive"
else
printf " %s %s%*s %s%*s %b%s\033[0m\n" \
"$name_pad" "$desc_val" "$desc_pad_n" "" \
"$peers_str" "$peers_pad_n" "" \
"$status_color" "$status_str"
fi
}
# Table version (kept for future display config)
function ui::group::list_header_table() {
printf "\n %-20s %-35s %-8s %s\n" "NAME" "DESCRIPTION" "PEERS" "STATUS"
printf " %s\n" "$(printf '─%.0s' {1..75})"
}
function ui::group::list_row_table() {
local name="${1:-}" desc="${2:-}" total="${3:-0}" blocked="${4:-0}"
local status_color="" status_str="active"
if [[ "$total" -gt 0 ]]; then
if [[ "$blocked" -eq "$total" ]]; then
status_color="\033[1;31m"; status_str="blocked"
elif [[ "$blocked" -gt 0 ]]; then
status_color="\033[1;33m"; status_str="blocked (${blocked}/${total})"
else
status_color="\033[1;32m"; status_str="active"
fi
fi
printf " %-20s %-35s %-8s %b\n" \
"$name" "${desc:-}" "$total" \
"${status_color}${status_str}\033[0m"
}
# ======================================================
# Show rendering
# ======================================================
# ui::group::show_member_row <name> <ip> <rule> <is_blocked> <w_name> <w_ip>
function ui::group::show_member_row() {
local name="${1:-}" ip="${2:-}" rule="${3:--}" is_blocked="${4:-false}" \
w_name="${5:-22}" w_ip="${6:-14}"
local name_pad ip_pad rule_pad
name_pad=$(printf "%-${w_name}s" "$name")
ip_pad=$(printf "%-${w_ip}s" "$ip")
rule_pad=$(printf "%-12s" "${rule:--}")
local status_color status_str
if [[ "$is_blocked" == "true" ]]; then
status_color="\033[1;31m"; status_str="blocked"
else
status_color="\033[1;32m"; status_str="active"
fi
printf " %s %s \033[2mrule:\033[0m %s %b%s\033[0m\n" \
"$name_pad" "$ip_pad" "$rule_pad" \
"$status_color" "$status_str"
}
# Table version
function ui::group::show_header_table() {
printf "\n %-28s %-15s %-12s %s\n" "NAME" "IP" "RULE" "STATUS"
printf " %s\n" "$(printf '─%.0s' {1..65})"
}
function ui::group::show_member_row_table() {
local name="${1:-}" ip="${2:-}" rule="${3:--}" is_blocked="${4:-false}"
local status_color status_str
[[ "$is_blocked" == "true" ]] && { status_color="\033[1;31m"; status_str="blocked"; } \
|| { status_color="\033[1;32m"; status_str="active"; }
printf " %-28s %-15s %-12s %b\n" \
"$name" "$ip" "${rule:--}" "${status_color}${status_str}\033[0m"
}
# ======================================================
# Helpers
# ======================================================
# ui::group::status_color <total> <blocked>
# Returns color code and status string for a group
# Usage: IFS='|' read -r color str <<< "$(ui::group::status "$total" "$blocked")"
function ui::group::status() {
local total="${1:-0}" blocked="${2:-0}"
if [[ "$total" -eq 0 ]]; then
echo "\033[2;37m|inactive"
elif [[ "$blocked" -eq "$total" ]]; then
echo "\033[1;31m|blocked"
elif [[ "$blocked" -gt 0 ]]; then
echo "\033[1;33m|partial (${blocked}/${total})"
else
echo "\033[1;32m|active"
fi
}

View file

@ -1,59 +0,0 @@
#!/usr/bin/env bash
# ui/net.module.sh — rendering for network services
# ======================================================
# List rendering
# ======================================================
# ui::net::list_row <name> <ip> <desc> <tags> <ports_display> <w_name> <w_ip> <w_ports>
function ui::net::list_row() {
local name="${1:-}" ip="${2:-}" desc="${3:-}" tags="${4:-}" ports="${5:-}" \
w_name="${6:-16}" w_ip="${7:-14}" w_ports="${8:-20}"
local name_pad ip_pad
name_pad=$(printf "%-${w_name}s" "$name")
ip_pad=$(printf "%-${w_ip}s" "$ip")
local ports_pad_n=$(( w_ports - ${#ports} ))
[[ $ports_pad_n -lt 0 ]] && ports_pad_n=0
local tags_display=""
[[ -n "$tags" ]] && tags_display=" \033[2m[${tags}]\033[0m"
printf " %s %s %s%*s %s%b\n" \
"$name_pad" "$ip_pad" "$ports" "$ports_pad_n" "" \
"${desc:--}" "$tags_display"
}
# Table version (kept for future display config)
function ui::net::list_header_table() {
printf "\n %-20s %-16s %-6s %s\n" "NAME" "IP" "PORTS" "DESCRIPTION"
printf " %s\n" "$(printf '─%.0s' {1..72})"
}
function ui::net::list_row_table() {
local name="${1:-}" ip="${2:-}" desc="${3:-}" tags="${4:-}" port_count="${5:-}"
local tag_display=""
[[ -n "$tags" ]] && tag_display=" \033[0;37m[${tags}]\033[0m"
printf " %-20s %-16s %-6s %s%b\n" \
"$name" "$ip" "${port_count}p" "${desc:-}" "$tag_display"
}
# ======================================================
# Show rendering
# ======================================================
# ui::net::show_port_row <port_name> <port> <proto> <desc>
function ui::net::show_port_row() {
local port_name="${1:-}" port="${2:-}" proto="${3:-}" desc="${4:-}"
local port_display=":${port}/${proto}"
local port_pad
port_pad=$(printf "%-14s" "$port_display")
if [[ -n "$desc" ]]; then
printf " %s \033[2m→\033[0m %-16s \033[2m# %s\033[0m\n" \
"$port_pad" "$port_name" "$desc"
else
printf " %s \033[2m→\033[0m %s\n" \
"$port_pad" "$port_name"
fi
}