feat: logs endpoint annotation, alignment, descending sort
- fw/wg events: raw_ip → resolved_name annotation (dim) - fw events: endpoint column with pre-resolved names (two-pass render) - fw events: raw IP:port dim suffix after service name - wg events: endpoint annotation in logs (same as watch) - fw/wg: descending sort default, --ascending/--descending flags - wg events: gap/offline indicator, threshold * 2 for offline label - fw_row: no-endpoint rows show dim — placeholder for alignment - section headers: dynamic width via tput cols
This commit is contained in:
parent
d5de344d99
commit
fb33aa1b6d
5 changed files with 176 additions and 60 deletions
|
|
@ -228,31 +228,62 @@ function cmd::logs::show_fw_events() {
|
|||
|
||||
[[ -z "$data" ]] && return 0
|
||||
|
||||
# Measure column widths
|
||||
local w_client=16 w_dest=20
|
||||
while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
|
||||
# ── Pass 1: resolve endpoints and measure widths ──
|
||||
local w_client=16 w_dest=20 w_endpoint=0
|
||||
local resolved_data=""
|
||||
|
||||
while IFS='|' read -r ts client dest_ip dest_port proto svc count src_endpoint; do
|
||||
[[ -z "$ts" ]] && continue
|
||||
|
||||
# Measure client
|
||||
(( ${#client} > w_client )) && w_client=${#client}
|
||||
local dest_display host_name
|
||||
host_name=$(hosts::resolve_ip "$dest_ip")
|
||||
if [[ -n "$host_name" ]]; then
|
||||
dest_display="$host_name"
|
||||
elif [[ -n "$svc" ]]; then
|
||||
[[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})"
|
||||
|
||||
# Build svc_display (for w_dest measurement)
|
||||
local svc_display=""
|
||||
if [[ -n "$svc" ]]; then
|
||||
[[ -n "$dest_port" ]] && svc_display="${svc}/${proto}" \
|
||||
|| svc_display="${svc} (${proto})"
|
||||
else
|
||||
[[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})"
|
||||
[[ -n "$dest_port" ]] && svc_display="${dest_ip}:${dest_port}/${proto}" \
|
||||
|| svc_display="${dest_ip} (${proto})"
|
||||
fi
|
||||
(( ${#dest_display} > w_dest )) && w_dest=${#dest_display}
|
||||
|
||||
# Build raw_suffix plain (no ANSI) for w_dest measurement
|
||||
local raw_plain=""
|
||||
if [[ -n "$svc" ]]; then
|
||||
[[ -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})" \
|
||||
|| raw_plain=" (${dest_ip})"
|
||||
fi
|
||||
|
||||
local full_dest_len=$(( ${#svc_display} + ${#raw_plain} ))
|
||||
(( full_dest_len > w_dest )) && w_dest=$full_dest_len
|
||||
|
||||
# Resolve endpoint once
|
||||
local src_resolved=""
|
||||
if [[ -n "$src_endpoint" ]]; then
|
||||
src_resolved=$(resolve::ip "$src_endpoint" 2>/dev/null || true)
|
||||
[[ "$src_resolved" == "$src_endpoint" ]] && src_resolved=""
|
||||
# Measure endpoint column: raw IP + " → resolved"
|
||||
local ep_display_len=${#src_endpoint}
|
||||
[[ -n "$src_resolved" ]] && ep_display_len=$(( ep_display_len + 4 + ${#src_resolved} ))
|
||||
(( ep_display_len > w_endpoint )) && w_endpoint=$ep_display_len
|
||||
fi
|
||||
|
||||
resolved_data+="${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}|${src_endpoint}|${src_resolved}"$'\n'
|
||||
done <<< "$data"
|
||||
|
||||
(( w_client += 2 ))
|
||||
(( w_dest += 2 ))
|
||||
[[ "$w_endpoint" -gt 0 ]] && (( w_endpoint += 2 ))
|
||||
|
||||
# ── Pass 2: render ──
|
||||
ui::logs::fw_section_header
|
||||
while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
|
||||
while IFS='|' read -r ts client dest_ip dest_port proto svc count src_endpoint src_resolved; do
|
||||
[[ -z "$ts" ]] && continue
|
||||
ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \
|
||||
"$proto" "$svc" "$count" "$w_client" "$w_dest"
|
||||
done <<< "$data"
|
||||
"$proto" "$svc" "$count" "$w_client" "$w_dest" \
|
||||
"$src_endpoint" "$src_resolved" "$w_endpoint"
|
||||
done <<< "$resolved_data"
|
||||
printf "\n"
|
||||
}
|
||||
|
||||
|
|
@ -277,21 +308,26 @@ function cmd::logs::show_wg_events() {
|
|||
local resolved_data=""
|
||||
while IFS='|' read -r ts client endpoint event count gap_seconds; do
|
||||
[[ -z "$ts" ]] && continue
|
||||
local endpoint_display
|
||||
endpoint_display=$(resolve::ip "$endpoint")
|
||||
[[ -z "$endpoint_display" ]] && endpoint_display="$endpoint"
|
||||
resolved_data+="${ts}|${client}|${endpoint_display}|${event}|${count}|${gap_seconds}"$'\n'
|
||||
(( ${#client} > w_client )) && w_client=${#client}
|
||||
(( ${#endpoint_display} > w_endpoint )) && w_endpoint=${#endpoint_display}
|
||||
local ep_len=${#endpoint}
|
||||
[[ -z "$endpoint" ]] && ep_len=1
|
||||
(( ep_len > w_endpoint )) && w_endpoint=$ep_len
|
||||
|
||||
# Resolve endpoint
|
||||
local resolved=""
|
||||
[[ -n "$endpoint" ]] && resolved=$(resolve::ip "$endpoint" 2>/dev/null || true)
|
||||
[[ "$resolved" == "$endpoint" ]] && resolved=""
|
||||
|
||||
resolved_data+="${ts}|${client}|${endpoint}|${event}|${count}|${gap_seconds}|${resolved}"$'\n'
|
||||
done <<< "$data"
|
||||
(( w_client += 2 ))
|
||||
(( w_endpoint += 2 ))
|
||||
(( w_endpoint += 18 ))
|
||||
|
||||
ui::logs::wg_section_header
|
||||
while IFS='|' read -r ts client endpoint event count gap_seconds; do
|
||||
while IFS='|' read -r ts client endpoint event count gap_seconds resolved; do
|
||||
[[ -z "$ts" ]] && continue
|
||||
ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \
|
||||
"$count" "$w_client" "$w_endpoint" "$gap_seconds"
|
||||
"$count" "$w_client" "$w_endpoint" "$gap_seconds" "$resolved"
|
||||
done <<< "$resolved_data"
|
||||
printf "\n"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1690,7 +1690,8 @@ commands = {
|
|||
args[7] if len(args) > 7 else '',
|
||||
args[8] if len(args) > 8 else '',
|
||||
args[9] if len(args) > 9 else '',
|
||||
args[10] if len(args) > 10 else 'desc'),
|
||||
args[10] if len(args) > 10 else 'desc',
|
||||
args[11] if len(args) > 11 else ''),
|
||||
'wg_events': lambda args: __import__('lib.events', fromlist=['wg_events']).wg_events(
|
||||
args[0], args[1], args[2],
|
||||
args[3] if len(args) > 3 else '50',
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -22,7 +22,8 @@ from lib.util import (
|
|||
# ──────────────────────────────────────────
|
||||
|
||||
def fw_events(file, filter_ip, filter_type, clients_dir, net_file,
|
||||
limit, collapse='1', since='', filter_dest_ip='', filter_dest_port='', sort_order='desc'):
|
||||
limit, collapse='1', since='', filter_dest_ip='',
|
||||
filter_dest_port='', sort_order='desc', endpoint_cache_file=''):
|
||||
"""
|
||||
Format firewall drop events with dedup, counts, and service annotation.
|
||||
|
||||
|
|
@ -42,6 +43,14 @@ def fw_events(file, filter_ip, filter_type, clients_dir, net_file,
|
|||
net_data = load_net_data(net_file)
|
||||
hosts_data = load_hosts_data(None) # hosts lookup done in bash for now
|
||||
|
||||
endpoint_cache = {}
|
||||
if endpoint_cache_file and os.path.exists(endpoint_cache_file):
|
||||
try:
|
||||
with open(endpoint_cache_file) as f:
|
||||
endpoint_cache = json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
since_dt = parse_since(since) if since else None
|
||||
|
||||
def _reverse(dest_ip, dest_port, proto):
|
||||
|
|
@ -130,8 +139,9 @@ def fw_events(file, filter_ip, filter_type, clients_dir, net_file,
|
|||
for hour_key, dt in output:
|
||||
client, dst, port, proto, svc_name, _ = hour_key
|
||||
count = hourly[hour_key]
|
||||
ts_fmt = fmt_ts_hour(dt.isoformat())
|
||||
print(f"{ts_fmt}|{client}|{dst}|{port}|{proto}|{svc_name}|{count}")
|
||||
ts_fmt = fmt_ts_hour(hourly_ts[hour_key].isoformat())
|
||||
src_endpoint = endpoint_cache.get(client, '')
|
||||
print(f"{ts_fmt}|{client}|{dst}|{port}|{proto}|{svc_name}|{count}|{src_endpoint}")
|
||||
|
||||
else:
|
||||
# Detailed — consecutive dedup only
|
||||
|
|
@ -172,9 +182,14 @@ def fw_events(file, filter_ip, filter_type, clients_dir, net_file,
|
|||
proto_num = int(e.get('ip.protocol', 0))
|
||||
proto = PROTO_MAP.get(proto_num, str(proto_num))
|
||||
client = ip_to_name.get(src, src)
|
||||
svc_name = _reverse(dst, port, proto)
|
||||
ts_fmt = fmt_ts(e.get('timestamp', ''))
|
||||
print(f"{ts_fmt}|{client}|{dst}|{port}|{proto}|{svc_name}|{count}")
|
||||
svc_name = reverse_lookup(net_data, dst, port, proto)
|
||||
src_endpoint = endpoint_cache.get(client, '')
|
||||
try:
|
||||
dt = datetime.fromisoformat(e.get('timestamp', ''))
|
||||
ts_fmt = dt.strftime(DATETIME_FMT)
|
||||
except Exception:
|
||||
ts_fmt = e.get('timestamp', '')
|
||||
print(f"{ts_fmt}|{client}|{dst}|{port}|{proto}|{svc_name}|{count}|{src_endpoint}")
|
||||
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ function ui::logs::build_dest() {
|
|||
}
|
||||
|
||||
function ui::logs::fw_section_header() {
|
||||
local cols=$(( $(tput cols 2>/dev/null || echo 80) - 4 ))
|
||||
printf " Firewall Drops\n"
|
||||
printf " %s\n" "$(printf '─%.0s' {1..42})"
|
||||
printf " %s\n" "$(printf '─%.0s' $(seq 1 $cols))"
|
||||
}
|
||||
|
||||
function ui::logs::wg_section_header() {
|
||||
local cols=$(( $(tput cols 2>/dev/null || echo 80) - 4 ))
|
||||
printf " WireGuard Events\n"
|
||||
printf " %s\n" "$(printf '─%.0s' {1..42})"
|
||||
printf " %s\n" "$(printf '─%.0s' $(seq 1 $cols))"
|
||||
}
|
||||
|
||||
function ui::logs::fw_section_header_table() {
|
||||
|
|
@ -35,17 +37,67 @@ function ui::logs::wg_section_header_table() {
|
|||
function ui::logs::fw_row() {
|
||||
local ts="${1:-}" client="${2:-}" dest_ip="${3:-}" dest_port="${4:-}" \
|
||||
proto="${5:-}" svc_name="${6:-}" count="${7:-1}" \
|
||||
w_client="${8:-20}" w_dest="${9:-30}"
|
||||
local dest_display
|
||||
dest_display=$(ui::logs::build_dest "$dest_ip" "$dest_port" "$proto" "$svc_name")
|
||||
w_client="${8:-20}" w_dest="${9:-30}" \
|
||||
src_endpoint="${10:-}" src_resolved="${11:-}" w_endpoint="${12:-0}"
|
||||
|
||||
local ts_pad client_pad
|
||||
ts_pad=$(printf "%-11s" "$ts")
|
||||
client_pad=$(printf "%-${w_client}s" "$client")
|
||||
|
||||
# ── Source endpoint — always render at w_endpoint width ──
|
||||
local src_padded=""
|
||||
if [[ "$w_endpoint" -gt 0 ]]; then
|
||||
if [[ -n "$src_endpoint" ]]; then
|
||||
local src_colored="$src_endpoint"
|
||||
[[ -n "$src_resolved" ]] && \
|
||||
src_colored="${src_endpoint} \033[2m→ ${src_resolved}\033[0m"
|
||||
src_padded=$(ui::pad_mb "$src_colored" "$w_endpoint")
|
||||
else
|
||||
# No endpoint — use dim dash padded to w_endpoint
|
||||
src_padded=$(ui::pad_mb "\033[2m—\033[0m" "$w_endpoint")
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Destination ──
|
||||
local svc_display=""
|
||||
local raw_suffix=""
|
||||
if [[ -n "$svc_name" ]]; then
|
||||
[[ -n "$dest_port" ]] && svc_display="${svc_name}/${proto}" \
|
||||
|| svc_display="${svc_name} (${proto})"
|
||||
[[ -n "$dest_port" ]] && raw_suffix=" \033[2m(${dest_ip}:${dest_port})\033[0m" \
|
||||
|| raw_suffix=" \033[2m(${dest_ip})\033[0m"
|
||||
else
|
||||
[[ -n "$dest_port" ]] && svc_display="${dest_ip}:${dest_port}/${proto}" \
|
||||
|| svc_display="${dest_ip} (${proto})"
|
||||
fi
|
||||
|
||||
# Pad so count aligns — based on full dest (svc + raw_suffix plain length)
|
||||
local raw_plain=""
|
||||
[[ -n "$svc_name" && -n "$dest_port" ]] && raw_plain=" (${dest_ip}:${dest_port})"
|
||||
[[ -n "$svc_name" && -z "$dest_port" ]] && raw_plain=" (${dest_ip})"
|
||||
local full_dest_len=$(( ${#svc_display} + ${#raw_plain} ))
|
||||
local dest_pad_n=$(( w_dest - full_dest_len ))
|
||||
[[ $dest_pad_n -lt 0 ]] && dest_pad_n=0
|
||||
|
||||
# ── Count ──
|
||||
local count_suffix=""
|
||||
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
||||
local client_pad dest_pad_n
|
||||
client_pad=$(printf "%-${w_client}s" "$client")
|
||||
dest_pad_n=$(( w_dest - ${#dest_display} ))
|
||||
[[ $dest_pad_n -lt 0 ]] && dest_pad_n=0
|
||||
printf " %s %s \033[1;31m→\033[0m %s%*s%b\n" \
|
||||
"$ts" "$client_pad" "$dest_display" "$dest_pad_n" "" "$count_suffix"
|
||||
|
||||
# ── Render ──
|
||||
if [[ "$w_endpoint" -gt 0 ]]; then
|
||||
printf " %s %s %b \033[1;31m→\033[0m %s%b%*s%b\n" \
|
||||
"$ts_pad" "$client_pad" \
|
||||
"$src_padded" \
|
||||
"$svc_display" "$raw_suffix" \
|
||||
"$dest_pad_n" "" \
|
||||
"$count_suffix"
|
||||
else
|
||||
printf " %s %s \033[1;31m→\033[0m %s%b%*s%b\n" \
|
||||
"$ts_pad" "$client_pad" \
|
||||
"$svc_display" "$raw_suffix" \
|
||||
"$dest_pad_n" "" \
|
||||
"$count_suffix"
|
||||
fi
|
||||
}
|
||||
|
||||
function ui::logs::fw_row_table() {
|
||||
|
|
@ -58,7 +110,7 @@ function ui::logs::fw_row_table() {
|
|||
function ui::logs::wg_row() {
|
||||
local ts="${1:-}" client="${2:-}" endpoint="${3:-}" event="${4:-}" \
|
||||
count="${5:-1}" w_client="${6:-20}" w_endpoint="${7:-20}" \
|
||||
gap_seconds="${8:-}"
|
||||
gap_seconds="${8:-}" resolved="${9:-}"
|
||||
|
||||
local event_color
|
||||
case "$event" in
|
||||
|
|
@ -70,13 +122,13 @@ function ui::logs::wg_row() {
|
|||
local count_suffix=""
|
||||
[[ "$count" -gt 1 ]] && count_suffix=" \033[2m(x${count})\033[0m"
|
||||
|
||||
# Gap suffix — only for handshakes with a meaningful gap
|
||||
# Gap suffix — offline label only when gap > threshold * 2
|
||||
local gap_suffix=""
|
||||
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" ]] && offline_label=" offline"
|
||||
[[ "$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
|
||||
|
|
@ -84,13 +136,25 @@ function ui::logs::wg_row() {
|
|||
fi
|
||||
fi
|
||||
|
||||
local client_pad endpoint_pad_n
|
||||
client_pad=$(printf "%-${w_client}s" "$client")
|
||||
endpoint_pad_n=$(( w_endpoint - ${#endpoint} ))
|
||||
[[ $endpoint_pad_n -lt 0 ]] && endpoint_pad_n=0
|
||||
# Build endpoint display: raw_ip [dim → resolved]
|
||||
# Use ui::pad_mb so ANSI annotation doesn't affect column alignment
|
||||
local endpoint_raw="${endpoint:--}"
|
||||
local endpoint_colored
|
||||
if [[ -n "$resolved" && -n "$endpoint" ]]; then
|
||||
endpoint_colored="${endpoint} \033[2m→ ${resolved}\033[0m"
|
||||
else
|
||||
endpoint_colored="$endpoint_raw"
|
||||
fi
|
||||
local endpoint_padded
|
||||
endpoint_padded=$(ui::pad_mb "$endpoint_colored" "$w_endpoint")
|
||||
|
||||
printf " %s %s %s%*s %b%s\033[0m%b%b\n" \
|
||||
"$ts" "$client_pad" "$endpoint" "$endpoint_pad_n" "" \
|
||||
local ts_pad client_pad
|
||||
ts_pad=$(printf "%-11s" "$ts")
|
||||
client_pad=$(printf "%-${w_client}s" "$client")
|
||||
|
||||
printf " %s %s %b %b%s\033[0m%b%b\n" \
|
||||
"$ts_pad" "$client_pad" \
|
||||
"$endpoint_padded" \
|
||||
"$event_color" "$event" "$count_suffix" "$gap_suffix"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue