Jenkins 联合构建 (v3.3 - 修复加载问题)

在 Jenkins 页面左上角添加状态显示、进度条和“联合构建”按钮

目前為 2025-10-24 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Jenkins 联合构建 (v3.3 - 修复加载问题)
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  在 Jenkins 页面左上角添加状态显示、进度条和“联合构建”按钮
// @author       Tandy
// @match        http://10.9.31.83:9001/job/sz-newcis-dev/*
// @grant        none
// @license      MIT
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // -----------------------------------------------------------------
    //  !!! 核心修复 !!!
    //  我们不再立即执行代码,而是定义一个 main 函数,
    //  并等待 "load" 事件触发 (即页面所有资源都加载完毕) 后再运行。
    //  我还添加了 @run-at document-idle 确保时机更晚。
    // -----------------------------------------------------------------

    // 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() {
        // --- 状态显示 ---
        statusDisplay = document.createElement('div');
        statusDisplay.id = 'gm-status-display';
        statusDisplay.style = `
            position: fixed; top: 10px; left: 10px; z-index: 9998; padding: 8px;
            background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 4px;
            width: 250px; font-family: Arial, sans-serif; font-size: 13px;
            box-sizing: border-box;
        `;
        statusDisplay.innerText = '准备就绪。';
        document.body.appendChild(statusDisplay);

        // --- 进度条 ---
        progressContainer = document.createElement('div');
        progressContainer.id = 'gm-progress-container';
        progressContainer.style = `
            position: fixed; top: 40px; left: 10px; z-index: 9998;
            width: 250px; 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);
        document.body.appendChild(progressContainer);

        // --- 按钮 ---
        const combinedButton = document.createElement('button');
        combinedButton.innerText = '▶ 启动联合构建';
        combinedButton.style = `
            position: fixed; left: 10px; top: 60px; z-index: 9999;
            padding: 8px 12px; color: white; border: none; border-radius: 4px;
            cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            width: 250px; text-align: left; background-color: #f0ad4e;
            box-sizing: border-box;
        `;
        combinedButton.onmouseover = function() { combinedButton.style.backgroundColor = '#ec971f'; };
        combinedButton.onmouseout = function() { combinedButton.style.backgroundColor = '#f0ad4e'; };
        combinedButton.onclick = startCombinedChain;
        document.body.appendChild(combinedButton);
    }

    // 3. 确保在 document.body 可用后才执行 UI 创建
    if (document.body) {
        createUI();
    } else {
        // 如果 body 还没好,就添加一个监听器
        window.addEventListener('load', createUI);
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址