Redesign console dashboard with btop-like TUI
Этот коммит содержится в:
@@ -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<height-1; i++)); do
|
||||
move $((row+i)) "$col"; printf "${FG_BLUE}│${RESET}"
|
||||
printf "${BG_PANEL}%s${RESET}" "$(repeat_char ' ' "$inner")"
|
||||
printf "${FG_BLUE}│${RESET}"
|
||||
done
|
||||
move $((row+height-1)) "$col"; printf "${FG_BLUE}╰%s╯${RESET}" "$(repeat_char '─' "$inner")"
|
||||
}
|
||||
|
||||
put() {
|
||||
local row="$1" col="$2" width="$3" text="$4"
|
||||
move "$row" "$col"
|
||||
printf "${BG_PANEL}"
|
||||
printf "%b" "$text" | cut -c 1-"$width"
|
||||
printf "${RESET}"
|
||||
}
|
||||
|
||||
bar() {
|
||||
local percent="$1" width="$2"
|
||||
local filled=$(( percent * width / 100 ))
|
||||
local empty=$(( width - filled ))
|
||||
local color="$FG_GREEN"
|
||||
if (( percent >= 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
|
||||
|
||||
Ссылка в новой задаче
Block a user