#!/usr/bin/env node const packageJson = require('./package.json'); const fetch = require('node-fetch'); const colors = require('colors'); const os = require('os'); const fs = require('fs'); const getopts = require('getopts'); const error = `[ERROR]: `.bold; console.log( `Grafana-Find (${packageJson.description})`.yellow.bold, `v${packageJson.version}`.cyan.bold ); const opts = getopts(process.argv.slice(2), { alias: { version: 'v', help: 'h' }, boolean: [ 'version', 'help' ] }); if (opts.version) { process.exit(); } if (opts.help) { console.log(`Usage:`.gray, `grafana-find`, ``.magenta); process.exit(); } if (!opts._[0]) { console.error(`${error}The string to search for is missing`.red); process.exit(1); } if (opts._.length > 1) { console.error(`${error}This command doesn't allow more parameters`.red); process.exit(1); } let config; const configPaths = [ os.homedir(), `${__dirname}` ]; for (const configPath of configPaths) { const configFile = `${configPath}/.grafana-find.json`; if (fs.existsSync(configFile)) { config = require(configFile); break; } } if (!config) { console.error(`${error}Configuration file not found, search paths: .grafana-find.json: ${configPaths.join(':')}\n`.red); process.exit(1); } const findAll = opts._[0]; const regexRawSQL = new RegExp(findAll, 'i'); const grafanaUrl = config.grafanaUrl; let user = config.user; let passw = config.password; let numberOfDashboards = 0; let totalObjects = 0; let numberOfPanels = 0; let numberOfVariables = 0; let numberOfObjects = 0; let titlePanels = new Array; let titleAlerts = new Array; let nameVariables = new Array; // URIs const grafanaApi = `${grafanaUrl}/api`; const urlOrganizations = `${grafanaUrl}/api/orgs`; const urlDashboards = `${grafanaApi}/search?orgId=`; const urlUID = `${grafanaApi}/dashboards/uid/`; const urlAlerts = `${grafanaApi}/v1/provisioning/alert-rules?orgId=`; async function main(){ if (!user) { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise(resolve => { rl.question(colors.green('Enter your user: '), resolve); }); user = `${answer}`; if (!answer) { console.error(`\n${error}You need to put a user\n`.red); process.exit(0); } rl.close(); } if (!passw) { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.stdoutMuted = true; const answer = await new Promise(resolve => { rl.question(colors.green('Enter your password: '), resolve); rl._writeToOutput = function _writeToOutput(stringToWrite) { if (rl.stdoutMuted) rl.output.write("*"); else rl.output.write(stringToWrite); }; }); passw = `${answer}`; if (!answer) { console.error(`\n${error}You need to put a password\n`.red); process.exit(0); } rl.close(); } const credentials = `Basic ` + Buffer.from(`${user}:${passw}`).toString('base64'); try { var response = await fetch(urlOrganizations, { method: "GET", headers: { Authorization: credentials } }); } catch (notfound) { console.error(`${error}Server '${config.grafanaUrl}' not found`.red); process.exit(1); }; let AllOrganizations = await response.json(); if (AllOrganizations.message === 'invalid username or password') { console.error(`\n${error}Invalid username or password\n`.red); process.exit(1); } console.clear(); console.log( `───────── Grafana-Find (${packageJson.description}) v${packageJson.version} ────────`.bold.bgYellow.bgBrightWhite ); console.log(colors.green.bold(`──────────────────── Starting process ────────────────────\n`)); for (let x in AllOrganizations) { console.log(colors.red.bold(`🏢 Organization: ${AllOrganizations[x].name} 🏢\n`.underline)); response = await fetch(`${urlDashboards}${AllOrganizations[x].id}`, { method: "GET", headers: { Authorization: credentials }, }); console.log(colors.white.bold(`🔎 Searching in dashboards...\n`)); if (response.status === 302) { response = await fetch(`${urlDashboards}${AllOrganizations[x].id}`, { method: 'GET', headers: { Accept: 'application/json' }, }); } let allUID = await response.json(); if (allUID.message === 'Unauthorized') { console.log(colors.red.bold(allUID.message)) process.exit(); } for (let i in allUID) { let url = `${urlUID}${allUID[i].uid}`; response = await fetch(url, { method: "GET", headers: { Authorization: credentials, }, redirect: 'manual' }); if (response.status === 404) { response = await fetch(url, { method: 'GET', headers: { Accept: 'application/json' }, redirect: 'manual' }); } let data = await response.json(); let isFound = false; let isFoundSomething = false; const dashboard = data.dashboard; if (dashboard) { if (dashboard.panels) for (const panel of dashboard.panels) { // Panels if (panel.targets) for (const target of panel.targets) { isFound = regexRawSQL.test(target.rawSql); if (isFound) { if (panel.title) if (panel.title==' ') titlePanels.push(`(null)`.italic); else titlePanels.push(panel.title); else titlePanels.push(`(undefined)`.italic); numberOfPanels++; isFoundSomething=true; } } // Rows if (panel.panels) for (const subpanel of panel.panels) { if (subpanel.targets) for (const target of subpanel.targets) { isFound = regexRawSQL.test(target.rawSql); if (isFound) { if (subpanel.title) if (subpanel.title==' ') titlePanels.push(`(null)`.italic); else titlePanels.push(subpanel.title); else titlePanels.push(`(undefined)`.italic); numberOfPanels++; isFoundSomething=true; } } } } if (dashboard.templating) for (const list of dashboard.templating.list) { isFound = regexRawSQL.test(list.query); if (isFound) { nameVariables.push(list.name) numberOfVariables++; isFoundSomething=true; } } } if (isFoundSomething) { const linkUrl = `${grafanaUrl}/d/${allUID[i].uid}?orgId=${AllOrganizations[x].id}`; console.log((linkUrl).yellow.underline, dashboard.title); if (numberOfPanels) { console.log(colors.cyan.bold(`[${numberOfPanels}] panels`)); for (let q in titlePanels) { if (q==(titlePanels.length-1)) { console.log(` └─${titlePanels[q]}`.cyan) break } console.log(` ├─${titlePanels[q]}`.cyan) } } if (numberOfVariables) { console.log(colors.magenta.bold(`[${numberOfVariables}] variables`)); for (let q in nameVariables) { if (q==(nameVariables.length-1)) { console.log(` └─${nameVariables[q]}`.magenta) break } console.log(` ├─${nameVariables[q]}`.magenta) } } console.log('') numberOfDashboards++; } titlePanels = []; nameVariables= []; numberOfObjects = numberOfPanels + numberOfVariables + numberOfObjects; numberOfPanels = 0; numberOfVariables = 0; } totalObjects = numberOfDashboards + totalObjects; if (!numberOfDashboards) console.log(`No results found\n`.gray); console.log(colors.white.bold(`🔎 Searching in alerts...\n`)); response = await fetch(`${urlAlerts}${AllOrganizations[x].id}`, { method: 'GET', headers: { Accept: 'application/json', Authorization: credentials }, redirect: 'manual' }); let allAlerts = await response.json(); isFound = isFoundSomething = false; if (allAlerts.title === 'Access denied') console.log(`${allAlerts.title}\n`.red); else { for (const alert of allAlerts) for (const data of alert.data) if (data?.model?.rawSql) { isFound = regexRawSQL.test(data.model.rawSql); if (isFound) { if (alert?.title) if (alert.title == ' ') titleAlerts.push(`(null)`.italic); else { const linkUrl = `${grafanaUrl}/alerting/${alert.uid}/edit?orgId=${AllOrganizations[x].id}`; console.log((linkUrl).yellow.underline, alert.title, '\n'); numberOfObjects++; totalObjects++; } else titleAlerts.push(`(undefined)`.italic); isFoundSomething = true; } } if (!isFoundSomething) console.log(`No results found\n`.gray); } }; console.log(colors.green.bold(`─────── Have been found ${numberOfObjects} similarities in ${totalObjects} objects ───────\n`)); if (!response) { console.log(`${error}The server don't exists`); process.exit(1); } process.exit(); } main();