前端嵌入Grafana 报表的自定义方案:隐藏导航栏保留筛选工具

在实际业务开发中,将 Grafana 报表嵌入到自研应用系统是非常常见的需求。官方提供了几种嵌入模式,但灵活性有限,无法满足所有场景。本文将介绍如何突破官方限制,实现"隐藏导航栏 + 保留筛选工具"的定制化嵌入效果。

一、官方支持的嵌入方式

Grafana 官方通过 URL 参数 kiosk 提供了三种嵌入模式:

1. 无参数模式

直接访问 Grafana 地址,不添加任何参数:

ruby 复制代码
http://your-grafana/d/dashboard-id/dashboard-name

效果:导航栏和筛选工具都正常显示,适用场景:需要完整 Grafana 功能的独立访问。

2. Kiosk TV 模式(kiosk=tv

ini 复制代码
http://your-grafana/d/dashboard-id/dashboard-name?kiosk=tv

效果:隐藏筛选工具,保留导航栏,适用场景:大屏展示、TV 模式轮播。

3. Kiosk 全屏模式(kiosk=1

ini 复制代码
http://your-grafana/d/dashboard-id/dashboard-name?kiosk=1

效果:同时隐藏导航栏和筛选工具,适用场景:纯展示型嵌入、无交互需求。

官方方案的局限性

从上述三种模式可以看出,官方不支持「隐藏导航栏 + 保留筛选工具」的组合,而这恰恰是许多业务系统需要的效果------既想让报表与应用融为一体,又希望用户能使用筛选功能进行交互。

二、自定义方案概述

核心思路

通过 Nginx 反向代理 Grafana 服务,前端使用 iframe 嵌入,并利用 JavaScript 动态修改 iframe 内的 DOM 元素,实现 UI 的精准控制。

技术栈

  • Grafana 版本:v10.2.3 (1e84fede54)
  • Nginx:反向代理
  • 前端:iframe + MutationObserver API

三、实施方案详解

3.1 Grafana 配置

修改 Grafana 配置文件 grafana.ini,关键配置如下:

ini 复制代码
[server]
domain = ''
root_url = %(protocol)s://%(domain)s/grafana/
[security]
allow_embedding = true
[auth.anonymous]
enabled = true
org_name = Main Org.
org_role = Viewer

配置要点:

  • root_url:设置 Grafana 访问路径前缀,与 Nginx 代理路径对应
  • allow_embedding:允许 iframe 嵌入(必须开启)
  • auth.anonymous:根据业务需求配置匿名访问权限 完整配置示例:
ini 复制代码
{
  "grafana.ini": "[analytics]
    check_for_updates = true
    [grafana_net]
    url = https://grafana.net
    [log]
    mode = console
    [paths]
    data = /var/lib/grafana/
    logs = /var/log/grafana
    plugins = /var/lib/grafana/plugins
    provisioning = /etc/grafana/provisioning
    [server]
    domain = ''
    root_url = %(protocol)s://%(domain)s/grafana/
    [security]
    allow_embedding = true
    [auth.anonymous]
    enabled = true
    org_name = Main Org.
    org_role = Viewer
  "
}

3.2 Nginx 反向代理配置

Nginx 配置分为三个部分:Grafana 代理、前端页面路由、静态资源服务。

bash 复制代码
server {
    listen 30609;
    server_name localhost;
    # Grafana 服务代理
    location /grafana/ {
        proxy_pass http://grafana-ip:3000/;
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        
        # WebSocket 支持
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 静态资源映射(可选)
        location ~ ^/grafana/(.*) {
            alias /var/www/grafana/$1;
            try_files $uri $uri/ =404;
        }
    }
    # 前端监控页面
    location /monitor {
        alias D:/temp/monitor.html;
        internal;
        default_type text/html;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }
    # 静态资源服务
    location /monitor/static/ {
        alias D:/temp/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

配置说明:

  1. Grafana 代理:将 /grafana/ 路径代理到 Grafana 服务,处理 WebSocket 连接(实时刷新功能需要)
  2. 前端页面:提供嵌入页面的访问入口,建议使用 internal 防止直接暴露
  3. 静态资源:支持加载本地 JS/CSS 资源

3.3 前端实现:动态 DOM 操作

核心思路是使用 MutationObserver 监听 iframe 内容变化,找到目标元素后进行样式修改。

xml 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Grafana 嵌入 - 自定义布局</title>
    <style>
        iframe {
            width: 100%;
            height: 1600px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <iframe id="myIframe" 
            src="http://127.0.0.1/grafana/d/200ac8fdbfbb74b39aff88118e4d1c2c/kubernetes-compute-resources-node-pods?orgId=1&refresh=10s" 
            frameborder="0">
    </iframe>
    <script>
        const iframe = document.getElementById('myIframe');
        
        iframe.onload = function () {
            const iframeDoc = iframe.contentDocument;
            // 使用 MutationObserver 监听 DOM 变化
            const observer = new MutationObserver(function (mutations) {
                // 隐藏顶部导航栏(Grafana v10.2.3 的导航栏类名)
                const toolbarElement = iframeDoc.querySelector('.css-srjygq');
                if (toolbarElement) {
                    toolbarElement.style.display = 'none';
                }
                // 移除导航栏占位空间
                const paddingElement = iframeDoc.querySelector('.css-60onds');
                if (paddingElement) {
                    paddingElement.style.paddingTop = '0';
                }
                // 找到目标元素后停止监听
                if (toolbarElement && paddingElement) {
                    observer.disconnect();
                    console.log('✅ 已隐藏导航栏,保留筛选工具');
                }
            });
            // 开始监听
            observer.observe(iframeDoc.body, {
                childList: true,
                subtree: true
            });
            // 超时保护:10秒后自动停止
            setTimeout(() => {
                observer.disconnect();
                console.warn('⏰ 超时:未找到目标元素');
            }, 10000);
        };
    </script>
</body>
</html>

实现要点:

  1. MutationObserver:动态监听 DOM 变化,避免使用 setInterval 轮询
  2. 元素选择器:.css-srjygq 是 Grafana v10.2.3 的导航栏类名(不同版本可能不同)
  3. 超时保护:防止目标元素不存在时无限监听

四、版本兼容性说明

关于 CSS 类名

Grafana 的 CSS 类名(如 .css-srjygq)是通过 CSS-in-JS 动态生成的,不同版本可能不同。如果你的 Grafana 版本不是 v10.2.3,需要自行定位目标元素: 定位方法:

  1. 在浏览器中打开 Grafana
  2. 按 F12 打开开发者工具
  3. 使用元素选择器定位导航栏
  4. 找到对应的 CSS 类名或 data-testid 属性 更稳健的选择器示例:
ini 复制代码
// 方案1:使用 data-testid(更稳定)
const toolbarElement = iframeDoc.querySelector('[data-testid="data-testid Navbar"]');
// 方案2:使用语义化选择器
const toolbarElement = iframeDoc.querySelector('nav[aria-label="Navbar"]');
// 方案3:组合选择器
const toolbarElement = iframeDoc.querySelector('header nav');

五、常见问题与解决方案

问题 1:跨域访问错误

原因:iframe 加载的页面与父页面不同源 解决方案:

  1. 确保 Nginx 代理配置正确
  2. 配置 Grafana 的 allow_embedding = true
  3. 如需跨域访问,考虑使用 postMessage 通信

问题 2:WebSocket 连接失败

现象:实时刷新功能不工作 解决方案: 确保 Nginx 配置了 WebSocket 支持:

bash 复制代码
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

问题 3:iframe 高度自适应

解决方案:

ini 复制代码
iframe.onload = function() {
    const iframeDoc = iframe.contentDocument;
    const height = iframeDoc.body.scrollHeight;
    iframe.style.height = height + 'px';
};
// 监听内容变化动态调整
const resizeObserver = new ResizeObserver(entries => {
    const height = iframe.contentDocument.body.scrollHeight;
    iframe.style.height = height + 'px';
});
resizeObserver.observe(iframe.contentDocument.body);

问题 4:筛选功能失效

原因:如果隐藏了错误的元素,可能影响交互 解决方案: 只隐藏导航栏容器,不要隐藏整个顶部区域。筛选工具通常在 .dashboard-container 的子元素中。

六、方案对比总结

方案 导航栏 筛选工具 实现难度 维护成本
无参数 ✅ 显示 ✅ 显示 简单
kiosk=tv ✅ 显示 ❌ 隐藏 简单
kiosk=1 ❌ 隐藏 ❌ 隐藏 简单
自定义方案 ❌ 隐藏 ✅ 显示 中等 中等

七、写在最后

这个方案通过 Nginx 代理 + DOM 操作的方式,填补了官方 kiosk 模式的空白,为业务系统集成 Grafana 提供了更灵活的选择。 注意事项:

  1. 版本兼容性:Grafana 升级时需要重新验证元素选择器
  2. 同源策略:确保应用与 Grafana 代理后的域名一致
  3. 性能优化:MutationObserver 找到元素后立即 disconnect
  4. 权限控制:根据业务需求配置 Grafana 的认证和授权 如果你的项目也遇到了类似的嵌入需求,希望这个方案能给你提供一些思路。在实际落地过程中,建议结合自身业务场景和 Grafana 版本进行适当调整。

参考资源:

相关推荐
Cherry的跨界思维1 天前
【AI测试全栈:质量】47、Vue+Prometheus+Grafana实战:打造全方位AI监控面板开发指南
vue.js·人工智能·ci/cd·grafana·prometheus·ai测试·ai全栈
予枫的编程笔记1 天前
【Kafka高级篇】Kafka监控不踩坑:JMX指标暴露+Prometheus+Grafana可视化全流程
kafka·grafana·prometheus·可观测性·jmx·kafka集群调优·中间件监控
belldeep9 天前
Grafana 和 influxDB 是什么?两者如何结合使用?
grafana·influxdb·开源监控平台
moxiaoran575313 天前
Linux搭建轻量级日志系统Loki+Grafana+Promtail
grafana
㳺三才人子13 天前
認識 Grafana
grafana
是阿楷啊20 天前
Java大厂面试场景:音视频场景中的Spring Boot与微服务实战
spring boot·redis·spring cloud·微服务·grafana·prometheus·java面试
xixingzhe221 天前
Prometheus+Grafana监控服务器
grafana·prometheus
南宫乘风22 天前
Loki 日志采集落地:从单机 Helm 部署到 Promtail 采集与 Grafana 查询
grafana