Grafana Dashboard 配置生成器

生成 Grafana Dashboard JSON 配置文件,支持 Panel、Row、Query、Template 变量等完整结构

📊 Dashboard 基础配置
📈 面板 (Panels)
🔀 模板变量 (Template Variables)
let panels = [ { title: 'CPU Usage', type: 'timeseries', query: '100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)', legend: '{{instance}}', unit: 'percent', span: 12, }, { title: 'Memory Usage', type: 'gauge', query: '(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100', legend: 'Memory Usage', unit: 'percent', span: 6, }, ]; let variables = [ { name: 'instance', type: 'query', label: 'Instance', query: 'label_values(up, instance)', current: 'All' }, ]; function renderPanels() { const el = document.getElementById('panelList'); if (panels.length === 0) { el.innerHTML = '
暂无面板,点击上方按钮添加
'; return; } el.innerHTML = panels.map((p, i) => `

${p.title || 'Panel #' + (i+1)}

`).join(''); } function addPanel() { panels.push({ title: '', type: 'timeseries', query: '', legend: '', unit: 'none', span: 12 }); renderPanels(); } function removePanel(i) { panels.splice(i, 1); renderPanels(); } function renderVars() { const el = document.getElementById('varList'); if (variables.length === 0) { el.innerHTML = '
暂无模板变量,点击上方按钮添加
'; return; } el.innerHTML = variables.map((v, i) => `

$${v.name || 'var' + (i+1)}

`).join(''); } function addVar() { variables.push({ name: '', type: 'query', label: '', query: '', current: 'All' }); renderVars(); } function removeVar(i) { variables.splice(i, 1); renderVars(); } function generate() { const title = document.getElementById('dashTitle').value || 'My Dashboard'; const uid = document.getElementById('dashUid').value || 'my-dashboard'; const datasource = document.getElementById('datasource').value; const timeFrom = document.getElementById('timeRange').value; const refresh = document.getElementById('refresh').value; const tagsStr = document.getElementById('tags').value; const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : ['monitoring']; const now = Date.now(); let dashboard = { annotations: { list: [ { builtIn: 1, datasource: { type: 'grafana', uid: '-- Grafana --' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', name: 'Annotations & Alerts', type: 'dashboard' } ] }, editable: true, fiscalYearStartMonth: 0, graphTooltip: 0, id: null, links: [], liveNow: false, panels: [], refresh: refresh, schemaVersion: 38, tags: tags, templating: { list: [] }, time: { from: `now-${timeFrom}`, to: 'now' }, timepicker: {}, timezone: 'browser', title: title, uid: uid, version: 1, weekStart: '' }; // Generate panels let yPos = 0; panels.forEach((p, idx) => { if (!p.title || !p.query) return; const panelHeight = p.type === 'table' ? 8 : (p.type === 'stat' || p.type === 'gauge' ? 5 : 8); const panelWidth = Math.min(Math.max(p.span || 12, 1), 24); let panelObj = { datasources: {}, fieldConfig: { defaults: { color: { mode: 'palette-classic' }, custom: { axisCenteredZero: false, axisColorMode: 'text', axisLabel: '', axisPlacement: 'auto', barAlignment: 0, drawStyle: 'line', fillOpacity: 10, gradientMode: 'none', hideFrom: { legend: false, tooltip: false, viz: false }, lineInterpolation: 'linear', lineWidth: 1, pointSize: 5, scaleDistribution: { type: 'linear' }, showPoints: 'auto', spans: false, stacking: { group: 'A', mode: 'none' }, thresholdsStyle: { mode: 'off' } }, mappings: [], thresholds: { mode: 'absolute', steps: [{ color: 'green', value: null }, { color: 'yellow', value: 70 }, { color: 'red', value: 90 }] }, unit: p.unit || 'none' }, overrides: [] }, gridPos: { h: panelHeight, w: panelWidth, x: ((idx * panelWidth) % 24), y: yPos }, id: idx + 1, options: { legend: { calcs: ['mean', 'max'], displayMode: 'list', placement: 'bottom' }, tooltip: { mode: 'single', sort: 'none' } }, targets: [{ datasource: { type: 'prometheus', uid: '${datasource}' }, editorMode: 'code', expr: p.query, legendFormat: p.legend || '', range: true, refId: 'A' }], title: p.title, type: p.type || 'timeseries' }; // Type-specific overrides if (p.type === 'stat') { panelObj.fieldConfig.defaults.custom = { ...panelObj.fieldConfig.defaults.custom, align: 'auto', cellOptions: { type: 'auto' }, inspect: false }; panelObj.options = { colorMode: 'value', graphMode: 'area', justifyMode: 'auto', orientation: 'auto', reduceOptions: { calcs: ['lastNotNull'], fields: '', values: false }, textMode: 'auto' }; } else if (p.type === 'gauge') { panelObj.options = { orientation: 'auto', reduceOptions: { calcs: ['lastNotNull'], fields: '', values: false }, showThresholdLabels: false, showThresholdMarkers: true }; } else if (p.type === 'bar') { panelObj.fieldConfig.defaults.custom.drawStyle = 'bars'; panelObj.fieldConfig.defaults.custom.lineWidth = 0; panelObj.fieldConfig.defaults.custom.fillOpacity = 80; } dashboard.panels.push(panelObj); // Update y position for next row if ((idx + 1) % Math.floor(24 / panelWidth) === 0) { yPos += panelHeight; } }); // Generate template variables variables.forEach((v, idx) => { if (!v.name) return; let tmplVar = { current: { selected: v.current === 'All', text: v.current || 'All', value: v.current === 'All' ? ['$__all'] : (v.current || '') }, description: '', error: null, hide: 0, includeAll: true, label: v.label || v.name, multi: true, name: v.name, options: [], query: { query: v.query || '', refId: `Var${idx}` }, refresh: 1, regex: '', skipUrlSync: false, sort: 0, type: v.type === 'custom' ? 'custom' : (v.type === 'interval' ? 'interval' : (v.type === 'datasource' ? 'datasource' : 'query')) }; dashboard.templating.list.push(tmplVar); }); const jsonStr = JSON.stringify(dashboard, null, 2); document.getElementById('resultOutput').textContent = jsonStr; document.getElementById('resultCard').style.display = ''; } function copyResult() { navigator.clipboard.writeText(document.getElementById('resultOutput').textContent).then(() => { event.target.textContent = '已复制!'; setTimeout(() => event.target.textContent = '复制', 1500); }); } function resetForm() { document.getElementById('dashTitle').value = 'Server Monitoring'; document.getElementById('dashUid').value = 'server-monitoring'; document.getElementById('datasource').value = 'Prometheus'; document.getElementById('timeRange').value = '1h'; document.getElementById('refresh').value = '30s'; document.getElementById('tags').value = 'prometheus, monitoring, server'; panels = [ { title: 'CPU Usage', type: 'timeseries', query: '100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)', legend: '{{instance}}', unit: 'percent', span: 12 }, { title: 'Memory Usage', type: 'gauge', query: '(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100', legend: 'Memory Usage', unit: 'percent', span: 6 }, ]; variables = [ { name: 'instance', type: 'query', label: 'Instance', query: 'label_values(up, instance)', current: 'All' }, ]; renderPanels(); renderVars(); document.getElementById('resultCard').style.display = 'none'; } renderPanels(); renderVars();