重新架构了整个models

This commit is contained in:
2025-09-07 16:47:12 +08:00
parent 1a4271232f
commit 1cac84b9d4
17 changed files with 1141 additions and 88 deletions

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务状态监控</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
@@ -52,13 +52,18 @@
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<i class="fa fa-server text-3xl mr-3"></i>
<i class="fas fa-server text-3xl mr-3"></i>
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold">服务状态监控</h1>
</div>
<div class="flex items-center space-x-2">
<i class="fa fa-refresh animate-spin mr-2"></i>
<span id="last-updated" class="text-sm md:text-base">最后更新: 加载中...</span>
</div>
<i class="fas fa-refresh animate-spin mr-2"></i>
<span id="last-updated" class="text-sm md:text-base">最后更新: 加载中...</span>
</div>
<div class="mt-4 md:mt-0">
<a href="/services/" class="bg-white text-primary px-4 py-2 rounded-lg font-medium hover:bg-gray-100 transition-colors">
<i class="fas fa-list mr-2"></i> 查看所有服务
</a>
</div>
</div>
<p class="mt-2 text-gray-200">实时监控系统服务运行状态与可靠性</p>
</div>
@@ -71,43 +76,72 @@
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-operational">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">正常运行</p>
<p class="text-gray-500 text-sm">正常</p>
<h3 id="operational-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-operational/10 flex items-center justify-center text-operational">
<i class="fa fa-check-circle text-xl"></i>
<i class="fas fa-check-circle text-xl"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-degraded">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">性能下降</p>
<p class="text-gray-500 text-sm">未知</p>
<h3 id="degraded-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-degraded/10 flex items-center justify-center text-degraded">
<i class="fa fa-exclamation-triangle text-xl"></i>
<i class="fas fa-question-circle text-xl"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-outage">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">服务中断</p>
<p class="text-gray-500 text-sm">故障</p>
<h3 id="outage-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-outage/10 flex items-center justify-center text-outage">
<i class="fa fa-times-circle text-xl"></i>
<i class="fas fa-times-circle text-xl"></i>
</div>
</div>
</div>
</div>
<!-- 最近异常 -->
<div class="bg-white rounded-xl p-6 card-shadow mb-10">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">最近异常</h2>
<a href="/services/?status=down" class="text-primary hover:underline text-sm font-medium">
查看全部 <i class="fas fa-arrow-right ml-1"></i>
</a>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">服务名称</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">消息</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">检测时间</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="recent-issues-tbody">
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-500">
<i class="fas fa-circle-o-notch fa-spin mr-2"></i> 加载中...
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 服务列表 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="services-container">
<!-- 服务卡片将通过JavaScript动态生成 -->
<div class="col-span-full text-center py-12 text-gray-500">
<i class="fa fa-circle-o-notch fa-spin text-3xl mb-4"></i>
<i class="fas fa-circle-o-notch fa-spin text-3xl mb-4"></i>
<p>加载服务数据中...</p>
</div>
</div>
@@ -123,20 +157,20 @@
<script>
// 状态样式映射
const statusStyles = {
operational: {
up: {
class: 'bg-operational text-white',
icon: 'fa-check-circle',
text: '正常运行'
icon: 'fas fa-check-circle',
text: '正常'
},
degraded: {
class: 'bg-degraded text-white',
icon: 'fa-exclamation-triangle',
text: '性能下降'
},
outage: {
down: {
class: 'bg-outage text-white',
icon: 'fa-times-circle',
text: '服务中断'
icon: 'fas fa-times-circle',
text: '故障'
},
unknown: {
class: 'bg-degraded text-white',
icon: 'fas fa-question-circle',
text: '未知'
}
};
@@ -151,19 +185,23 @@
// 统计各状态数量
const counts = {
operational: 0,
degraded: 0,
outage: 0
up: 0,
down: 0,
unknown: 0
};
data.forEach(service => {
counts[service.status]++;
if (service.latest_status) {
counts[service.latest_status.toLowerCase()]++;
} else {
counts.unknown++;
}
});
// 更新统计数字
document.getElementById('operational-count').textContent = counts.operational;
document.getElementById('degraded-count').textContent = counts.degraded;
document.getElementById('outage-count').textContent = counts.outage;
document.getElementById('operational-count').textContent = counts.up;
document.getElementById('degraded-count').textContent = counts.unknown;
document.getElementById('outage-count').textContent = counts.down;
// 生成服务卡片
const container = document.getElementById('services-container');
@@ -172,7 +210,7 @@
if (data.length === 0) {
container.innerHTML = `
<div class="col-span-full text-center py-12 text-gray-500">
<i class="fa fa-info-circle text-3xl mb-4"></i>
<i class="fas fa-info-circle text-3xl mb-4"></i>
<p>暂无服务数据</p>
</div>
`;
@@ -180,7 +218,8 @@
}
data.forEach(service => {
const style = statusStyles[service.status];
const status = service.latest_status ? service.latest_status.toLowerCase() : 'unknown';
const style = statusStyles[status];
const card = document.createElement('div');
card.className = 'bg-white rounded-xl overflow-hidden card-shadow card-hover';
card.innerHTML = `
@@ -188,29 +227,41 @@
<div class="flex justify-between items-start mb-4">
<h3 class="text-xl font-bold text-gray-800">${service.name}</h3>
<span class="px-3 py-1 rounded-full text-sm font-medium ${style.class}">
<i class="fa ${style.icon} mr-1"></i> ${style.text}
<i class="fas ${style.icon} mr-1"></i> ${style.text}
</span>
</div>
<p class="text-gray-600 mb-4">${service.description}</p>
<div class="text-sm text-gray-600 mb-2">
<span class="inline-block bg-gray-100 rounded-full px-3 py-1 text-xs font-medium text-gray-800 mr-2">
${service.group}
</span>
<span class="inline-block bg-gray-100 rounded-full px-3 py-1 text-xs font-medium text-gray-800">
${service.check_type}
</span>
</div>
<p class="text-gray-600 mb-4">${service.description || '暂无描述'}</p>
<div class="grid grid-cols-2 gap-3 text-sm">
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-gray-500">IP地址</p>
<p class="font-medium">${service.ip_address}</p>
<p class="text-gray-500">主机/IP</p>
<p class="font-medium">${service.host}</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-gray-500">端口</p>
<p class="font-medium">${service.port}</p>
<p class="font-medium">${service.port || 'N/A'}</p>
</div>
${service.latest_response_time ? `
<div class="bg-gray-50 p-3 rounded-lg col-span-2">
<p class="text-gray-500">可靠率</p>
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
<div class="${style.class} h-2 rounded-full" style="width: ${service.reliability}%"></div>
</div>
<p class="font-medium mt-1">${service.reliability}%</p>
<p class="text-gray-500">响应时间</p>
<p class="font-medium">${service.latest_response_time} ms</p>
</div>
` : ''}
</div>
<div class="mt-4 text-xs text-gray-500">
<p>最后更新: ${new Date(service.last_updated).toLocaleString()}</p>
<p>最后检测: ${service.latest_check_time ? new Date(service.latest_check_time).toLocaleString() : '从未检测'}</p>
</div>
<div class="mt-4">
<a href="/services/${service.id}/" class="text-primary hover:underline text-sm font-medium">
查看详情 <i class="fas fa-arrow-right ml-1"></i>
</a>
</div>
</div>
`;
@@ -222,7 +273,7 @@
const container = document.getElementById('services-container');
container.innerHTML = `
<div class="col-span-full text-center py-12 text-red-500">
<i class="fa fa-exclamation-circle text-3xl mb-4"></i>
<i class="fas fa-exclamation-circle text-3xl mb-4"></i>
<p>加载服务数据失败,请刷新页面重试</p>
</div>
`;

View File

@@ -0,0 +1,340 @@
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务详情 - 服务状态监控</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165dff',
operational: '#36d399',
degraded: '#fbbd23',
outage: '#f87272',
dark: '#1e293b',
light: '#f8fafc'
},
fontfamily: {
inter: ['inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translatey(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.gradient-bg {
background: linear-gradient(135deg, #165dff 0%, #0a2463 100%);
}
}
</style>
</head>
<body class="bg-gray-50 font-inter min-h-screen">
<!-- 顶部导航 -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<a href="/" class="flex items-center">
<i class="fas fa-server text-3xl mr-3"></i>
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold">服务状态监控</h1>
</a>
</div>
<div class="flex items-center space-x-4">
<a href="/" class="text-white hover:text-gray-200">
<i class="fas fa-home mr-2"></i>首页
</a>
<a href="/services/" class="text-white hover:text-gray-200">
<i class="fas fa-list mr-2"></i>服务列表
</a>
</div>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto px-4 py-8">
<!-- 服务基本信息 -->
<div class="bg-white rounded-xl p-6 card-shadow mb-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-800">{{ service.name }}</h2>
<div class="flex items-center mt-2">
{% if service.latest_status == 'UP' %}
<span class="px-3 inline-flex text-sm leading-5 font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check-circle mr-1"></i> 正常
</span>
{% elif service.latest_status == 'DOWN' %}
<span class="px-3 inline-flex text-sm leading-5 font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-times-circle mr-1"></i> 故障
</span>
{% else %}
<span class="px-3 inline-flex text-sm leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
<i class="fas fa-question-circle mr-1"></i> 未知
</span>
{% endif %}
<span class="ml-3 text-sm text-gray-500">
<i class="fas fa-clock-o mr-1"></i>
{% if service.latest_check_time %}最后检测: {{ service.latest_check_time|date:"y-m-d h:i:s" }}{% else %}从未检测{% endif %}
</span>
</div>
</div>
<a href="/services/" class="mt-4 md:mt-0 bg-gray-100 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-200 transition-colors">
<i class="fas fa-arrow-left mr-2"></i>返回服务列表
</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold text-gray-700 mb-3">基本信息</h3>
<div class="space-y-2">
<div class="flex">
<span class="w-32 text-gray-500">服务名称:</span>
<span class="text-gray-800">{{ service.name }}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">所属分组:</span>
<span class="text-gray-800">{{ service.group.name }}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">主机/IP:</span>
<span class="text-gray-800">{{ service.host }}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">端口:</span>
<span class="text-gray-800">{% if service.port %}{{ service.port }}{% else %}N/A{% endif %}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">检测类型:</span>
<span class="text-gray-800">{{ service.get_check_type_display }}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">是否启用:</span>
<span class="text-gray-800">{% if service.is_active %}是{% else %}否{% endif %}</span>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-700 mb-3">最新状态</h3>
<div class="space-y-2">
<div class="flex">
<span class="w-32 text-gray-500">当前状态:</span>
<span class="text-gray-800">
{% if service.latest_status == 'UP' %}正常
{% elif service.latest_status == 'DOWN' %}故障
{% else %}未知{% endif %}
</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">响应时间:</span>
<span class="text-gray-800">{% if service.latest_response_time %}{{ service.latest_response_time }} ms{% else %}N/A{% endif %}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">最后检测:</span>
<span class="text-gray-800">{% if service.latest_check_time %}{{ service.latest_check_time|date:"y-m-d h:i:s" }}{% else %}从未检测{% endif %}</span>
</div>
<div class="flex">
<span class="w-32 text-gray-500">检测消息:</span>
<span class="text-gray-800">{% if service.latest_message %}{{ service.latest_message }}{% else %}无{% endif %}</span>
</div>
</div>
</div>
</div>
{% if service.description %}
<div class="mt-6">
<h3 class="text-lg font-semibold text-gray-700 mb-3">描述</h3>
<p class="text-gray-600">{{ service.description }}</p>
</div>
{% endif %}
</div>
<!-- 响应时间趋势图 -->
<div class="bg-white rounded-xl p-6 card-shadow mb-8">
<h3 class="text-xl font-bold text-gray-800 mb-4">响应时间趋势 (最近24小时)</h3>
<div class="h-64">
<canvas id="response-time-chart"></canvas>
</div>
</div>
<!-- 状态变化时间轴 -->
<div class="bg-white rounded-xl p-6 card-shadow mb-8">
<h3 class="text-xl font-bold text-gray-800 mb-4">状态变化时间轴</h3>
<div class="relative">
<div class="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-200"></div>
<div class="space-y-4" id="status-timeline">
{% for record in status_changes %}
<div class="relative pl-10">
<div class="absolute left-0 w-8 h-8 rounded-full flex items-center justify-center
{% if record.status == 'UP' %}bg-green-100 text-green-600{% elif record.status == 'DOWN' %}bg-red-100 text-red-600{% else %}bg-yellow-100 text-yellow-600{% endif %}
">
{% if record.status == 'UP' %}<i class="fas fa-check"></i>
{% elif record.status == 'DOWN' %}<i class="fas fa-times"></i>
{% else %}<i class="fas fa-question"></i>{% endif %}
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex justify-between">
<span class="font-medium">
{% if record.status == 'UP' %}正常
{% elif record.status == 'DOWN' %}故障
{% else %}未知{% endif %}
</span>
<span class="text-sm text-gray-500">{{ record.checked_at|date:"y-m-d h:i:s" }}</span>
</div>
{% if record.message %}
<p class="text-sm text-gray-600 mt-1">{{ record.message }}</p>
{% endif %}
{% if record.response_time %}
<p class="text-sm text-gray-500 mt-1">响应时间: {{ record.response_time }} ms</p>
{% endif %}
</div>
</div>
{% empty %}
<div class="text-center py-4 text-gray-500">
<i class="fas fa-info-circle text-2xl mb-2"></i>
<p>暂无状态变化记录</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- 最近检测记录 -->
<div class="bg-white rounded-xl overflow-hidden card-shadow">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-xl font-bold text-gray-800">最近检测记录</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">检测时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">响应时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">消息</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for record in recent_records %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ record.checked_at|date:"y-m-d h:i:s" }}</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if record.status == 'UP' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check-circle mr-1"></i> 正常
</span>
{% elif record.status == 'DOWN' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-times-circle mr-1"></i> 故障
</span>
{% else %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
<i class="fas fa-question-circle mr-1"></i> 未知
</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if record.response_time %}{{ record.response_time }} ms{% else %}N/A{% endif %}
</td>
<td class="px-6 py-4 text-sm text-gray-500 max-w-md truncate">
{% if record.message %}{{ record.message }}{% else %}无{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-500">
<i class="fas fa-info-circle text-2xl mb-2"></i>
<p>暂无检测记录</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-dark text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p>© 2025 服务状态监控系统</p>
</div>
</footer>
<script>
// 响应时间趋势图
const ctx = document.getelementbyid('response-time-chart').getcontext('2d');
const responsetimechart = new chart(ctx, {
type: 'line',
data: {
labels: {{ chart_labels|safe }},
datasets: [{
label: '响应时间 (ms)',
data: {{ chart_data|safe }},
backgroundcolor: 'rgba(22, 93, 255, 0.1)',
bordercolor: '#165dff',
borderwidth: 2,
tension: 0.3,
pointbackgroundcolor: '#165dff',
pointbordercolor: '#fff',
pointborderwidth: 2,
pointradius: 4,
pointhoverradius: 6
}]
},
options: {
responsive: true,
maintainaspectratio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `响应时间: ${context.parsed.y} ms`;
}
}
}
},
scales: {
y: {
beginatzero: true,
title: {
display: true,
text: '响应时间 (ms)'
}
},
x: {
title: {
display: true,
text: '时间'
}
}
}
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,216 @@
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务列表 - 服务状态监控</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165dff',
operational: '#36d399',
degraded: '#fbbd23',
outage: '#f87272',
dark: '#1e293b',
light: '#f8fafc'
},
fontfamily: {
inter: ['inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translatey(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.gradient-bg {
background: linear-gradient(135deg, #165dff 0%, #0a2463 100%);
}
}
</style>
</head>
<body class="bg-gray-50 font-inter min-h-screen">
<!-- 顶部导航 -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<a href="/" class="flex items-center">
<i class="fas fa-server text-3xl mr-3"></i>
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold">服务状态监控</h1>
</a>
</div>
<div class="flex items-center space-x-4">
<a href="/" class="text-white hover:text-gray-200">
<i class="fas fa-home mr-2"></i>首页
</a>
<a href="/services/" class="text-white font-medium border-b-2 border-white pb-1">
<i class="fas fa-list mr-2"></i>服务列表
</a>
</div>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto px-4 py-8">
<!-- 筛选和搜索 -->
<div class="bg-white rounded-xl p-6 card-shadow mb-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<h2 class="text-xl font-bold text-gray-800">服务列表</h2>
<div class="flex flex-col md:flex-row gap-3 w-full md:w-auto">
<div class="relative">
<select id="group-filter" class="appearance-none bg-gray-50 border border-gray-300 text-gray-700 py-2 px-4 pr-8 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-primary w-full md:w-auto">
<option value="">所有分组</option>
{% for group in groups %}
<option value="{{ group.name }}" {% if current_group == group.name %}selected{% endif %}>{{ group.name }}</option>
{% endfor %}
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="relative">
<select id="status-filter" class="appearance-none bg-gray-50 border border-gray-300 text-gray-700 py-2 px-4 pr-8 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-primary w-full md:w-auto">
<option value="">所有状态</option>
<option value="up" {% if current_status == 'up' %}selected{% endif %}>正常</option>
<option value="down" {% if current_status == 'down' %}selected{% endif %}>故障</option>
<option value="unknown" {% if current_status == 'unknown' %}selected{% endif %}>未知</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="relative">
<input type="text" id="search-input" placeholder="搜索服务..." value="{{ search_query }}" class="bg-gray-50 border border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-primary w-full md:w-auto">
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<i class="fas fa-search text-gray-500"></i>
</div>
</div>
<button id="search-button" class="bg-primary text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors">
<i class="fas fa-filter mr-2"></i>筛选
</button>
</div>
</div>
</div>
<!-- 服务表格 -->
<div class="bg-white rounded-xl overflow-hidden card-shadow">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">服务名称</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">分组</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">主机/IP</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后检测时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">响应时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="services-tbody">
{% for service in services %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ service.name }}</div>
<div class="text-sm text-gray-500">{{ service.check_type }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ service.group.name }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ service.host }}</div>
<div class="text-sm text-gray-500">{% if service.port %}{{ service.port }}{% else %}N/A{% endif %}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if service.latest_status == 'UP' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check-circle mr-1"></i> 正常
</span>
{% elif service.latest_status == 'DOWN' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-times-circle mr-1"></i> 故障
</span>
{% else %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
<i class="fas fa-question-circle mr-1"></i> 未知
</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if service.latest_check_time %}{{ service.latest_check_time|date:"y-m-d h:i" }}{% else %}从未检测{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if service.latest_response_time %}{{ service.latest_response_time }} ms{% else %}N/A{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="/services/{{ service.id }}/" class="text-primary hover:text-blue-700">
<i class="fas fa-eye mr-1"></i> 查看详情
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
<i class="fas fa-info-circle text-2xl mb-2"></i>
<p>暂无服务数据</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-dark text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p>© 2025 服务状态监控系统</p>
</div>
</footer>
<script>
// 筛选和搜索功能
document.getelementbyid('search-button').addeventlistener('click', function() {
const groupfilter = document.getelementbyid('group-filter').value;
const statusfilter = document.getelementbyid('status-filter').value;
const searchquery = document.getelementbyid('search-input').value;
let url = '/services/?';
const params = [];
if (groupfilter) params.push('group=' + encodeuricomponent(groupfilter));
if (statusfilter) params.push('status=' + encodeuricomponent(statusfilter));
if (searchquery) params.push('search=' + encodeuricomponent(searchquery));
window.location.href = url + params.join('&');
});
// 回车键触发搜索
document.getelementbyid('search-input').addeventlistener('keypress', function(e) {
if (e.key === 'enter') {
document.getelementbyid('search-button').click();
}
});
</script>
</body>
</html>