Created
August 18, 2020 14:53
-
-
Save breezewish/a8c2f766178853dfbc8335b9dfe2a3dd to your computer and use it in GitHub Desktop.
Export current Grafana dashboard (for Grafana v6.x.x)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
function escapeHtml(unsafe) { | |
return unsafe | |
.replace(/&/g, "&") | |
.replace(/</g, "<") | |
.replace(/>/g, ">") | |
.replace(/"/g, """) | |
.replace(/'/g, "'"); | |
} | |
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