diff --git a/scripts/virtuality_console_dashboard.sh b/scripts/virtuality_console_dashboard.sh index 97e5045..65cc6df 100644 --- a/scripts/virtuality_console_dashboard.sh +++ b/scripts/virtuality_console_dashboard.sh @@ -1,10 +1,80 @@ #!/usr/bin/env bash set -euo pipefail -refresh_interval="${VIRTUALITY_DASHBOARD_INTERVAL:-5}" +# Virtuality Console Dashboard +# btop-like compact TUI for physical server console tty1 -has_cmd() { - command -v "$1" >/dev/null 2>&1 +refresh_interval="${VIRTUALITY_DASHBOARD_INTERVAL:-2}" + +has_cmd() { command -v "$1" >/dev/null 2>&1; } + +ESC="\033" +RESET="${ESC}[0m" +BOLD="${ESC}[1m" +DIM="${ESC}[2m" +FG_CYAN="${ESC}[38;5;51m" +FG_BLUE="${ESC}[38;5;39m" +FG_GREEN="${ESC}[38;5;46m" +FG_YELLOW="${ESC}[38;5;226m" +FG_RED="${ESC}[38;5;196m" +FG_MAGENTA="${ESC}[38;5;201m" +FG_GRAY="${ESC}[38;5;245m" +FG_WHITE="${ESC}[38;5;255m" +BG_PANEL="${ESC}[48;5;235m" +BG_HEADER="${ESC}[48;5;24m" + +move() { printf "${ESC}[%s;%sH" "$1" "$2"; } +clear_screen() { printf "${ESC}[2J${ESC}[H"; } +hide_cursor() { printf "${ESC}[?25l"; } +show_cursor() { printf "${ESC}[?25h${RESET}"; } +trap show_cursor EXIT + +term_cols() { tput cols 2>/dev/null || echo 120; } +term_rows() { tput lines 2>/dev/null || echo 40; } + +repeat_char() { + local char="$1" count="$2" + printf '%*s' "$count" '' | tr ' ' "$char" +} + +strip_ansi() { + sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' +} + +print_fit() { + local text="$1" width="$2" + printf "%-${width}.${width}s" "$text" +} + +box() { + local row="$1" col="$2" height="$3" width="$4" title="$5" + local inner=$((width - 2)) + move "$row" "$col"; printf "${FG_BLUE}╭%s╮${RESET}" "$(repeat_char '─' "$inner")" + move "$row" $((col + 2)); printf "${BG_PANEL}${FG_CYAN}${BOLD} %s ${RESET}" "$title" + for ((i=1; i= 85 )); then color="$FG_RED"; elif (( percent >= 65 )); then color="$FG_YELLOW"; fi + printf "%b%s%b%s" "$color" "$(repeat_char '█' "$filled")" "$DIM" "$(repeat_char '░' "$empty")" + printf "%b" "$RESET" } service_state() { @@ -16,78 +86,139 @@ service_state() { fi } +state_badge() { + local state="$1" + case "$state" in + active) printf "${FG_GREEN}● active${RESET}" ;; + inactive) printf "${FG_YELLOW}● inactive${RESET}" ;; + failed) printf "${FG_RED}● failed${RESET}" ;; + missing) printf "${FG_GRAY}● missing${RESET}" ;; + *) printf "${FG_YELLOW}● %s${RESET}" "$state" ;; + esac +} + +get_cpu_usage() { + local a b idle_a total_a idle_b total_b diff_idle diff_total usage + read -r _ a b c idle_a rest < /proc/stat + total_a=$((a+b+c+idle_a)) + sleep 0.15 + read -r _ a b c idle_b rest < /proc/stat + total_b=$((a+b+c+idle_b)) + diff_idle=$((idle_b-idle_a)) + diff_total=$((total_b-total_a)) + if (( diff_total <= 0 )); then echo 0; else echo $(( (100 * (diff_total - diff_idle)) / diff_total )); fi +} + +get_mem_percent() { + free | awk '/Mem:/ {printf "%d", ($3/$2)*100}' +} + +get_disk_percent() { + df / | awk 'NR==2 {gsub("%", "", $5); print $5}' +} + while true; do - clear - echo "============================================================" - echo " VIRTUALITY NODE DASHBOARD" - echo "============================================================" - echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" - echo "Hostname: $(hostname)" - echo "Uptime: $(uptime -p 2>/dev/null || true)" - echo "Kernel: $(uname -r)" - if [[ -f /etc/os-release ]]; then - . /etc/os-release - echo "OS: ${PRETTY_NAME}" - fi - echo + cols="$(term_cols)" + rows="$(term_rows)" + [[ "$cols" -lt 100 ]] && cols=100 + [[ "$rows" -lt 32 ]] && rows=32 - echo "[SYSTEM]" - echo "Load: $(awk '{print $1, $2, $3}' /proc/loadavg)" - echo "CPU virt: $(grep -E -q '(vmx|svm)' /proc/cpuinfo && echo OK || echo WARNING)" - free -h | awk '/Mem:/ {print "Memory: used " $3 " / total " $2}' - df -h / | awk 'NR==2 {print "Root FS: used " $3 " / total " $2 " (" $5 ")"}' - echo + cpu="$(get_cpu_usage)" + mem="$(get_mem_percent)" + disk="$(get_disk_percent)" + load="$(awk '{print $1" "$2" "$3}' /proc/loadavg)" + uptime_text="$(uptime -p 2>/dev/null | sed 's/up //' || true)" + host="$(hostname)" + now="$(date '+%Y-%m-%d %H:%M:%S')" + ip_main="$(hostname -I 2>/dev/null | awk '{print $1}')" + gateway="$(ip route | awk '/default/ {print $3" via "$5; exit}')" - echo "[NETWORK]" - ip -br a 2>/dev/null | sed 's/^/ /' || true - echo "Gateway: $(ip route | awk '/default/ {print $3 " via " $5; exit}')" - echo + clear_screen + hide_cursor - echo "[VIRTUALITY SERVICES]" - printf " %-24s %s\n" "libvirtd.service" "$(service_state libvirtd.service)" - printf " %-24s %s\n" "virtlogd.service" "$(service_state virtlogd.service)" - printf " %-24s %s\n" "cockpit.socket" "$(service_state cockpit.socket)" - printf " %-24s %s\n" "libvirt-guests.service" "$(service_state libvirt-guests.service)" - echo + move 1 1 + printf "${BG_HEADER}${FG_WHITE}${BOLD} %-20s ${FG_CYAN}%-30s ${FG_GRAY}%s ${RESET}" "VIRTUALITY" "$host" "$now" + printf "%s" "$(repeat_char ' ' $((cols-70 > 0 ? cols-70 : 1)))" - echo "[LIBVIRT STORAGE POOLS]" - if has_cmd virsh; then - virsh pool-list --all 2>/dev/null | sed 's/^/ /' || echo " virsh pool-list unavailable" + # Layout + left_w=48 + right_w=$((cols - left_w - 5)) + [[ "$right_w" -lt 48 ]] && right_w=48 + + box 3 2 10 "$left_w" "SYSTEM" + box 3 $((left_w + 4)) 10 "$right_w" "SERVICES" + box 14 2 8 "$left_w" "NETWORK" + box 14 $((left_w + 4)) 8 "$right_w" "VIRTUAL MACHINES" + box 23 2 8 "$left_w" "STORAGE" + box 23 $((left_w + 4)) 8 "$right_w" "LIBVIRT POOLS" + + # System panel + put 5 4 $((left_w-4)) "${FG_GRAY}CPU ${FG_WHITE}${cpu}% $(bar "$cpu" 26)" + put 6 4 $((left_w-4)) "${FG_GRAY}RAM ${FG_WHITE}${mem}% $(bar "$mem" 26)" + put 7 4 $((left_w-4)) "${FG_GRAY}DSK ${FG_WHITE}${disk}% $(bar "$disk" 26)" + put 8 4 $((left_w-4)) "${FG_GRAY}Load:${RESET} ${FG_WHITE}${load}${RESET}" + put 9 4 $((left_w-4)) "${FG_GRAY}Uptime:${RESET} ${FG_WHITE}${uptime_text}${RESET}" + put 10 4 $((left_w-4)) "${FG_GRAY}Kernel:${RESET} ${FG_WHITE}$(uname -r)${RESET}" + + # Services panel + svc_col=$((left_w + 6)) + svc_w=$((right_w - 4)) + put 5 "$svc_col" "$svc_w" "libvirtd.service $(state_badge "$(service_state libvirtd.service)")" + put 6 "$svc_col" "$svc_w" "virtlogd.service $(state_badge "$(service_state virtlogd.service)")" + put 7 "$svc_col" "$svc_w" "cockpit.socket $(state_badge "$(service_state cockpit.socket)")" + put 8 "$svc_col" "$svc_w" "libvirt-guests.service $(state_badge "$(service_state libvirt-guests.service)")" + put 10 "$svc_col" "$svc_w" "${FG_GRAY}Cockpit:${RESET} ${FG_WHITE}https://${ip_main}:9090${RESET}" + + # Network panel + put 16 4 $((left_w-4)) "${FG_GRAY}Main IP:${RESET} ${FG_GREEN}${ip_main}${RESET}" + put 17 4 $((left_w-4)) "${FG_GRAY}Gateway:${RESET} ${FG_WHITE}${gateway}${RESET}" + net_lines="$(ip -br a 2>/dev/null | head -n 3)" + n=0 + while IFS= read -r line; do + put $((18+n)) 4 $((left_w-4)) "${FG_WHITE}${line}${RESET}" + n=$((n+1)) + done <<< "$net_lines" + + # VM panel + vm_col=$((left_w + 6)) + vm_lines="$(virsh list --all 2>/dev/null | tail -n +3 | sed '/^$/d' | head -n 4 || true)" + if [[ -z "$vm_lines" ]]; then + put 16 "$vm_col" "$svc_w" "${FG_GRAY}VM пока нет. Следующий этап — создать тестовую VM.${RESET}" else - echo " virsh not installed" + n=0 + while IFS= read -r line; do + put $((16+n)) "$vm_col" "$svc_w" "${FG_WHITE}${line}${RESET}" + n=$((n+1)) + done <<< "$vm_lines" fi - echo - echo "[VIRTUAL MACHINES]" - if has_cmd virsh; then - virsh list --all 2>/dev/null | sed 's/^/ /' || echo " virsh list unavailable" - else - echo " virsh not installed" - fi - echo - - echo "[STORAGE PATHS]" + # Storage panel + srow=25 for dir in /var/lib/virtuality/iso /var/lib/virtuality/images /var/lib/virtuality/backups /var/log/virtuality; do if [[ -d "$dir" ]]; then used="$(du -sh "$dir" 2>/dev/null | awk '{print $1}')" - printf " %-34s %s\n" "$dir" "$used" + put "$srow" 4 $((left_w-4)) "${FG_GRAY}${dir}:${RESET} ${FG_WHITE}${used}${RESET}" else - printf " %-34s %s\n" "$dir" "missing" + put "$srow" 4 $((left_w-4)) "${FG_GRAY}${dir}:${RESET} ${FG_RED}missing${RESET}" fi + srow=$((srow+1)) done - echo - echo "[FIREWALL]" - if has_cmd ufw; then - ufw status 2>/dev/null | head -n 12 | sed 's/^/ /' || true + # Pools panel + pool_lines="$(virsh pool-list --all 2>/dev/null | tail -n +3 | sed '/^$/d' | head -n 4 || true)" + if [[ -z "$pool_lines" ]]; then + put 25 "$vm_col" "$svc_w" "${FG_GRAY}Storage pools unavailable${RESET}" else - echo " ufw not installed" + n=0 + while IFS= read -r line; do + put $((25+n)) "$vm_col" "$svc_w" "${FG_WHITE}${line}${RESET}" + n=$((n+1)) + done <<< "$pool_lines" fi - echo - echo "============================================================" - echo "Refresh: ${refresh_interval}s | btop: press Ctrl+Alt+F2 login and run bt" - echo "Stop dashboard temporarily: sudo systemctl stop virtuality-console-dashboard" - echo "============================================================" + move $((rows-2)) 2 + printf "${FG_GRAY}Refresh:${RESET} ${FG_WHITE}${refresh_interval}s${RESET} ${FG_GRAY}|${RESET} ${FG_CYAN}Ctrl+Alt+F2${RESET} login ${FG_GRAY}|${RESET} ${FG_GREEN}bt${RESET}=btop ${FG_GRAY}|${RESET} stop: ${FG_YELLOW}sudo systemctl stop virtuality-console-dashboard${RESET}" + move $((rows-1)) 1 + printf "${RESET}" sleep "$refresh_interval" done