LoadinG

网站状态检测工具 实时监控网站可用性、响应时间及状态码 HTML+JS代码

本文阅读 4 分钟
首页 程序代码 正文

功能介绍:

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

1. 网址管理功能

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

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

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

2. 监测功能

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

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

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

3. 日志记录与显示功能

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

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

代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网站状态监测工具 - 微安博客</title>
    <style>
        :root {
            --primary-color: #4361ee;
            --primary-hover: #3a56d4;
            --secondary-color: #3f37c9;
            --success-color: #4cc9f0;
            --danger-color: #f72585;
            --warning-color: #f8961e;
            --light-color: #f8f9fa;
            --dark-color: #212529;
            --gray-color: #6c757d;
            --border-color: #dee2e6;
            --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            --transition: all 0.3s ease;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
            background-color: #f5f7fa;
            color: var(--dark-color);
            line-height: 1.6;
            display: flex;
            flex-direction: column;
            min-height: 100vh;
            padding: 20px;
            position: relative;
        }

        .container {
            display: flex;
            width: 100%;
            max-width: 1400px;
            margin: 0 auto;
            gap: 20px;
            flex: 1;
        }

        .panel {
            background-color: white;
            border-radius: 10px;
            box-shadow: var(--card-shadow);
            padding: 25px;
            display: flex;
            flex-direction: column;
        }

        .left-panel {
            flex: 1;
            min-width: 350px;
        }

        .right-panel {
            flex: 2;
        }

        h1 {
            color: var(--primary-color);
            margin-bottom: 25px;
            text-align: center;
            font-weight: 600;
            font-size: 1.8rem;
        }

        .input-group {
            margin-bottom: 20px;
            display: flex;
            gap: 10px;
        }

        input {
            flex: 1;
            padding: 12px 15px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            font-size: 1rem;
            transition: var(--transition);
        }

        input:focus {
            outline: none;
            border-color: var(--primary-color);
            box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
        }

        input.invalid {
            border-color: var(--danger-color);
            animation: shake 0.5s ease-in-out;
        }

        @keyframes shake {
            0%, 100% { transform: translateX(0); }
            20%, 60% { transform: translateX(-5px); }
            40%, 80% { transform: translateX(5px); }
        }

        button {
            padding: 12px 20px;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 500;
            font-size: 1rem;
            transition: var(--transition);
            white-space: nowrap;
        }

        button:hover {
            background-color: var(--primary-hover);
            transform: translateY(-1px);
        }

        button:active {
            transform: translateY(0);
        }

        #monitor-button.started {
            background-color: var(--danger-color);
        }

        #monitor-button.started:hover {
            background-color: #e5177e;
        }

        .url-list {
            flex: 1;
            overflow-y: auto;
            margin-top: 15px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 5px;
            max-height: calc(100% - 180px);
        }

        .url-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 15px;
            margin-bottom: 8px;
            border-radius: 5px;
            background-color: var(--light-color);
            transition: var(--transition);
        }

        .url-item:hover {
            transform: translateX(3px);
        }

        .url-item.normal {
            border-left: 4px solid #4cc9f0;
        }

        .url-item.error {
            border-left: 4px solid var(--danger-color);
        }

        .url-text {
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            margin-right: 10px;
        }

        .delete-btn {
            background-color: var(--danger-color);
            padding: 6px 12px;
            font-size: 0.85rem;
        }

        .delete-btn:hover {
            background-color: #e5177e;
        }

        .filter-container {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
            gap: 10px;
        }

        .filter-container label {
            font-weight: 500;
            color: var(--gray-color);
        }

        #filter-url {
            flex: 1;
            padding: 10px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
        }

        .log-container {
            flex: 1;
            overflow-y: auto;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 10px;
            background-color: #f8f9fa;
            max-height: calc(100vh - 200px);
        }

        .log-entry {
            padding: 10px 15px;
            margin-bottom: 8px;
            border-radius: 5px;
            font-size: 0.9rem;
            display: flex;
            flex-wrap: wrap;
            gap: 5px 15px;
        }

        .log-entry.normal {
            background-color: rgba(76, 201, 240, 0.1);
            border-left: 3px solid #4cc9f0;
        }

        .log-entry.error {
            background-color: rgba(247, 37, 133, 0.1);
            border-left: 3px solid var(--danger-color);
        }

        .log-time {
            color: var(--gray-color);
            font-weight: 500;
        }

        .log-url {
            flex: 1;
            color: var(--primary-color);
            font-weight: 500;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .log-status {
            font-weight: 600;
        }

        .log-status.normal {
            color: #4cc9f0;
        }

        .log-status.error {
            color: var(--danger-color);
        }

        .log-details {
            width: 100%;
            margin-top: 5px;
            color: var(--gray-color);
            font-size: 0.85rem;
        }

        .interval-input {
            width: 80px;
            text-align: center;
        }

        .footer {
            text-align: center;
            padding: 15px 0;
            color: var(--gray-color);
            font-size: 0.9rem;
            margin-top: 20px;
        }

        @media (max-width: 768px) {
            body {
                padding: 10px;
            }
            
            .container {
                flex-direction: column;
            }
            
            .left-panel, .right-panel {
                min-width: auto;
            }
            
            .input-group {
                flex-direction: column;
            }
            
            .log-container {
                max-height: 300px;
            }
            
            .url-list {
                max-height: 200px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="panel left-panel">
            <h1>网站状态监测工具</h1>
            
            <div class="input-group">
                <input type="text" id="url-input" placeholder="输入网址 (如: example.com)">
                <button id="add-url">添加网址</button>
            </div>
            
            <div class="input-group">
                <input type="number" id="interval" class="interval-input" value="60" min="10">
                <button id="monitor-button">开始监测</button>
            </div>
            
            <div class="url-list" id="url-list"></div>
        </div>
        
        <div class="panel right-panel">
            <div class="filter-container">
                <label for="filter-url">筛选:</label>
                <select id="filter-url">
                    <option value="">全部网址</option>
                </select>
            </div>
            
            <div class="log-container" id="log-container"></div>
        </div>
    </div>

    <footer class="footer">
        &#169;&#32;&#50;&#48;&#50;&#53;&#32;&#24494;&#23433;&#21338;&#23458;&#32;&#45;&#32;&#98;&#108;&#111;&#103;&#46;&#119;&#101;&#105;&#97;&#110;&#101;&#116;&#46;&#99;&#111;&#109;&#32;&#65;&#108;&#108;&#32;&#82;&#105;&#103;&#104;&#116;&#115;&#32;&#82;&#101;&#115;&#101;&#114;&#118;&#101;&#100;
    </footer>

    <script>
        // DOM元素
        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 = 2;
        const MAX_LOGS = 200;
        let isNetworkConnected = true;

        // 添加URL
        addUrlButton.addEventListener('click', addUrl);

        // 回车添加URL
        urlInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') addUrl();
        });

        function addUrl() {
            let url = urlInput.value.trim();
            if (!url) {
                showInputError('请输入有效的网址');
                return;
            }
            
            // 自动补全协议
            if (!url.startsWith('http://') && !url.startsWith('https://')) {
                url = 'https://' + url;
            }
            
            // 验证URL格式
            try {
                new URL(url);
            } catch (e) {
                showInputError('请输入有效的网址 (如: example.com)');
                return;
            }
            
            // 检查是否已存在
            if (urls.includes(url)) {
                showInputError('该网址已存在监测列表中');
                return;
            }
            
            // 添加URL到列表
            urls.push(url);
            addUrlToList(url);
            addUrlToFilter(url);
            urlInput.value = '';
            urlInput.focus();
        }
        
        function showInputError(message) {
            urlInput.classList.add('invalid');
            setTimeout(() => {
                urlInput.classList.remove('invalid');
            }, 500);
            alert(message);
        }
        
        function addUrlToList(url) {
            const item = document.createElement('div');
            item.className = 'url-item';
            item.dataset.url = url;
            
            const urlText = document.createElement('span');
            urlText.className = 'url-text';
            urlText.textContent = url;
            
            const deleteBtn = document.createElement('button');
            deleteBtn.className = 'delete-btn';
            deleteBtn.textContent = '删除';
            deleteBtn.addEventListener('click', () => removeUrl(url, item));
            
            item.appendChild(urlText);
            item.appendChild(deleteBtn);
            urlList.appendChild(item);
        }
        
        function addUrlToFilter(url) {
            const option = document.createElement('option');
            option.value = url;
            option.textContent = new URL(url).hostname;
            filterUrlSelect.appendChild(option);
        }
        
        function removeUrl(url, element) {
            const index = urls.indexOf(url);
            if (index > -1) {
                urls.splice(index, 1);
                urlList.removeChild(element);
                removeUrlFromFilter(url);
            }
        }
        
        function removeUrlFromFilter(url) {
            const options = Array.from(filterUrlSelect.options);
            const optionToRemove = options.find(option => option.value === url);
            if (optionToRemove) {
                filterUrlSelect.removeChild(optionToRemove);
            }
        }

        // 监测控制
        monitorButton.addEventListener('click', toggleMonitoring);

        function toggleMonitoring() {
            if (urls.length === 0) {
                alert('请先添加要监测的网址');
                return;
            }
            
            if (!isMonitoring) {
                startMonitoring();
            } else {
                stopMonitoring();
            }
        }
        
        function startMonitoring() {
            const interval = parseInt(intervalInput.value) * 1000;
            if (interval < 10000) {
                alert('监测间隔不能小于10秒');
                return;
            }
                
            // 立即检测一次所有添加的网址
            checkAllUrls();
                
            // 按间隔时间循环检测
            intervalId = setInterval(checkAllUrls, interval);
                
            monitorButton.classList.add('started');
            monitorButton.textContent = '停止监测';
            isMonitoring = true;
        }
        
        function stopMonitoring() {
            clearInterval(intervalId);
            monitorButton.classList.remove('started');
            monitorButton.textContent = '开始监测';
            isMonitoring = false;
        }
        
        function checkAllUrls() {
            if (isNetworkConnected) {
                urls.forEach(url => checkUrl(url, 0));
            }
        }

        // 筛选日志
        filterUrlSelect.addEventListener('change', () => {
            displayLogs(filterUrlSelect.value);
        });

        // 检测URL可用性
        function checkUrl(url, retryCount) {
            if (!isNetworkConnected) {
                addLog(url, null, '网络连接异常');
                return;
            }
            
            const startTime = performance.now();
            const controller = new AbortController();
            const timeoutId = setTimeout(() => {
                controller.abort();
            }, 10000); // 10秒超时

            // 使用更宽松的检测方式
            fetch(url, {
                method: 'GET',
                mode: 'no-cors',
                signal: controller.signal,
                redirect: 'follow',
                cache: 'no-store'
            })
            .then(() => {
                clearTimeout(timeoutId);
                const responseTime = performance.now() - startTime;
                addLog(url, responseTime);
                isNetworkConnected = true;
            })
            .catch(error => {
                clearTimeout(timeoutId);
                const responseTime = performance.now() - startTime;
                
                if (retryCount < MAX_RETRIES) {
                    setTimeout(() => checkUrl(url, retryCount + 1), 1000);
                } else {
                    addLog(url, responseTime, getErrorMessage(error));
                }
            });
        }
        
        function getErrorMessage(error) {
            if (error.name === 'AbortError') {
                return '请求超时 (10秒)';
            } else if (error.message.includes('NetworkError')) {
                isNetworkConnected = false;
                return '网络错误,请检查网络连接';
            }
            return error.message || '无法访问该网站';
        }

        // 日志处理
        function addLog(url, responseTime, errorMessage) {
            const isNormal = !errorMessage;
            const timestamp = new Date().toLocaleString();
            
            const log = {
                url,
                status: isNormal ? 'normal' : 'error',
                responseTime: isNormal ? Math.round(responseTime) : null,
                timestamp,
                error: errorMessage
            };
            
            // 添加到日志列表
            logs.push(log);
            if (logs.length > MAX_LOGS) {
                logs.shift();
            }
            
            // 更新显示
            displayLogs(filterUrlSelect.value);
            updateUrlStatus(url, isNormal);
        }
        
        function updateUrlStatus(url, isNormal) {
            const items = document.querySelectorAll('.url-item');
            items.forEach(item => {
                if (item.dataset.url === url) {
                    item.className = `url-item ${isNormal ? 'normal' : 'error'}`;
                }
            });
        }
        
        function displayLogs(filterUrl) {
            logContainer.innerHTML = '';
            const filteredLogs = filterUrl ? logs.filter(log => log.url === filterUrl) : logs;
            
            filteredLogs.forEach(log => {
                const logEntry = document.createElement('div');
                logEntry.className = `log-entry ${log.status}`;
                
                const timeSpan = document.createElement('span');
                timeSpan.className = 'log-time';
                timeSpan.textContent = log.timestamp;
                
                const urlSpan = document.createElement('span');
                urlSpan.className = 'log-url';
                urlSpan.textContent = new URL(log.url).hostname;
                
                const statusSpan = document.createElement('span');
                statusSpan.className = `log-status ${log.status}`;
                statusSpan.textContent = log.status === 'normal' ? '正常' : '异常';
                
                logEntry.appendChild(timeSpan);
                logEntry.appendChild(urlSpan);
                logEntry.appendChild(statusSpan);
                
                if (log.responseTime !== null) {
                    const timeDetail = document.createElement('span');
                    timeDetail.textContent = `响应: ${log.responseTime}ms`;
                    logEntry.appendChild(timeDetail);
                }
                
                if (log.error) {
                    const errorDetail = document.createElement('div');
                    errorDetail.className = 'log-details';
                    errorDetail.textContent = `错误: ${log.error}`;
                    logEntry.appendChild(errorDetail);
                }
                
                logContainer.appendChild(logEntry);
            });
            
            // 自动滚动到最底部
            logContainer.scrollTop = logContainer.scrollHeight;
        }
    </script>
</body>
</html>
文章采用:署名-非商业性使用-相同方式知识共享 署名 4.0 协议国际版 (CC BY-NC-SA 4.0) 许可协议授权。
免责声明:本页面资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系客服并出示版权证明以便删除。
分享
UEditorPlus 开源免费编辑器轻量级代码编辑工具下载
« 上一篇 06-14

发表评论 Comment

您必须 后才能发表评论哦~
昵称
请输入您的昵称
邮箱
输入QQ邮箱可获取头像
网址
可通过昵称访问您网站
快捷回复: 验证码:
让大家也知道你的独特见解
已有 0 条评论

动态快讯

热门文章

QQ客服:3236485 QQ群号:530123520

在线时间:09:00-18:00

扫描二维码

联系官方客服微信号

扫描二维码

关注官方微信公众号

{"error":400,"message":"over quota"}