功能介绍:
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">
© 2025 微安博客 - blog.weianet.com All Rights Reserved
</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) 许可协议授权。
免责声明:本页面资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系客服并出示版权证明以便删除。
免责声明:本页面资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系客服并出示版权证明以便删除。