feat: logs --resolved flag, logs clean, performance improvements
- logs --resolved: show only resolved names, hide raw IPs - logs clean: remove keepalive handshakes via json::clean_handshakes - batch_resolve: single Python call for all endpoint resolutions - fw_row/wg_row: native bash padding replaces ui::pad_mb (5x speedup) - fw_row/wg_row: correct arrow byte counting (→ = 3 bytes, 1 visible) - help: updated with new subcommands and flags - on_load: --resolved, --ascending, --descending registered
This commit is contained in:
parent
fb33aa1b6d
commit
7120199004
4 changed files with 296 additions and 86 deletions
|
|
@ -22,19 +22,21 @@ function cmd::logs::on_load() {
|
||||||
flag::register --event
|
flag::register --event
|
||||||
flag::register --ascending
|
flag::register --ascending
|
||||||
flag::register --descending
|
flag::register --descending
|
||||||
|
flag::register --resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmd::logs::help() {
|
function cmd::logs::help() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: wgctl logs [subcommand] [options]
|
Usage: wgctl logs [subcommand] [options]
|
||||||
|
|
||||||
Show or manage WireGuard and firewall activity logs.
|
Show or manage WireGuard and firewall activity logs.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
show (default) Show activity logs
|
show (default) Show activity logs
|
||||||
|
clean Remove keepalive handshakes (deduplicate)
|
||||||
remove, rm Remove log entries
|
remove, rm Remove log entries
|
||||||
rotate Remove entries older than N days
|
rotate Remove entries older than N days
|
||||||
|
|
||||||
Options for show:
|
Options for show:
|
||||||
--name <name> Filter by client name
|
--name <name> Filter by client name
|
||||||
--type <type> Filter by device type
|
--type <type> Filter by device type
|
||||||
|
|
@ -49,7 +51,14 @@ Options for show:
|
||||||
--detailed Show all deduplicated events (bypass hourly collapse)
|
--detailed Show all deduplicated events (bypass hourly collapse)
|
||||||
--follow, -f Follow logs in real time
|
--follow, -f Follow logs in real time
|
||||||
--raw Show raw IPs without service annotation
|
--raw Show raw IPs without service annotation
|
||||||
|
--resolved Show only resolved names, hide raw IPs
|
||||||
|
--ascending Sort oldest first
|
||||||
|
--descending Sort newest first (default)
|
||||||
|
|
||||||
|
Options for clean:
|
||||||
|
--wg Clean only WireGuard events (default: handshakes only)
|
||||||
|
--force Skip confirmation
|
||||||
|
|
||||||
Options for remove:
|
Options for remove:
|
||||||
--name <name> Remove entries for specific peer
|
--name <name> Remove entries for specific peer
|
||||||
--all Remove all log entries
|
--all Remove all log entries
|
||||||
|
|
@ -57,11 +66,11 @@ Options for remove:
|
||||||
--wg Remove only WireGuard events
|
--wg Remove only WireGuard events
|
||||||
--before <days> Remove entries older than N days
|
--before <days> Remove entries older than N days
|
||||||
--force Skip confirmation
|
--force Skip confirmation
|
||||||
|
|
||||||
Options for rotate:
|
Options for rotate:
|
||||||
--days <n> Days to keep (default: 7)
|
--days <n> Days to keep (default: 7)
|
||||||
--force Skip confirmation
|
--force Skip confirmation
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
wgctl logs
|
wgctl logs
|
||||||
wgctl logs --since 2h
|
wgctl logs --since 2h
|
||||||
|
|
@ -73,8 +82,11 @@ Examples:
|
||||||
wgctl logs --wg --event attempt
|
wgctl logs --wg --event attempt
|
||||||
wgctl logs --wg --event handshake --since 24h
|
wgctl logs --wg --event handshake --since 24h
|
||||||
wgctl logs --detailed
|
wgctl logs --detailed
|
||||||
|
wgctl logs --resolved
|
||||||
wgctl logs --merged
|
wgctl logs --merged
|
||||||
wgctl logs --follow
|
wgctl logs --follow
|
||||||
|
wgctl logs clean
|
||||||
|
wgctl logs clean --force
|
||||||
wgctl logs remove --name phone-nuno
|
wgctl logs remove --name phone-nuno
|
||||||
wgctl logs rotate --days 30
|
wgctl logs rotate --days 30
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -92,6 +104,7 @@ function cmd::logs::run() {
|
||||||
show) cmd::logs::show "$@" ;;
|
show) cmd::logs::show "$@" ;;
|
||||||
remove|rm|del) cmd::logs::remove "$@" ;;
|
remove|rm|del) cmd::logs::remove "$@" ;;
|
||||||
rotate) cmd::logs::rotate "$@" ;;
|
rotate) cmd::logs::rotate "$@" ;;
|
||||||
|
clean) cmd::logs::clean "$@" ;;
|
||||||
help) cmd::logs::help ;;
|
help) cmd::logs::help ;;
|
||||||
*)
|
*)
|
||||||
log::error "Unknown subcommand: '${subcmd}'"
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
|
@ -107,6 +120,7 @@ function cmd::logs::show() {
|
||||||
local raw=false detailed=false
|
local raw=false detailed=false
|
||||||
local filter_service="" filter_event=""
|
local filter_service="" filter_event=""
|
||||||
local sort_order="desc"
|
local sort_order="desc"
|
||||||
|
local resolved=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -121,6 +135,7 @@ function cmd::logs::show() {
|
||||||
--merged) merged=true; shift ;;
|
--merged) merged=true; shift ;;
|
||||||
--follow|-f) follow=true; shift ;;
|
--follow|-f) follow=true; shift ;;
|
||||||
--raw) raw=true; shift ;;
|
--raw) raw=true; shift ;;
|
||||||
|
--resolved) resolved=true; shift ;;
|
||||||
--detailed) detailed=true; shift ;;
|
--detailed) detailed=true; shift ;;
|
||||||
--help) cmd::logs::help; return ;;
|
--help) cmd::logs::help; return ;;
|
||||||
*)
|
*)
|
||||||
|
|
@ -184,11 +199,11 @@ function cmd::logs::show() {
|
||||||
|
|
||||||
$wg_only || fw_output=$(cmd::logs::show_fw_events \
|
$wg_only || fw_output=$(cmd::logs::show_fw_events \
|
||||||
"$filter_ip" "$name" "$type" "$limit" "$net_file" \
|
"$filter_ip" "$name" "$type" "$limit" "$net_file" \
|
||||||
"$collapse" "$since" "$filter_dest_ip" "$filter_dest_port" "$sort_order")
|
"$collapse" "$since" "$filter_dest_ip" "$filter_dest_port" "$sort_order" "$resolved")
|
||||||
|
|
||||||
$fw_only || wg_output=$(cmd::logs::show_wg_events \
|
$fw_only || wg_output=$(cmd::logs::show_wg_events \
|
||||||
"$filter_ip" "$name" "$type" "$limit" \
|
"$filter_ip" "$name" "$type" "$limit" \
|
||||||
"$collapse" "$since" "$filter_event" "$sort_order")
|
"$collapse" "$since" "$filter_event" "$sort_order" "$resolved")
|
||||||
|
|
||||||
if [[ -z "$(echo "$fw_output" | tr -d '[:space:]')" && \
|
if [[ -z "$(echo "$fw_output" | tr -d '[:space:]')" && \
|
||||||
-z "$(echo "$wg_output" | tr -d '[:space:]')" ]]; then
|
-z "$(echo "$wg_output" | tr -d '[:space:]')" ]]; then
|
||||||
|
|
@ -213,7 +228,7 @@ function cmd::logs::show_fw_events() {
|
||||||
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
|
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
|
||||||
limit="${4:-50}" net_file="${5:-}" collapse="${6:-1}" \
|
limit="${4:-50}" net_file="${5:-}" collapse="${6:-1}" \
|
||||||
since="${7:-}" filter_dest_ip="${8:-}" filter_dest_port="${9:-}" \
|
since="${7:-}" filter_dest_ip="${8:-}" filter_dest_port="${9:-}" \
|
||||||
sort_order="${10:-desc}"
|
sort_order="${10:-desc}" resolved_only="${11:-false}"
|
||||||
|
|
||||||
[[ ! -f "$FW_EVENTS_LOG" ]] && return 0
|
[[ ! -f "$FW_EVENTS_LOG" ]] && return 0
|
||||||
|
|
||||||
|
|
@ -228,17 +243,29 @@ function cmd::logs::show_fw_events() {
|
||||||
|
|
||||||
[[ -z "$data" ]] && return 0
|
[[ -z "$data" ]] && return 0
|
||||||
|
|
||||||
# ── Pass 1: resolve endpoints and measure widths ──
|
# ── Collect unique endpoints for batch resolution ──
|
||||||
|
local -a ep_list=()
|
||||||
|
while IFS='|' read -r ts client dest_ip dest_port proto svc count src_endpoint; do
|
||||||
|
[[ -z "$ts" || -z "$src_endpoint" ]] && continue
|
||||||
|
ep_list+=("$src_endpoint")
|
||||||
|
done <<< "$data"
|
||||||
|
|
||||||
|
declare -A resolve_cache=()
|
||||||
|
if [[ ${#ep_list[@]} -gt 0 ]]; then
|
||||||
|
while IFS='|' read -r ip name; do
|
||||||
|
[[ -n "$ip" ]] && resolve_cache["$ip"]="$name"
|
||||||
|
done < <(json::batch_resolve "${ep_list[@]}" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Pass 1: measure widths ──
|
||||||
local w_client=16 w_dest=20 w_endpoint=0
|
local w_client=16 w_dest=20 w_endpoint=0
|
||||||
local resolved_data=""
|
local resolved_data=""
|
||||||
|
|
||||||
while IFS='|' read -r ts client dest_ip dest_port proto svc count src_endpoint; do
|
while IFS='|' read -r ts client dest_ip dest_port proto svc count src_endpoint; do
|
||||||
[[ -z "$ts" ]] && continue
|
[[ -z "$ts" ]] && continue
|
||||||
|
|
||||||
# Measure client
|
|
||||||
(( ${#client} > w_client )) && w_client=${#client}
|
(( ${#client} > w_client )) && w_client=${#client}
|
||||||
|
|
||||||
# Build svc_display (for w_dest measurement)
|
|
||||||
local svc_display=""
|
local svc_display=""
|
||||||
if [[ -n "$svc" ]]; then
|
if [[ -n "$svc" ]]; then
|
||||||
[[ -n "$dest_port" ]] && svc_display="${svc}/${proto}" \
|
[[ -n "$dest_port" ]] && svc_display="${svc}/${proto}" \
|
||||||
|
|
@ -248,32 +275,39 @@ function cmd::logs::show_fw_events() {
|
||||||
|| svc_display="${dest_ip} (${proto})"
|
|| svc_display="${dest_ip} (${proto})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build raw_suffix plain (no ANSI) for w_dest measurement
|
local measure_len
|
||||||
local raw_plain=""
|
if $resolved_only; then
|
||||||
if [[ -n "$svc" ]]; then
|
measure_len=${#svc_display}
|
||||||
[[ -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})" \
|
else
|
||||||
|| raw_plain=" (${dest_ip})"
|
local raw_plain=""
|
||||||
|
[[ -n "$svc" && -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})"
|
||||||
|
[[ -n "$svc" && -z "$dest_port" ]] && raw_plain=" (${dest_ip})"
|
||||||
|
measure_len=$(( ${#svc_display} + ${#raw_plain} ))
|
||||||
fi
|
fi
|
||||||
|
(( measure_len > w_dest )) && w_dest=$measure_len
|
||||||
|
|
||||||
local full_dest_len=$(( ${#svc_display} + ${#raw_plain} ))
|
|
||||||
(( full_dest_len > w_dest )) && w_dest=$full_dest_len
|
|
||||||
|
|
||||||
# Resolve endpoint once
|
|
||||||
local src_resolved=""
|
local src_resolved=""
|
||||||
if [[ -n "$src_endpoint" ]]; then
|
if [[ -n "$src_endpoint" ]]; then
|
||||||
src_resolved=$(resolve::ip "$src_endpoint" 2>/dev/null || true)
|
src_resolved="${resolve_cache[$src_endpoint]:-}"
|
||||||
[[ "$src_resolved" == "$src_endpoint" ]] && src_resolved=""
|
[[ "$src_resolved" == "$src_endpoint" ]] && src_resolved=""
|
||||||
# Measure endpoint column: raw IP + " → resolved"
|
|
||||||
local ep_display_len=${#src_endpoint}
|
local ep_measure_len
|
||||||
[[ -n "$src_resolved" ]] && ep_display_len=$(( ep_display_len + 4 + ${#src_resolved} ))
|
if $resolved_only; then
|
||||||
(( ep_display_len > w_endpoint )) && w_endpoint=$ep_display_len
|
ep_measure_len=${#src_resolved}
|
||||||
|
[[ -z "$src_resolved" ]] && ep_measure_len=${#src_endpoint}
|
||||||
|
else
|
||||||
|
ep_measure_len=${#src_endpoint}
|
||||||
|
[[ -n "$src_resolved" ]] && \
|
||||||
|
ep_measure_len=$(( ${#src_endpoint} + 4 + ${#src_resolved} ))
|
||||||
|
fi
|
||||||
|
(( ep_measure_len > w_endpoint )) && w_endpoint=$ep_measure_len
|
||||||
fi
|
fi
|
||||||
|
|
||||||
resolved_data+="${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}|${src_endpoint}|${src_resolved}"$'\n'
|
resolved_data+="${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}|${src_endpoint}|${src_resolved}"$'\n'
|
||||||
done <<< "$data"
|
done <<< "$data"
|
||||||
|
|
||||||
(( w_client += 2 ))
|
(( w_client += 2 ))
|
||||||
(( w_dest += 2 ))
|
(( w_dest += 2 ))
|
||||||
[[ "$w_endpoint" -gt 0 ]] && (( w_endpoint += 2 ))
|
[[ "$w_endpoint" -gt 0 ]] && (( w_endpoint += 2 ))
|
||||||
|
|
||||||
# ── Pass 2: render ──
|
# ── Pass 2: render ──
|
||||||
|
|
@ -282,7 +316,7 @@ function cmd::logs::show_fw_events() {
|
||||||
[[ -z "$ts" ]] && continue
|
[[ -z "$ts" ]] && continue
|
||||||
ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \
|
ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \
|
||||||
"$proto" "$svc" "$count" "$w_client" "$w_dest" \
|
"$proto" "$svc" "$count" "$w_client" "$w_dest" \
|
||||||
"$src_endpoint" "$src_resolved" "$w_endpoint"
|
"$src_endpoint" "$src_resolved" "$w_endpoint" "$resolved_only"
|
||||||
done <<< "$resolved_data"
|
done <<< "$resolved_data"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +324,8 @@ function cmd::logs::show_fw_events() {
|
||||||
function cmd::logs::show_wg_events() {
|
function cmd::logs::show_wg_events() {
|
||||||
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
|
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
|
||||||
limit="${4:-50}" collapse="${5:-1}" \
|
limit="${4:-50}" collapse="${5:-1}" \
|
||||||
since="${6:-}" filter_event="${7:-}" sort_order="${8:-desc}"
|
since="${6:-}" filter_event="${7:-}" sort_order="${8:-desc}" \
|
||||||
|
resolved_only="${9:-false}"
|
||||||
|
|
||||||
[[ ! -f "$WG_EVENTS_LOG" ]] && return 0
|
[[ ! -f "$WG_EVENTS_LOG" ]] && return 0
|
||||||
|
|
||||||
|
|
@ -300,34 +335,69 @@ function cmd::logs::show_wg_events() {
|
||||||
"$limit" "$collapse" "$since" "$filter_event" \
|
"$limit" "$collapse" "$since" "$filter_event" \
|
||||||
"$(ctx::endpoint_cache)" "$sort_order" \
|
"$(ctx::endpoint_cache)" "$sort_order" \
|
||||||
2>/dev/null)
|
2>/dev/null)
|
||||||
|
|
||||||
[[ -z "$data" ]] && return 0
|
[[ -z "$data" ]] && return 0
|
||||||
|
|
||||||
# Resolve endpoints and measure column widths
|
# ── Collect unique endpoints for batch resolution ──
|
||||||
|
local -a ep_list=()
|
||||||
|
while IFS='|' read -r ts client endpoint event count gap_seconds; do
|
||||||
|
[[ -z "$ts" || -z "$endpoint" ]] && continue
|
||||||
|
ep_list+=("$endpoint")
|
||||||
|
done <<< "$data"
|
||||||
|
|
||||||
|
declare -A resolve_cache=()
|
||||||
|
if [[ ${#ep_list[@]} -gt 0 ]]; then
|
||||||
|
while IFS='|' read -r ip name; do
|
||||||
|
[[ -n "$ip" ]] && resolve_cache["$ip"]="$name"
|
||||||
|
done < <(json::batch_resolve "${ep_list[@]}" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Measure widths ──
|
||||||
local w_client=16 w_endpoint=16
|
local w_client=16 w_endpoint=16
|
||||||
local resolved_data=""
|
local resolved_data=""
|
||||||
|
|
||||||
while IFS='|' read -r ts client endpoint event count gap_seconds; do
|
while IFS='|' read -r ts client endpoint event count gap_seconds; do
|
||||||
[[ -z "$ts" ]] && continue
|
[[ -z "$ts" ]] && continue
|
||||||
(( ${#client} > w_client )) && w_client=${#client}
|
(( ${#client} > w_client )) && w_client=${#client}
|
||||||
local ep_len=${#endpoint}
|
|
||||||
[[ -z "$endpoint" ]] && ep_len=1
|
|
||||||
(( ep_len > w_endpoint )) && w_endpoint=$ep_len
|
|
||||||
|
|
||||||
# Resolve endpoint
|
|
||||||
local resolved=""
|
local resolved=""
|
||||||
[[ -n "$endpoint" ]] && resolved=$(resolve::ip "$endpoint" 2>/dev/null || true)
|
if [[ -n "$endpoint" ]]; then
|
||||||
[[ "$resolved" == "$endpoint" ]] && resolved=""
|
resolved="${resolve_cache[$endpoint]:-}"
|
||||||
|
[[ "$resolved" == "$endpoint" ]] && resolved=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ep_raw="${endpoint:--}"
|
||||||
|
local ep_measure_len
|
||||||
|
if $resolved_only; then
|
||||||
|
local ep_display="${resolved:-$endpoint}"
|
||||||
|
[[ -z "$ep_display" ]] && ep_display="-"
|
||||||
|
ep_measure_len=${#ep_display}
|
||||||
|
else
|
||||||
|
ep_measure_len=${#ep_raw}
|
||||||
|
[[ -n "$resolved" && -n "$endpoint" ]] && \
|
||||||
|
ep_measure_len=$(( ${#endpoint} + 4 + ${#resolved} ))
|
||||||
|
fi
|
||||||
|
(( ep_measure_len > w_endpoint )) && w_endpoint=$ep_measure_len
|
||||||
|
|
||||||
resolved_data+="${ts}|${client}|${endpoint}|${event}|${count}|${gap_seconds}|${resolved}"$'\n'
|
resolved_data+="${ts}|${client}|${endpoint}|${event}|${count}|${gap_seconds}|${resolved}"$'\n'
|
||||||
done <<< "$data"
|
done <<< "$data"
|
||||||
|
|
||||||
(( w_client += 2 ))
|
(( w_client += 2 ))
|
||||||
(( w_endpoint += 18 ))
|
(( w_endpoint += 2 ))
|
||||||
|
|
||||||
|
# ── Render ──
|
||||||
ui::logs::wg_section_header
|
ui::logs::wg_section_header
|
||||||
while IFS='|' read -r ts client endpoint event count gap_seconds resolved; do
|
while IFS='|' read -r ts client endpoint event count gap_seconds resolved; do
|
||||||
[[ -z "$ts" ]] && continue
|
[[ -z "$ts" ]] && continue
|
||||||
ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \
|
if $resolved_only; then
|
||||||
"$count" "$w_client" "$w_endpoint" "$gap_seconds" "$resolved"
|
local ep_display="${resolved:-$endpoint}"
|
||||||
|
[[ -z "$ep_display" ]] && ep_display="-"
|
||||||
|
ui::logs::wg_row "$ts" "$client" "$ep_display" "$event" \
|
||||||
|
"$count" "$w_client" "$w_endpoint" "$gap_seconds" ""
|
||||||
|
else
|
||||||
|
ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \
|
||||||
|
"$count" "$w_client" "$w_endpoint" "$gap_seconds" "$resolved"
|
||||||
|
fi
|
||||||
done <<< "$resolved_data"
|
done <<< "$resolved_data"
|
||||||
printf "\n"
|
printf "\n"
|
||||||
}
|
}
|
||||||
|
|
@ -520,4 +590,31 @@ function cmd::logs::rotate() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log::wg_success "Rotated ${total} entries older than ${days} days (wg: ${removed_wg}, fw: ${removed_fw})"
|
log::wg_success "Rotated ${total} entries older than ${days} days (wg: ${removed_wg}, fw: ${removed_fw})"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::logs::clean() {
|
||||||
|
local force=false wg_only=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--force) force=true; shift ;;
|
||||||
|
--wg) wg_only=true; shift ;;
|
||||||
|
--help) cmd::logs::help; return ;;
|
||||||
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! $force; then
|
||||||
|
read -r -p "Remove keepalive handshakes from events.log? [y/N] " confirm
|
||||||
|
case "$confirm" in [yY]*) ;; *) log::info "Aborted"; return 0 ;; esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
local removed
|
||||||
|
removed=$(json::clean_handshakes "$WG_EVENTS_LOG" "${WG_HANDSHAKE_CHECK_TIME_SEC:-300}")
|
||||||
|
|
||||||
|
if [[ "$removed" -eq 0 ]]; then
|
||||||
|
log::wg_warning "No keepalive handshakes found to remove"
|
||||||
|
else
|
||||||
|
log::wg_success "Removed ${removed} keepalive handshake entries"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +118,9 @@ function json::hosts_remove() { python3 "$JSON_HELPER" hosts_remove "$@" </dev/n
|
||||||
function json::hosts_exists() { python3 "$JSON_HELPER" hosts_exists "$@" </dev/null; }
|
function json::hosts_exists() { python3 "$JSON_HELPER" hosts_exists "$@" </dev/null; }
|
||||||
function json::hosts_lookup() { python3 "$JSON_HELPER" hosts_lookup "$@" </dev/null; }
|
function json::hosts_lookup() { python3 "$JSON_HELPER" hosts_lookup "$@" </dev/null; }
|
||||||
|
|
||||||
|
function json::clean_handshakes() { python3 "$JSON_HELPER" clean_handshakes "$@" </dev/null; }
|
||||||
|
function json::batch_resolve() { python3 "$JSON_HELPER" batch_resolve "$(ctx::hosts)" "$(ctx::net)" "$@" </dev/null; }
|
||||||
|
|
||||||
function json::peer_transfer() {
|
function json::peer_transfer() {
|
||||||
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \
|
||||||
ACTIVITY_TOTAL_MED="$(config::activity_total_med)" \
|
ACTIVITY_TOTAL_MED="$(config::activity_total_med)" \
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,62 @@ def audit_fw_counts(clients_dir):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def clean_handshakes(file, threshold_sec):
|
||||||
|
"""Remove keepalive handshakes — keep only new session handshakes."""
|
||||||
|
threshold = int(threshold_sec) if threshold_sec else 300
|
||||||
|
last_ts = {}
|
||||||
|
kept = []
|
||||||
|
removed = 0
|
||||||
|
try:
|
||||||
|
with open(file) as f:
|
||||||
|
for line in f:
|
||||||
|
try:
|
||||||
|
e = json.loads(line.strip())
|
||||||
|
if e.get('event') == 'handshake':
|
||||||
|
client = e.get('client', '')
|
||||||
|
ts_str = e.get('timestamp', '')
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
dt = datetime.fromisoformat(ts_str)
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=timezone.utc)
|
||||||
|
ts = dt.timestamp()
|
||||||
|
last = last_ts.get(client, 0)
|
||||||
|
if ts - last >= threshold:
|
||||||
|
kept.append(line)
|
||||||
|
last_ts[client] = ts
|
||||||
|
else:
|
||||||
|
removed += 1
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
kept.append(line)
|
||||||
|
with open(file, 'w') as f:
|
||||||
|
f.writelines(kept)
|
||||||
|
print(removed)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def batch_resolve(hosts_file, net_file, *ips):
|
||||||
|
"""
|
||||||
|
Resolve multiple IPs at once.
|
||||||
|
Output: ip|resolved_name per line (resolved_name=ip if no match)
|
||||||
|
"""
|
||||||
|
from lib.util import load_hosts_data, load_net_data, hosts_lookup, reverse_lookup
|
||||||
|
hosts_data = load_hosts_data(hosts_file)
|
||||||
|
net_data = load_net_data(net_file)
|
||||||
|
seen = set()
|
||||||
|
for ip in ips:
|
||||||
|
if not ip or ip in seen:
|
||||||
|
continue
|
||||||
|
seen.add(ip)
|
||||||
|
name = hosts_lookup(hosts_data, ip)
|
||||||
|
if not name:
|
||||||
|
name = reverse_lookup(net_data, ip)
|
||||||
|
print(f"{ip}|{name or ''}")
|
||||||
|
|
||||||
# ── Rules (kept inline — candidates for lib/rules.py) ────────────────────
|
# ── Rules (kept inline — candidates for lib/rules.py) ────────────────────
|
||||||
|
|
||||||
def find_rule_file(rules_dir, rule_name):
|
def find_rule_file(rules_dir, rule_name):
|
||||||
|
|
@ -1836,6 +1892,8 @@ commands = {
|
||||||
'hosts_remove': lambda args: hosts_remove(args[0], args[1], args[2]),
|
'hosts_remove': lambda args: hosts_remove(args[0], args[1], args[2]),
|
||||||
'hosts_exists': lambda args: hosts_exists(args[0], args[1], args[2]),
|
'hosts_exists': lambda args: hosts_exists(args[0], args[1], args[2]),
|
||||||
'hosts_lookup': lambda args: hosts_lookup(args[0], args[1]),
|
'hosts_lookup': lambda args: hosts_lookup(args[0], args[1]),
|
||||||
|
'clean_handshakes': lambda args: clean_handshakes(args[0], args[1] if len(args) > 1 else '300'),
|
||||||
|
'batch_resolve': lambda args: batch_resolve(args[0], args[1], *args[2:]),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Main ─────────────────────────────────────────────────────────────────────
|
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -38,52 +38,71 @@ function ui::logs::fw_row() {
|
||||||
local ts="${1:-}" client="${2:-}" dest_ip="${3:-}" dest_port="${4:-}" \
|
local ts="${1:-}" client="${2:-}" dest_ip="${3:-}" dest_port="${4:-}" \
|
||||||
proto="${5:-}" svc_name="${6:-}" count="${7:-1}" \
|
proto="${5:-}" svc_name="${6:-}" count="${7:-1}" \
|
||||||
w_client="${8:-20}" w_dest="${9:-30}" \
|
w_client="${8:-20}" w_dest="${9:-30}" \
|
||||||
src_endpoint="${10:-}" src_resolved="${11:-}" w_endpoint="${12:-0}"
|
src_endpoint="${10:-}" src_resolved="${11:-}" \
|
||||||
|
w_endpoint="${12:-0}" resolved_only="${13:-false}"
|
||||||
|
|
||||||
local ts_pad client_pad
|
local ts_pad client_pad
|
||||||
ts_pad=$(printf "%-11s" "$ts")
|
ts_pad=$(printf "%-11s" "$ts")
|
||||||
client_pad=$(printf "%-${w_client}s" "$client")
|
client_pad=$(printf "%-${w_client}s" "$client")
|
||||||
|
|
||||||
# ── Source endpoint — always render at w_endpoint width ──
|
# ── Source endpoint ──
|
||||||
|
# " → " = 5 bytes, 3 visible chars → overcount by _UI_ARROW_EXTRA=2
|
||||||
local src_padded=""
|
local src_padded=""
|
||||||
if [[ "$w_endpoint" -gt 0 ]]; then
|
if [[ "$w_endpoint" -gt 0 ]]; then
|
||||||
|
local src_colored src_visible_len pad_n
|
||||||
|
|
||||||
if [[ -n "$src_endpoint" ]]; then
|
if [[ -n "$src_endpoint" ]]; then
|
||||||
local src_colored="$src_endpoint"
|
if $resolved_only; then
|
||||||
[[ -n "$src_resolved" ]] && \
|
src_colored="${src_resolved:-$src_endpoint}"
|
||||||
src_colored="${src_endpoint} \033[2m→ ${src_resolved}\033[0m"
|
src_visible_len=${#src_colored}
|
||||||
src_padded=$(ui::pad_mb "$src_colored" "$w_endpoint")
|
else
|
||||||
|
if [[ -n "$src_resolved" ]]; then
|
||||||
|
src_colored="${src_endpoint} \033[2m→ ${src_resolved}\033[0m"
|
||||||
|
# bytes: endpoint + " → " (5 bytes, 3 visible) + resolved
|
||||||
|
# visible: endpoint + 3 + resolved
|
||||||
|
src_visible_len=$(( ${#src_endpoint} + 3 + ${#src_resolved} ))
|
||||||
|
else
|
||||||
|
src_colored="${src_endpoint}"
|
||||||
|
src_visible_len=${#src_endpoint}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# No endpoint — use dim dash padded to w_endpoint
|
# — is 3 bytes, 1 visible char
|
||||||
src_padded=$(ui::pad_mb "\033[2m—\033[0m" "$w_endpoint")
|
src_colored="\033[2m—\033[0m"
|
||||||
|
src_visible_len=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
pad_n=$(( w_endpoint - src_visible_len ))
|
||||||
|
[[ $pad_n -lt 0 ]] && pad_n=0
|
||||||
|
src_padded=$(printf "%b%*s" "$src_colored" "$pad_n" "")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Destination ──
|
# ── Destination ──
|
||||||
local svc_display=""
|
local svc_display="" raw_suffix=""
|
||||||
local raw_suffix=""
|
|
||||||
if [[ -n "$svc_name" ]]; then
|
if [[ -n "$svc_name" ]]; then
|
||||||
[[ -n "$dest_port" ]] && svc_display="${svc_name}/${proto}" \
|
[[ -n "$dest_port" ]] && svc_display="${svc_name}/${proto}" \
|
||||||
|| svc_display="${svc_name} (${proto})"
|
|| svc_display="${svc_name} (${proto})"
|
||||||
[[ -n "$dest_port" ]] && raw_suffix=" \033[2m(${dest_ip}:${dest_port})\033[0m" \
|
if ! $resolved_only; then
|
||||||
|| raw_suffix=" \033[2m(${dest_ip})\033[0m"
|
[[ -n "$dest_port" ]] && raw_suffix=" \033[2m(${dest_ip}:${dest_port})\033[0m" \
|
||||||
|
|| raw_suffix=" \033[2m(${dest_ip})\033[0m"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
[[ -n "$dest_port" ]] && svc_display="${dest_ip}:${dest_port}/${proto}" \
|
[[ -n "$dest_port" ]] && svc_display="${dest_ip}:${dest_port}/${proto}" \
|
||||||
|| svc_display="${dest_ip} (${proto})"
|
|| svc_display="${dest_ip} (${proto})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pad so count aligns — based on full dest (svc + raw_suffix plain length)
|
|
||||||
local raw_plain=""
|
local raw_plain=""
|
||||||
[[ -n "$svc_name" && -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})"
|
if ! $resolved_only; then
|
||||||
[[ -n "$svc_name" && -z "$dest_port" ]] && raw_plain=" (${dest_ip})"
|
[[ -n "$svc_name" && -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})"
|
||||||
|
[[ -n "$svc_name" && -z "$dest_port" ]] && raw_plain=" (${dest_ip})"
|
||||||
|
fi
|
||||||
local full_dest_len=$(( ${#svc_display} + ${#raw_plain} ))
|
local full_dest_len=$(( ${#svc_display} + ${#raw_plain} ))
|
||||||
local dest_pad_n=$(( w_dest - full_dest_len ))
|
local dest_pad_n=$(( w_dest - full_dest_len ))
|
||||||
[[ $dest_pad_n -lt 0 ]] && dest_pad_n=0
|
[[ $dest_pad_n -lt 0 ]] && dest_pad_n=0
|
||||||
|
|
||||||
# ── Count ──
|
|
||||||
local count_suffix=""
|
local count_suffix=""
|
||||||
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
||||||
|
|
||||||
# ── Render ──
|
|
||||||
if [[ "$w_endpoint" -gt 0 ]]; then
|
if [[ "$w_endpoint" -gt 0 ]]; then
|
||||||
printf " %s %s %b \033[1;31m→\033[0m %s%b%*s%b\n" \
|
printf " %s %s %b \033[1;31m→\033[0m %s%b%*s%b\n" \
|
||||||
"$ts_pad" "$client_pad" \
|
"$ts_pad" "$client_pad" \
|
||||||
|
|
@ -111,17 +130,17 @@ function ui::logs::wg_row() {
|
||||||
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
|
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
|
||||||
count="${5:-1}" w_client="${6:-20}" w_endpoint="${7:-20}" \
|
count="${5:-1}" w_client="${6:-20}" w_endpoint="${7:-20}" \
|
||||||
gap_seconds="${8:-}" resolved="${9:-}"
|
gap_seconds="${8:-}" resolved="${9:-}"
|
||||||
|
|
||||||
local event_color
|
local event_color
|
||||||
case "$event" in
|
case "$event" in
|
||||||
handshake) event_color="\033[1;32m" ;;
|
handshake) event_color="\033[1;32m" ;;
|
||||||
attempt) event_color="\033[1;31m" ;;
|
attempt) event_color="\033[1;31m" ;;
|
||||||
*) event_color="\033[0;37m" ;;
|
*) event_color="\033[0;37m" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
local count_suffix=""
|
local count_suffix=""
|
||||||
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
||||||
|
|
||||||
# Gap suffix — offline label only when gap > threshold * 2
|
# Gap suffix — offline label only when gap > threshold * 2
|
||||||
local gap_suffix=""
|
local gap_suffix=""
|
||||||
if [[ "$event" == "handshake" && -n "$gap_seconds" && "$gap_seconds" -gt 0 ]]; then
|
if [[ "$event" == "handshake" && -n "$gap_seconds" && "$gap_seconds" -gt 0 ]]; then
|
||||||
|
|
@ -135,23 +154,28 @@ function ui::logs::wg_row() {
|
||||||
gap_suffix=" \033[2m↑ $(( gap_int / 60 ))m${offline_label}\033[0m"
|
gap_suffix=" \033[2m↑ $(( gap_int / 60 ))m${offline_label}\033[0m"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build endpoint display: raw_ip [dim → resolved]
|
# ── Endpoint — native padding, no ui::pad_mb ──
|
||||||
# Use ui::pad_mb so ANSI annotation doesn't affect column alignment
|
local endpoint_colored endpoint_visible_len
|
||||||
local endpoint_raw="${endpoint:--}"
|
local endpoint_raw="${endpoint:--}"
|
||||||
local endpoint_colored
|
|
||||||
if [[ -n "$resolved" && -n "$endpoint" ]]; then
|
if [[ -n "$resolved" && -n "$endpoint" ]]; then
|
||||||
endpoint_colored="${endpoint} \033[2m→ ${resolved}\033[0m"
|
endpoint_colored="${endpoint} \033[2m→ ${resolved}\033[0m"
|
||||||
|
endpoint_visible_len=$(( ${#endpoint} + 4 + ${#resolved} - _UI_ARROW_EXTRA ))
|
||||||
else
|
else
|
||||||
endpoint_colored="$endpoint_raw"
|
endpoint_colored="$endpoint_raw"
|
||||||
|
# "-" is 1 char; endpoint may be empty
|
||||||
|
[[ -n "$endpoint" ]] && endpoint_visible_len=${#endpoint} \
|
||||||
|
|| endpoint_visible_len=1
|
||||||
fi
|
fi
|
||||||
local endpoint_padded
|
|
||||||
endpoint_padded=$(ui::pad_mb "$endpoint_colored" "$w_endpoint")
|
local ep_pad_n=$(( w_endpoint - endpoint_visible_len ))
|
||||||
|
[[ $ep_pad_n -lt 0 ]] && ep_pad_n=0
|
||||||
|
local endpoint_padded=$(printf "%b%*s" "$endpoint_colored" "$ep_pad_n" "")
|
||||||
local ts_pad client_pad
|
local ts_pad client_pad
|
||||||
ts_pad=$(printf "%-11s" "$ts")
|
ts_pad=$(printf "%-11s" "$ts")
|
||||||
client_pad=$(printf "%-${w_client}s" "$client")
|
client_pad=$(printf "%-${w_client}s" "$client")
|
||||||
|
|
||||||
printf " %s %s %b %b%s\033[0m%b%b\n" \
|
printf " %s %s %b %b%s\033[0m%b%b\n" \
|
||||||
"$ts_pad" "$client_pad" \
|
"$ts_pad" "$client_pad" \
|
||||||
"$endpoint_padded" \
|
"$endpoint_padded" \
|
||||||
|
|
@ -191,9 +215,10 @@ function ui::watch::fw_row() {
|
||||||
"$ts_pad" "$src" "$client_pad" "$dest_display" "$dest_pad_n" ""
|
"$ts_pad" "$src" "$client_pad" "$dest_display" "$dest_pad_n" ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function ui::watch::wg_row() {
|
function ui::logs::wg_row() {
|
||||||
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
|
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
|
||||||
w_client="${5:-20}" w_endpoint="${6:-18}"
|
count="${5:-1}" w_client="${6:-20}" w_endpoint="${7:-20}" \
|
||||||
|
gap_seconds="${8:-}" resolved="${9:-}"
|
||||||
|
|
||||||
local event_color
|
local event_color
|
||||||
case "$event" in
|
case "$event" in
|
||||||
|
|
@ -202,23 +227,50 @@ function ui::watch::wg_row() {
|
||||||
*) event_color="\033[0;37m" ;;
|
*) event_color="\033[0;37m" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# "wg" is always 2 visible chars — no padding needed
|
local count_suffix=""
|
||||||
local src="${_UI_WATCH_WG_COLOR}wg\033[0m"
|
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
||||||
|
|
||||||
# Use "-" not "—" to avoid multi-byte padding issues
|
local gap_suffix=""
|
||||||
local endpoint_display="${endpoint:--}"
|
if [[ "$event" == "handshake" && -n "$gap_seconds" && "$gap_seconds" -gt 0 ]]; then
|
||||||
|
local gap_int="$gap_seconds"
|
||||||
|
local threshold="${WG_HANDSHAKE_CHECK_TIME_SEC:-300}"
|
||||||
|
local offline_label=""
|
||||||
|
[[ "$gap_int" -gt $(( threshold * 2 )) ]] && offline_label=" offline"
|
||||||
|
if (( gap_int >= 3600 )); then
|
||||||
|
gap_suffix=" \033[2m↑ $(( gap_int / 3600 ))h${offline_label}\033[0m"
|
||||||
|
elif (( gap_int >= 60 )); then
|
||||||
|
gap_suffix=" \033[2m↑ $(( gap_int / 60 ))m${offline_label}\033[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
local ts_pad client_pad endpoint_pad_n
|
# ── Endpoint padding ──
|
||||||
|
# " → " = 5 bytes, 3 visible → visible = endpoint + 3 + resolved
|
||||||
|
local endpoint_colored endpoint_visible_len
|
||||||
|
if [[ -n "$resolved" && -n "$endpoint" ]]; then
|
||||||
|
endpoint_colored="${endpoint} \033[2m→ ${resolved}\033[0m"
|
||||||
|
endpoint_visible_len=$(( ${#endpoint} + 3 + ${#resolved} ))
|
||||||
|
elif [[ -n "$endpoint" ]]; then
|
||||||
|
endpoint_colored="${endpoint}"
|
||||||
|
endpoint_visible_len=${#endpoint}
|
||||||
|
else
|
||||||
|
endpoint_colored="-"
|
||||||
|
endpoint_visible_len=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ep_pad_n=$(( w_endpoint - endpoint_visible_len ))
|
||||||
|
[[ $ep_pad_n -lt 0 ]] && ep_pad_n=0
|
||||||
|
local endpoint_padded
|
||||||
|
endpoint_padded=$(printf "%b%*s" "$endpoint_colored" "$ep_pad_n" "")
|
||||||
|
|
||||||
|
local ts_pad client_pad
|
||||||
ts_pad=$(printf "%-11s" "$ts")
|
ts_pad=$(printf "%-11s" "$ts")
|
||||||
client_pad=$(printf "%-${w_client}s" "$client")
|
client_pad=$(printf "%-${w_client}s" "$client")
|
||||||
endpoint_pad_n=$(( w_endpoint - ${#endpoint_display} ))
|
|
||||||
[[ $endpoint_pad_n -lt 0 ]] && endpoint_pad_n=0
|
|
||||||
|
|
||||||
printf " %s %b %s %s%*s %b%s\033[0m\n" \
|
printf " %s %s %b %b%s\033[0m%b%b\n" \
|
||||||
"$ts_pad" "$src" "$client_pad" "$endpoint_display" "$endpoint_pad_n" "" \
|
"$ts_pad" "$client_pad" \
|
||||||
"$event_color" "$event"
|
"$endpoint_padded" \
|
||||||
|
"$event_color" "$event" "$count_suffix" "$gap_suffix"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ui::watch::header_table() {
|
function ui::watch::header_table() {
|
||||||
printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \
|
printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue