mirror of
https://github.com/ChainSafe/lodestar.git
synced 2026-01-10 08:08:16 -05:00
chore: add script to pull dashboards from remote API (#5525)
Update scripts
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -62,6 +62,9 @@ packages/beacon-node/trusted_setup.txt
|
||||
# docker-compose .env file
|
||||
.env
|
||||
|
||||
# Grafana secret
|
||||
.secrets.env
|
||||
|
||||
# Git artifacts
|
||||
packages/cli/.git-data.json
|
||||
|
||||
|
||||
109
scripts/download_dashboards.mjs
Normal file
109
scripts/download_dashboards.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable
|
||||
@typescript-eslint/explicit-function-return-type,
|
||||
@typescript-eslint/naming-convention,
|
||||
import/no-extraneous-dependencies,
|
||||
no-console
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
import {lintGrafanaDashboard, readGrafanaDashboard, writeGrafanaDashboard} from "./lint-grafana-dashboard.mjs";
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// Create a file `.secrets.env` with envs
|
||||
// ```
|
||||
// GRAFANA_API_KEY=$token
|
||||
// GRAFANA_URL=https://yourgrafanaapi.io
|
||||
// ```
|
||||
//
|
||||
// Run
|
||||
// ```
|
||||
// node scripts/download_dashboards.mjs
|
||||
// ```
|
||||
//
|
||||
// Check git diff of resulting files, commit, push and open PR
|
||||
|
||||
// load environment variables from .env file
|
||||
dotenv.config({path: ".secrets.env"});
|
||||
|
||||
const OUTDIR = "./dashboards";
|
||||
const UID_PREFIX_WHITELIST = "lodestar_";
|
||||
const {GRAFANA_API_KEY, GRAFANA_URL} = process.env;
|
||||
|
||||
if (!GRAFANA_API_KEY) throw Error("GRAFANA_API_KEY not set");
|
||||
if (!GRAFANA_URL) throw Error("GRAFANA_URL not set");
|
||||
|
||||
// Fetch all dashboard uids
|
||||
/** @type {{data: DashboardMeta[]}} */
|
||||
const dashboardListRes = await axios.get(`${GRAFANA_URL}/api/search`, {
|
||||
headers: {Authorization: `Bearer ${GRAFANA_API_KEY}`},
|
||||
});
|
||||
|
||||
// Iterate through each dashboard uid and download the dashboard data
|
||||
for (const dashboardMeta of dashboardListRes.data) {
|
||||
if (!dashboardMeta.uid.startsWith(UID_PREFIX_WHITELIST)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note, currently Grafana API does NOT support returning a dashboard with the
|
||||
// "Export for sharing externally" toggle turned on.
|
||||
// https://community.grafana.com/t/export-dashboard-for-external-use-via-http-api/50716
|
||||
|
||||
/** @type {{data: DashboardGet}} */
|
||||
const dashboardDataRes = await axios.get(`${GRAFANA_URL}/api/dashboards/uid/${dashboardMeta.uid}`, {
|
||||
headers: {Authorization: `Bearer ${GRAFANA_API_KEY}`},
|
||||
});
|
||||
|
||||
const outpath = path.join(OUTDIR, `${dashboardMeta.uid}.json`);
|
||||
|
||||
// Only update dashboards that exist locally. Sometimes developers duplicate dashboards on the Grafana server
|
||||
// to test some new panels, with names like $uid_2.json. This script ignores those duplicates.
|
||||
// >> To add a new dashboard touch a file with filename `$uid.json`
|
||||
if (fs.existsSync(outpath)) {
|
||||
const prevDashboard = readGrafanaDashboard(outpath);
|
||||
|
||||
// Lint dashboard to match target format
|
||||
const newDashboard = lintGrafanaDashboard(dashboardDataRes.data.dashboard);
|
||||
|
||||
// Set version to same to reduce diff
|
||||
newDashboard.version = prevDashboard.version;
|
||||
|
||||
// Save dashboard data to a JSON file
|
||||
writeGrafanaDashboard(outpath, newDashboard);
|
||||
console.log(`saved ${outpath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// id: 39,
|
||||
// uid: '1iQudJZVk',
|
||||
// title: 'Alerts',
|
||||
// uri: 'db/alerts',
|
||||
// url: '/dashboards/f/1iQudJZVk/alerts',
|
||||
// slug: '',
|
||||
// type: 'dash-folder',
|
||||
// tags: [],
|
||||
// isStarred: false,
|
||||
// sortMeta: 0
|
||||
// },
|
||||
/**
|
||||
* @typedef {Object} DashboardMeta
|
||||
* @property {number} id
|
||||
* @property {string} uid
|
||||
* @property {string} title
|
||||
* @property {string} uri
|
||||
* @property {string} url
|
||||
* @property {string} slug
|
||||
* @property {string} type
|
||||
* @property {string[]} tags
|
||||
* @property {boolean} isStarred
|
||||
* @property {number} sortMeta
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DashboardGet
|
||||
* @property {import('./lint-grafana-dashboard.mjs').Dashboard} dashboard
|
||||
*/
|
||||
308
scripts/lint-grafana-dashboard.mjs
Normal file
308
scripts/lint-grafana-dashboard.mjs
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs";
|
||||
|
||||
// Logic for linting
|
||||
//
|
||||
// >> FOR SCRIPT USE `scripts/lint-grafana-dashboards.mjs`
|
||||
|
||||
/* eslint-disable
|
||||
@typescript-eslint/no-unsafe-assignment,
|
||||
@typescript-eslint/explicit-function-return-type,
|
||||
@typescript-eslint/naming-convention,
|
||||
no-console
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Dashboard
|
||||
* @property {any} [__elements]
|
||||
* @property {Input[]} [__inputs]
|
||||
* @property {any} [__requires]
|
||||
* @property {null} [id]
|
||||
* @property {Panel[]} [panels]
|
||||
* @property {string[]} [tags]
|
||||
* @property {Link[]} [links]
|
||||
* @property {Time} [time]
|
||||
* @property {Timepicker} [timepicker]
|
||||
* @property {string} [timezone]
|
||||
* @property {string} [weekStart]
|
||||
* @property {string} [refresh]
|
||||
* @property {Templating} [templating]
|
||||
* @property {number} version
|
||||
*
|
||||
* @typedef {Object} Input
|
||||
* @property {string} description - The description of the input.
|
||||
* @property {string} label - The label of the input.
|
||||
* @property {string} name - The name of the input.
|
||||
* @property {string} [pluginId] - The plugin id of the input, if applicable.
|
||||
* @property {string} [pluginName] - The plugin name of the input, if applicable.
|
||||
* @property {string} type - The type of the input.
|
||||
* @property {string} [value] - The value of the input, if applicable.
|
||||
*
|
||||
* @typedef {Object} Panel
|
||||
* @property {boolean} [collapsed]
|
||||
* @property {string} title
|
||||
* @property {Datasource} [datasource]
|
||||
* @property {Target[]} [targets]
|
||||
* @property {Panel[]} [panels]
|
||||
*
|
||||
* @typedef {Object} Target
|
||||
* @property {Datasource} [datasource]
|
||||
* @property {boolean} [exemplar]
|
||||
* @property {string} [expr]
|
||||
*
|
||||
* @typedef {Object} Datasource
|
||||
* @property {string} type
|
||||
* @property {string} uid
|
||||
*
|
||||
* @typedef {Object} Link
|
||||
* @property {string} title
|
||||
*
|
||||
* @typedef {Object} Time
|
||||
* @property {string} from
|
||||
* @property {string} to
|
||||
*
|
||||
* @typedef {Object} Timepicker
|
||||
* @property {string[]} refresh_intervals
|
||||
*
|
||||
* @typedef {Object} Templating
|
||||
* @property {TemplatingListItem[]} [list]
|
||||
*
|
||||
* @typedef {Object} TemplatingListItem
|
||||
* @property {string} name
|
||||
*/
|
||||
|
||||
const variableNameDatasource = "DS_PROMETHEUS";
|
||||
const variableNameRateInterval = "rate_interval";
|
||||
const variableNameFilters = "Filters";
|
||||
|
||||
/**
|
||||
* @param {Dashboard} json
|
||||
* @returns {Dashboard}
|
||||
*/
|
||||
export function lintGrafanaDashboard(json) {
|
||||
// Drop properties added by "Export for sharing externally" to keep diff consistent
|
||||
delete json.__elements;
|
||||
delete json.__requires;
|
||||
|
||||
// Ensure __inputs is first property to reduce diff
|
||||
json = {
|
||||
__inputs: [
|
||||
{
|
||||
description: "",
|
||||
label: "Prometheus",
|
||||
name: variableNameDatasource,
|
||||
pluginId: "prometheus",
|
||||
pluginName: "Prometheus",
|
||||
type: "datasource",
|
||||
},
|
||||
{
|
||||
description: "",
|
||||
label: "Beacon node job name",
|
||||
name: "VAR_BEACON_JOB",
|
||||
type: "constant",
|
||||
value: "beacon",
|
||||
},
|
||||
],
|
||||
...json,
|
||||
};
|
||||
|
||||
// null id to match "Export for sharing externally" format, only set to null if set to respect order
|
||||
if (json !== undefined) {
|
||||
json.id = null;
|
||||
}
|
||||
|
||||
if (json.panels) {
|
||||
assertPanels(json.panels);
|
||||
}
|
||||
|
||||
const LODESTAR_TAG = "lodestar";
|
||||
|
||||
// Force add lodestar tag for easy navigation
|
||||
if (!json.tags) json.tags = [];
|
||||
if (!json.tags.includes(LODESTAR_TAG)) json.tags.push(LODESTAR_TAG);
|
||||
|
||||
// Add links section
|
||||
const LINK_TITLE = "Lodestar dashboards";
|
||||
|
||||
if (!json.links) json.links = [];
|
||||
if (!json.links.some((link) => link.title === LINK_TITLE)) {
|
||||
json.links.push({
|
||||
asDropdown: true,
|
||||
icon: "external link",
|
||||
includeVars: true,
|
||||
keepTime: true,
|
||||
tags: [LODESTAR_TAG],
|
||||
targetBlank: false,
|
||||
title: LINK_TITLE,
|
||||
tooltip: "",
|
||||
type: "dashboards",
|
||||
url: "",
|
||||
});
|
||||
}
|
||||
|
||||
// Force time on a constant time window
|
||||
json.refresh = "10s";
|
||||
json.time = {
|
||||
from: "now-24h",
|
||||
to: "now",
|
||||
};
|
||||
|
||||
// Force timezone and time settings
|
||||
json.timezone = "utc";
|
||||
json.weekStart = "monday";
|
||||
if (!json.timepicker) json.timepicker = {};
|
||||
json.timepicker.refresh_intervals = ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"];
|
||||
|
||||
// Add common variables
|
||||
if (!json.templating) json.templating = {};
|
||||
if (!json.templating.list) json.templating.list = [];
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameDatasource, {
|
||||
current: {selected: false, text: "default", value: "default"},
|
||||
hide: 0,
|
||||
includeAll: false,
|
||||
label: "datasource",
|
||||
multi: false,
|
||||
name: variableNameDatasource,
|
||||
options: [],
|
||||
query: "prometheus",
|
||||
queryValue: "",
|
||||
refresh: 1,
|
||||
regex: "",
|
||||
skipUrlSync: false,
|
||||
type: "datasource",
|
||||
});
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameRateInterval, {
|
||||
auto: true,
|
||||
auto_count: 30,
|
||||
auto_min: "10s",
|
||||
current: {selected: false, text: "1h", value: "1h"},
|
||||
hide: 0,
|
||||
label: "rate() interval",
|
||||
name: variableNameRateInterval,
|
||||
options: [
|
||||
{selected: false, text: "auto", value: "$__auto_interval_rate_interval"},
|
||||
{selected: false, text: "1m", value: "1m"},
|
||||
{selected: false, text: "10m", value: "10m"},
|
||||
{selected: false, text: "30m", value: "30m"},
|
||||
{selected: true, text: "1h", value: "1h"},
|
||||
{selected: false, text: "6h", value: "6h"},
|
||||
{selected: false, text: "12h", value: "12h"},
|
||||
{selected: false, text: "1d", value: "1d"},
|
||||
{selected: false, text: "7d", value: "7d"},
|
||||
{selected: false, text: "14d", value: "14d"},
|
||||
{selected: false, text: "30d", value: "30d"},
|
||||
],
|
||||
query: "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
|
||||
queryValue: "",
|
||||
refresh: 2,
|
||||
skipUrlSync: false,
|
||||
type: "interval",
|
||||
});
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameFilters, {
|
||||
datasource: {
|
||||
type: "prometheus",
|
||||
uid: "prometheus_local",
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
condition: "",
|
||||
key: "instance",
|
||||
operator: "=",
|
||||
value: "unstable-lg1k-hzax41",
|
||||
},
|
||||
],
|
||||
hide: 0,
|
||||
name: "Filters",
|
||||
skipUrlSync: false,
|
||||
type: "adhoc",
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filepath
|
||||
* @returns {Dashboard}
|
||||
*/
|
||||
export function readGrafanaDashboard(filepath) {
|
||||
const jsonStr = fs.readFileSync(filepath, "utf8");
|
||||
/** @type {Dashboard} */
|
||||
const json = JSON.parse(jsonStr);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filepath
|
||||
* @param {Dashboard} json
|
||||
*/
|
||||
export function writeGrafanaDashboard(filepath, json) {
|
||||
// Add new line
|
||||
const jsonStrOut = JSON.stringify(json, null, 2) + "\n";
|
||||
fs.writeFileSync(filepath, jsonStrOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dashboard} json
|
||||
* @param {string} varName
|
||||
* @param {TemplatingListItem} item
|
||||
*/
|
||||
function assertTemplatingListItemContent(json, varName, item) {
|
||||
if (!json.templating) json.templating = {};
|
||||
if (!json.templating.list) json.templating.list = [];
|
||||
|
||||
const index = json.templating.list.findIndex((item) => item.name === varName);
|
||||
|
||||
if (index < 0) {
|
||||
// No match, push new item
|
||||
json.templating.list.push(item);
|
||||
} else {
|
||||
// Match replace content
|
||||
json.templating.list[index] = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Panel[]} panels
|
||||
*/
|
||||
function assertPanels(panels) {
|
||||
for (const panel of panels) {
|
||||
if (panel.collapsed === true) {
|
||||
throw Error(`all panels must be expanded, go to the UI and expand panel with title ${panel.title}`);
|
||||
}
|
||||
|
||||
// Panel datasource must point to the datasource variable
|
||||
if (panel.datasource) {
|
||||
panel.datasource.type = "prometheus";
|
||||
panel.datasource.uid = `\${${variableNameDatasource}}`;
|
||||
}
|
||||
|
||||
if (panel.targets) {
|
||||
for (const target of panel.targets) {
|
||||
// All panels must point to the datasource variable
|
||||
if (target.datasource) {
|
||||
target.datasource.type = "prometheus";
|
||||
target.datasource.uid = `\${${variableNameDatasource}}`;
|
||||
}
|
||||
|
||||
// Disable exemplar
|
||||
if (target.exemplar !== undefined) {
|
||||
target.exemplar = false;
|
||||
}
|
||||
|
||||
// Force usage of interval variable
|
||||
if (target.expr) {
|
||||
target.expr.replace(/\$__rate_interval/g, `$${variableNameRateInterval}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check nested panels
|
||||
if (panel.panels) {
|
||||
assertPanels(panel.panels);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,245 +2,28 @@
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {lintGrafanaDashboard, readGrafanaDashboard, writeGrafanaDashboard} from "./lint-grafana-dashboard.mjs";
|
||||
|
||||
// USAGE:
|
||||
// node scripts/lint-grafana-dashboards.mjs ./dashboards
|
||||
|
||||
/* eslint-disable
|
||||
@typescript-eslint/no-unsafe-assignment,
|
||||
@typescript-eslint/explicit-function-return-type,
|
||||
@typescript-eslint/naming-convention,
|
||||
no-console
|
||||
*/
|
||||
|
||||
const dirpath = process.argv[2];
|
||||
if (!dirpath) throw Error("Must provide dirpath argument");
|
||||
|
||||
const filenames = fs.readdirSync(dirpath);
|
||||
if (filenames.length === 0) throw Error(`Empty dir ${dirpath}`);
|
||||
|
||||
/**
|
||||
* @typedef {Object} Dashboard
|
||||
* @property {Panel[]} [panels]
|
||||
* @property {string[]} [tags]
|
||||
* @property {Link[]} [links]
|
||||
* @property {Time} [time]
|
||||
* @property {Timepicker} [timepicker]
|
||||
* @property {string} [timezone]
|
||||
* @property {string} [weekStart]
|
||||
* @property {string} [refresh]
|
||||
* @property {Templating} [templating]
|
||||
*
|
||||
* @typedef {Object} Panel
|
||||
* @property {Datasource} [datasource]
|
||||
* @property {Target[]} [targets]
|
||||
* @property {Panel[]} [panels]
|
||||
*
|
||||
* @typedef {Object} Target
|
||||
* @property {Datasource} [datasource]
|
||||
* @property {boolean} [exemplar]
|
||||
* @property {string} [expr]
|
||||
*
|
||||
* @typedef {Object} Datasource
|
||||
* @property {string} type
|
||||
* @property {string} uid
|
||||
*
|
||||
* @typedef {Object} Link
|
||||
* @property {string} title
|
||||
*
|
||||
* @typedef {Object} Time
|
||||
* @property {string} from
|
||||
* @property {string} to
|
||||
*
|
||||
* @typedef {Object} Timepicker
|
||||
* @property {string[]} refresh_intervals
|
||||
*
|
||||
* @typedef {Object} Templating
|
||||
* @property {TemplatingListItem[]} [list]
|
||||
*
|
||||
* @typedef {Object} TemplatingListItem
|
||||
* @property {string} name
|
||||
*/
|
||||
|
||||
const variableNameDatasource = "DS_PROMETHEUS";
|
||||
const variableNameRateInterval = "rate_interval";
|
||||
const variableNameFilters = "Filters";
|
||||
|
||||
for (const filename of filenames) {
|
||||
if (!filename.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const jsonStr = fs.readFileSync(path.join(dirpath, filename), "utf8");
|
||||
/** @type {Dashboard} */
|
||||
const json = JSON.parse(jsonStr);
|
||||
|
||||
if (json.panels) {
|
||||
assertPanels(json.panels);
|
||||
}
|
||||
|
||||
const LODESTAR_TAG = "lodestar";
|
||||
|
||||
// Force add lodestar tag for easy navigation
|
||||
if (!json.tags) json.tags = [];
|
||||
if (!json.tags.includes(LODESTAR_TAG)) json.tags.push(LODESTAR_TAG);
|
||||
|
||||
// Add links section
|
||||
const LINK_TITLE = "Lodestar dashboards";
|
||||
|
||||
if (!json.links) json.links = [];
|
||||
if (!json.links.some((link) => link.title === LINK_TITLE)) {
|
||||
json.links.push({
|
||||
asDropdown: true,
|
||||
icon: "external link",
|
||||
includeVars: true,
|
||||
keepTime: true,
|
||||
tags: [LODESTAR_TAG],
|
||||
targetBlank: false,
|
||||
title: LINK_TITLE,
|
||||
tooltip: "",
|
||||
type: "dashboards",
|
||||
url: "",
|
||||
});
|
||||
}
|
||||
|
||||
// Force time on a constant time window
|
||||
json.refresh = "10s";
|
||||
json.time = {
|
||||
from: "now-24h",
|
||||
to: "now",
|
||||
};
|
||||
|
||||
// Force timezone and time settings
|
||||
json.timezone = "utc";
|
||||
json.weekStart = "monday";
|
||||
if (!json.timepicker) json.timepicker = {};
|
||||
json.timepicker.refresh_intervals = ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"];
|
||||
|
||||
// Add common variables
|
||||
if (!json.templating) json.templating = {};
|
||||
if (!json.templating.list) json.templating.list = [];
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameDatasource, {
|
||||
current: {selected: false, text: "default", value: "default"},
|
||||
hide: 0,
|
||||
includeAll: false,
|
||||
label: "datasource",
|
||||
multi: false,
|
||||
name: variableNameDatasource,
|
||||
options: [],
|
||||
query: "prometheus",
|
||||
queryValue: "",
|
||||
refresh: 1,
|
||||
regex: "",
|
||||
skipUrlSync: false,
|
||||
type: "datasource",
|
||||
});
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameRateInterval, {
|
||||
auto: true,
|
||||
auto_count: 30,
|
||||
auto_min: "10s",
|
||||
current: {selected: false, text: "1h", value: "1h"},
|
||||
hide: 0,
|
||||
label: "rate() interval",
|
||||
name: variableNameRateInterval,
|
||||
options: [
|
||||
{selected: false, text: "auto", value: "$__auto_interval_rate_interval"},
|
||||
{selected: false, text: "1m", value: "1m"},
|
||||
{selected: false, text: "10m", value: "10m"},
|
||||
{selected: false, text: "30m", value: "30m"},
|
||||
{selected: true, text: "1h", value: "1h"},
|
||||
{selected: false, text: "6h", value: "6h"},
|
||||
{selected: false, text: "12h", value: "12h"},
|
||||
{selected: false, text: "1d", value: "1d"},
|
||||
{selected: false, text: "7d", value: "7d"},
|
||||
{selected: false, text: "14d", value: "14d"},
|
||||
{selected: false, text: "30d", value: "30d"},
|
||||
],
|
||||
query: "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
|
||||
queryValue: "",
|
||||
refresh: 2,
|
||||
skipUrlSync: false,
|
||||
type: "interval",
|
||||
});
|
||||
|
||||
assertTemplatingListItemContent(json, variableNameFilters, {
|
||||
datasource: {
|
||||
type: "prometheus",
|
||||
uid: "prometheus_local",
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
condition: "",
|
||||
key: "instance",
|
||||
operator: "=",
|
||||
value: "unstable-lg1k-hzax41",
|
||||
},
|
||||
],
|
||||
hide: 0,
|
||||
name: "Filters",
|
||||
skipUrlSync: false,
|
||||
type: "adhoc",
|
||||
});
|
||||
|
||||
// Add new line
|
||||
const jsonStrOut = JSON.stringify(json, null, 2) + "\n";
|
||||
fs.writeFileSync(path.join(dirpath, filename), jsonStrOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dashboard} json
|
||||
* @param {string} varName
|
||||
* @param {TemplatingListItem} item
|
||||
*/
|
||||
function assertTemplatingListItemContent(json, varName, item) {
|
||||
if (!json.templating) json.templating = {};
|
||||
if (!json.templating.list) json.templating.list = [];
|
||||
|
||||
const index = json.templating.list.findIndex((item) => item.name === varName);
|
||||
|
||||
if (index < 0) {
|
||||
// No match, push new item
|
||||
json.templating.list.push(item);
|
||||
} else {
|
||||
// Match replace content
|
||||
json.templating.list[index] = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Panel[]} panels
|
||||
*/
|
||||
function assertPanels(panels) {
|
||||
for (const panel of panels) {
|
||||
// Panel datasource must point to the datasource variable
|
||||
if (panel.datasource) {
|
||||
panel.datasource.type = "prometheus";
|
||||
panel.datasource.uid = `\${${variableNameDatasource}}`;
|
||||
}
|
||||
if (panel.targets) {
|
||||
for (const target of panel.targets) {
|
||||
// All panels must point to the datasource variable
|
||||
if (target.datasource) {
|
||||
target.datasource.type = "prometheus";
|
||||
target.datasource.uid = `\${${variableNameDatasource}}`;
|
||||
}
|
||||
|
||||
// Disable exemplar
|
||||
if (target.exemplar !== undefined) {
|
||||
target.exemplar = false;
|
||||
}
|
||||
|
||||
// Force usage of interval variable
|
||||
if (target.expr) {
|
||||
target.expr.replace(/\$__rate_interval/g, `$${variableNameRateInterval}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Recursively check nested panels
|
||||
if (panel.panels) {
|
||||
assertPanels(panel.panels);
|
||||
}
|
||||
const filepath = path.join(dirpath, filename);
|
||||
try {
|
||||
const json = lintGrafanaDashboard(readGrafanaDashboard(filepath));
|
||||
writeGrafanaDashboard(filepath, json);
|
||||
} catch (e) {
|
||||
e.message = `file ${filepath}: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user