refactor: test suite improvements, peers::get_type, dead code removal, add::run helpers, ui::col_width attempt
This commit is contained in:
parent
312f1f973c
commit
8ca3669c6c
6 changed files with 75 additions and 652 deletions
|
|
@ -147,100 +147,71 @@ function cmd::add::run() {
|
|||
esac
|
||||
done
|
||||
|
||||
# --guest shorthand
|
||||
if $guest; then
|
||||
type="guest"
|
||||
fi
|
||||
$guest && type="guest"
|
||||
|
||||
# Resolve guest subtype
|
||||
local effective_type="$type"
|
||||
if [[ "$type" == "guest" && -n "$subtype" ]]; then
|
||||
# Validate subtype
|
||||
local valid_subtypes="desktop laptop phone tablet"
|
||||
if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
|
||||
log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
|
||||
return 1
|
||||
fi
|
||||
effective_type="guest-${subtype}"
|
||||
fi
|
||||
local effective_type
|
||||
effective_type=$(cmd::add::_resolve_type "$type" "$subtype") || return 1
|
||||
|
||||
# Build full client name
|
||||
local full_name="${type}-${name}"
|
||||
|
||||
# Validate
|
||||
cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
|
||||
|
||||
# Resolve tunnel mode — flag > device default
|
||||
if [[ -z "$tunnel" ]]; then
|
||||
tunnel=$(config::default_tunnel_for "$type")
|
||||
fi
|
||||
[[ -z "$tunnel" ]] && tunnel=$(config::default_tunnel_for "$type")
|
||||
[[ -z "$rule" ]] && rule=$(cmd::add::_default_rule "$type")
|
||||
|
||||
# Determine rule — explicit flag > type default
|
||||
if [[ -z "$rule" ]]; then
|
||||
if config::is_guest_type "$type"; then
|
||||
rule="guest"
|
||||
else
|
||||
rule="user"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate rule if specified
|
||||
if ! rule::exists "$rule"; then
|
||||
log::error "Rule not found: ${rule}"
|
||||
return 1
|
||||
fi
|
||||
rule::exists "$rule" || { log::error "Rule not found: ${rule}"; return 1; }
|
||||
|
||||
local allowed_ips
|
||||
allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1
|
||||
|
||||
log::section "Adding client: ${full_name}"
|
||||
|
||||
# Auto-assign IP if not provided
|
||||
if [[ -z "$ip" ]]; then
|
||||
ip=$(ip::next_for_type "$effective_type") || return 1
|
||||
fi
|
||||
[[ -z "$ip" ]] && ip=$(ip::next_for_type "$effective_type") || return 1
|
||||
|
||||
log::wg_add "Name: ${full_name}"
|
||||
log::wg_add "Type: ${type}"
|
||||
log::wg_add "IP: ${ip}"
|
||||
log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
|
||||
log::wg_add "Rule: ${rule:-none}"
|
||||
|
||||
# Generate keys
|
||||
keys::generate_pair "$full_name" || return 1
|
||||
|
||||
# Create client config
|
||||
peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1
|
||||
[[ -n "$subtype" ]] && peers::set_meta "$full_name" "subtype" "$subtype"
|
||||
|
||||
# Store subtype in meta
|
||||
if [[ -n "$subtype" ]]; then
|
||||
peers::set_meta "$full_name" "subtype" "$subtype"
|
||||
fi
|
||||
|
||||
# Add peer to server config
|
||||
local public_key
|
||||
public_key=$(keys::public "$full_name") || return 1
|
||||
peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
|
||||
|
||||
log::wg_add "Rule: ${rule:-none}"
|
||||
|
||||
# Apply presets
|
||||
for preset in "${presets[@]}"; do
|
||||
fw::apply_preset "$preset" "${ip}" || return 1
|
||||
fw::apply_preset "$preset" "$ip" || return 1
|
||||
done
|
||||
|
||||
# Apply rule
|
||||
if [[ -n "$rule" ]]; then
|
||||
rule::apply "$rule" "$ip" || return 1
|
||||
fi
|
||||
|
||||
# Reload WireGuard
|
||||
[[ -n "$rule" ]] && rule::apply "$rule" "$ip" || return 1
|
||||
peers::reload || return 1
|
||||
|
||||
log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
|
||||
cmd::add::_show_result "$full_name" "${subtype:-$type}"
|
||||
}
|
||||
|
||||
# Show QR for mobile by default, config for desktop/laptop
|
||||
local display_type="${subtype:-$type}"
|
||||
function cmd::add::_resolve_type() {
|
||||
local type="$1" subtype="$2"
|
||||
if [[ "$type" == "guest" && -n "$subtype" ]]; then
|
||||
local valid_subtypes="desktop laptop phone tablet"
|
||||
if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
|
||||
log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
|
||||
return 1
|
||||
fi
|
||||
echo "guest-${subtype}"
|
||||
else
|
||||
echo "$type"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmd::add::_default_rule() {
|
||||
local type="$1"
|
||||
config::is_guest_type "$type" && echo "guest" || echo "user"
|
||||
}
|
||||
|
||||
function cmd::add::_show_result() {
|
||||
local full_name="$1" display_type="$2"
|
||||
if cmd::add::is_mobile "$display_type"; then
|
||||
keys::qr "$full_name"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -127,9 +127,13 @@ function cmd::group::list() {
|
|||
local short_desc="${desc:0:33}"
|
||||
[[ ${#desc} -gt 33 ]] && short_desc="${short_desc}..."
|
||||
|
||||
printf " %-20s %-35s %-8s %b\n" \
|
||||
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"
|
||||
|
|
@ -521,7 +525,11 @@ function cmd::group::block() {
|
|||
|
||||
local peers_list=()
|
||||
mapfile -t peers_list < <(group::peers "$name")
|
||||
[[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0
|
||||
|
||||
if [[ ${#peers_list[@]} -eq 0 ]] || [[ -z "${peers_list[0]:-}" ]]; then
|
||||
log::wg_warning "Group '${name}' has no peers"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# log::section "Blocking group: ${name}"
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ function cmd::list::_format_status() {
|
|||
echo -e "${color}${conn_str}${modifier}\033[0m"
|
||||
}
|
||||
|
||||
function cmd::list::_get_type() {
|
||||
function peers::get_type() {
|
||||
local ip="$1"
|
||||
local type="unknown"
|
||||
for t in $(config::device_types); do
|
||||
|
|
@ -277,7 +277,7 @@ function cmd::list::show_client() {
|
|||
public_key=$(keys::public "$name" 2>/dev/null || echo "unknown")
|
||||
|
||||
local type
|
||||
type=$(cmd::list::_get_type "$ip")
|
||||
type=$(peers::get_type "$ip")
|
||||
|
||||
local endpoint="—"
|
||||
if peers::is_blocked "$name"; then
|
||||
|
|
@ -487,7 +487,7 @@ function cmd::list::_iter_confs() {
|
|||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||
fi
|
||||
local type
|
||||
type=$(cmd::list::_get_type "$ip")
|
||||
type=$(peers::get_type "$ip")
|
||||
[[ -n "$filter_type" && "$type" != "$filter_type" ]] && continue
|
||||
"$callback" "$client_name" "$ip" "$type"
|
||||
done
|
||||
|
|
|
|||
|
|
@ -1,560 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# ============================================
|
||||
# Lifecycle
|
||||
# ============================================
|
||||
|
||||
function cmd::list::on_load() {
|
||||
flag::register --type
|
||||
flag::register --online
|
||||
flag::register --offline
|
||||
flag::register --restricted
|
||||
flag::register --blocked
|
||||
flag::register --allowed
|
||||
flag::register --detailed
|
||||
flag::register --name
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Help
|
||||
# ============================================
|
||||
|
||||
function cmd::list::help() {
|
||||
cat <<EOF
|
||||
Usage: wgctl list [options]
|
||||
|
||||
List all WireGuard clients.
|
||||
|
||||
Options:
|
||||
--type <type> Filter by device type
|
||||
--online Show only connected clients
|
||||
--offline Show only disconnected clients
|
||||
--allowed Show only fully allowed clients
|
||||
--restricted Show only restricted clients
|
||||
--blocked Show only blocked clients
|
||||
--detailed Show full detail cards for all clients
|
||||
--name <name> Show detail card for a single client
|
||||
|
||||
Examples:
|
||||
wgctl list
|
||||
wgctl ls --type phone
|
||||
wgctl list --online
|
||||
wgctl list --blocked
|
||||
wgctl list --allowed
|
||||
wgctl list --restricted
|
||||
wgctl list --detailed
|
||||
wgctl list --name phone-nuno
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Status Helpers
|
||||
# ============================================
|
||||
|
||||
function cmd::list::last_handshake_ts() {
|
||||
local public_key="$1"
|
||||
wg show "$(config::interface)" latest-handshakes 2>/dev/null \
|
||||
| grep "^${public_key}" \
|
||||
| awk '{print $2}'
|
||||
}
|
||||
|
||||
function cmd::list::last_dropped_ts() {
|
||||
local client_ip="$1"
|
||||
journalctl -k --grep "wgctl-dropped: " 2>/dev/null \
|
||||
| grep "SRC=${client_ip}" \
|
||||
| tail -1 \
|
||||
| awk '{print $1, $2, $3}'
|
||||
}
|
||||
|
||||
function cmd::list::is_connected() {
|
||||
local public_key="$1"
|
||||
local ts
|
||||
ts=$(cmd::list::last_handshake_ts "$public_key")
|
||||
[[ -z "$ts" || "$ts" == "0" ]] && return 1
|
||||
local now diff
|
||||
now=$(date +%s)
|
||||
diff=$(( now - ts ))
|
||||
(( diff < 180 ))
|
||||
}
|
||||
|
||||
function cmd::list::is_attempting() {
|
||||
local name="$1"
|
||||
local ts
|
||||
ts=$(monitor::last_attempt "$name")
|
||||
[[ -z "$ts" ]] && return 1
|
||||
|
||||
local now attempt_ts diff
|
||||
now=$(date +%s)
|
||||
attempt_ts=$(python3 -c "
|
||||
from datetime import datetime, timezone
|
||||
dt = datetime.fromisoformat('${ts}')
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
print(int(dt.timestamp()))
|
||||
" 2>/dev/null || echo 0)
|
||||
|
||||
diff=$(( now - attempt_ts ))
|
||||
(( diff < 180 ))
|
||||
}
|
||||
|
||||
function cmd::list::is_blocked() {
|
||||
local name="$1"
|
||||
peers::is_blocked "$name"
|
||||
}
|
||||
|
||||
function cmd::list::is_restricted() {
|
||||
local name="$1"
|
||||
[[ -f "$(ctx::block::path "${name}.block")" ]]
|
||||
}
|
||||
|
||||
function cmd::list::format_last_seen() {
|
||||
local name="$1"
|
||||
local public_key="$2"
|
||||
local ip="$3"
|
||||
|
||||
if cmd::list::is_blocked "$name"; then
|
||||
local ts
|
||||
ts=$(monitor::last_attempt "$name")
|
||||
if [[ -n "$ts" ]]; then
|
||||
# Format ISO timestamp
|
||||
local formatted
|
||||
formatted=$(python3 -c "
|
||||
from datetime import datetime, timezone
|
||||
dt = datetime.fromisoformat('${ts}')
|
||||
print(dt.strftime('%Y-%m-%d %H:%M'))
|
||||
" 2>/dev/null || echo "$ts")
|
||||
echo "${formatted} (dropped)"
|
||||
else
|
||||
echo "—"
|
||||
fi
|
||||
else
|
||||
local ts
|
||||
ts=$(cmd::list::last_handshake_ts "$public_key")
|
||||
if [[ -z "$ts" || "$ts" == "0" ]]; then
|
||||
echo "—"
|
||||
else
|
||||
local formatted
|
||||
formatted=$(date -d "@${ts}" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$ts")
|
||||
echo "${formatted} (handshake)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function cmd::list::format_status() {
|
||||
local name="$1"
|
||||
local public_key="$2"
|
||||
local ip="$3" # new
|
||||
|
||||
local connected=false
|
||||
local blocked=false
|
||||
local restricted=false
|
||||
|
||||
cmd::list::is_blocked "$name" && blocked=true
|
||||
cmd::list::is_restricted "$name" && restricted=true
|
||||
|
||||
if $blocked; then
|
||||
cmd::list::is_attempting "$name" && connected=true
|
||||
modifier=" (blocked)"
|
||||
elif $restricted; then
|
||||
cmd::list::is_connected "$public_key" && connected=true
|
||||
modifier=" (restricted)"
|
||||
else
|
||||
cmd::list::is_connected "$public_key" && connected=true
|
||||
modifier=""
|
||||
fi
|
||||
|
||||
local conn_str
|
||||
$connected && conn_str="online" || conn_str="offline"
|
||||
|
||||
local status="${conn_str}${modifier}"
|
||||
|
||||
local color
|
||||
if $blocked; then
|
||||
color="\033[1;31m"
|
||||
elif $restricted; then
|
||||
color="\033[1;33m"
|
||||
elif $connected; then
|
||||
color="\033[1;32m"
|
||||
else
|
||||
color="\033[0;37m"
|
||||
fi
|
||||
|
||||
echo -e "${color}${status}\033[0m"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Display Type
|
||||
# ============================================
|
||||
|
||||
function cmd::list::display_type() {
|
||||
local name="$1"
|
||||
local type="$2"
|
||||
|
||||
# log::debug "$(config::is_guest_type "$type")"
|
||||
if config::is_guest_type "$type"; then
|
||||
local subtype
|
||||
subtype=$(peers::get_meta "$name" "subtype")
|
||||
# log::debug "$subtype"
|
||||
|
||||
if [[ -n "$subtype" ]]; then
|
||||
echo "guest/${subtype}"
|
||||
else
|
||||
echo "guest"
|
||||
fi
|
||||
else
|
||||
echo "$type"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Detail Card
|
||||
# ============================================
|
||||
|
||||
function cmd::list::show_client() {
|
||||
local name="$1"
|
||||
local dir
|
||||
dir="$(ctx::clients)"
|
||||
local conf="${dir}/${name}.conf"
|
||||
|
||||
if [[ ! -f "$conf" ]]; then
|
||||
log::error "Client not found: ${name}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||
|
||||
local allowed_ips
|
||||
allowed_ips=$(grep "^AllowedIPs" "$conf" | awk '{print $3}')
|
||||
|
||||
local public_key
|
||||
public_key=$(keys::public "$name" 2>/dev/null || echo "unknown")
|
||||
|
||||
# Get endpoint
|
||||
local endpoint="—"
|
||||
if cmd::list::is_blocked "$name"; then
|
||||
local ep
|
||||
ep=$(monitor::last_endpoint "$name")
|
||||
[[ -n "$ep" ]] && endpoint="$ep"
|
||||
else
|
||||
local ep
|
||||
ep=$(monitor::endpoint_for_key "$public_key")
|
||||
[[ -n "$ep" ]] && endpoint="$ep"
|
||||
fi
|
||||
|
||||
# Determine type
|
||||
local type="unknown"
|
||||
for t in $(config::device_types); do
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$t")
|
||||
if string::starts_with "$ip" "$subnet."; then
|
||||
type="$t"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
local status
|
||||
status=$(cmd::list::format_status "$name" "$public_key" "$ip")
|
||||
|
||||
local last_seen
|
||||
last_seen=$(cmd::list::format_last_seen "$name" "$public_key" "$ip")
|
||||
|
||||
# Block rules
|
||||
local block_file
|
||||
block_file="$(ctx::block::path "${name}.block")"
|
||||
local blocks=""
|
||||
|
||||
if [[ -f "$block_file" ]] && [[ -s "$block_file" ]]; then
|
||||
while IFS=" " read -r client_ip target port proto; do
|
||||
if [[ -z "$target" ]]; then
|
||||
blocks+=" all traffic blocked\n"
|
||||
else
|
||||
local rule=" ${target}"
|
||||
[[ -n "$port" ]] && rule+=":${port}/${proto}"
|
||||
blocks+="${rule}\n"
|
||||
fi
|
||||
done < "$block_file"
|
||||
fi
|
||||
|
||||
local sep
|
||||
sep="$(printf '─%.0s' {1..50})"
|
||||
|
||||
echo ""
|
||||
echo " ${sep}"
|
||||
printf " \033[1;34m%-20s\033[0m %s\n" "Client:" "$name"
|
||||
echo " ${sep}"
|
||||
printf " %-20s %s\n" "IP:" "$ip"
|
||||
printf " %-20s %s\n" "Type:" "$type"
|
||||
printf " %-20s %b\n" "Status:" "$status"
|
||||
printf " %-20s %s\n" "Endpoint:" "$endpoint"
|
||||
printf " %-20s %s\n" "Last seen:" "$last_seen"
|
||||
printf " %-20s %s\n" "Allowed IPs:" "$allowed_ips"
|
||||
printf " %-20s %s\n" "Public key:" "$public_key"
|
||||
|
||||
if [[ -z "$blocks" ]]; then
|
||||
printf " %-20s %s\n" "Blocks:" "none"
|
||||
elif [[ "$blocks" == *"all traffic blocked"* ]]; then
|
||||
printf " %-20s \033[1;31mAll\033[0m\n" "Blocks:"
|
||||
else
|
||||
printf " %-20s\n" "Blocks:"
|
||||
echo -e "$blocks"
|
||||
fi
|
||||
|
||||
echo " ${sep}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Run
|
||||
# ============================================
|
||||
|
||||
function cmd::list::run() {
|
||||
local filter_type=""
|
||||
local online_only=false
|
||||
local offline_only=false
|
||||
local restricted_only=false
|
||||
local blocked_only=false
|
||||
local allowed_only=false
|
||||
local detailed=false
|
||||
local single_name=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--type) filter_type="$2"; shift 2 ;;
|
||||
--online) online_only=true; shift ;;
|
||||
--offline) offline_only=true; shift ;;
|
||||
--restricted) restricted_only=true; shift ;;
|
||||
--blocked) blocked_only=true; shift ;;
|
||||
--allowed) allowed_only=true; shift ;;
|
||||
--detailed) detailed=true; shift ;;
|
||||
--name) single_name="$2"; shift 2 ;;
|
||||
--help) cmd::list::help; return ;;
|
||||
*)
|
||||
log::error "Unknown flag: $1"
|
||||
cmd::list::help
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Single client detail card
|
||||
if [[ -n "$single_name" ]]; then
|
||||
cmd::list::show_client "$single_name"
|
||||
return
|
||||
fi
|
||||
|
||||
local dir
|
||||
dir="$(ctx::clients)"
|
||||
local confs=("${dir}"/*.conf)
|
||||
|
||||
if [[ ! -f "${confs[0]}" ]]; then
|
||||
log::wg_list "No clients configured"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# START - GROUP SECTION
|
||||
# Check if any groups exist
|
||||
local has_groups=false
|
||||
local groups_dir
|
||||
groups_dir="$(ctx::groups)"
|
||||
local group_files=("${groups_dir}"/*.group)
|
||||
[[ -f "${group_files[0]}" ]] && has_groups=true
|
||||
|
||||
# Precompute peer->group map
|
||||
declare -A peer_group_map
|
||||
if $has_groups; then
|
||||
while IFS=":" read -r peer_name group_name; do
|
||||
[[ -n "$peer_name" ]] && peer_group_map["$peer_name"]="$group_name"
|
||||
done < <(json::peer_group_map "$groups_dir")
|
||||
fi
|
||||
# END - GROUP SECTION
|
||||
|
||||
# Detailed mode — cards only, no table
|
||||
if $detailed; then
|
||||
log::section "WireGuard Clients"
|
||||
for conf in "${dir}"/*.conf; do
|
||||
[[ -f "$conf" ]] || continue
|
||||
local client_name
|
||||
client_name=$(basename "$conf" .conf)
|
||||
|
||||
# Apply type filter
|
||||
if [[ -n "$filter_type" ]]; then
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||
local type="unknown"
|
||||
for t in $(config::device_types); do
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$t")
|
||||
if string::starts_with "$ip" "$subnet."; then
|
||||
type="$t"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[[ "$type" != "$filter_type" ]] && continue
|
||||
fi
|
||||
|
||||
cmd::list::show_client "$client_name"
|
||||
done
|
||||
return
|
||||
fi
|
||||
|
||||
# Normal table view
|
||||
log::section "WireGuard Clients"
|
||||
|
||||
if $has_groups; then
|
||||
printf "\n %-28s %-15s %-13s %-12s %-12s %-22s %s\n" \
|
||||
"NAME" "IP" "TYPE" "RULE" "GROUP" "STATUS" "LAST SEEN"
|
||||
printf " %s\n" "$(printf '─%.0s' {1..135})"
|
||||
else
|
||||
printf "\n %-28s %-15s %-13s %-12s %-22s %s\n" \
|
||||
"NAME" "IP" "TYPE" "RULE" "STATUS" "LAST SEEN"
|
||||
printf " %s\n" "$(printf '─%.0s' {1..107})"
|
||||
fi
|
||||
|
||||
for conf in "${dir}"/*.conf; do
|
||||
[[ -f "$conf" ]] || continue
|
||||
|
||||
local client_name
|
||||
client_name=$(basename "$conf" .conf)
|
||||
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||
|
||||
# Determine type
|
||||
local type="unknown"
|
||||
for t in $(config::device_types); do
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$t")
|
||||
if string::starts_with "$ip" "$subnet."; then
|
||||
type="$t"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Apply type filter
|
||||
if [[ -n "$filter_type" && "$type" != "$filter_type" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local public_key
|
||||
public_key=$(keys::public "$client_name" 2>/dev/null || echo "")
|
||||
|
||||
# Apply filters
|
||||
if $online_only && ! cmd::list::is_connected "$public_key"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if $offline_only && cmd::list::is_connected "$public_key"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if $restricted_only && ! cmd::list::is_restricted "$client_name"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if $blocked_only && ! cmd::list::is_blocked "$client_name"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if $allowed_only && { cmd::list::is_blocked "$client_name" || cmd::list::is_restricted "$client_name"; }; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local status
|
||||
status=$(cmd::list::format_status "$client_name" "$public_key" "$ip")
|
||||
|
||||
local last_seen
|
||||
last_seen=$(cmd::list::format_last_seen "$client_name" "$public_key" "$ip")
|
||||
|
||||
local display_type
|
||||
display_type=$(cmd::list::display_type "$client_name" "$type")
|
||||
# log::debug "display_type called with name=$client_name type=$type"
|
||||
|
||||
local rule
|
||||
rule=$(peers::effective_rule "$client_name")
|
||||
rule="${rule:-—}"
|
||||
|
||||
local padded_status
|
||||
padded_status=$(cmd::list::pad_status "$status" 25)
|
||||
local group_display="—"
|
||||
if $has_groups; then
|
||||
group_display="${peer_group_map[$client_name]:-—}"
|
||||
fi
|
||||
|
||||
local rule_col_width=12
|
||||
[[ "$rule" == "—" ]] && rule_col_width=14
|
||||
|
||||
local group_col_width=12
|
||||
[[ "$group_display" == "—" ]] && group_col_width=14
|
||||
|
||||
if $has_groups; then
|
||||
printf " %-28s %-15s %-13s %-${rule_col_width}s %-${group_col_width}s %s %s\n" \
|
||||
"$client_name" "$ip" "$display_type" "$rule" \
|
||||
"$group_display" "$padded_status" "$last_seen"
|
||||
else
|
||||
printf " %-28s %-15s %-13s %-12s %s %s\n" \
|
||||
"$client_name" "$ip" "$display_type" "$rule" \
|
||||
"$padded_status" "$last_seen"
|
||||
fi
|
||||
done
|
||||
|
||||
if $has_groups; then
|
||||
printf " %s\n" "$(printf '─%.0s' {1..135})"
|
||||
else
|
||||
printf " %s\n" "$(printf '─%.0s' {1..107})"
|
||||
fi
|
||||
|
||||
local group_summary=""
|
||||
if $has_groups; then
|
||||
declare -A group_counts
|
||||
for peer in "${!peer_group_map[@]}"; do
|
||||
local g="${peer_group_map[$peer]}"
|
||||
group_counts["$g"]=$(( ${group_counts["$g"]:-0} + 1 )) || true
|
||||
done
|
||||
for g in "${!group_counts[@]}"; do
|
||||
group_summary+="${group_counts[$g]} in ${g}, "
|
||||
done
|
||||
group_summary="${group_summary%, }"
|
||||
fi
|
||||
|
||||
cmd::list::_render_summary "$group_summary"
|
||||
|
||||
printf "\n"
|
||||
}
|
||||
|
||||
function cmd::list::_render_summary() {
|
||||
local group_summary="${1:-}"
|
||||
# Summary line
|
||||
local total online_count
|
||||
total=$(peers::all | wc -l)
|
||||
|
||||
# Count by rule
|
||||
declare -A rule_summary
|
||||
while IFS= read -r peer_name; do
|
||||
local r
|
||||
r=$(peers::effective_rule "$peer_name")
|
||||
rule_summary["$r"]=$(( ${rule_summary["$r"]:-0} + 1 ))
|
||||
done < <(peers::all)
|
||||
|
||||
local summary=""
|
||||
for r in "${!rule_summary[@]}"; do
|
||||
summary+="${rule_summary[$r]} ${r}, "
|
||||
done
|
||||
summary="${summary%, }" # remove trailing comma
|
||||
|
||||
if [[ -n "$group_summary" ]]; then
|
||||
printf "\n Showing %s peers [%s] — %s\n\n" "$total" "$summary" "$group_summary"
|
||||
else
|
||||
printf "\n Showing %s peers [%s]\n\n" "$total" "$summary"
|
||||
fi
|
||||
}
|
||||
|
||||
# Strip ANSI codes to measure visible length, then pad manually
|
||||
function cmd::list::pad_status() {
|
||||
local status="$1"
|
||||
local width="${2:-20}"
|
||||
local visible
|
||||
visible=$(echo -e "$status" | sed 's/\x1b\[[0-9;]*m//g')
|
||||
local pad=$(( width - ${#visible} ))
|
||||
printf "%b%${pad}s" "$status" ""
|
||||
}
|
||||
|
|
@ -179,39 +179,43 @@ function cmd::test::section_fw() {
|
|||
function cmd::test::section_destructive() {
|
||||
test::section "Destructive (modifying state)"
|
||||
|
||||
# Cleanup from any previous failed run
|
||||
/usr/local/bin/wgctl remove --name phone-testunit --force > /dev/null 2>&1 || true
|
||||
/usr/local/bin/wgctl group remove --name testgroup --force > /dev/null 2>&1 || true
|
||||
|
||||
# Add test peer
|
||||
cmd::test::run_cmd "add phone peer" "added successfully" \
|
||||
wgctl add --name testunit --type phone --force
|
||||
add --name testunit --type phone
|
||||
|
||||
# Block/unblock
|
||||
cmd::test::run_cmd "block peer" "blocked" \
|
||||
wgctl block --name phone-testunit --force
|
||||
block --name phone-testunit
|
||||
cmd::test::run_cmd "list shows blocked" "blocked" \
|
||||
wgctl list --blocked
|
||||
list --blocked
|
||||
cmd::test::run_cmd "unblock peer" "unblocked" \
|
||||
wgctl unblock --name phone-testunit --force
|
||||
unblock --name phone-testunit
|
||||
|
||||
# Rule assign/unassign
|
||||
cmd::test::run_cmd "rule assign" "Assigned" \
|
||||
wgctl rule assign --name admin --peer phone-testunit
|
||||
rule assign --name admin --peer phone-testunit
|
||||
cmd::test::run_cmd "rule unassign" "Unassigned" \
|
||||
wgctl rule unassign --peer phone-testunit
|
||||
rule unassign --peer phone-testunit
|
||||
|
||||
# Group operations
|
||||
cmd::test::run_cmd "group add" "created" \
|
||||
wgctl group add --name testgroup --desc "Test group"
|
||||
group add --name testgroup --desc "Test group"
|
||||
cmd::test::run_cmd "group peer add" "Added" \
|
||||
wgctl group peer add --name testgroup --peer phone-testunit
|
||||
cmd::test::run_cmd "group block" "Blocked" \
|
||||
wgctl group block --name testgroup
|
||||
cmd::test::run_cmd "group unblock" "Unblocked" \
|
||||
wgctl group unblock --name testgroup
|
||||
group peer add --name testgroup --peer phone-testunit
|
||||
cmd::test::run_cmd "group block" "have been blocked" \
|
||||
group block --name testgroup
|
||||
cmd::test::run_cmd "group unblock" "have been unblocked" \
|
||||
group unblock --name testgroup
|
||||
cmd::test::run_cmd "group remove" "removed" \
|
||||
wgctl group remove --name testgroup --force
|
||||
group remove --name testgroup --force
|
||||
|
||||
# Remove test peer
|
||||
cmd::test::run_cmd "remove phone peer" "removed" \
|
||||
wgctl remove --name phone-testunit --force
|
||||
remove --name phone-testunit --force
|
||||
}
|
||||
|
||||
# ============================================
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"phone-fred": "94.63.0.129",
|
||||
"phone-helena": "148.69.38.203",
|
||||
"phone-nuno": "148.69.48.87",
|
||||
"phone-helena": "148.69.38.9",
|
||||
"phone-nuno": "94.63.0.129",
|
||||
"tablet-nuno": "148.69.202.5",
|
||||
"guest-zephyr": "86.120.152.105",
|
||||
"guest-zephyr": "5.13.82.5",
|
||||
"guest-zephyr-test": "94.63.0.129",
|
||||
"desktop-roboclean": "46.189.215.231"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue