393 lines
14 KiB
Bash
Executable File
393 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================
|
|
# tmux 实时监控面板 — 管理与显示
|
|
#
|
|
# 使用方法:
|
|
# bash scripts/tmux-monitor.sh # 创建监控面板 (5秒刷新)
|
|
# bash scripts/tmux-monitor.sh <秒数> # 创建监控面板 (自定义刷新)
|
|
# bash scripts/tmux-monitor.sh -k # 关闭监控面板
|
|
# bash scripts/tmux-monitor.sh -r # 重启监控面板
|
|
# bash scripts/tmux-monitor.sh -h # 显示帮助
|
|
# bash scripts/tmux-monitor.sh --display # (内部) 运行显示循环
|
|
# ==============================================================================
|
|
|
|
SCRIPT_PATH="/workspace/scripts/tmux-monitor.sh"
|
|
|
|
# ── 参数解析 ──────────────────────────────────────────────
|
|
|
|
ACTION="create"
|
|
INTERVAL=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--display)
|
|
ACTION="display"; shift
|
|
;;
|
|
-k|--kill)
|
|
ACTION="kill"; shift
|
|
;;
|
|
-r|--restart)
|
|
ACTION="restart"; shift
|
|
;;
|
|
-h|--help)
|
|
ACTION="help"; shift
|
|
;;
|
|
-*)
|
|
echo "未知选项: $1" >&2; exit 1
|
|
;;
|
|
*)
|
|
INTERVAL="$1"; shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
INTERVAL="${INTERVAL:-1}"
|
|
|
|
# ── 前置检查 ──────────────────────────────────────────────
|
|
|
|
if ! command -v tmux &>/dev/null; then
|
|
echo "错误: tmux 未安装" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── 管理函数 ──────────────────────────────────────────────
|
|
|
|
find_monitor_pane() {
|
|
tmux list-panes -F '#{pane_index} #{pane_start_command}' 2>/dev/null \
|
|
| grep -i 'tmux-monitor' \
|
|
| head -1 \
|
|
| awk '{print $1}'
|
|
}
|
|
|
|
do_kill() {
|
|
local pane_idx
|
|
pane_idx=$(find_monitor_pane)
|
|
if [ -n "$pane_idx" ]; then
|
|
tmux kill-pane -t "$pane_idx"
|
|
echo "监控面板 (Pane ${pane_idx}) 已关闭"
|
|
return 0
|
|
else
|
|
echo "未找到运行中的监控面板"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
do_create() {
|
|
if [ -z "${TMUX:-}" ]; then
|
|
echo "错误: 需要在 tmux 会话内运行" >&2
|
|
return 1
|
|
fi
|
|
|
|
# 关闭已有监控面板
|
|
local pane_idx
|
|
pane_idx=$(find_monitor_pane)
|
|
if [ -n "$pane_idx" ]; then
|
|
tmux kill-pane -t "$pane_idx"
|
|
fi
|
|
|
|
# 右侧监控面板固定宽度 60
|
|
local monitor_width=60
|
|
|
|
tmux split-window -h -l "$monitor_width" \
|
|
"TMUX_MONITOR_INTERVAL=${INTERVAL} bash ${SCRIPT_PATH} --display"
|
|
|
|
echo "监控面板已启动 (${INTERVAL} 秒刷新)"
|
|
}
|
|
|
|
# ── 非显示操作: 执行后退出 ───────────────────────────────
|
|
|
|
if [ "$ACTION" != "display" ]; then
|
|
case "$ACTION" in
|
|
help)
|
|
echo "用法: bash $(basename "$0") [选项] [刷新间隔]"
|
|
echo ""
|
|
echo "选项:"
|
|
echo " (空) 创建监控面板 (默认 ${INTERVAL} 秒刷新)"
|
|
echo " <秒数> 创建监控面板 (自定义刷新间隔)"
|
|
echo " -k, --kill 关闭监控面板"
|
|
echo " -r, --restart 重启监控面板"
|
|
echo " -h, --help 显示帮助信息"
|
|
;;
|
|
kill)
|
|
do_kill
|
|
;;
|
|
restart)
|
|
do_kill 2>/dev/null || true
|
|
do_create
|
|
;;
|
|
create)
|
|
do_create
|
|
;;
|
|
esac
|
|
exit $?
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════
|
|
# 以下为 display 模式: 运行监控显示循环
|
|
# ══════════════════════════════════════════════════════════
|
|
|
|
# 颜色定义 — 使用 $'...' ANSI-C 引号,赋值时即产生真正的 ESC 字节
|
|
readonly R=$'\033[0;31m' # RED
|
|
readonly G=$'\033[0;32m' # GREEN
|
|
readonly Y=$'\033[0;33m' # YELLOW
|
|
readonly B=$'\033[0;34m' # BLUE
|
|
readonly P=$'\033[0;35m' # PURPLE
|
|
readonly C=$'\033[0;36m' # CYAN
|
|
readonly D=$'\033[0;90m' # GRAY/DIM
|
|
readonly BD=$'\033[1m' # BOLD
|
|
readonly N=$'\033[0m' # RESET
|
|
|
|
# 刷新间隔(秒)— 由管理模式通过 TMUX_MONITOR_INTERVAL 传入
|
|
INTERVAL="${TMUX_MONITOR_INTERVAL:-1}"
|
|
|
|
# ── 工具函数 ──────────────────────────────────────────────
|
|
|
|
# 生成指定宽度的分隔线
|
|
make_sep() {
|
|
local width="${1:-80}"
|
|
local sep_char='─'
|
|
printf '%*s' "$width" '' | tr ' ' "$sep_char"
|
|
}
|
|
|
|
# ── 显示层级常量 ─────────────────────────────────────────
|
|
# 根据终端宽度决定显示详细程度
|
|
LVL_MINIMAL=0 # < 50 列:仅显示核心信息
|
|
LVL_COMPACT=1 # 50-69 列:省略 PID、路径、子命令
|
|
LVL_NORMAL=2 # 70-99 列:省略子命令详情
|
|
LVL_FULL=3 # >= 100 列:显示全部
|
|
|
|
get_display_level() {
|
|
local cols="$1"
|
|
if [ "$cols" -lt 50 ]; then
|
|
echo "$LVL_MINIMAL"
|
|
elif [ "$cols" -lt 70 ]; then
|
|
echo "$LVL_COMPACT"
|
|
elif [ "$cols" -lt 100 ]; then
|
|
echo "$LVL_NORMAL"
|
|
else
|
|
echo "$LVL_FULL"
|
|
fi
|
|
}
|
|
|
|
# 记录终端尺寸,用于检测 resize
|
|
last_cols=0
|
|
last_rows=0
|
|
|
|
while true; do
|
|
# ── 检测终端尺寸变化,清屏防重叠 ──
|
|
cur_cols=$(tput cols 2>/dev/null || echo 80)
|
|
cur_rows=$(tput lines 2>/dev/null || echo 24)
|
|
if [ "$cur_cols" != "$last_cols" ] || [ "$cur_rows" != "$last_rows" ]; then
|
|
tput clear
|
|
last_cols=$cur_cols
|
|
last_rows=$cur_rows
|
|
fi
|
|
|
|
# 当前显示层级
|
|
level=$(get_display_level "$cur_cols")
|
|
|
|
# 动态分隔线
|
|
SEP=$(make_sep "$cur_cols")
|
|
|
|
# ── 构建输出到缓冲区 ──
|
|
buf=""
|
|
buf="${buf}\n"
|
|
buf="${buf}${BD}${SEP}${N}\n"
|
|
|
|
# 标题行:自适应宽度
|
|
title=" tmux 实时监控 $(date '+%H:%M:%S')"
|
|
if [ "$level" -ge "$LVL_COMPACT" ]; then
|
|
buf="${buf}${C}${BD}${title}${N}\n"
|
|
else
|
|
buf="${buf}${C}${BD} 监控 $(date '+%H:%M:%S')${N}\n"
|
|
fi
|
|
|
|
buf="${buf}${BD}${SEP}${N}\n"
|
|
buf="${buf}\n"
|
|
|
|
cur_session=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
|
|
# 一次性获取所有面板信息(含状态字段)
|
|
panes_info=$(tmux list-panes -a -F \
|
|
'#{session_name}|#{session_created}|#{session_attached}|#{window_index}|#{window_name}|#{window_active}|#{window_width}x#{window_height}|#{pane_index}|#{pane_title}|#{pane_current_command}|#{pane_current_path}|#{pane_active}|#{pane_pid}|#{pane_width}x#{pane_height}|#{pane_dead}|#{pane_dead_status}|#{pane_in_mode}|#{pane_mode}' \
|
|
2>/dev/null)
|
|
|
|
total_sessions=0
|
|
total_windows=0
|
|
total_panes=0
|
|
session_idx=0
|
|
|
|
if [ -n "$panes_info" ]; then
|
|
prev_session=""
|
|
prev_window=""
|
|
|
|
while IFS='|' read -r ssn s_created s_attached widx wname w_active w_size pidx ptitle cmd path active pid p_size p_dead p_dead_status p_in_mode p_mode; do
|
|
# ── 新会话 ──
|
|
if [ "$ssn" != "$prev_session" ]; then
|
|
[ -n "$prev_session" ] && buf="${buf}\n"
|
|
total_sessions=$((total_sessions + 1))
|
|
session_idx=$((session_idx + 1))
|
|
|
|
if [ "$ssn" = "$cur_session" ]; then
|
|
s_marker="${G}●${N}"
|
|
s_name="${BD}${G}${ssn}${N}"
|
|
else
|
|
s_marker="${D}○${N}"
|
|
s_name="${P}${ssn}${N}"
|
|
fi
|
|
|
|
# Session 状态标签
|
|
s_status=""
|
|
if [ "$s_attached" = "0" ]; then
|
|
s_status=" ${Y}${BD}[detached]${N}"
|
|
fi
|
|
|
|
s_time=""
|
|
if [ "$level" -ge "$LVL_NORMAL" ] && [ -n "$s_created" ] && [ "$s_created" -gt 0 ] 2>/dev/null; then
|
|
s_time=$(date -d "@$s_created" '+%m-%d %H:%M' 2>/dev/null)
|
|
fi
|
|
|
|
# 根据宽度调整 Session 行
|
|
if [ "$level" -ge "$LVL_COMPACT" ]; then
|
|
s_line=" ${s_marker} ${BD}Session${N} ${session_idx} ${s_name}${s_status}"
|
|
else
|
|
s_line=" ${s_marker} ${BD}S${N}${session_idx} ${s_name}${s_status}"
|
|
fi
|
|
[ -n "$s_time" ] && s_line="${s_line} ${D}[自 ${s_time}]${N}"
|
|
buf="${buf}${s_line}\n"
|
|
|
|
prev_session="$ssn"
|
|
prev_window=""
|
|
fi
|
|
|
|
# ── 新窗口 ──
|
|
if [ "$widx:$wname" != "$prev_window" ]; then
|
|
total_windows=$((total_windows + 1))
|
|
|
|
if [ "$w_active" = "1" ] && [ "$ssn" = "$cur_session" ]; then
|
|
w_marker="${G}●${N}"
|
|
w_name="${BD}${G}${wname}${N}"
|
|
else
|
|
w_marker="${D}○${N}"
|
|
w_name="${Y}${wname}${N}"
|
|
fi
|
|
|
|
# Window 状态标签
|
|
w_status=""
|
|
if [ "$w_active" = "1" ] && [ "$ssn" = "$cur_session" ]; then
|
|
w_status=" ${G}[active]${N}"
|
|
fi
|
|
|
|
# 根据宽度决定是否显示尺寸
|
|
if [ "$level" -ge "$LVL_COMPACT" ]; then
|
|
w_line=" ${D}├─${N} ${w_marker} ${BD}Win${N} ${widx} ${w_name}${w_status} ${D}[${w_size}]${N}"
|
|
else
|
|
w_line=" ${D}├${N} ${w_marker} ${w_name}${w_status}"
|
|
fi
|
|
buf="${buf}${w_line}\n"
|
|
prev_window="$widx:$wname"
|
|
fi
|
|
|
|
# ── 面板 ──
|
|
total_panes=$((total_panes + 1))
|
|
|
|
if [ "$active" = "1" ] && [ "$ssn" = "$cur_session" ]; then
|
|
p_marker="${G}●${N}"
|
|
else
|
|
p_marker="${D}○${N}"
|
|
fi
|
|
|
|
case "$cmd" in
|
|
bash|zsh|fish|sh) cmd_color="$D" ;;
|
|
vim|nvim|vi) cmd_color="$G" ;;
|
|
python|python3) cmd_color="$Y" ;;
|
|
node) cmd_color="$G" ;;
|
|
ssh) cmd_color="$R" ;;
|
|
tmux-monitor.sh|tmux_monitor.sh) cmd_color="$C" ;;
|
|
*) cmd_color="$N" ;;
|
|
esac
|
|
|
|
# 路径缩写:根据层级决定缩写程度
|
|
short_path=""
|
|
if [ "$level" -ge "$LVL_NORMAL" ]; then
|
|
short_path=$(echo "$path" | sed "s|^$HOME|~|" | awk -F/ '{
|
|
if(NF>3) print ".."/$(NF-1)/$NF; else print $0
|
|
}')
|
|
elif [ "$level" -ge "$LVL_COMPACT" ]; then
|
|
short_path=$(echo "$path" | sed "s|^$HOME|~|" | awk -F/ '{
|
|
print $NF
|
|
}')
|
|
fi
|
|
|
|
# 子命令:仅在 FULL 级别显示
|
|
child_cmd=""
|
|
if [ "$level" -ge "$LVL_FULL" ] && [ "$(uname)" = "Linux" ]; then
|
|
child_pid=$(ps --ppid "$pid" -o pid= 2>/dev/null | head -1 | tr -d ' ')
|
|
if [ -n "$child_pid" ]; then
|
|
child_cmd=$(ps -p "$child_pid" -o args= 2>/dev/null)
|
|
fi
|
|
fi
|
|
|
|
# 格式: 根据层级决定显示内容
|
|
p_title="${ptitle}"
|
|
[ -z "$p_title" ] && p_title="$cmd"
|
|
|
|
# Pane 状态标签
|
|
p_status=""
|
|
if [ "$p_dead" = "1" ]; then
|
|
p_status=" ${R}${BD}[dead:${p_dead_status}]${N}"
|
|
fi
|
|
if [ "$p_in_mode" = "1" ] && [ -n "$p_mode" ]; then
|
|
p_status="${p_status} ${Y}[${p_mode}]${N}"
|
|
fi
|
|
if [ "$active" = "1" ] && [ "$ssn" = "$cur_session" ]; then
|
|
p_status="${p_status} ${G}[active]${N}"
|
|
fi
|
|
|
|
if [ "$level" -ge "$LVL_NORMAL" ]; then
|
|
# NORMAL / FULL: Pane 编号 + 标题 + 命令 + 尺寸 + 状态
|
|
p_line=" ${D}│ ├─${N} ${p_marker} ${BD}Pane${N} ${pidx} ${C}${p_title}${N} ${cmd_color}${cmd}${N} ${D}[${p_size}]${N}${p_status}"
|
|
if [ "$level" -ge "$LVL_FULL" ]; then
|
|
p_line="${p_line} ${D}PID:${pid}${N}"
|
|
fi
|
|
elif [ "$level" -ge "$LVL_COMPACT" ]; then
|
|
# COMPACT: Pane 编号 + 标题 + 命令
|
|
p_line=" ${D}│ ├${N} ${p_marker} ${BD}P${N}${pidx} ${C}${p_title}${N} ${cmd_color}${cmd}${N}"
|
|
else
|
|
# MINIMAL: 仅标题 + 命令
|
|
p_line=" ${D}│${N} ${p_marker} ${C}${p_title}${N}"
|
|
fi
|
|
|
|
buf="${buf}${p_line}\n"
|
|
|
|
if [ -n "$child_cmd" ]; then
|
|
c_line=" ${D}│ │ ↳${N} ${D}${child_cmd}${N}"
|
|
buf="${buf}${c_line}\n"
|
|
fi
|
|
|
|
if [ -n "$short_path" ]; then
|
|
sp_line=" ${D}│ │ ${N}${D}${short_path}${N}"
|
|
buf="${buf}${sp_line}\n"
|
|
fi
|
|
|
|
done <<< "$panes_info"
|
|
fi
|
|
|
|
# ── 系统概要 ──
|
|
buf="${buf}\n"
|
|
buf="${buf}${D}${SEP}${N}\n"
|
|
buf="${buf} ${BD}总计:${N} ${total_sessions} 会话 | ${total_windows} 窗口 | ${total_panes} 面板\n"
|
|
buf="${buf}${D}${SEP}${N}\n"
|
|
buf="${buf} ${D}每 ${INTERVAL} 秒刷新 | Ctrl+C 退出${N}\n"
|
|
|
|
# ── 双缓冲输出:移到顶部 → 写入 → 清除尾部残留 ──
|
|
# 每行末尾添加 \033[K(清除到行尾),防止短行覆盖长行时残留旧字符
|
|
buf="${buf//\\n/\\033[K\\n}"
|
|
|
|
tput cup 0 0
|
|
printf '%b' "$buf"
|
|
tput ed
|
|
|
|
sleep "$INTERVAL"
|
|
done
|