签入版本
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker Disk Cleanup Script
|
||||
# Description: 清理Docker无用资源,释放磁盘空间
|
||||
# Author: Your Name
|
||||
# Date: $(date +%Y-%m-%d)
|
||||
|
||||
set -euo pipefail # 更安全的执行模式:遇到错误退出,未定义变量报错
|
||||
|
||||
# 配置变量 - 可根据需要调整
|
||||
DRY_RUN=false # 设置为 true 时只显示将要执行的操作,而不实际执行
|
||||
CLEAN_UNUSED_IMAGES=true # 清理未被使用的镜像
|
||||
CLEAN_DANGLING_IMAGES=true # 清理悬空镜像(无标签且未被使用)
|
||||
CLEAN_EXITED_CONTAINERS=true # 清理已退出的容器
|
||||
CLEAN_BUILD_CACHE=true # 清理构建缓存
|
||||
CLEAN_DANGLING_VOLUMES=false # 清理悬空卷(警告:这可能删除重要数据!)
|
||||
CLEAN_UNUSED_NETWORKS=true # 清理未使用的网络
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
}
|
||||
|
||||
# 危险操作确认函数
|
||||
confirm_action() {
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "干运行模式:将执行 '$1'"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "执行: $1"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 主清理函数
|
||||
clean_docker() {
|
||||
log "开始 Docker 磁盘清理..."
|
||||
log "当前 Docker 磁盘使用情况:"
|
||||
docker system df
|
||||
|
||||
# 清理已退出的容器
|
||||
if [ "$CLEAN_EXITED_CONTAINERS" = true ]; then
|
||||
log "查找已退出的容器..."
|
||||
exited_containers=$(docker ps -aq -f status=exited)
|
||||
if [ -n "$exited_containers" ]; then
|
||||
log "找到已退出的容器: $exited_containers"
|
||||
confirm_action "docker rm $exited_containers" && \
|
||||
docker rm $exited_containers 2>/dev/null || \
|
||||
log "警告: 删除某些容器时出错(可能正在使用中)"
|
||||
else
|
||||
log "未找到已退出的容器"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 清理悬空镜像
|
||||
if [ "$CLEAN_DANGLING_IMAGES" = true ]; then
|
||||
log "清理悬空镜像..."
|
||||
confirm_action "docker image prune -f" && \
|
||||
docker image prune -f
|
||||
fi
|
||||
|
||||
# 清理未使用的镜像
|
||||
if [ "$CLEAN_UNUSED_IMAGES" = true ]; then
|
||||
log "清理未被使用的镜像..."
|
||||
unused_images=$(docker images -q --filter "dangling=false")
|
||||
if [ -n "$unused_images" ]; then
|
||||
log "找到未使用的镜像,建议手动检查后再删除"
|
||||
# 更安全的做法是手动确认,这里只做提示
|
||||
fi
|
||||
fi
|
||||
|
||||
# 清理构建缓存
|
||||
if [ "$CLEAN_BUILD_CACHE" = true ]; then
|
||||
log "清理构建缓存..."
|
||||
confirm_action "docker builder prune -f" && \
|
||||
docker builder prune -f
|
||||
fi
|
||||
|
||||
# 清理未使用的网络
|
||||
if [ "$CLEAN_UNUSED_NETWORKS" = true ]; then
|
||||
log "清理未使用的网络..."
|
||||
confirm_action "docker network prune -f" && \
|
||||
docker network prune -f
|
||||
fi
|
||||
|
||||
# 清理悬空卷(危险操作,默认关闭)
|
||||
if [ "$CLEAN_DANGLING_VOLUMES" = true ]; then
|
||||
log "警告:即将清理悬空卷,这可能删除重要数据!"
|
||||
read -p "确认继续?(y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
confirm_action "docker volume prune -f" && \
|
||||
docker volume prune -f
|
||||
else
|
||||
log "已跳过卷清理"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 使用 system prune 进行最终清理(相对安全)
|
||||
log "执行系统清理..."
|
||||
confirm_action "docker system prune -f" && \
|
||||
docker system prune -f
|
||||
|
||||
log "清理完成后的 Docker 磁盘使用情况:"
|
||||
docker system df
|
||||
log "Docker 磁盘清理完成!"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
使用方式: $0 [选项]
|
||||
|
||||
选项:
|
||||
-d, --dry-run 干运行模式,只显示将要执行的操作
|
||||
-v, --verbose 详细输出模式
|
||||
-h, --help 显示此帮助信息
|
||||
|
||||
示例:
|
||||
$0 # 正常执行清理
|
||||
$0 --dry-run # 只显示将要执行的操作而不实际执行
|
||||
EOF
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-d|--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
-v|--verbose)
|
||||
set -x
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log "错误: 未知选项 $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查 Docker 是否可用
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log "错误: Docker 未安装或未在 PATH 中找到"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查是否具有 Docker 权限
|
||||
if ! docker info &> /dev/null; then
|
||||
log "错误: 无 Docker 访问权限或 Docker 守护进程未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行主函数
|
||||
clean_docker
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Jenkins Workspace Cleanup Script
|
||||
# Description: 安全清理Jenkins工作区,保留最近构建和重要文件
|
||||
# Author: System Admin
|
||||
# Date: $(date +%Y-%m-%d)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 配置变量 - 根据实际情况修改
|
||||
JENKINS_HOME="${JENKINS_HOME:-/usr/local/docker/jenkins/jenkins_home}" # Jenkins主目录
|
||||
WORKSPACE_DIR="${JENKINS_HOME}/workspace" # 工作区目录
|
||||
DRY_RUN=false # 干运行模式
|
||||
RETENTION_DAYS=1 # 保留最近N天的构建
|
||||
MIN_FREE_SPACE_GB=20 # 最小保留磁盘空间(GB)
|
||||
EXCLUDE_PATTERNS=(".*/builds/.*" ".*/lastStable.*" ".*/lastSuccessful.*" ".*/nextBuildNumber.*") # 排除模式
|
||||
LOG_FILE="/var/log/jenkins-cleanup.log" # 日志文件
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "[${timestamp}] ${message}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 检查目录是否存在
|
||||
check_directories() {
|
||||
if [ ! -d "$JENKINS_HOME" ]; then
|
||||
log "错误: Jenkins目录不存在: $JENKINS_HOME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$WORKSPACE_DIR" ]; then
|
||||
log "错误: 工作区目录不存在: $WORKSPACE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查磁盘空间
|
||||
check_disk_space() {
|
||||
local available_space_gb=$(df -BG "$JENKINS_HOME" | awk 'NR==2 {print $4}' | sed 's/G//')
|
||||
|
||||
if [ "$available_space_gb" -ge "$MIN_FREE_SPACE_GB" ]; then
|
||||
log "磁盘空间充足 (${available_space_gb}GB可用),跳过清理"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "磁盘空间不足 (${available_space_gb}GB可用),需要清理"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 安全删除目录
|
||||
safe_delete() {
|
||||
local dir_path="$1"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "干运行: 将删除目录 - ${dir_path}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 检查目录是否重要(包含排除模式)
|
||||
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
|
||||
if [[ "$dir_path" =~ $pattern ]]; then
|
||||
log "跳过重要目录: ${dir_path}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查目录是否在最近修改时间内
|
||||
local last_modified=$(stat -c %Y "$dir_path")
|
||||
local current_time=$(date +%s)
|
||||
local age_days=$(( (current_time - last_modified) / 86400 ))
|
||||
|
||||
if [ "$age_days" -le "$RETENTION_DAYS" ]; then
|
||||
log "跳过最近修改的目录 (${age_days}天前): ${dir_path}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 执行删除
|
||||
if rm -rf "$dir_path" 2>/dev/null; then
|
||||
log "已删除: ${dir_path} (${age_days}天未修改)"
|
||||
return 0
|
||||
else
|
||||
log "警告: 删除失败: ${dir_path}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理工作区
|
||||
clean_workspace() {
|
||||
log "开始清理Jenkins工作区..."
|
||||
log "工作区目录: ${WORKSPACE_DIR}"
|
||||
log "保留最近 ${RETENTION_DAYS} 天的构建"
|
||||
log "最小保留空间: ${MIN_FREE_SPACE_GB}GB"
|
||||
|
||||
local total_size_before=$(du -sh "$WORKSPACE_DIR" 2>/dev/null | cut -f1)
|
||||
log "清理前工作区大小: ${total_size_before}"
|
||||
|
||||
# 查找并清理旧的工作区目录
|
||||
local cleaned_count=0
|
||||
local error_count=0
|
||||
|
||||
find "$WORKSPACE_DIR" -maxdepth 2 -type d -name "*" | while read -r dir_path; do
|
||||
# 跳过根目录和一级目录
|
||||
if [ "$dir_path" = "$WORKSPACE_DIR" ] || [ "$(dirname "$dir_path")" = "$WORKSPACE_DIR" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if safe_delete "$dir_path"; then
|
||||
((cleaned_count++)) || true
|
||||
else
|
||||
((error_count++)) || true
|
||||
fi
|
||||
done
|
||||
|
||||
local total_size_after=$(du -sh "$WORKSPACE_DIR" 2>/dev/null | cut -f1)
|
||||
|
||||
log "清理完成: 删除了 ${cleaned_count} 个目录, ${error_count} 个错误"
|
||||
log "清理后工作区大小: ${total_size_after}"
|
||||
log "释放空间: $(echo "$total_size_before -> $total_size_after")"
|
||||
}
|
||||
|
||||
# 清理构建历史(可选)
|
||||
clean_builds() {
|
||||
local builds_dir="${JENKINS_HOME}/jobs"
|
||||
local max_builds_to_keep=20 # 每个作业保留的最大构建数
|
||||
|
||||
log "开始清理旧的构建历史..."
|
||||
|
||||
find "$builds_dir" -name "builds" -type d 2>/dev/null | while read -r builds_path; do
|
||||
local job_dir=$(dirname "$builds_path")
|
||||
local job_name=$(basename "$job_dir")
|
||||
|
||||
# 获取所有构建目录并按编号排序
|
||||
local build_dirs=$(find "$builds_path" -maxdepth 1 -type d -name "[0-9]*" | sort -Vr)
|
||||
local build_count=$(echo "$build_dirs" | wc -l)
|
||||
|
||||
if [ "$build_count" -gt "$max_builds_to_keep" ]; then
|
||||
local to_delete_count=$((build_count - max_builds_to_keep))
|
||||
local deleted_count=0
|
||||
|
||||
echo "$build_dirs" | tail -n "$to_delete_count" | while read -r build_dir; do
|
||||
if safe_delete "$build_dir"; then
|
||||
((deleted_count++)) || true
|
||||
fi
|
||||
done
|
||||
|
||||
log "作业 ${job_name}: 保留 ${max_builds_to_keep} 个构建,删除 ${deleted_count} 个旧构建"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
使用方式: $0 [选项]
|
||||
|
||||
选项:
|
||||
-d, --dry-run 干运行模式,只显示将要执行的操作
|
||||
-a, --all 清理工作区和构建历史
|
||||
-w, --workspace 只清理工作区(默认)
|
||||
-b, --builds 只清理构建历史
|
||||
-r DAYS, --retention-days DAYS 设置保留天数(默认: 7)
|
||||
-h, --help 显示此帮助信息
|
||||
|
||||
示例:
|
||||
$0 # 默认清理工作区
|
||||
$0 --dry-run # 干运行模式
|
||||
$0 --all # 清理工作区和构建历史
|
||||
$0 --retention-days 30 # 保留最近30天的构建
|
||||
EOF
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
MODE="workspace"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-d|--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
-a|--all)
|
||||
MODE="all"
|
||||
shift
|
||||
;;
|
||||
-w|--workspace)
|
||||
MODE="workspace"
|
||||
shift
|
||||
;;
|
||||
-b|--builds)
|
||||
MODE="builds"
|
||||
shift
|
||||
;;
|
||||
-r|--retention-days)
|
||||
RETENTION_DAYS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log "错误: 未知选项 $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 主执行函数
|
||||
main() {
|
||||
log "=== Jenkins 工作区清理开始 ==="
|
||||
log "模式: ${MODE}, 干运行: ${DRY_RUN}, 保留天数: ${RETENTION_DAYS}"
|
||||
|
||||
check_directories
|
||||
|
||||
# 检查磁盘空间,如果充足且不是强制清理,则跳过
|
||||
if ! check_disk_space && [ "$MODE" != "all" ]; then
|
||||
log "磁盘空间充足,跳过清理"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$MODE" in
|
||||
"workspace")
|
||||
clean_workspace
|
||||
;;
|
||||
"builds")
|
||||
clean_builds
|
||||
;;
|
||||
"all")
|
||||
clean_workspace
|
||||
clean_builds
|
||||
;;
|
||||
esac
|
||||
|
||||
log "=== Jenkins 工作区清理完成 ==="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,475 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jenkins构建通知 - ${projectName}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #24292e;
|
||||
background-color: #f6f8fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
padding: 28px 32px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #f6f8fa 100%);
|
||||
}
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
border-radius: 24px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.status-success {
|
||||
background-color: rgba(46, 188, 79, 0.15);
|
||||
color: #26a641;
|
||||
}
|
||||
.status-failure {
|
||||
background-color: rgba(218, 54, 51, 0.15);
|
||||
color: #da3633;
|
||||
}
|
||||
.status-unstable {
|
||||
background-color: rgba(242, 211, 38, 0.15);
|
||||
color: #b08800;
|
||||
}
|
||||
.status-unknown {
|
||||
background-color: rgba(158, 158, 158, 0.15);
|
||||
color: #6e7781;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
color: #1f2328;
|
||||
}
|
||||
.header p {
|
||||
color: #656d76;
|
||||
font-size: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.content {
|
||||
padding: 32px;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
color: #1f2328;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-title svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.info-label {
|
||||
font-size: 13px;
|
||||
color: #656d76;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.info-value {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #1f2328;
|
||||
word-break: break-word;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.badge-env {
|
||||
background-color: rgba(84, 174, 255, 0.15);
|
||||
color: #218bff;
|
||||
}
|
||||
.badge-success {
|
||||
background-color: rgba(46, 188, 79, 0.15);
|
||||
color: #26a641;
|
||||
}
|
||||
.badge-failure {
|
||||
background-color: rgba(218, 54, 51, 0.15);
|
||||
color: #da3633;
|
||||
}
|
||||
.badge-warning {
|
||||
background-color: rgba(242, 211, 38, 0.15);
|
||||
color: #b08800;
|
||||
}
|
||||
.badge-secondary {
|
||||
background-color: rgba(158, 158, 158, 0.15);
|
||||
color: #6e7781;
|
||||
}
|
||||
.build-details {
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #0969da;
|
||||
}
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #0969da;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
transition: background-color 0.2s;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.cta-button:hover {
|
||||
background-color: #0858c7;
|
||||
}
|
||||
.footer {
|
||||
padding: 24px 32px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #656d76;
|
||||
background-color: #f6f8fa;
|
||||
border-top: 1px solid #eaecef;
|
||||
}
|
||||
.footer-links {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.footer-links a {
|
||||
color: #0969da;
|
||||
text-decoration: none;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.footer-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.timeline {
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.timeline:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 2px;
|
||||
background-color: #d0d7de;
|
||||
}
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.timeline-item:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 8px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #0969da;
|
||||
}
|
||||
.timeline-item.success:before {
|
||||
background-color: #26a641;
|
||||
}
|
||||
.timeline-item.failure:before {
|
||||
background-color: #da3633;
|
||||
}
|
||||
.timeline-item.warning:before {
|
||||
background-color: #b08800;
|
||||
}
|
||||
.timeline-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.timeline-desc {
|
||||
font-size: 14px;
|
||||
color: #656d76;
|
||||
}
|
||||
.stage-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.stage-table th,
|
||||
.stage-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
.stage-table th {
|
||||
background-color: #f6f8fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
.stage-table tr:hover {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
.stage-success {
|
||||
color: #26a641;
|
||||
}
|
||||
.stage-failure {
|
||||
color: #da3633;
|
||||
}
|
||||
.stage-warning {
|
||||
color: #b08800;
|
||||
}
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.summary-card {
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.summary-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.summary-label {
|
||||
font-size: 14px;
|
||||
color: #656d76;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.info-grid, .summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.content {
|
||||
padding: 24px;
|
||||
}
|
||||
.header {
|
||||
padding: 24px;
|
||||
}
|
||||
.stage-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
.stage-table th,
|
||||
.stage-table td {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="status-badge ${statusClass}">
|
||||
${statusEmoji} ${statusText}
|
||||
</div>
|
||||
<h1>${projectName}</h1>
|
||||
<p>构建 #${buildNumber} • ${buildTime} • 耗时 ${buildDuration}</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 构建摘要 -->
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm1-11a1 1 0 1 0-2 0v3a1 1 0 0 0 .293.707l2.828 2.829a1 1 0 1 0 1.415-1.415L9 6.586V5z"/>
|
||||
</svg>
|
||||
构建摘要
|
||||
</h2>
|
||||
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card">
|
||||
<div class="summary-value">${totalStages}</div>
|
||||
<div class="summary-label">总Stage数</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="summary-value">${successStages}</div>
|
||||
<div class="summary-label">成功Stage数</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="summary-value">${failedStages}</div>
|
||||
<div class="summary-label">失败Stage数</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="summary-value">${successRate}%</div>
|
||||
<div class="summary-label">成功率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">项目名称</span>
|
||||
<span class="info-value">${projectName}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建状态</span>
|
||||
<span class="info-value">
|
||||
<span class="badge ${badgeClass}">${badgeText}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建编号</span>
|
||||
<span class="info-value">#${buildNumber}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建ID</span>
|
||||
<span class="info-value">${buildId}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建时间</span>
|
||||
<span class="info-value">${buildTime}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建时长</span>
|
||||
<span class="info-value">${buildDuration}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">环境</span>
|
||||
<span class="info-value">
|
||||
<span class="badge badge-env">${profiles}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">构建节点</span>
|
||||
<span class="info-value">${nodeName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Git信息 -->
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
Git信息
|
||||
</h2>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">代码分支</span>
|
||||
<span class="info-value">${gitBranch}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">提交版本</span>
|
||||
<span class="info-value">${gitCommitShort}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">完整提交ID</span>
|
||||
<span class="info-value">${gitCommit}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">代码库</span>
|
||||
<span class="info-value">${gitUrl}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 构建产物和部署信息 -->
|
||||
<div class="build-details">
|
||||
<h3 style="margin-bottom: 16px;">构建产物</h3>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">镜像名称</span>
|
||||
<span class="info-value">${imageFullName}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">镜像标签</span>
|
||||
<span class="info-value">${imageTags}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 20px 0 16px 0;">部署信息</h3>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">部署策略</span>
|
||||
<span class="info-value">${deploymentStrategy}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">健康检查</span>
|
||||
<span class="info-value">
|
||||
<span class="badge badge-success">${healthCheckStatus}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 构建时间线 -->
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"/>
|
||||
</svg>
|
||||
构建时间线
|
||||
</h2>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-item success">
|
||||
<div class="timeline-title">代码检出</div>
|
||||
<div class="timeline-desc">从 ${gitBranch} 分支成功检出代码</div>
|
||||
</div>
|
||||
${stagesContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div style="text-align: center; margin: 28px 0;">
|
||||
<a href="${buildUrl}" class="cta-button">查看构建详情</a>
|
||||
<p style="font-size: 14px; color: #656d76; margin-top: 8px;">点击查看完整的构建日志和详细信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>此邮件由Jenkins自动发送,请勿直接回复。</p>
|
||||
<!-- <p>Jenkins服务器: ${jenkinsUrl}</p> -->
|
||||
<div class="footer-links">
|
||||
<a href="${jenkinsUrl}">访问Jenkins</a> •
|
||||
<a href="${buildUrl}">构建详情</a> •
|
||||
<a href="${gitUrl}">代码库</a>
|
||||
</div>
|
||||
<p style="margin-top: 12px;">© 2025 小蚂蚁云 DevOps Team. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user