You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Grafana Polystat如何实现悬停元素调用POST API展示自定义详情

实现可行性结论

该需求完全可以通过原生HTML+JavaScript实现,不需要深入掌握前端开发技能,也不需要修改Polystat插件源码、从零开发自定义可视化面板,整体开发量很小。

分步实现指引
  • 第一步:给Python Lambda接口配置跨域规则
    因为Grafana前端页面对Lambda发起的是跨域POST请求,必须在Lambda返回头中加入CORS配置,参考代码如下:

    import json
    
    def lambda_handler(event, context):
        # 这里写你原有的查询RDS详情的逻辑
        rds_detail = {
            "instance_id": "db-xxx",
            "instance_type": "db.r6g.large",
            "engine": "PostgreSQL",
            "engine_version": "14.7",
            "endpoint": "db-xxx.xxx.us-east-1.rds.amazonaws.com:5432",
            "status": "available"
        }
        return {
            "statusCode": 200,
            "headers": {
                "Access-Control-Allow-Origin": "你的Grafana服务访问域名",
                "Access-Control-Allow-Methods": "POST, OPTIONS",
                "Access-Control-Allow-Headers": "Content-Type"
            },
            "body": json.dumps(rds_detail)
        }
    

    本地调试阶段可以临时把Access-Control-Allow-Origin设为*,正式上线必须替换为实际Grafana域名,避免跨域安全风险。

  • 第二步:确保Polystat面板可提取实例唯一标识
    确认你当前Polystat绑定的查询语句返回结果中,带有db_instance_identifier(RDS实例ID)维度,且该值会渲染在每个Polystat块的可见文本/标签属性中,后续脚本将通过这个值识别当前悬停的实例。如果当前Polystat块只展示资源消耗数值,把实例ID设为块的副标题即可,不需要额外改查询逻辑。

  • 第三步:注入悬停监听脚本
    不需要修改Polystat插件配置,在当前仪表盘新增一个文本面板,把面板高度调整为1、透明度设为100%做成隐藏面板,切换到面板的HTML/JavaScript编辑模式,粘贴以下基础代码:

    // 防抖控制+本地缓存,避免频繁触发请求
    let hoverTimer = null;
    const detailCache = new Map();
    const HOVER_DELAY = 300; // 悬停300ms才触发请求,避免误触
    
    // 监听全局鼠标悬停事件
    document.addEventListener('mouseover', async (e) => {
        // 匹配Polystat的实例块元素,类名和你实际渲染的不一致可以通过F12开发者工具调整
        const targetBlock = e.target.closest('.polystat-panel .polystat-item');
        if (!targetBlock) return;
    
        // 提取当前块对应的实例ID,选择器根据实际渲染结构调整
        const instanceId = targetBlock.querySelector('.item-label')?.textContent?.trim();
        if (!instanceId) return;
    
        clearTimeout(hoverTimer);
        hoverTimer = setTimeout(async () => {
            let instanceDetail;
            // 命中缓存直接用,不重复发请求
            if (detailCache.has(instanceId)) {
                instanceDetail = detailCache.get(instanceId);
            } else {
                // 发起POST请求拉取详情
                const res = await fetch('替换为你的Lambda API访问地址', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({instance_id: instanceId})
                });
                instanceDetail = await res.json();
                detailCache.set(instanceId, instanceDetail);
            }
            // 渲染自定义详情弹窗
            renderTooltip(targetBlock, instanceDetail);
        }, HOVER_DELAY);
    });
    
    // 鼠标移出实例块时销毁弹窗
    document.addEventListener('mouseout', (e) => {
        if (e.target.closest('.polystat-panel .polystat-item')) {
            clearTimeout(hoverTimer);
            document.getElementById('custom-rds-tooltip')?.remove();
        }
    });
    
    // 弹窗渲染逻辑
    function renderTooltip(anchorEl, detail) {
        // 先移除已存在的旧弹窗
        document.getElementById('custom-rds-tooltip')?.remove();
        const tooltip = document.createElement('div');
        tooltip.id = 'custom-rds-tooltip';
        // 弹窗样式可自行调整,匹配Grafana原生风格
        tooltip.style.cssText = `
            position: absolute;
            background: #161719;
            border: 1px solid #3d3d3d;
            border-radius: 4px;
            padding: 12px;
            z-index: 9999;
            min-width: 280px;
            color: #ffffff;
            font-size: 13px;
            line-height: 1.8;
            box-shadow: 0 4px 12px rgba(0,0,0,0.4);
        `;
        // 填充详情内容,字段和你Lambda返回的结构对齐即可
        tooltip.innerHTML = `
            <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px">${detail.instance_id}</div>
            <div>实例规格:${detail.instance_type}</div>
            <div>数据库引擎:${detail.engine} ${detail.engine_version}</div>
            <div>访问端点:<code style="background:#262628; padding:2px 6px; border-radius:3px">${detail.endpoint}</code></div>
            <div>运行状态:<span style="color: #73bf69">${detail.status}</span></div>
        `;
        // 把弹窗定位到实例块下方
        const rect = anchorEl.getBoundingClientRect();
        tooltip.style.top = `${rect.bottom + window.scrollY + 8}px`;
        tooltip.style.left = `${rect.left + window.scrollX}px`;
        document.body.appendChild(tooltip);
    }
    

    如果你的Grafana版本开启了严格CSP策略,不允许在原生文本面板执行脚本,可以替换使用社区的HTML面板插件,逻辑完全一致。

  • 第四步:调试适配
    打开浏览器F12开发者工具,悬停Polystat实例块时,先检查是否能正确提取到实例ID,如果提取失败就调整代码中的元素选择器(直接用开发者工具选中对应元素复制类名即可);如果请求报错,优先检查Lambda的CORS配置、请求入参格式是否和Lambda预期一致。

可选优化点
  • 给请求过程加加载态:弹窗初始显示「实例详情加载中...」,请求返回后再替换为实际内容,体验更好
  • 给本地缓存加过期时间:比如设置5分钟自动失效,避免实例配置变更后前端长期展示旧数据
  • 可以把Polystat默认的原生Tooltip通过CSS隐藏,避免和自定义弹窗同时出现
  • 弹窗样式可以完全对齐Grafana原生Tooltip的设计,视觉上更统一

内容的提问来源于stack exchange,提问作者dbNovice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 21:27:35