前端嵌入Grafana 报表的自定义方案:隐藏导航栏保留筛选工具
在实际业务开发中,将 Grafana 报表嵌入到自研应用系统是非常常见的需求。官方提供了几种嵌入模式,但灵活性有限,无法满足所有场景。本文将介绍如何突破官方限制,实现"隐藏导航栏 + 保留筛选工具"的定制化嵌入效果。
一、官方支持的嵌入方式
Grafana 官方通过 URL 参数 kiosk 提供了三种嵌入模式:
1. 无参数模式
直接访问 Grafana 地址,不添加任何参数:
http://your-grafana/d/dashboard-id/dashboard-name
效果:导航栏和筛选工具都正常显示,适用场景:需要完整 Grafana 功能的独立访问。
2. Kiosk TV 模式(kiosk=tv)
http://your-grafana/d/dashboard-id/dashboard-name?kiosk=tv
效果:隐藏筛选工具,保留导航栏,适用场景:大屏展示、TV 模式轮播。
3. Kiosk 全屏模式(kiosk=1)
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,关键配置如下:
[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:根据业务需求配置匿名访问权限 完整配置示例:
{
"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 代理、前端页面路由、静态资源服务。
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";
}
}
配置说明:
- Grafana 代理:将
/grafana/路径代理到 Grafana 服务,处理 WebSocket 连接(实时刷新功能需要) - 前端页面:提供嵌入页面的访问入口,建议使用
internal防止直接暴露 - 静态资源:支持加载本地 JS/CSS 资源
3.3 前端实现:动态 DOM 操作
核心思路是使用 MutationObserver 监听 iframe 内容变化,找到目标元素后进行样式修改。
<!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>
实现要点:
- MutationObserver:动态监听 DOM 变化,避免使用
setInterval轮询 - 元素选择器:
.css-srjygq是 Grafana v10.2.3 的导航栏类名(不同版本可能不同) - 超时保护:防止目标元素不存在时无限监听
四、版本兼容性说明
关于 CSS 类名
Grafana 的 CSS 类名(如 .css-srjygq)是通过 CSS-in-JS 动态生成的,不同版本可能不同。如果你的 Grafana 版本不是 v10.2.3,需要自行定位目标元素: 定位方法:
- 在浏览器中打开 Grafana
- 按 F12 打开开发者工具
- 使用元素选择器定位导航栏
- 找到对应的 CSS 类名或
data-testid属性 更稳健的选择器示例:
// 方案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 加载的页面与父页面不同源 解决方案:
- 确保 Nginx 代理配置正确
- 配置 Grafana 的
allow_embedding = true - 如需跨域访问,考虑使用
postMessage通信
问题 2:WebSocket 连接失败
现象:实时刷新功能不工作 解决方案: 确保 Nginx 配置了 WebSocket 支持:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
问题 3:iframe 高度自适应
解决方案:
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 提供了更灵活的选择。 注意事项:
- 版本兼容性:Grafana 升级时需要重新验证元素选择器
- 同源策略:确保应用与 Grafana 代理后的域名一致
- 性能优化:MutationObserver 找到元素后立即 disconnect
- 权限控制:根据业务需求配置 Grafana 的认证和授权 如果你的项目也遇到了类似的嵌入需求,希望这个方案能给你提供一些思路。在实际落地过程中,建议结合自身业务场景和 Grafana 版本进行适当调整。
参考资源: