wgctl/commands/activity.command.sh
Nuno Duque Nunes 087f735790 feat: --json output for group/rule/identity/net/activity
- cmd::group::_output_json: groups array with peer/blocked counts
- cmd::rule::_output_json: rules with extends array, is_base bool fix
- cmd::identity::_output_json: identities with types/rules as arrays
- cmd::net::_output_json: services with tags array and port count
- cmd::activity::_output_json: peers with nested services array
- all commands: command::mixin json_output registered in on_load
2026-05-27 00:36:30 +00:00

245 lines
6.8 KiB
Bash

#!/usr/bin/env bash
# activity.command.sh — WireGuard activity snapshot
# ============================================
# Lifecycle
# ============================================
function cmd::activity::on_load() {
load_module net
flag::register --peer
flag::register --service
flag::register --ip
flag::register --hours
flag::register --type
flag::register --dropped
command::mixin json_output
}
# ============================================
# Help
# ============================================
function cmd::activity::help() {
cat <<EOF
Usage: wgctl activity [options]
Show WireGuard activity — transfer totals and firewall drops per peer.
Data sources: wg show transfer, fw_events.log
Options:
--peer <name> Filter by peer name
--service <name> Filter by service (e.g. truenas, proxmox:web-ui)
--ip <ip> Filter by destination IP
--hours <n> Time window in hours (default: 24, 0 = all time)
--type <type> Filter by device type (combined with --peer)
--dropped Show only peers with at least one drop
Examples:
wgctl activity
wgctl activity --dropped
wgctl activity --peer phone-nuno
wgctl activity --service truenas
wgctl activity --hours 0
wgctl activity --ip 10.0.0.101
EOF
}
# ============================================
# Run
# ============================================
function cmd::activity::run() {
local filter_peer="" filter_service="" filter_ip="" filter_type=""
local hours=24 dropped_only=false
while [[ $# -gt 0 ]]; do
case "$1" in
--peer) filter_peer="$2"; shift 2 ;;
--service) filter_service="$2"; shift 2 ;;
--ip) filter_ip="$2"; shift 2 ;;
--type) filter_type="$2"; shift 2 ;;
--hours) hours="$2"; shift 2 ;;
--dropped) dropped_only=true; shift ;;
--help) cmd::activity::help; return ;;
*)
log::error "Unknown flag: $1"
cmd::activity::help
return 1
;;
esac
done
if command::json; then
cmd::activity::_output_json "$hours"
return 0
fi
# Resolve peer name if type provided
if [[ -n "$filter_peer" && -n "$filter_type" ]]; then
filter_peer=$(peers::resolve_and_require "$filter_peer" "$filter_type") || return 1
fi
# Resolve --service to IP
local service_ip=""
if [[ -n "$filter_service" ]]; then
service_ip=$(net::resolve "$filter_service" 2>/dev/null | head -1 | cut -d: -f1) || true
if [[ -z "$service_ip" ]]; then
log::error "Service not found: ${filter_service}"
return 1
fi
fi
[[ -n "$filter_ip" ]] && service_ip="$filter_ip"
# Fetch aggregated data
local data
data=$(json::activity_aggregate \
"$(ctx::fw_events_log)" \
"$(ctx::events_log)" \
"$(config::interface)" \
"$(ctx::net)" \
"$(ctx::clients)" \
"$(ctx::meta)" \
"$hours" \
"$filter_peer" \
"$service_ip" 2>/dev/null)
if [[ -z "$data" ]]; then
log::wg_warning "No activity data found"
return 0
fi
# Measure column widths
local w_peer=16 w_drops=1
while IFS='|' read -r type rest; do
case "$type" in
peer)
local name drops
name=$(echo "$rest" | cut -d'|' -f1)
drops=$(echo "$rest" | cut -d'|' -f4)
(( ${#name} > w_peer )) && w_peer=${#name}
(( ${#drops} > w_drops )) && w_drops=${#drops}
;;
service)
local count
count=$(echo "$rest" | cut -d'|' -f3)
(( ${#count} > w_drops )) && w_drops=${#count}
;;
esac
done <<< "$data"
(( w_peer += 2 ))
# Compute column where drop count starts on peer row:
# " " (2) + name (w_peer) + " ↓" (3) + rx (10) + " ↑" (3) + tx (10) + " " (2)
# ↓ 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=$(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"
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"
local svc_drop_word="drops"
[[ "$drop_count" -eq 1 ]] && svc_drop_word="drop"
ui::activity::service_row \
"$dest_display" "$drop_count" "$svc_drop_word" "$drops_col" "$w_drops"
;;
esac
done <<< "$data"
echo ""
}
function cmd::activity::_output_json() {
local hours="${1:-24}"
local data
data=$(json::activity_aggregate \
"$(ctx::fw_events_log)" "$(ctx::events_log)" \
"$(config::interface)" "$(ctx::net)" \
"$(ctx::clients)" "$(ctx::meta)" \
"$hours" "" "" 2>/dev/null)
local -a peers=()
local current_peer="" current_services=""
local -a current_svc_list=()
while IFS='|' read -r record_type rest; do
case "$record_type" in
peer)
# Flush previous peer
if [[ -n "$current_peer" ]]; then
local svc_array
svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -)
peers+=("${current_peer},\"services\":[${svc_array:-}]}")
current_svc_list=()
fi
local name rx tx drops
IFS='|' read -r name rx tx drops <<< "$rest"
current_peer=$(printf '{"name":"%s","rx":%s,"tx":%s,"drops":%s' \
"$name" "$rx" "$tx" "$drops")
;;
service)
local peer dest count
IFS='|' read -r peer dest count <<< "$rest"
current_svc_list+=("$(printf '{"dest":"%s","drops":%s}' "$dest" "$count")")
;;
esac
done <<< "$data"
# Flush last peer
if [[ -n "$current_peer" ]]; then
local svc_array
svc_array=$(printf '%s\n' "${current_svc_list[@]:-}" | paste -sd ',' -)
peers+=("${current_peer},\"services\":[${svc_array:-}]}")
fi
local count=${#peers[@]}
local array
array=$(printf '%s\n' "${peers[@]:-}" | paste -sd ',' -)
printf '{"peers":[%s]}' "${array:-}" | json::envelope "activity" "$count"
}