Skip to content

Instantly share code, notes, and snippets.

@breezewish
Created August 18, 2020 14:53
Show Gist options
  • Save breezewish/a8c2f766178853dfbc8335b9dfe2a3dd to your computer and use it in GitHub Desktop.
Save breezewish/a8c2f766178853dfbc8335b9dfe2a3dd to your computer and use it in GitHub Desktop.
Export current Grafana dashboard (for Grafana v6.x.x)
(function () {
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function DExporter() {
this.$injector = angular.element(document.body).injector();
this.dashboard = this.$injector.get('dashboardSrv').getCurrent();
this.timeSrv = this.$injector.get('timeSrv');
}
DExporter.prototype.prepareStyles = function () {
const styleText = `
.__dexport_hint {
transition: opacity .2s linear;
opacity: 0.8;
position: fixed;
right: 0;
width: 600px;
top: 100px;
background: #FFF;
color: #000;
z-index: 9999;
font-size: 18px;
padding: 20px;
border-left: 3px solid #faad14;
}
.__dexport_hint:hover {
opacity: 1;
}
.__dexport_hint strong {
color: #fa541c;
}
.__dexport_hint button {
font-size: 16px;
margin-right: 10px;
padding: 5px 10px;
border: 1px solid #CCC;
color: #222;
background: #FFF;
transition: background .1s linear, color .1s linear;
}
.__dexport_hint button:hover {
background: #faad14 !important;
color: #FFF !important;
}
.theme-light .__dexport_hint {
background: #000;
color: #FFF;
}
.theme-light .__dexport_hint button {
color: #FFF;
background: #000;
border-color: #666;
}
`;
const style = document.createElement("style");
style.type = "text/css";
style.innerText = styleText;
document.head.appendChild(style);
this.styleElement = style;
}
DExporter.prototype.cleanUp = function () {
if (this.styleElement) {
document.head.removeChild(this.styleElement);
this.styleElement = null;
}
if (this.progressStyleElement) {
document.head.removeChild(this.progressStyleElement);
this.progressStyleElement = null;
}
if (this.hintDiv) {
document.body.removeChild(this.hintDiv);
this.hintDiv = null;
}
if (this.progressDiv) {
document.body.removeChild(this.progressDiv);
this.progressDiv = null;
}
if (this.lastRefresh) {
this.timeSrv.setAutoRefresh(this.lastRefresh);
this.lastRefresh = null;
}
delete this.dashboard.snapshot;
this.dashboard.forEachPanel(panel => {
delete panel.snapshotData;
});
this.dashboard.annotations.list.forEach(annotation => {
delete annotation.snapshotData;
});
if (this.progressTimer) {
clearInterval(this.progressTimer);
this.progressTimer = null;
}
}
DExporter.prototype.showHint = function () {
this.prepareStyles();
const hintDiv = document.createElement("div");
hintDiv.innerHTML = `
<div class="__dexport_hint">
<p>将导出当前名为 <strong>${escapeHtml(this.dashboard.title)}</strong> 的监控页面</p>
<p>注意:只有已展开面板内的监控会被导出。</p>
<p><button id="dexport_expand">展开所有面板</button><button id="dexport_next">继续</button><button id="dexport_cancel">取消</button></p>
</div>
`
hintDiv.querySelector("#dexport_expand").onclick = () => {
this.dashboard.expandRows();
}
hintDiv.querySelector("#dexport_cancel").onclick = () => {
this.cleanUp();
}
hintDiv.querySelector("#dexport_next").onclick = () => {
this.proceed();
}
document.body.appendChild(hintDiv);
this.hintDiv = hintDiv;
}
DExporter.prototype.prepareProgressStyles = function () {
const styleText = `
.panel-loading {
width: 100%;
height: 100%;
background: rgba(255,255,255,0.4);
pointer-events: none;
color: #000;
text-align:center;
}
.panel-loading::after {
font-size: 20px;
content: 'Fetching data...';
}
.theme-light .panel-loading {
background: rgba(0,0,0,0.4);
color: #FFF;
}
`;
const style = document.createElement("style");
style.type = "text/css";
style.innerText = styleText;
document.head.appendChild(style);
this.progressStyleElement = style;
}
DExporter.prototype.getPanelLoadingStatus = function () {
const allSpinners = document.querySelectorAll('.panel-loading');
let finished = 0;
allSpinners.forEach(spinner => {
if (spinner.classList.contains("ng-hide")) {
finished++;
}
});
return {
all: allSpinners.length,
finished: finished,
};
}
const exportButtonTextInProgress = '等不及了,就按现在已获取到的数据导出吧!';
const exportButtonTextFinished = '导出为文件';
const progressHintInProgress = '正在更新各个面板加载监控数据。';
const progressHintFinished = '所有面板监控数据加载完毕。';
DExporter.prototype.updateProgressSpanAndButton = function (status) {
if (this.progressDiv) {
const span = this.progressDiv.querySelector('#dexport_progress');
if (span) {
const percent = (status.finished / status.all * 100).toFixed(1);
span.innerText = `${percent}% (${status.finished} / ${status.all})`;
}
const button = this.progressDiv.querySelector('#dexport_export');
if (button) {
if (status.finished === status.all) {
button.innerText = exportButtonTextFinished;
} else {
button.innerText = exportButtonTextInProgress;
}
}
const hint = this.progressDiv.querySelector('#dexport_progress_hint');
if (hint) {
if (status.finished === status.all) {
hint.innerText = progressHintFinished;
} else {
hint.innerText = progressHintInProgress;
}
}
}
}
DExporter.prototype.startProgressUpdater = function () {
if (!this.progressDiv) {
return;
}
if (this.progressTimer) {
return;
}
this.progressTimer = setInterval(() => {
const status = this.getPanelLoadingStatus();
this.updateProgressSpanAndButton(status);
if (status.finished === status.all) {
clearInterval(this.progressTimer);
this.progressTimer = null;
}
}, 200);
}
DExporter.prototype.proceed = function () {
document.body.removeChild(this.hintDiv);
this.hintDiv = null;
// Disable refresh
this.lastRefresh = this.dashboard.refresh;
this.timeSrv.setAutoRefresh(null);
this.dashboard.snapshot = { timestamp: new Date() };
this.prepareProgressStyles();
const progressDiv = document.createElement("div");
progressDiv.innerHTML = `
<div class="__dexport_hint">
<p><span id="dexport_progress_hint">${progressHintInProgress}</span>加载进度:<strong><span id="dexport_progress">0%</span></strong></p>
<p><button id="dexport_export">${exportButtonTextInProgress}</button><button id="dexport_cancel">放弃导出</button></p>
</div>
`;
progressDiv.querySelector("#dexport_cancel").onclick = () => {
this.cleanUp();
}
progressDiv.querySelector("#dexport_export").onclick = () => {
this.exportAndDownload();
}
document.body.appendChild(progressDiv);
this.progressDiv = progressDiv;
this.dashboard.startRefresh();
setInterval(this.startProgressUpdater(), 1000);
}
DExporter.prototype.exportAndDownload = function () {
const data = this.dashboard.getSaveModelClone();
data.time = this.timeSrv.timeRange();
const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.href = url;
link.download = `${this.dashboard.title}_${new Date().toISOString()}.json`;
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}, 0);
this.cleanUp();
}
let de;
try {
de = new DExporter();
de.showHint();
} catch (e) {
console.log(e);
alert('不支持的 Grafana 页面,请确保是在 Grafana 监控网页上执行的脚本,以及是受支持的 Grafana 版本(支持 Grafana 6.x.x)');
return;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment