更新 Docker容器管理面板

This commit is contained in:
2026-01-22 21:02:33 +08:00
parent a67d1e936e
commit efbbd6314e

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# 简单实用的 Docker 容器管理脚本 # 简单实用的 Docker 容器管理脚本 (优化版)
# 颜色定义 # 颜色定义
RED='\033[0;31m' RED='\033[0;31m'
@@ -8,53 +8,83 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
PURPLE='\033[0;35m' PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
# 检查 Docker # 检查 Docker 及权限
check_docker() { check_docker() {
if ! command -v docker &> /dev/null; then if ! command -v docker &> /dev/null; then
echo -e "${RED}Docker安装${NC}" echo -e "${RED}错误: 未检测到 Docker,请先安装${NC}"
exit 1 exit 1
fi fi
# 检查是否有权限执行 docker 命令
if ! docker ps &> /dev/null; then
echo -e "${RED}错误: 当前用户无权运行 Docker 命令。${NC}"
echo -e "${YELLOW}请尝试使用 sudo 运行此脚本,或将用户加入 docker 用户组。${NC}"
exit 1
fi
}
# 格式化输出表头
print_table_header() {
printf "${BLUE}%-5s | %-25s | %-12s | %-20s${NC}\n" "编号" "容器名称" "状态" "镜像"
echo "------|---------------------------|--------------|----------------------"
}
# 格式化输出行
print_table_row() {
printf "%-5s | %-25s | %-12b | %-20s\n" "$1" "$2" "$3" "$4"
} }
# 显示容器列表 # 显示容器列表
show_containers() { show_containers() {
echo -e "\n${BLUE}=== 容器列表 ===${NC}" echo -e "\n${BLUE}=== 容器列表 ===${NC}"
echo "编号 | 容器名称 | 状态 | 镜像" print_table_header
echo "----|----------|------|------"
local count=0 local count=0
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | tail -n +2 | while read line; do # 获取数据并暂存避免管道导致的子shell变量丢失问题
mapfile -t lines < <(docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | tail -n +2)
for line in "${lines[@]}"; do
if [ -n "$line" ]; then if [ -n "$line" ]; then
count=$((count + 1)) count=$((count + 1))
name=$(echo "$line" | awk '{print $1}') name=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | awk '{print $2}') status_raw=$(echo "$line" | awk '{print $2}')
image=$(echo "$line" | awk '{for(i=3;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//') image=$(echo "$line" | awk '{for(i=3;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//')
# 状态显示 # 截断过长的名称以保持表格整洁
if [[ "$status" == "Up"* ]]; then display_name=$name
if [ ${#display_name} -gt 23 ]; then display_name="${display_name:0:20}..."; fi
# 截断过长的镜像名
display_image=$image
if [ ${#display_image} -gt 18 ]; then display_image="${display_image:0:15}..."; fi
# 状态美化
if [[ "$status_raw" == "Up"* ]]; then
status_display="${GREEN}运行中${NC}" status_display="${GREEN}运行中${NC}"
elif [[ "$status" == "Exited"* ]]; then elif [[ "$status_raw" == "Exited"* ]]; then
status_display="${RED}已停止${NC}" status_display="${RED}已停止${NC}"
else else
status_display="${YELLOW}$status${NC}" status_display="${YELLOW}$status_raw${NC}"
fi fi
echo -e "$count | $name | $status_display | $image" print_table_row "$count" "$display_name" "$status_display" "$display_image"
fi fi
done done
echo echo
} }
# 显示镜像列表 # 显示镜像列表 (优化对齐)
show_images() { show_images() {
echo -e "\n${PURPLE}=== 镜像列表 ===${NC}" echo -e "\n${PURPLE}=== 镜像列表 ===${NC}"
echo "编号 | 镜像名称:标签 | 镜像ID | 大小 | 创建时间" printf "${PURPLE}%-5s | %-30s | %-12s | %-8s | %-15s${NC}\n" "编号" "镜像名称:标签" "镜像ID" "大小" "创建时间"
echo "----|---------------|--------|------|----------" echo "------|--------------------------------|--------------|----------|----------------"
local count=0 local count=0
docker images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}}\t{{.CreatedSince}}" | tail -n +2 | while read line; do mapfile -t lines < <(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}}\t{{.CreatedSince}}" | tail -n +2)
for line in "${lines[@]}"; do
if [ -n "$line" ]; then if [ -n "$line" ]; then
count=$((count + 1)) count=$((count + 1))
repo_tag=$(echo "$line" | awk '{print $1}') repo_tag=$(echo "$line" | awk '{print $1}')
@@ -62,241 +92,160 @@ show_images() {
size=$(echo "$line" | awk '{print $3}') size=$(echo "$line" | awk '{print $3}')
created=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//') created=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf $i" "; print ""}' | sed 's/ $//')
# 截断过长的镜像名称 # 截断处理
if [ ${#repo_tag} -gt 25 ]; then if [ ${#repo_tag} -gt 28 ]; then repo_tag="${repo_tag:0:25}..."; fi
repo_tag="${repo_tag:0:22}..."
fi
# 截断镜像ID printf "%-5s | %-30s | %-12s | %-8s | %-15s\n" "$count" "$repo_tag" "$image_id" "$size" "$created"
image_id_short="${image_id:0:12}"
echo -e "$count | $repo_tag | $image_id_short | $size | $created"
fi fi
done done
echo echo
} }
# 通过编号获取容器名称 # 通用获取名称函数 (复用逻辑)
get_container_name() { get_name_by_index() {
local number=$1 local number=$1
local count=0 local type=$2 # "container" or "image"
docker ps -a --format "table {{.Names}}" | tail -n +2 | while read name; do local cmd=""
if [ -n "$name" ]; then
count=$((count + 1)) if [ "$type" == "container" ]; then
if [ $count -eq $number ]; then cmd="docker ps -a --format '{{.Names}}'"
echo "$name" else
fi cmd="docker images --format '{{.ID}}'"
fi fi
done
# 动态获取第N行
eval "$cmd" | sed -n "${number}p"
} }
# 通过编号获取镜像ID # 进入容器终端 (新增功能)
get_image_id() { enter_container() {
local number=$1 local container_name=$1
local count=0 echo -e "${YELLOW}尝试进入容器: $container_name${NC}"
docker images --format "table {{.ID}}" | tail -n +2 | while read image_id; do echo -e "提示: 输入 ${RED}exit${NC} 可退出容器终端"
if [ -n "$image_id" ]; then echo
count=$((count + 1))
if [ $count -eq $number ]; then # 优先尝试 bash失败则尝试 sh
echo "$image_id" if docker exec -it "$container_name" /bin/bash 2>/dev/null; then
fi return
fi elif docker exec -it "$container_name" /bin/sh 2>/dev/null; then
done return
else
echo -e "${RED}无法进入容器终端 (可能容器未包含 bash 或 sh)${NC}"
read -p "按回车键继续..."
fi
} }
# 批量删除容器 # 批量删除容器
batch_delete_containers() { batch_delete_containers() {
local numbers="$1" local numbers="$1"
local confirm="$2" local confirm="$2"
# 处理输入,支持空格和逗号分隔
numbers=$(echo "$numbers" | sed 's/,/ /g') numbers=$(echo "$numbers" | sed 's/,/ /g')
local success_count=0 local success_count=0
local fail_count=0
for number in $numbers; do for number in $numbers; do
if [[ "$number" =~ ^[0-9]+$ ]]; then if [[ "$number" =~ ^[0-9]+$ ]]; then
container_name=$(get_container_name $number) container_name=$(get_name_by_index $number "container")
if [ -n "$container_name" ]; then if [ -n "$container_name" ]; then
if [ "$confirm" != "y" ]; then if [ "$confirm" != "y" ]; then
echo -e "${YELLOW}删除容器: $container_name${NC}" read -p "确认删除容器 $container_name? (y/N): " c
read -p "确认删除?(Y/n): " confirm_delete [[ "$c" != "y" && "$c" != "Y" ]] && continue
confirm_delete=${confirm_delete:-Y} # 默认Y
if [[ "$confirm_delete" != "y" && "$confirm_delete" != "Y" ]]; then
echo -e "${GREEN}跳过: $container_name${NC}"
continue
fi
fi fi
if docker rm -f "$container_name" &> /dev/null; then if docker rm -f "$container_name" &> /dev/null; then
echo -e "${GREEN}✓ 删除成功: $container_name${NC}" echo -e "${GREEN}✓ 删除成功: $container_name${NC}"
success_count=$((success_count + 1)) ((success_count++))
else else
echo -e "${RED}✗ 删除失败: $container_name${NC}" echo -e "${RED}✗ 删除失败: $container_name${NC}"
fail_count=$((fail_count + 1))
fi fi
else else
echo -e "${RED}✗ 容器编号不存在: $number${NC}" echo -e "${RED}✗ 编号 $number 不存在${NC}"
fail_count=$((fail_count + 1))
fi fi
else
echo -e "${RED}✗ 无效编号: $number${NC}"
fail_count=$((fail_count + 1))
fi fi
done done
[ $success_count -gt 0 ] && echo -e "${GREEN}共删除 $success_count 个容器${NC}"
echo
if [ $success_count -gt 0 ]; then
echo -e "${GREEN}成功删除 $success_count 个容器${NC}"
fi
if [ $fail_count -gt 0 ]; then
echo -e "${RED}删除失败 $fail_count 个${NC}"
fi
} }
# 批量删除镜像 # 批量删除镜像
batch_delete_images() { batch_delete_images() {
local numbers="$1" local numbers="$1"
local confirm="$2" local confirm="$2"
# 处理输入,支持空格和逗号分隔
numbers=$(echo "$numbers" | sed 's/,/ /g') numbers=$(echo "$numbers" | sed 's/,/ /g')
local success_count=0 local success_count=0
local fail_count=0
for number in $numbers; do for number in $numbers; do
if [[ "$number" =~ ^[0-9]+$ ]]; then if [[ "$number" =~ ^[0-9]+$ ]]; then
image_id=$(get_image_id $number) image_id=$(get_name_by_index $number "image")
if [ -n "$image_id" ]; then if [ -n "$image_id" ]; then
# 获取镜像名称用于显示 # 获取友好名称用于显示
image_name=$(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}" | grep "$image_id" | awk '{print $1}') image_name=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "REPOSITORY" | grep "$image_id" | head -n 1)
[ -z "$image_name" ] && image_name=$image_id
if [ "$confirm" != "y" ]; then if [ "$confirm" != "y" ]; then
echo -e "${YELLOW}删除镜像: $image_name${NC}" read -p "确认删除镜像 $image_name? (y/N): " c
read -p "确认删除?(Y/n): " confirm_delete [[ "$c" != "y" && "$c" != "Y" ]] && continue
confirm_delete=${confirm_delete:-Y} # 默认Y
if [[ "$confirm_delete" != "y" && "$confirm_delete" != "Y" ]]; then
echo -e "${GREEN}跳过: $image_name${NC}"
continue
fi
fi fi
if docker rmi "$image_id" &> /dev/null; then if docker rmi "$image_id" &> /dev/null; then
echo -e "${GREEN}✓ 删除成功: $image_name${NC}" echo -e "${GREEN}✓ 删除成功: $image_name${NC}"
success_count=$((success_count + 1)) ((success_count++))
else else
echo -e "${RED}✗ 删除失败: $image_name (可能有容器在使用此镜像)${NC}" echo -e "${RED}✗ 删除失败 (可能被容器占用): $image_name${NC}"
fail_count=$((fail_count + 1))
fi fi
else
echo -e "${RED}✗ 镜像编号不存在: $number${NC}"
fail_count=$((fail_count + 1))
fi fi
else
echo -e "${RED}✗ 无效编号: $number${NC}"
fail_count=$((fail_count + 1))
fi fi
done done
echo
if [ $success_count -gt 0 ]; then
echo -e "${GREEN}成功删除 $success_count 个镜像${NC}"
fi
if [ $fail_count -gt 0 ]; then
echo -e "${RED}删除失败 $fail_count 个${NC}"
fi
} }
# 清理悬空镜像 # 清理悬空镜像
clean_dangling_images() { clean_dangling_images() {
echo -e "${YELLOW}清理悬空镜像...${NC}" local count=$(docker images -f "dangling=true" -q | wc -l)
local dangling_count=$(docker images -f "dangling=true" -q | wc -l) if [ "$count" -gt 0 ]; then
if [ $dangling_count -gt 0 ]; then read -p "发现 $count 个悬空镜像,是否清理?(y/N): " confirm
read -p "确认删除 $dangling_count 个悬空镜像?(Y/n): " confirm_dangling if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
confirm_dangling=${confirm_dangling:-Y}
if [[ "$confirm_dangling" == "y" || "$confirm_dangling" == "Y" ]]; then
docker image prune -f docker image prune -f
echo -e "${GREEN}清理 $dangling_count 个悬空镜像${NC}" echo -e "${GREEN}清理完成${NC}"
else
echo -e "${GREEN}取消清理${NC}"
fi fi
else else
echo -e "${GREEN}没有悬空镜像${NC}" echo -e "${GREEN}系统很干净,没有悬空镜像${NC}"
fi fi
read -p "按回车键继续..."
} }
# 镜像管理菜单 # 镜像管理菜单
image_management() { image_management() {
while true; do while true; do
clear clear
echo -e "${PURPLE}" echo -e "${PURPLE}========================================${NC}"
echo "========================================" echo -e "${PURPLE} Docker 镜像管理${NC}"
echo " Docker 镜像管理" echo -e "${PURPLE}========================================${NC}"
echo "========================================"
echo -e "${NC}"
show_images show_images
echo -e "${PURPLE}镜像操作选项:${NC}" echo -e "${PURPLE}操作选项:${NC}"
echo "1. 删除镜像 (支持批量: 1 2 3 或 1,2,3)" echo "1. 删除镜像 (例如: 1 或 1,2,3)"
echo "2. 批量删除所有镜像" echo "2. 清空所有镜像 (慎用)"
echo "3. 清理悬空镜像" echo "3. 清理悬空镜像 (<none>)"
echo "4. 返回主菜单" echo "4. 返回主菜单"
echo echo
read -p "请选择: " choice
read -p "请选择操作: " choice
case $choice in case $choice in
1) 1)
echo
echo -e "${YELLOW}删除镜像 (支持批量删除)${NC}"
echo "输入单个编号: 1"
echo "输入多个编号: 1 2 3 或 1,2,3"
echo "输入 all 删除所有镜像"
read -p "请输入镜像编号: " input read -p "请输入镜像编号: " input
batch_delete_images "$input" "n"
if [[ "$input" == "all" ]]; then read -p "按回车键继续..."
echo -e "${RED}警告:将删除所有镜像!${NC}"
read -p "确认删除所有镜像?(Y/n): " confirm_all
confirm_all=${confirm_all:-Y}
if [[ "$confirm_all" == "y" || "$confirm_all" == "Y" ]]; then
total_count=$(docker images -q | wc -l)
docker rmi -f $(docker images -q) &> /dev/null
echo -e "${GREEN}已删除所有 $total_count 个镜像${NC}"
else
echo -e "${GREEN}取消删除${NC}"
fi
else
batch_delete_images "$input" "n"
fi
;; ;;
2) 2)
echo -e "${RED}警告:删除所有镜像${NC}" read -p "警告:确定删除所有镜像吗?(yes/N): " confirm
read -p "确认删除所有镜像?(Y/n): " confirm_all if [ "$confirm" == "yes" ]; then
confirm_all=${confirm_all:-Y} docker rmi -f $(docker images -q) 2>/dev/null
if [[ "$confirm_all" == "y" || "$confirm_all" == "Y" ]]; then echo -e "${GREEN}已清空所有镜像${NC}"
total_count=$(docker images -q | wc -l)
docker rmi -f $(docker images -q) &> /dev/null
echo -e "${GREEN}已删除所有 $total_count 个镜像${NC}"
else
echo -e "${GREEN}取消删除${NC}"
fi fi
read -p "按回车键继续..."
;; ;;
3) 3) clean_dangling_images ;;
clean_dangling_images 4) return ;;
;; *) echo -e "${RED}无效输入${NC}"; sleep 1 ;;
4)
return
;;
*)
echo -e "${RED}无效选择${NC}"
;;
esac esac
echo
read -p "按回车键继续..."
done done
} }
@@ -304,124 +253,74 @@ image_management() {
main_menu() { main_menu() {
while true; do while true; do
clear clear
echo -e "${BLUE}" echo -e "${BLUE}========================================${NC}"
echo "========================================" echo -e "${BLUE} Docker 容器可视化管理 v2.0${NC}"
echo " 简单 Docker 容器管理" echo -e "${BLUE}========================================${NC}"
echo "========================================"
echo -e "${NC}"
show_containers show_containers
echo -e "${BLUE}容器操作选项:${NC}" echo -e "${BLUE}容器操作:${NC} ${CYAN}高级功能:${NC}"
echo "1. 启动容器" printf "%-35s %-35s\n" "1. 启动容器" "7. 进入容器终端 (Exec)"
echo "2. 停止容器" printf "%-35s %-35s\n" "2. 停止容器" "8. 实时资源监控 (Stats)"
echo "3. 重启容器" printf "%-35s %-35s\n" "3. 重启容器" "9. 镜像管理菜单"
echo "4. 删除容器 (支持批量: 1 2 3 或 1,2,3)" printf "%-35s\n" "4. 查看日志"
echo "5. 批量删除所有已停止容器" echo
echo "6. 查看容器日志" echo -e "${RED}危险操作:${NC}"
echo -e "${PURPLE}镜像操作选项:${NC}" echo "5. 删除容器 (例如: 1 或 1,2,3)"
echo "7. 查看和管理镜像" echo "6. 清理所有已停止容器"
echo "0. 退出" echo "0. 退出"
echo echo
read -p "请选择操作: " choice read -p "请选择操作 [0-9]: " choice
# 处理需要选择容器的操作
if [[ "1 2 3 4 7" =~ "$choice" ]]; then
read -p "请输入容器编号: " num
if [[ ! "$num" =~ ^[0-9]+$ ]]; then
echo -e "${RED}请输入有效数字${NC}"; sleep 1; continue
fi
name=$(get_name_by_index $num "container")
if [ -z "$name" ]; then
echo -e "${RED}编号不存在${NC}"; sleep 1; continue
fi
case $choice in
1) docker start "$name" && echo -e "${GREEN}已启动 $name${NC}" ;;
2) docker stop "$name" && echo -e "${GREEN}已停止 $name${NC}" ;;
3) docker restart "$name" && echo -e "${GREEN}已重启 $name${NC}" ;;
4) docker logs -f --tail 100 "$name" ;;
7) enter_container "$name" ;;
esac
[ "$choice" != "7" ] && [ "$choice" != "4" ] && sleep 1
continue
fi
# 处理其他操作
case $choice in case $choice in
1|2|3|6)
echo
read -p "请输入容器编号: " container_number
if ! [[ "$container_number" =~ ^[0-9]+$ ]]; then
echo -e "${RED}无效的编号${NC}"
read -p "按回车键继续..."
continue
fi
container_name=$(get_container_name $container_number)
if [ -z "$container_name" ]; then
echo -e "${RED}容器编号不存在${NC}"
read -p "按回车键继续..."
continue
fi
case $choice in
1)
echo -e "${YELLOW}启动容器: $container_name${NC}"
docker start "$container_name" && echo -e "${GREEN}启动成功${NC}" || echo -e "${RED}启动失败${NC}"
;;
2)
echo -e "${YELLOW}停止容器: $container_name${NC}"
docker stop "$container_name" && echo -e "${GREEN}停止成功${NC}" || echo -e "${RED}停止失败${NC}"
;;
3)
echo -e "${YELLOW}重启容器: $container_name${NC}"
docker restart "$container_name" && echo -e "${GREEN}重启成功${NC}" || echo -e "${RED}重启失败${NC}"
;;
6)
echo -e "${BLUE}查看容器日志: $container_name${NC}"
echo "按 Ctrl+C 退出"
docker logs -f "$container_name"
continue # 日志查看后直接继续,不等回车
;;
esac
;;
4)
echo
echo -e "${YELLOW}删除容器 (支持批量删除)${NC}"
echo "输入单个编号: 1"
echo "输入多个编号: 1 2 3 或 1,2,3"
echo "输入 all 删除所有容器"
read -p "请输入容器编号: " input
if [[ "$input" == "all" ]]; then
echo -e "${RED}警告:将删除所有容器!${NC}"
read -p "确认删除所有容器?(Y/n): " confirm_all
confirm_all=${confirm_all:-Y}
if [[ "$confirm_all" == "y" || "$confirm_all" == "Y" ]]; then
total_count=$(docker ps -aq | wc -l)
docker rm -f $(docker ps -aq) &> /dev/null
echo -e "${GREEN}已删除所有 $total_count 个容器${NC}"
else
echo -e "${GREEN}取消删除${NC}"
fi
else
batch_delete_containers "$input" "n"
fi
;;
5) 5)
echo -e "${YELLOW}批量删除所有已停止的容器...${NC}" read -p "请输入要删除的容器编号 (支持批量): " nums
stopped_count=$(docker ps -aq -f status=exited | wc -l) batch_delete_containers "$nums" "n"
if [ $stopped_count -gt 0 ]; then read -p "按回车键继续..."
read -p "确认删除 $stopped_count 个已停止容器?(Y/n): " confirm_stopped ;;
confirm_stopped=${confirm_stopped:-Y} 6)
if [[ "$confirm_stopped" == "y" || "$confirm_stopped" == "Y" ]]; then read -p "确认清理所有已停止的容器?(y/N): " c
docker rm $(docker ps -aq -f status=exited) &> /dev/null if [[ "$c" == "y" || "$c" == "Y" ]]; then
echo -e "${GREEN}已删除 $stopped_count 个已停止容器${NC}" docker container prune -f
else echo -e "${GREEN}清理完成${NC}"
echo -e "${GREEN}取消删除${NC}"
fi
else
echo -e "${GREEN}没有已停止的容器${NC}"
fi fi
sleep 1
;; ;;
7) 8)
image_management echo -e "${CYAN}正在打开资源监控 (按 Ctrl+C 退出)...${NC}"
;; docker stats
0)
echo
echo -e "${GREEN}再见!${NC}"
exit 0
;;
*)
echo -e "${RED}无效选择${NC}"
;; ;;
9) image_management ;;
0) echo -e "${GREEN}再见!${NC}"; exit 0 ;;
*) echo -e "${RED}无效选择${NC}"; sleep 1 ;;
esac esac
echo
read -p "按回车键继续..."
done done
} }
# 启动脚本 # 启动
check_docker check_docker
main_menu main_menu