在 Jenkins 页面底部添加悬浮的状态显示、进度条和“联合构建”按钮
当前为
// ==UserScript==
// @name Jenkins 联合构建 (v4.0 - 底部悬浮)
// @namespace http://tampermonkey.net/
// @version 4.0
// @description 在 Jenkins 页面底部添加悬浮的状态显示、进度条和“联合构建”按钮
// @author Tandy (修改 by Gemini)
// @match http://10.9.31.83:9001/job/sz-newcis-dev/*
// @grant none
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// -----------------------------------------------------------------
// v4.0 更新日志:
// 1. UI 从左上角移至底部悬浮工具栏。
// 2. 按钮在左侧,状态/进度条在右侧。
// 3. 修复了 v3.3 的加载逻辑。
// -----------------------------------------------------------------
// 1. 定义所有函数
function getMyJenkinsCrumb() {
const crumbInput = document.querySelector('input[name="Jenkins-Crumb"]');
if (crumbInput) {
return crumbInput.value;
}
console.error("未能找到 Jenkins Crumb input 元素。");
return null;
}
let statusDisplay, progressBar, progressContainer;
function updateStatus(message, isError = false) {
if (!statusDisplay) return; // 安全检查
// console.log(message);
statusDisplay.innerText = message;
statusDisplay.style.color = isError ? 'red' : 'black';
statusDisplay.style.borderColor = isError ? 'red' : '#ccc';
}
function startVisibleTimer(durationMs) {
return new Promise(resolve => {
if (!progressContainer || !progressBar) {
console.error("进度条元素未初始化。");
return resolve(); // 立即结束,防止卡住
}
progressContainer.style.display = 'block';
progressBar.style.width = '0%';
const startTime = Date.now();
const updateIntervalMs = 100;
const timerInterval = setInterval(() => {
const elapsed = Date.now() - startTime;
if (elapsed >= durationMs) {
clearInterval(timerInterval);
progressContainer.style.display = 'none';
progressBar.style.width = '0%';
resolve();
} else {
const progressPercent = (elapsed / durationMs) * 100;
const remainingSeconds = (durationMs - elapsed) / 1000;
progressBar.style.width = `${progressPercent}%`;
updateStatus(`步骤 2: 等待中... ${remainingSeconds.toFixed(1)}s`);
}
}, updateIntervalMs);
});
}
async function triggerSingleBuild(jobName, buildUrl, crumb) {
updateStatus(`[${jobName}] 正在请求构建...`);
try {
const response = await fetch(buildUrl, {
method: 'POST',
headers: { 'Jenkins-Crumb': crumb },
body: null
});
if (response.ok) {
updateStatus(`[${jobName}] 构建请求已成功发送!`);
return true;
} else {
updateStatus(`[${jobName}] 构建请求失败!状态: ${response.status}`, true);
return false;
}
} catch (error) {
updateStatus(`[${jobName}] 发送请求时发生网络错误: ${error}`, true);
return false;
}
}
async function startCombinedChain() {
const crumb = getMyJenkinsCrumb();
if (!crumb) {
updateStatus("错误:无法获取 Crumb。", true);
return;
}
updateStatus('步骤 1: 正在同时触发 Common 和 API ...');
const commonUrl = 'http://10.9.31.83:9001/job/sz-newcis-dev/job/sz-newcis-dev_cis-common/build?delay=0sec';
const apiUrl = 'http://10.9.31.83:9001/job/sz-newcis-dev/job/sz-newcis-dev_cis-api/build?delay=0sec';
const parallelBuilds = [
triggerSingleBuild('Common', commonUrl, crumb),
triggerSingleBuild('API', apiUrl, crumb)
];
const results = await Promise.all(parallelBuilds);
const allSucceeded = results.every(res => res === true);
if (!allSucceeded) {
updateStatus("步骤 1 失败:Common 或 API 构建请求失败。构建链中止。", true);
return;
}
updateStatus('Common 和 API 构建已全部触发。');
await startVisibleTimer(60000);
updateStatus('步骤 3: 正在触发 Bill Service ...');
const billUrl = 'http://10.9.31.83:9001/job/sz-newcis-dev/job/sz-newcis-dev_cis-bill-service/build?delay=0sec';
let success = await triggerSingleBuild('Bill Service', billUrl, crumb);
if (!success) {
updateStatus("构建链因 Bill Service 失败而中止。", true);
return;
}
updateStatus('步骤 4: 正在触发 Customer Service ...');
const customerUrl = 'http://10.9.31.83:9001/job/sz-newcis-dev/job/sz-newcis-dev_cis-customer-service/build?delay=0sec';
success = await triggerSingleBuild('Customer Service', customerUrl, crumb);
if (!success) {
updateStatus("Customer Service 构建失败。构建链结束。", true);
return;
}
updateStatus("联合构建链 (Common/API -> Bill -> Customer) 全部完成。");
}
// 2. (已修改) 定义我们的 "主" 函数,用于创建和附加 UI 元素
function createUI() {
// --- 1. 创建主悬浮容器 (页脚工具栏) ---
const footerBar = document.createElement('div');
footerBar.id = 'gm-footer-bar';
footerBar.style = `
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 8px 12px;
background-color: #f0f0f0;
border-top: 1px solid #ccc;
z-index: 9997;
display: flex;
justify-content: space-between; /* 核心:实现左右分离 */
align-items: center;
box-sizing: border-box;
font-family: Arial, sans-serif;
`;
// --- 2. 创建左侧容器 (用于按钮) ---
const leftControls = document.createElement('div');
leftControls.id = 'gm-left-controls';
// --- 3. 创建右侧容器 (用于状态和进度条) ---
const rightControls = document.createElement('div');
rightControls.id = 'gm-right-controls';
rightControls.style = `
display: flex;
flex-direction: column; /* 状态和进度条垂直堆叠 */
width: 250px; /* 保持原始宽度 */
`;
// --- 状态显示 (样式修改) ---
statusDisplay = document.createElement('div');
statusDisplay.id = 'gm-status-display';
statusDisplay.style = `
/* 移除了 position, top, left, z-index */
padding: 8px;
background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 4px;
width: 100%; /* 宽度由父容器 rightControls 控制 */
font-size: 13px;
box-sizing: border-box;
margin-bottom: 5px; /* 与下方进度条的间距 */
`;
statusDisplay.innerText = '准备就绪。';
// --- 进度条 (样式修改) ---
progressContainer = document.createElement('div');
progressContainer.id = 'gm-progress-container';
progressContainer.style = `
/* 移除了 position, top, left, z-index */
width: 100%; /* 宽度由父容器 rightControls 控制 */
height: 10px; background-color: #e9ecef;
border: 1px solid #ced4da; border-radius: 4px;
box-sizing: border-box; display: none;
`;
progressBar = document.createElement('div');
progressBar.id = 'gm-progress-bar';
progressBar.style = `
height: 100%; width: 0%; background-color: #007bff;
border-radius: 2px; transition: width 0.1s linear;
`;
progressContainer.appendChild(progressBar);
// --- 按钮 (样式修改) ---
const combinedButton = document.createElement('button');
combinedButton.innerText = '▶ 启动联合构建';
combinedButton.style = `
/* 移除了 position, left, top, z-index, width */
padding: 8px 12px; color: white; border: none; border-radius: 4px;
cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2);
text-align: left; background-color: #f0ad4e;
box-sizing: border-box;
font-size: 13px; /* 保持字体一致 */
`;
combinedButton.onmouseover = function() { combinedButton.style.backgroundColor = '#ec971f'; };
combinedButton.onmouseout = function() { combinedButton.style.backgroundColor = '#f0ad4e'; };
combinedButton.onclick = startCombinedChain;
// --- 4. 组装 DOM ---
// 按钮放入左侧
leftControls.appendChild(combinedButton);
// 状态和进度条放入右侧
rightControls.appendChild(statusDisplay);
rightControls.appendChild(progressContainer);
// 左右侧放入主工具栏
footerBar.appendChild(leftControls);
footerBar.appendChild(rightControls);
// 主工具栏放入页面
document.body.appendChild(footerBar);
updateStatus('联合构建工具已加载。');
}
// 3. (已恢复) 确保在 document.body 可用后才执行 UI 创建
if (document.body) {
createUI();
} else {
// 如果 body 还没好,就添加一个监听器
window.addEventListener('load', createUI);
}
})();