前言

因为使用了uptimerobot监控了网站,所以之前写了个工具在工具箱里,但是每次想要知道最近网站运行状态都需要访问工具箱,就很麻烦

刚好在WordPress建站的时候就看到了张洪HEO的网站底部有个uptimekuma的运行状态显示

于是自己就想搞一个,切记,这不是基于uptimekuma的,而是基于uptimerobot的

效果

监控卡片

教程

首先,我们需要在source/_data文件夹中创建一个aside.yml文件,然后将如下代码放进去

- name: uptime
  title: 服务状态
  icon: fas fa-server
  no_head: true
  content_html: |
    <div class="uptime-dashboard">
      <div class="uptime-header">
        <div class="uptime-title">
          <i class="fas fa-chart-line"></i>
          <span>实时监控</span>
        </div>
        <div class="uptime-summary" id="uptimeSummary">
          <span class="summary-text">加载中...</span>
        </div>
      </div>
      <div class="uptime-services" id="uptimeServices">
        <!-- 动态内容将由JavaScript填充 -->
      </div>
    </div> 

再然后进入到_config.solitude.yml中,将如下代码添加到extendsbody

    - <script src="/js/uptime-status.js"></script>

看到这里相信不少人知道了,接下来我们需要在source文件夹的js文件夹中创建一个uptime-status.js文件

然后将如下代码放进去,其中的密钥要从Uptime Robot获取Read-only API key

class UptimeStatus {
    constructor() {
        this.isInitialized = false;
        this.apiKey = '';//你的api密钥
        this.apiUrl = 'https://api.uptimerobot.com/v2/getMonitors';
        this.config = {
            statusText: {
                normal: '正常',
                warning: '异常',
                error: '故障',
                loading: '检查中...',
                unknown: '未知',
                noApi: '未配置API',
                apiError: 'API错误',
                networkError: '网络错误',
                noService: '无监控服务'
            }
        };
        this.init();
    }

    init() {
        // 等待DOM加载完成
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => this.initStatus());
        } else {
            this.initStatus();
        }

        // Pjax兼容
        document.addEventListener('pjax:complete', () => this.initStatus());
    }

    initStatus() {
        const uptimeCard = document.querySelector('.uptime-dashboard');
        if (!uptimeCard) return;

        this.isInitialized = true;
        this.fetchServices();
        
        // 设置定时刷新
        setInterval(() => {
            if (this.isInitialized) {
                this.fetchServices();
            }
        }, 30000); // 30秒刷新一次
    }

    async fetchServices() {
        try {
            // 获取服务列表和状态
            const servicesData = await this.getServicesData();
            if (servicesData && servicesData.length > 0) {
                this.renderServices(servicesData);
                this.updateSummary(servicesData);
            } else {
                console.warn('API返回的服务数据为空');
                this.renderServices(this.getDefaultServices());
                this.updateSummary([]);
            }
        } catch (error) {
            console.error('获取服务状态失败:', error);
            this.renderServices(this.getDefaultServices());
            this.updateSummary([]);
        }
    }

    async getServicesData() {
        try {
            console.log('正在调用UptimeRobot API...');
            
            // 使用与用户提供的代码相同的请求方式
            const requestData = {
                api_key: this.apiKey,
                format: "json",
                logs: 1
            };
            
            const response = await fetch(this.apiUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(requestData)
            });

            console.log('API响应状态:', response.status);

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();
            console.log('API返回数据:', data);
            
            if (data.stat !== 'ok') {
                throw new Error(`API error: ${data.error?.message || 'Unknown error'}`);
            }

            if (!data.monitors || data.monitors.length === 0) {
                console.warn('API返回的monitors数组为空');
                return [];
            }

            // 转换UptimeRobot数据格式
            const services = data.monitors.map(monitor => ({
                id: monitor.id,
                name: monitor.friendly_name,
                status: this.mapUptimeStatus(monitor.status),
                uptime: parseFloat(monitor.all_time_uptime_ratio || '0'),
                url: monitor.url
            }));

            console.log('转换后的服务数据:', services);
            return services;

        } catch (error) {
            console.error('UptimeRobot API调用失败:', error);
            throw error;
        }
    }

    mapUptimeStatus(uptimeStatus) {
        // UptimeRobot状态映射
        // 0: paused, 1: not checked yet, 2: up, 8: seems down, 9: down
        switch (uptimeStatus) {
            case 2:
                return 'normal';
            case 8:
                return 'warning';
            case 9:
                return 'error';
            case 0:
                return 'unknown';
            default:
                return 'loading';
        }
    }

    getDefaultServices() {
        return [
            {
                id: 'default',
                name: '正在获取服务状态...',
                status: 'loading',
                uptime: 0,
                url: '#'
            }
        ];
    }

    renderServices(services) {
        const container = document.getElementById('uptimeServices');
        if (!container) return;

        console.log('渲染服务数据:', services);

        container.innerHTML = services.map(service => `
            <div class="service-item" data-service="${service.id}">
                <div class="service-name">${service.name}</div>
                <div class="service-status">
                    <span class="status-indicator status-${service.status}"></span>
                    <span class="status-text">${this.getStatusText(service.status)}</span>
                </div>
            </div>
        `).join('');
    }

    updateSummary(services) {
        const summaryElement = document.getElementById('uptimeSummary');
        if (!summaryElement) return;

        if (services.length === 0) {
            summaryElement.innerHTML = '<span class="summary-text">无监控服务</span>';
            return;
        }

        const totalServices = services.length;
        const normalServices = services.filter(s => s.status === 'normal').length;
        const warningServices = services.filter(s => s.status === 'warning').length;
        const errorServices = services.filter(s => s.status === 'error').length;

        let summaryText = '';
        if (errorServices > 0) {
            summaryText = `${errorServices}个服务异常`;
        } else if (warningServices > 0) {
            summaryText = `${warningServices}个服务警告`;
        } else if (normalServices === totalServices) {
            summaryText = '所有服务正常';
        } else {
            summaryText = `${normalServices}/${totalServices}个服务正常`;
        }

        summaryElement.innerHTML = `<span class="summary-text">${summaryText}</span>`;
    }

    getStatusText(status) {
        const statusMap = this.config.statusText;
        return statusMap[status] || statusMap.unknown;
    }
}

// 初始化
new UptimeStatus(); 

有人要问,代码中没有看到样式啊,接下来就是样式部分,在你的自定义css文件中添加如下代码

/* Uptime 服务状态样式 - 全新设计 */
.uptime-dashboard {
  background: var(--card-bg);
  border-radius: 12px;
  padding: 16px;
  color: var(--font-color);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  position: relative;
  overflow: hidden;
  

  
  .uptime-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    position: relative;
    z-index: 1;
    
    .uptime-title {
      display: flex;
      align-items: center;
      gap: 8px;
      font-weight: 600;
      font-size: 14px;
      
      i {
        font-size: 16px;
        color: rgba(255, 255, 255, 0.9);
      }
    }
    
    .uptime-summary {
      .summary-text {
        font-size: 12px;
        opacity: 0.9;
        font-weight: 500;
      }
    }
  }
  
  .uptime-services {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    
    .service-item {
      display: flex;
      flex-direction: row;
      align-items: center;
      justify-content: space-between;
      padding: 8px 10px;
      background: var(--card-bg);
      border-radius: 8px;
      border: 1px solid #e5e7eb;
      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      
      &:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
        background: var(--hover-bg);
      }
      
      .service-name {
        font-size: 11px;
        font-weight: 500;
        color: var(--font-color);
        line-height: 1.2;
        word-break: break-word;
        flex: 1;
        margin-right: 8px;
      }
      
      .service-status {
        display: flex;
        align-items: center;
        gap: 4px;
        flex-shrink: 0;
        
        .status-indicator {
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          transition: all 0.3s ease;
          
          &::after {
            content: '';
            position: absolute;
            top: -1px;
            left: -1px;
            right: -1px;
            bottom: -1px;
            border-radius: 50%;
            background: inherit;
            opacity: 0.3;
            filter: blur(2px);
          }
          
          &.status-normal {
            background: #4ade80;
            box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.3);
          }
          
          &.status-warning {
            background: #fbbf24;
            box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.3);
          }
          
          &.status-error {
            background: #f87171;
            box-shadow: 0 0 0 1px rgba(248, 113, 113, 0.3);
          }
          
          &.status-loading {
            background: #60a5fa;
            animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
          }
          
          &.status-unknown {
            background: #9ca3af;
            box-shadow: 0 0 0 1px rgba(156, 163, 175, 0.3);
          }
        }
        
        .status-text {
          font-size: 9px;
          font-weight: 600;
          color: var(--font-color);
          text-transform: uppercase;
          letter-spacing: 0.3px;
        }
      }
      
      /* 单数服务居中显示 */
      &:last-child:nth-child(odd) {
        grid-column: 1 / -1;
        max-width: 50%;
        justify-self: center;
      }
    }
  }
}

/* 深色模式适配 */
[data-theme="dark"] .uptime-dashboard {
  background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
  
  .uptime-services .service-item {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(255, 255, 255, 0.1);
    
    &:hover {
      background: rgba(255, 255, 255, 0.1);
    }
  }
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

最后将uptime放到_config.solitude.yml文件中的asidehomenoSticky中即可

至此,教程结束,一键三连就可以看到最终的效果啦