项目背景:
手上维护了一个整个地市的众多网络设备,以硬盘录像机(NVR)为主
时常要查询他们在线状态,因为是散布在各个学校,需要在市,区平台上查看视频监控。
遇到不在线的,要让学校处理。
这里我是在内网部署了Uptime Kuma,并设置了状态页面
地址比如:http://172.20.0.32:3001/status/zkwg
效果还不错,唯一的缺点就是只能在内网查看,外网是打不开的,而且还有多个内网,他们之间又不互通。
查起来有点费劲。
于是就用它自带的Cloudflare Tunnel搞了个内网穿透,用API整合数据。
以下是最终的效果:
很直观的展示在线状态,ping值,最后一次上线时间,还把离线的排最上面。
说不多说,开始今天的教程:
打开Zero Trust页面
https://one.dash.cloudflare.com/
创建Tunnels隧道
复制上面的,下一步。
- cloudflared.exe service install mQyOGFlMzM0NWExZDFiMDAzZWI1ZTgiLCJ0IjoiOGZmMGFj
复制代码 注意install后面的一长串就是Cloudflare Tunnel 令牌
CF上的配置暂时就设置好了。
回到Uptime Kuma后台
把上面的Cloudflare Tunnel 令牌贴进来,启动Cloudflare。 当然机器是要联互联网的。。。
回到CF看隧道状态,显示正常就表示OK了
如果不讲究太多,直接用域名就可以访问了!
效果图是这个:
我这里并不想用这个域名访问,因为是我自己的域名。
我需要用公司的域名,我要把他们嵌入到html网页来展示。
因为换了域名,就涉及到跨域访问,要在CF上配置。
先点击域名进来,找到规则
创建 响应头转换规则
这样我就能用公司的域名来添加这些监控信息了
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
- <title>XHZK设备状态展示</title>
- <style>
- body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- background-color: #f5f5f5;
- }
- .container {
- max-width: 800px;
- margin: 0 auto;
- padding: 20px;
- }
- h1 {
- text-align: center;
- color: #333;
- margin-bottom: 20px;
- }
- .status-box {
- background-color: white;
- border-radius: 8px;
- padding: 15px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- border: 1px solid #ddd;
- }
- .status-box h2 {
- margin-top: 0;
- font-size: 1.1em;
- color: #333;
- border-bottom: 1px solid #eee;
- padding-bottom: 10px;
- margin-bottom: 15px;
- }
- .status-list {
- list-style: none;
- padding: 0;
- margin: 0;
- }
- .status-list li {
- padding: 10px 15px;
- border-bottom: 1px solid #eee;
- display: flex;
- flex-direction: column;
- }
- .status-list li:last-child {
- border-bottom: none;
- }
- .up::before {
- content: '● ';
- color: green;
- }
- .down::before {
- content: '● ';
- color: red;
- }
- .status-change {
- font-size: 0.9em;
- color: #666;
- margin-top: 4px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>XHZK设备状态展示</h1>
- <div class="status-box">
- <h2>设备在线状态</h2>
- <ul id="ts-list" class="status-list"></ul>
- </div>
- </div>
- <script>
- const tsPage = {
- id: 'ts',
- configUrl: 'https://xh.yourmain.xyz/api/status-page/zkwg',
- heartbeatUrl: 'https://xh.yourmain.xyz/api/status-page/heartbeat/zkwg',
- listId: 'ts-list'
- };
- function formatTime(timestamp) {
- if (!timestamp) return '未知';
- const date = new Date(timestamp);
- return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
- }
- // 判断是否为“有效在线”
- function isOffline(heartbeat) {
- return heartbeat.status === 0 ||
- (heartbeat.ping != null && heartbeat.ping > 1000) ||
- /timeout|fail|offline/i.test(heartbeat.msg || '');
- }
- async function loadStatus(page) {
- const list = document.getElementById(page.listId);
- list.innerHTML = '<li>加载中...</li>';
- try {
- // 获取配置数据
- const configResponse = await fetch(page.configUrl, {
- method: 'GET',
- headers: { 'Origin': 'https://status.jmxy.cc' }
- });
- if (!configResponse.ok) throw new Error(`Config HTTP ${configResponse.status}`);
- const configData = await configResponse.json();
- // 获取心跳数据
- const heartbeatResponse = await fetch(page.heartbeatUrl, {
- method: 'GET',
- headers: { 'Origin': 'https://status.jmxy.cc' }
- });
- if (!heartbeatResponse.ok) throw new Error(`Heartbeat HTTP ${heartbeatResponse.status}`);
- const heartbeatData = await heartbeatResponse.json();
- list.innerHTML = '';
- // 构建 ID → 名称映射
- const monitorMap = {};
- configData.publicGroupList?.forEach(group => {
- group.monitorList?.forEach(monitor => {
- monitorMap[monitor.id] = monitor.name;
- });
- });
- const monitors = [];
- for (const [id, heartbeats] of Object.entries(heartbeatData.heartbeatList || {})) {
- if (!heartbeats.length || !monitorMap[id]) continue;
- const latest = heartbeats[heartbeats.length - 1];
- const currentStatus = isOffline(latest) ? 0 : 1;
- let lastOnline = null;
- let lastOffline = null;
- // 倒序查找最后一次稳定状态变化
- for (let i = heartbeats.length - 1; i >= 0; i--) {
- const hb = heartbeats[i];
- const status = isOffline(hb) ? 0 : 1;
- if (currentStatus === 0 && status === 1 && lastOnline === null) {
- lastOnline = hb.time;
- }
- if (currentStatus === 1 && status === 0 && lastOffline === null) {
- lastOffline = hb.time;
- }
- }
- monitors.push({
- name: monitorMap[id],
- status: currentStatus,
- ping: latest.ping,
- lastOnline,
- lastOffline
- });
- }
- // 排序:DOWN 在前,UP 在后;同状态按名称排序
- monitors.sort((a, b) => {
- if (a.status !== b.status) return a.status - b.status;
- return a.name.localeCompare(b.name);
- });
- // 渲染列表
- if (monitors.length === 0) {
- list.innerHTML = '<li class="error">无监控数据</li>';
- } else {
- monitors.forEach(monitor => {
- const li = document.createElement('li');
- li.className = monitor.status === 1 ? 'up' : 'down';
- let statusText = `状态: ${monitor.status === 1 ? '在线' : '离线'}`;
- if (monitor.ping != null) {
- statusText += ` (${monitor.ping.toFixed(2)}ms)`;
- }
- let changeText = '';
- if (monitor.status === 1) {
- changeText += `<div class="status-change">最近一次离线时间:${monitor.lastOffline ? formatTime(monitor.lastOffline) : '无记录'}</div>`;
- } else {
- changeText += `<div class="status-change">最近一次上线时间:${monitor.lastOnline ? formatTime(monitor.lastOnline) : '无记录'}</div>`;
- }
- li.innerHTML = `<span>${monitor.name}</span><span>${statusText}${changeText}</span>`;
- list.appendChild(li);
- });
- }
- } catch (error) {
- console.error(`加载 ${page.id} 状态失败:`, error);
- list.innerHTML = `<li class="error">错误: ${error.message}</li>`;
- }
- }
- // 初次加载
- loadStatus(tsPage);
- // 每 30 秒刷新一次
- setInterval(() => {
- loadStatus(tsPage);
- }, 30000);
- </script>
- </body>
- </html>
复制代码 用AI写的,也没什么技术含量,把域名替换成自己的就行。
至此我们就可以用 https://status.yourmain.com/test.html来查看监控状态了
|