功能介绍

HTML+JS开发一个网站状态检测网站,用来监测网站是否可以正常访问,可以快速知道自己网站运行状态。
1. 网址管理功能

添加网址:用户可以在输入框中输入要监测的网页地址,点击 “添加网址” 按钮,工具会对输入的网址进行格式验证。验证通过后,网址会被添加到左侧面板的列表中,并且列表项后有 “删除” 按钮。

删除网址:在左侧面板的网址列表中,每个网址后面都有一个 “删除” 按钮,点击该按钮可以将对应的网址从监测列表中移除,同时也会从筛选下拉框中移除该网址选项。

筛选网址:右侧面板有一个 “筛选网址” 的下拉框,用户可以选择具体的网址进行筛选,只显示该网址的监测日志,也可以选择 “全部” 来显示所有网址的监测日志。

2. 监测功能

设置监测间隔:用户可以在输入框中设置监测间隔时间(单位为秒),默认值为 60 秒。

开始 / 停止监测:点击 “开始监测” 按钮,工具会立即对所有已添加的网址进行一次检测,之后按照设置的监测间隔时间循环检测。点击 “停止监测” 按钮可停止监测。

重试机制:在进行网址检测时,如果请求失败,会进行最多 3 次重试,若重试后仍失败,则记录错误日志。

3. 日志记录与显示功能

日志记录:每次对网址进行检测后,会记录网址的状态(正常或异常)、响应时间、时间戳以及错误信息(若有)。日志记录会存储在 logs 数组中,当日志数量超过 1000 条时,会移除最早的日志记录。

日志显示:右侧面板的日志容器会显示所有或筛选后的监测日志,正常状态的日志为黑色,异常状态的日志为红色。日志会自动滚动到最底部以显示最新信息。

代码分享

<!DOCTYPE html>
<html>
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网站监测工具</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f9f9f9;
            display: flex;
            margin: 0;
            padding: 0;
            height: 100vh;
        }
 
        /* 调整左右面板的高度,减去一行日志的大致高度(假设为 30px) */
        .left-panel,
        .right-panel {
            flex: 1;
            padding: 20px;
            background-color: #fff;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            margin: 20px;
            height: calc(100vh - 40px - 30px);
            overflow-y: auto;
            display: flex;
            flex-direction: column;
        }
 
        .right-panel {
            flex: 2;
        }
 
        h1 {
            color: #333;
            margin-bottom: 20px;
            text-align: center;
        }
 
        .input-container {
            margin-bottom: 15px;
            display: flex;
            align-items: center;
        }
 
        input {
            padding: 10px;
            margin-right: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            flex: 1;
            transition: all 0.3s ease;
        }
 
        input.invalid {
            color: red;
            animation: shake 0.5s ease-in-out 3;
        }
 
        @keyframes shake {
            0% {
                transform: translateX(0);
            }
            25% {
                transform: translateX(-5px);
            }
            50% {
                transform: translateX(5px);
            }
            75% {
                transform: translateX(-5px);
            }
            100% {
                transform: translateX(0);
            }
        }
 
        button {
            padding: 10px 15px;
            background-color: #007BFF;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }
 
        button:hover {
            background-color: #0056b3;
        }
 
        #monitor-button.started {
            background-color: #dc3545;
        }
 
        ul {
            list-style-type: none;
            padding: 0;
            margin-top: 10px;
            /* 假设每个网址高度约 30px,显示 5 个网址的高度 */
            height: calc(5 * (30px + 5px));
            overflow-y: auto;
            flex: 1;
        }
 
        li {
            background-color: #f4f4f9;
            padding: 10px;
            margin-bottom: 5px;
            border-radius: 4px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
 
        #log-container {
            margin-top: 10px;
            height: calc(11 * (30px + 5px));
            overflow-y: auto;
            border: 1px solid #ccc;
            padding: 10px;
            border-radius: 4px;
        }
 
        #filter-container {
            margin-bottom: 10px;
        }
 
        #filter-url {
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            width: 100%;
        }
    </style>
</head>
 
<body>
    <div>
        <h1>网站监测工具</h1>
        <div>
            <input type="text" id="url-input" placeholder="输入要监测的网页地址">
            <button id="add-url">添加网址</button>
        </div>
        <div>
            <label for="interval">监测间隔 (秒):</label>
            <input type="number" id="interval" value="60">
            <button id="monitor-button">开始监测</button>
        </div>
        <ul id="url-list"></ul>
    </div>
    <div>
        <div id="filter-container">
            <label for="filter-url">筛选网址:</label>
            <select id="filter-url">
                <option value="">全部</option>
            </select>
        </div>
        <div id="log-container"></div>
    </div>
    <script>
        const urlInput = document.getElementById('url-input');
        const addUrlButton = document.getElementById('add-url');
        const intervalInput = document.getElementById('interval');
        const monitorButton = document.getElementById('monitor-button');
        const urlList = document.getElementById('url-list');
        const logContainer = document.getElementById('log-container');
        const filterUrlSelect = document.getElementById('filter-url');
 
        let urls = [];
        let logs = [];
        let intervalId;
        let isMonitoring = false;
        const MAX_RETRIES = 3;
        const MAX_LOGS = 1000;
        let isNetworkConnected = true;
 
        addUrlButton.addEventListener('click', () => {
            let url = urlInput.value.trim();
            if (!url) {
                urlInput.classList.add('invalid');
                setTimeout(() => {
                    urlInput.classList.remove('invalid');
                }, 1500);
                alert('请输入有效的网址');
                return;
            }
            if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('www.')) {
                url = 'https://' + url;
            } else if (url.startsWith('www.')) {
                url = 'https://' + url;
            }
            if (!/^https?:\/\/.+/i.test(url)) {
                urlInput.classList.add('invalid');
                setTimeout(() => {
                    urlInput.classList.remove('invalid');
                }, 1500);
                alert('请输入以 http://、https:// 或 www. 开头的有效网址');
                return;
            }
            urlInput.classList.remove('invalid');
            urls.push(url);
            const li = document.createElement('li');
            li.textContent = url;
            const deleteButton = document.createElement('button');
            deleteButton.textContent = '删除';
            deleteButton.addEventListener('click', () => {
                const index = urls.indexOf(url);
                if (index > -1) {
                    urls.splice(index, 1);
                    urlList.removeChild(li);
                    const options = Array.from(filterUrlSelect.options);
                    const optionToRemove = options.find(option => option.value === url);
                    if (optionToRemove) {
                        filterUrlSelect.removeChild(optionToRemove);
                    }
                }
            });
            li.appendChild(deleteButton);
            urlList.appendChild(li);
            urlInput.value = '';
 
            const option = document.createElement('option');
            option.value = url;
            option.textContent = url;
            filterUrlSelect.appendChild(option);
        });
 
        monitorButton.addEventListener('click', () => {
            if (!isMonitoring) {
                const interval = parseInt(intervalInput.value) * 1000;
                // 立即检测一次所有添加的网址
                urls.forEach(url => {
                    checkUrl(url, 0);
                });
                // 按间隔时间循环检测
                intervalId = setInterval(() => {
                    const newInterval = parseInt(intervalInput.value) * 1000;
                    if (newInterval!== interval) {
                        clearInterval(intervalId);
                        intervalId = setInterval(() => {
                            urls.forEach(url => {
                                checkUrl(url, 0);
                            });
                        }, newInterval);
                    }
                    if (isNetworkConnected) {
                        urls.forEach(url => {
                            checkUrl(url, 0);
                        });
                    }
                }, interval);
                monitorButton.classList.add('started');
                monitorButton.textContent = '停止监测';
                isMonitoring = true;
            } else {
                clearInterval(intervalId);
                monitorButton.classList.remove('started');
                monitorButton.textContent = '开始监测';
                isMonitoring = false;
            }
        });
 
        filterUrlSelect.addEventListener('change', () => {
            const selectedUrl = filterUrlSelect.value;
            displayLogs(selectedUrl);
        });
 
        function monitorUrlsImmediately(urls, interval) {
            // 先立即执行一次监测
            urls.forEach(url => {
                checkUrl(url, 0);
            });
            // 再按间隔时间循环监测
            intervalId = setInterval(() => {
                urls.forEach(url => {
                    checkUrl(url, 0);
                });
            }, interval);
        }
 
        function checkUrl(url, retryCount) {
            if (!isNetworkConnected) {
                handleLog(url, null, '电脑网络连接异常');
                return;
            }
            const startTime = performance.now();
            const controller = new AbortController();
            const signal = controller.signal;
            const timeoutId = setTimeout(() => {
                controller.abort();
            }, 5000);
 
            const requestOptions = {
                mode: 'cors',
                signal: signal,
                headers: {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
                }
            };
 
            fetch(url, requestOptions)
              .then(() => {
                    clearTimeout(timeoutId);
                    const endTime = performance.now();
                    const responseTime = endTime - startTime;
                    handleLog(url, responseTime, null);
                    isNetworkConnected = true;
                })
              .catch(error => {
                    clearTimeout(timeoutId);
                    const endTime = performance.now();
                    const responseTime = endTime - startTime;
                    if (retryCount < MAX_RETRIES) {
                        setTimeout(() => {
                            checkUrl(url, retryCount + 1);
                        }, 1000);
                    } else {
                        let errorMessage = error.message;
                        if (error.name === 'AbortError') {
                            errorMessage = '请求超时';
                        } else if (error.message.includes('NetworkError')) {
                            errorMessage = '网络错误,请检查网络连接或dai理设置';
                            isNetworkConnected = false;
                        }
                        handleLog(url, responseTime, errorMessage);
                    }
                });
        }
 
        function handleLog(url, responseTime, errorMessage) {
            let status;
            if (errorMessage === '电脑网络连接异常') {
                status = '异常';
            } else if (responseTime!== null && responseTime < 800) {
                status = '正常';
                errorMessage = null;
            } else {
                status = '异常';
                if (!errorMessage) {
                    errorMessage = `响应时间过长: ${responseTime} 毫秒`;
                }
            }
            const log = {
                url,
                status,
                responseTime: responseTime!== null? Math.round(responseTime) : null,
                timestamp: new Date().toLocaleString(),
                error: errorMessage
            };
            logs.push(log);
            if (logs.length > MAX_LOGS) {
                logs.shift();
            }
            displayLogs(filterUrlSelect.value);
            updateUrlColor(url, status === '正常');
        }
 
        function updateUrlColor(url, isNormal) {
            const listItems = urlList.getElementsByTagName('li');
            for (let i = 0; i < listItems.length; i++) {
                if (listItems[i].textContent.includes(url)) {
                    if (isNormal) {
                        listItems[i].style.color = 'black';
                    } else {
                        listItems[i].style.color = 'red';
                    }
                    break;
                }
            }
        }
 
        function displayLogs(filterUrl) {
            logContainer.innerHTML = '';
            const filteredLogs = filterUrl? logs.filter(log => log.url === filterUrl) : logs;
            filteredLogs.forEach(log => {
                const logEntry = document.createElement('p');
                let statusColor;
                if (log.status === '正常') {
                    statusColor = 'black';
                } else {
                    statusColor = 'red';
                }
                logEntry.style.color = statusColor;
                logEntry.textContent = `${log.timestamp} - ${log.url} - 状态: ${log.status}`;
                if (log.responseTime!== null) {
                    logEntry.textContent += ` - 响应时间: ${log.responseTime} 毫秒`;
                }
                if (log.error) {
                    logEntry.textContent += ` - 错误信息: ${log.error}`;
                }
                logContainer.appendChild(logEntry);
            });
            // 自动滚动到最底部显示最新信息
            logContainer.scrollTop = logContainer.scrollHeight;
        }
    </script>
</body>
 
</html>