diff --git a/install.sh b/install.sh index 45eaf2b..70babad 100755 --- a/install.sh +++ b/install.sh @@ -38,11 +38,11 @@ show_main_menu() { local line; line=$(printf '━%.0s' $(seq 1 $W)) local line2; line2=$(printf '─%.0s' $(seq 1 $W)) - # ── Заголовок ── + # ── Заголовок (без правого бордера — ANSI ломает выравнивание) ── echo "" - echo -e " ${BOLD}${CYAN}┏${line}┓${NC}" - echo -e " ${BOLD}${CYAN}┃${NC} ${BOLD}${WHITE}GoTelegram v${GOTELEGRAM_VERSION}${NC} Панель управления ${BOLD}${CYAN}┃${NC}" - echo -e " ${BOLD}${CYAN}┗${line}┛${NC}" + echo -e " ${BOLD}${CYAN}━${line}━${NC}" + echo -e " ${BOLD}${WHITE} GoTelegram v${GOTELEGRAM_VERSION}${NC} ${DIM}— Панель управления${NC}" + echo -e " ${BOLD}${CYAN}━${line}━${NC}" # ── Здоровье сервисов ── echo "" @@ -109,9 +109,10 @@ show_main_menu() { echo -e " ${BOLD}${WHITE}Ссылка для Telegram:${NC}" echo -e " ${GREEN}${link}${NC}" - echo "" if command -v qrencode &>/dev/null; then + echo "" + echo "" qrencode -t UTF8 -m 1 "$link" 2>/dev/null | while IFS= read -r qr_line; do echo " ${qr_line}" done diff --git a/lib/stats.sh b/lib/stats.sh index 799ba73..adcee19 100755 --- a/lib/stats.sh +++ b/lib/stats.sh @@ -85,16 +85,19 @@ EOF fi # Save snapshot for rate calculation (one per minute) - local minute_key=$(date +%s -d "1 minute ago" +%Y%m%d%H%M 2>/dev/null || date +%Y%m%d%H%M) + local minute_key + minute_key=$(date +%Y%m%d%H%M 2>/dev/null) local snapshot_file="$SNAPSHOTS_DIR/snap_${minute_key}.json" cp "$CURRENT_SNAPSHOT" "$snapshot_file" 2>/dev/null # Append to history CSV (once per minute, check if last entry is fresh) if [[ -f "$HISTORY_FILE" ]]; then - local last_ts=$(tail -1 "$HISTORY_FILE" 2>/dev/null | cut -d, -f1) + local last_ts + last_ts=$(grep -E '^[0-9]' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f1) + last_ts="${last_ts:-0}" local current_minute=$((ts - (ts % 60))) - if [[ -z "$last_ts" ]] || [[ $((current_minute - last_ts)) -ge 60 ]]; then + if [[ "$last_ts" -eq 0 ]] || [[ $((current_minute - last_ts)) -ge 60 ]]; then echo "$current_minute,$proxy_bytes,$site_bytes" >> "$HISTORY_FILE" 2>/dev/null # Cleanup old entries (keep only 365 days) @@ -158,82 +161,61 @@ format_rate() { fi } +# Safely convert value to integer (returns 0 for empty/non-numeric) +_to_int() { + local val="${1:-0}" + # Strip non-numeric chars, default to 0 + val="${val//[^0-9]/}" + echo "${val:-0}" +} + +# Calculate diff safely (never negative, never crashes on empty) +_safe_diff() { + local a=$(_to_int "$1") + local b=$(_to_int "$2") + local d=$((a - b)) + (( d < 0 )) && d=0 + echo "$d" +} + # Calculate traffic rates and totals from history stats_calculate_rates() { local traffic_type="$1" # "proxy" or "site" local col_idx=2 # proxy_bytes is column 2 + [[ "$traffic_type" == "site" ]] && col_idx=3 - [[ "$traffic_type" == "site" ]] && col_idx=3 # site_bytes is column 3 + local now + now=$(date +%s) - local now=$(date +%s) - local result="" + # Get latest data line (skip header with grep -E '^[0-9]') + local bytes_now + bytes_now=$(_to_int "$(grep -E '^[0-9]' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f"$col_idx")") - # 1 minute rate - local ts_1m=$((now - 60)) - local bytes_now=$(tail -1 "$HISTORY_FILE" 2>/dev/null | cut -d, -f"$col_idx") - local bytes_1m=$(awk -F, -v ts="$ts_1m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_1m=$((bytes_now - (bytes_1m > 0 ? bytes_1m : bytes_now))) - [[ $diff_1m -lt 0 ]] && diff_1m=0 - local rate_1m=$((diff_1m / 60)) - local bytes_1m_fmt=$(format_bytes "$diff_1m") - local rate_1m_fmt=$(format_rate "$rate_1m") + local periods="60 300 3600 86400 604800 2592000 31536000" + local results="" - # 5 minute rate - local ts_5m=$((now - 300)) - local bytes_5m=$(awk -F, -v ts="$ts_5m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_5m=$((bytes_now - (bytes_5m > 0 ? bytes_5m : bytes_now))) - [[ $diff_5m -lt 0 ]] && diff_5m=0 - local rate_5m=$((diff_5m / 300)) - local bytes_5m_fmt=$(format_bytes "$diff_5m") - local rate_5m_fmt=$(format_rate "$rate_5m") + for secs in $periods; do + local target_ts=$((now - secs)) + # Find closest entry at or after target timestamp (skip header) + local old_val + old_val=$(_to_int "$(awk -F, -v ts="$target_ts" '$1 ~ /^[0-9]/ && $1 <= ts' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f"$col_idx")") - # 60 minute rate - local ts_60m=$((now - 3600)) - local bytes_60m=$(awk -F, -v ts="$ts_60m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_60m=$((bytes_now - (bytes_60m > 0 ? bytes_60m : bytes_now))) - [[ $diff_60m -lt 0 ]] && diff_60m=0 - local rate_60m=$((diff_60m / 3600)) - local bytes_60m_fmt=$(format_bytes "$diff_60m") - local rate_60m_fmt=$(format_rate "$rate_60m") + local diff + diff=$(_safe_diff "$bytes_now" "$old_val") + local rate=$(( secs > 0 ? diff / secs : 0 )) - # 1 day total - local ts_1d=$((now - 86400)) - local bytes_1d=$(awk -F, -v ts="$ts_1d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_1d=$((bytes_now - (bytes_1d > 0 ? bytes_1d : bytes_now))) - [[ $diff_1d -lt 0 ]] && diff_1d=0 - local rate_1d=$((diff_1d > 0 ? diff_1d / 86400 : 0)) - local bytes_1d_fmt=$(format_bytes "$diff_1d") - local rate_1d_fmt=$(format_rate "$rate_1d") + local bytes_fmt rate_fmt + bytes_fmt=$(format_bytes "$diff") + rate_fmt=$(format_rate "$rate") - # 7 days total - local ts_7d=$((now - 604800)) - local bytes_7d=$(awk -F, -v ts="$ts_7d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_7d=$((bytes_now - (bytes_7d > 0 ? bytes_7d : bytes_now))) - [[ $diff_7d -lt 0 ]] && diff_7d=0 - local rate_7d=$((diff_7d > 0 ? diff_7d / 604800 : 0)) - local bytes_7d_fmt=$(format_bytes "$diff_7d") - local rate_7d_fmt=$(format_rate "$rate_7d") + if [ -z "$results" ]; then + results="${bytes_fmt}|${rate_fmt}" + else + results="${results}|${bytes_fmt}|${rate_fmt}" + fi + done - # 30 days total - local ts_30d=$((now - 2592000)) - local bytes_30d=$(awk -F, -v ts="$ts_30d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_30d=$((bytes_now - (bytes_30d > 0 ? bytes_30d : bytes_now))) - [[ $diff_30d -lt 0 ]] && diff_30d=0 - local rate_30d=$((diff_30d > 0 ? diff_30d / 2592000 : 0)) - local bytes_30d_fmt=$(format_bytes "$diff_30d") - local rate_30d_fmt=$(format_rate "$rate_30d") - - # 365 days total - local ts_365d=$((now - 31536000)) - local bytes_365d=$(awk -F, -v ts="$ts_365d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") - local diff_365d=$((bytes_now - (bytes_365d > 0 ? bytes_365d : bytes_now))) - [[ $diff_365d -lt 0 ]] && diff_365d=0 - local rate_365d=$((diff_365d > 0 ? diff_365d / 31536000 : 0)) - local bytes_365d_fmt=$(format_bytes "$diff_365d") - local rate_365d_fmt=$(format_rate "$rate_365d") - - # Return as pipe-delimited format for table display - echo "$bytes_1m_fmt|$rate_1m_fmt|$bytes_5m_fmt|$rate_5m_fmt|$bytes_60m_fmt|$rate_60m_fmt|$bytes_1d_fmt|$rate_1d_fmt|$bytes_7d_fmt|$rate_7d_fmt|$bytes_30d_fmt|$rate_30d_fmt|$bytes_365d_fmt|$rate_365d_fmt" + echo "$results" } # Main display function for traffic statistics