├── .github └── FUNDING.yml ├── docs ├── tut_bat_graph.png ├── tut_bat_query.png ├── tut_bat_result.png ├── tut_bat_options.png ├── tut_violations_query.png ├── tut_violations_result.png └── tut_violations_options.png ├── src ├── assets │ ├── screenshot_main.png │ └── logo.svg ├── css │ ├── panel.dark.css │ ├── panel.light.css │ └── panel.base.css ├── plugin.json ├── partials │ ├── template.html │ └── options.html ├── value_formatter.ts ├── graph_tooltip.ts ├── panel_config.ts ├── progress_bar.ts ├── mapper.ts └── module.ts ├── dist ├── assets │ ├── screenshot_main.png │ └── logo.svg ├── css │ ├── panel.dark.css │ ├── panel.light.css │ └── panel.base.css ├── directives │ ├── progress.html │ ├── template.html │ └── multibar_progress.html ├── plugin.json ├── partials │ ├── template.html │ └── options.html ├── README.md ├── module.js.map └── module.js ├── .gitignore ├── tsconfig.json ├── jest.config.js ├── .vscode └── launch.json ├── LICENSE.md ├── package.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: corpglory 4 | -------------------------------------------------------------------------------- /docs/tut_bat_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_bat_graph.png -------------------------------------------------------------------------------- /docs/tut_bat_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_bat_query.png -------------------------------------------------------------------------------- /docs/tut_bat_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_bat_result.png -------------------------------------------------------------------------------- /docs/tut_bat_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_bat_options.png -------------------------------------------------------------------------------- /docs/tut_violations_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_violations_query.png -------------------------------------------------------------------------------- /docs/tut_violations_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_violations_result.png -------------------------------------------------------------------------------- /src/assets/screenshot_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/src/assets/screenshot_main.png -------------------------------------------------------------------------------- /dist/assets/screenshot_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/dist/assets/screenshot_main.png -------------------------------------------------------------------------------- /docs/tut_violations_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CorpGlory/grafana-progress-list/HEAD/docs/tut_violations_options.png -------------------------------------------------------------------------------- /dist/css/panel.dark.css: -------------------------------------------------------------------------------- 1 | .progress-bar .progress-bar-line-row { 2 | background-color: rgba(255,255,255,0.1); 3 | } 4 | 5 | .progress-bar-active { 6 | color: white; 7 | } 8 | -------------------------------------------------------------------------------- /src/css/panel.dark.css: -------------------------------------------------------------------------------- 1 | .progress-bar .progress-bar-line-row { 2 | background-color: rgba(255,255,255,0.1); 3 | } 4 | 5 | .progress-bar-active { 6 | color: white; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | 4 | 5 | # Editor junk 6 | *.sublime-workspace 7 | *.swp 8 | .idea/ 9 | *.iml 10 | 11 | .notouch 12 | .DS_Store 13 | .tscache 14 | -------------------------------------------------------------------------------- /dist/css/panel.light.css: -------------------------------------------------------------------------------- 1 | .progress-bar .progress-bar-line-row { 2 | background-color: rgba(0,0,0,0.1); 3 | } 4 | 5 | .progress-bar-active { 6 | color: rgba(0,0,0,0.6); 7 | font-weight: bold; 8 | } 9 | -------------------------------------------------------------------------------- /src/css/panel.light.css: -------------------------------------------------------------------------------- 1 | .progress-bar .progress-bar-line-row { 2 | background-color: rgba(0,0,0,0.1); 3 | } 4 | 5 | .progress-bar-active { 6 | color: rgba(0,0,0,0.6); 7 | font-weight: bold; 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "module": "none", 5 | "target": "es6", 6 | "rootDir": "./src", 7 | "inlineSourceMap": false, 8 | "sourceMap": true 9 | }, 10 | "include": [ 11 | "./src/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | "globals": { 4 | "ts-jest": { 5 | "tsConfigFile": "tsconfig.jest.json" 6 | } 7 | }, 8 | "transform": { 9 | "\\.ts?": "/node_modules/ts-jest/preprocessor.js" 10 | }, 11 | "testRegex": "(\\.|/)([jt]est)\\.[jt]s$", 12 | "moduleFileExtensions": [ 13 | "ts", 14 | "js", 15 | "json" 16 | ], 17 | "setupFiles": [ 18 | "/tests/setup_tests.ts" 19 | ] 20 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome against localhost", 8 | "url": "http://localhost:3000/", 9 | "sourceMaps": true, 10 | "pathMapping": { 11 | "/public/plugins/corpglory-progresslist-panel": "${workspaceFolder}/dist" 12 | }, 13 | "webRoot": "${workspaceFolder}", 14 | "sourceMapPathOverrides": { 15 | "webpack:///./*": "${webRoot}/src/*" 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /dist/directives/progress.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
14 | 18 | {{ item.formattedValue }} 19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 CorpGlory Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /dist/css/panel.base.css: -------------------------------------------------------------------------------- 1 | .progress-list-panel { 2 | height: 100%; 3 | overflow-y: auto; 4 | } 5 | 6 | .progress-bar { 7 | position: relative; 8 | height: 32px; 9 | margin-bottom: 8px; 10 | } 11 | 12 | .progress-bar .progress-bar-line-row { 13 | display: flex; 14 | justify-content: flex-start; 15 | margin-top: 0px; 16 | width: calc(100% - 8px); 17 | border-radius: 2px; 18 | } 19 | 20 | .progress-bar .bars-container { 21 | display: flex; 22 | } 23 | 24 | .progress-bar .progress-bar-value { 25 | margin-right: 14px; 26 | margin-bottom: 0px; 27 | } 28 | 29 | .progress-bar .progress-bar-line-row .progress-bar-line { 30 | z-index: 2; 31 | top: 0px; 32 | background: darkgreen; 33 | border-radius: 2px; 34 | } 35 | 36 | .progress-bar .progress-bar-title-row { 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: center; 40 | z-index: 4; 41 | width: 100%; 42 | height: 16px; 43 | } 44 | 45 | .progress-bar .progress-bar-title { 46 | margin-bottom: 0px; 47 | margin-left: 4px; 48 | } 49 | 50 | p.error { 51 | color: red; 52 | } 53 | -------------------------------------------------------------------------------- /src/css/panel.base.css: -------------------------------------------------------------------------------- 1 | .progress-list-panel { 2 | height: 100%; 3 | overflow-y: auto; 4 | } 5 | 6 | .progress-bar { 7 | position: relative; 8 | height: 32px; 9 | margin-bottom: 8px; 10 | } 11 | 12 | .progress-bar .progress-bar-line-row { 13 | display: flex; 14 | justify-content: flex-start; 15 | margin-top: 0px; 16 | width: calc(100% - 8px); 17 | border-radius: 2px; 18 | } 19 | 20 | .progress-bar .bars-container { 21 | display: flex; 22 | } 23 | 24 | .progress-bar .progress-bar-value { 25 | margin-right: 14px; 26 | margin-bottom: 0px; 27 | } 28 | 29 | .progress-bar .progress-bar-line-row .progress-bar-line { 30 | z-index: 2; 31 | top: 0px; 32 | background: darkgreen; 33 | border-radius: 2px; 34 | } 35 | 36 | .progress-bar .progress-bar-title-row { 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: center; 40 | z-index: 4; 41 | width: 100%; 42 | height: 16px; 43 | } 44 | 45 | .progress-bar .progress-bar-title { 46 | margin-bottom: 0px; 47 | margin-left: 4px; 48 | } 49 | 50 | p.error { 51 | color: red; 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-progress-list-plugin", 3 | "version": "1.0.9", 4 | "description": "Show list of progress items by mapping your data.", 5 | "main": "src/module.js", 6 | "scripts": { 7 | "build": "webpack --config build/webpack.prod.conf.js", 8 | "dev": "webpack --config build/webpack.dev.conf.js" 9 | }, 10 | "keywords": [ 11 | "grafana", 12 | "plugin", 13 | "progress list" 14 | ], 15 | "author": "CorpGlory Inc.", 16 | "license": "MIT", 17 | "repository": "https://github.com/CorpGlory/grafana-progress-list", 18 | "devDependencies": { 19 | "@types/angular": "^1.5.8", 20 | "@types/grafana": "git+https://git@github.com/CorpGlory/types-grafana.git", 21 | "@types/jquery": "^3.3.31", 22 | "@types/lodash": "^4.14.74", 23 | "babel-core": "^6.26.0", 24 | "babel-loader": "^7.1.2", 25 | "babel-preset-env": "^1.6.0", 26 | "copy-webpack-plugin": "^5.0.3", 27 | "loader-utils": "^1.1.0", 28 | "ts-loader": "^4.5.0", 29 | "typescript": "^2.9.2", 30 | "webpack": "4.7.0", 31 | "webpack-cli": "^2.1.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Progress List", 4 | "id": "corpglory-progresslist-panel", 5 | "info": { 6 | "description": "A panel showing list of progress-like items in one board", 7 | "author": { 8 | "name": "CorpGlory Inc.", 9 | "url": "https://github.com/corpglory/grafana-progress-list" 10 | }, 11 | "keywords": [ ], 12 | "logos": { 13 | "small": "assets/logo.svg", 14 | "large": "assets/logo.svg" 15 | }, 16 | "links": [{ 17 | "name": "Project site", 18 | "url": "https://github.com/CorpGlory/grafana-progress-list" 19 | },{ 20 | "name": "Tutorials", 21 | "url": "https://github.com/CorpGlory/grafana-progress-list/wiki" 22 | }, { 23 | "name": "Discussion", 24 | "url": "https://community.grafana.com/t/progress-list-panel/3286" 25 | }], 26 | "screenshots": [{ 27 | "name": "Main", 28 | "path": "assets/screenshot_main.png" 29 | }], 30 | "version": "1.0.9", 31 | "updated": "2021-02-15" 32 | }, 33 | "dependencies": { 34 | "grafanaVersion": "4.1.1+", 35 | "plugins": [ ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Progress List", 4 | "id": "corpglory-progresslist-panel", 5 | "info": { 6 | "description": "A panel showing list of progress-like items in one board", 7 | "author": { 8 | "name": "CorpGlory Inc.", 9 | "url": "https://github.com/corpglory/grafana-progress-list" 10 | }, 11 | "keywords": [ ], 12 | "logos": { 13 | "small": "assets/logo.svg", 14 | "large": "assets/logo.svg" 15 | }, 16 | "links": [{ 17 | "name": "Project site", 18 | "url": "https://github.com/CorpGlory/grafana-progress-list" 19 | },{ 20 | "name": "Tutorials", 21 | "url": "https://github.com/CorpGlory/grafana-progress-list/wiki" 22 | }, { 23 | "name": "Discussion", 24 | "url": "https://community.grafana.com/t/progress-list-panel/3286" 25 | }], 26 | "screenshots": [{ 27 | "name": "Main", 28 | "path": "assets/screenshot_main.png" 29 | }], 30 | "version": "1.0.9", 31 | "updated": "2021-02-15" 32 | }, 33 | "dependencies": { 34 | "grafanaVersion": "4.1.1+", 35 | "plugins": [ ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dist/directives/template.html: -------------------------------------------------------------------------------- 1 |
2 |
9 |
13 |
27 | 31 | {{ item.formattedValue }} 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /dist/directives/multibar_progress.html: -------------------------------------------------------------------------------- 1 |
2 |
9 |
13 |
27 | 31 | {{ item.formattedValue }} 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /dist/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /dist/partials/template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
10 |
17 |

22 |

{{ progressBar.formattedTotalValue }}

26 |
27 |
28 |
32 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/partials/template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
10 |
17 |

22 |

{{ progressBar.formattedTotalValue }}

26 |
27 |
28 |
32 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/value_formatter.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | 4 | export function getFormattedValue( 5 | value: number, 6 | prefix: string, 7 | postfix: string, 8 | decimals: number 9 | ): string { 10 | return `${prefix}${getFormattedFloat(value, decimals)}${postfix}`; 11 | } 12 | 13 | function getFormattedFloat(value: number, decimals: number): string { 14 | let dm = getDecimalsForValue(value, decimals).decimals; 15 | 16 | if(dm === 0) { 17 | return Math.round(value).toString(); 18 | } 19 | 20 | let fv = value; 21 | for(let i = 0; i < dm; i++) { 22 | fv *= 10; 23 | }; 24 | let fvs = Math.round(fv).toString(); 25 | return fvs.substr(0, fvs.length - dm) + '.' + fvs.substr(fvs.length - dm); 26 | } 27 | 28 | function getDecimalsForValue(value: number, decimals?: number) { 29 | // based on https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts 30 | if(_.isNumber(decimals)) { 31 | return { 32 | decimals, 33 | scaledDecimals: null 34 | }; 35 | } 36 | 37 | let delta = value / 2; 38 | let dec = -Math.floor(Math.log(delta) / Math.LN10); 39 | 40 | let magn = Math.pow(10, -dec), 41 | norm = delta / magn, // norm is between 1.0 and 10.0 42 | size; 43 | 44 | if(norm < 1.5) { 45 | size = 1; 46 | } else if (norm < 3) { 47 | size = 2; 48 | // special case for 2.5, requires an extra decimal 49 | if (norm > 2.25) { 50 | size = 2.5; 51 | ++dec; 52 | } 53 | } else if(norm < 7.5) { 54 | size = 5; 55 | } else { 56 | size = 10; 57 | } 58 | 59 | size *= magn; 60 | 61 | // reduce starting decimals if not needed 62 | if(Math.floor(value) === value) { 63 | dec = 0; 64 | } 65 | 66 | let result: any = {}; 67 | result.decimals = Math.max(0, dec); 68 | result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; 69 | 70 | return result; 71 | } 72 | -------------------------------------------------------------------------------- /src/graph_tooltip.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import { Bar, ProgressBar } from './progress_bar' 4 | import { TooltipMode } from './panel_config'; 5 | 6 | 7 | export type Position = { 8 | pageX: number, 9 | pageY: number 10 | }; 11 | 12 | // TODO: check if we need this 13 | export type Serie = { 14 | datapoints: [number, number][], 15 | target: string, 16 | alias?: string 17 | }; 18 | 19 | export class GraphTooltip { 20 | 21 | private $tooltip: JQuery; 22 | private _visible = false; 23 | 24 | constructor() { 25 | this.$tooltip = $('
'); 26 | } 27 | 28 | show(pos: Position, progressBars: ProgressBar[], mode: TooltipMode): void { 29 | if(mode == TooltipMode.NONE) { 30 | return; 31 | } 32 | this._visible = true; 33 | // TODO: use more vue/react approach here 34 | // TODO: maybe wrap this rendering logic into classes 35 | if(mode == TooltipMode.SINGLE) { 36 | let activeItem = _.find(progressBars, item => item.active); 37 | var html = `
Current value
`; 38 | if(activeItem === undefined) { 39 | throw new Error( 40 | 'Can`t find any active item to show current value in tooltip' 41 | ); 42 | } 43 | html += progressBar2Html(activeItem); 44 | } else if (mode == TooltipMode.ALL_SERIES) { 45 | // TODO: build this string faster 46 | var html = progressBars.map(progressBar2Html).join(''); 47 | } else { 48 | throw new Error('unknown tooltip type'); 49 | } 50 | 51 | // TODO: move this "20" to a constant 52 | // TODO: check how this work when `pos` is close to the page bottom edge 53 | (this.$tooltip.html(html) as any).place_tt(pos.pageX + 20, pos.pageY).show(); 54 | } 55 | 56 | hide(): void { 57 | this._visible = false; 58 | this.$tooltip.hide(); 59 | } 60 | 61 | get visible(): boolean { return this._visible; } 62 | 63 | } 64 | 65 | /** VIEW **/ 66 | 67 | function progressBar2Html(progressBar: ProgressBar): string { 68 | return ` 69 |
70 |
71 | ${progressBar.active ? '' : ''} 72 | ${progressBar.title} 73 | ${progressBar.active ? '' : ''} 74 |
75 | ${progressBarBars2Html(progressBar.bars)} 76 |
77 | `; 78 | } 79 | 80 | function progressBarBars2Html(bars: Bar[]): string { 81 | return bars.map(bar => ` 82 |
83 | ${bar.value} 84 |
85 | `).join(''); 86 | } 87 | -------------------------------------------------------------------------------- /src/panel_config.ts: -------------------------------------------------------------------------------- 1 | export enum StatType { 2 | CURRENT = 'current', 3 | MIN = 'min', 4 | MAX = 'max', 5 | TOTAL = 'total' 6 | }; 7 | 8 | export enum TitleViewOptions { 9 | SEPARATE_TITLE_LINE = 'Separate title line', 10 | INLINE = 'Inline' 11 | }; 12 | 13 | export enum ColoringType { 14 | PALLETE = 'pallete', 15 | THRESHOLDS = 'thresholds', 16 | KEY_MAPPING = 'key mapping' 17 | } 18 | 19 | export enum ValueLabelType { 20 | PERCENTAGE = 'percentage', 21 | ABSOLUTE = 'absolute' 22 | } 23 | 24 | export enum TooltipMode { 25 | NONE = 'none', 26 | SINGLE = 'single', 27 | ALL_SERIES = 'all series' 28 | }; 29 | 30 | export const DEFAULT_FONT_SIZE = 14; 31 | 32 | export const DEFAULTS = { 33 | keyColumn: '', 34 | skipColumns: [], 35 | statNameOptionValue: StatType.CURRENT, 36 | statProgressMaxValue: null, 37 | coloringType: ColoringType.PALLETE, 38 | titleViewType: TitleViewOptions.SEPARATE_TITLE_LINE, 39 | sortingOrder: 'none', 40 | valueLabelType: ValueLabelType.ABSOLUTE, 41 | alias: '', 42 | prefix: '', 43 | postfix: '', 44 | valueSize: DEFAULT_FONT_SIZE, 45 | titleSize: DEFAULT_FONT_SIZE, 46 | thresholds: '10, 30', 47 | // https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts#L57 48 | colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'], 49 | colorsKeyMappingDefault: 'rgba(245, 54, 54, 0.9)', 50 | colorKeyMappings: [], 51 | nullMapping: undefined, 52 | tooltipMode: TooltipMode.ALL_SERIES, 53 | opacity: 0.5, 54 | limit: 50 55 | }; 56 | 57 | 58 | export class PanelConfig { 59 | private _panel: any; 60 | public constructor(panel: any) { 61 | this._panel = panel; 62 | 63 | // migrations 64 | if(this.getValue('coloringType') === 'auto') { 65 | this.setValue('coloringType', ColoringType.PALLETE); 66 | } 67 | 68 | const skipColumn = this.getValue('skipColumn'); 69 | if(skipColumn !== undefined && skipColumn !== '') { 70 | this.setValue('skipColumn', undefined); 71 | this.setValue('skipColumns', [skipColumn]); 72 | } 73 | } 74 | 75 | public getValue(key: string): any { 76 | return this._panel[key]; 77 | } 78 | 79 | public setValue(key: string, value: any): void { 80 | this._panel[key] = value; 81 | } 82 | 83 | private _pluginDirName: string; 84 | public get pluginDirName(): string { 85 | if(!this._pluginDirName) { 86 | var panels = window['grafanaBootData'].settings.panels; 87 | var thisPanel = panels[this._panel.type]; 88 | // the system loader preprends publib to the url, 89 | // add a .. to go back one level 90 | this._pluginDirName = thisPanel.baseUrl + '/'; 91 | } 92 | return this._pluginDirName; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # Progress List Panel 2 | 3 | 4 | 5 | ## About 6 | 7 | A panel showing list of progress-like items in one board. More about [development of the plugin](https://corpglory.com/s/grafana-progress-list/). 8 | 9 | ## How To Use 10 | 11 | 1. Create a metric query where result looks like this: `[(time, key, value)]` 12 | 2. Goto "Options" tab and choose aggregation function and other options 13 | 14 | More info in [**tutorials**](https://github.com/CorpGlory/grafana-progress-list/wiki) 15 | 16 | Progress list will try to aggregate all values by key using chosen aggregate function. 17 | 18 | Progress list can be thought of as many simple [Singlestat Panels](http://docs.grafana.org/features/panels/singlestat/). But items are generated from the query, rather than defined manually. 19 | 20 | 21 | ## Installation 22 | 23 | ### Linux 24 | - Navigate to either: 25 | - `/data/plugins` (when Grafana is installed from tarball or source) 26 | - or `/var/lib/grafana/plugins` (when Grafana is installed from `.deb`/`.rpm` package) 27 | 28 | - Download Progress List Panel 29 | ``` 30 | wget https://github.com/CorpGlory/grafana-progress-list/archive/v1.0.9.tar.gz 31 | ``` 32 | 33 | - Unpack downloaded files 34 | ``` 35 | tar -zxvf v1.0.9.tar.gz 36 | ``` 37 | 38 | - Restart grafana-server 39 | - For Grafana installed via Standalone Linux Binaries: 40 | - Stop any running instances of grafana-server 41 | - Start grafana-server by: 42 | ```$GRAFANA_PATH/bin/grafana-server``` 43 | - For grafana installed via Package Manager: 44 | - type in ```systemctl restart grafana-server``` 45 | 46 | ### Grafana in Docker 47 | You can install Progress List Panel to Grafana in Docker passing it as environment variable (as described in [Grafana docs](http://docs.grafana.org/installation/docker/#installing-plugins-from-other-sources)) 48 | 49 | ```bash 50 | docker run \ 51 | -p 3000:3000 \ 52 | -e "GF_INSTALL_PLUGINS=https://github.com/CorpGlory/grafana-progress-list/archive/v1.0.9.zip;corpglory-progresslist-panel" \ 53 | grafana/grafana 54 | ``` 55 | 56 | ## See Also 57 | * Dicussion on [Grafana Community Forum](https://community.grafana.com/t/progress-list-panel/3286) 58 | * Based on [Webpack Typescript Template](https://github.com/CorpGlory/grafana-plugin-template-webpack-typescript) 59 | * more about Grafana from CorpGlory: https://corpglory.com/t/grafana/ 60 | * https://github.com/chartwerk/grafana-chartwerk-app -- advanced Grafana plugins from CorpGlory 61 | 62 | ## About CorpGlory Inc. 63 | The project developed by [CorpGlory Inc.](https://corpglory.com/), a company which provides high quality software development, data visualization, Grafana and monitoring consulting. 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Progress List Panel 2 | 3 | Screenshot 2021-02-16 at 18 24 36 4 | 5 | ## About 6 | 7 | A panel showing list of progress-like items in one board. 8 | 9 | ## How To Use 10 | 11 | 1. Create a metric query with Table data format 12 | 2. Go to "Options" tab and select which column to use as bars title and which columns to skip 13 | 3. Customize Progress List appearance by using other options 14 | 15 | For more information, visit the [**wiki**](https://github.com/CorpGlory/grafana-progress-list/wiki) 16 | 17 | Progress list can be thought of as many simple [Singlestat Panels](http://docs.grafana.org/features/panels/singlestat/). But items are generated from the query, rather than defined manually. 18 | 19 | ## Demo 20 | 21 | [Explore demo](https://grafana.corpglory.com/d/2v8-HypGk/progress-list-01-basic?orgId=4) 22 | 23 | ## Display options 24 | * Columns type selection (Key column, Skip Column) 25 | * Tooltip selection (all series/single) 26 | * Value labeling (Prefix, Postfix, Size, Decimals, Null Value) 27 | * Selection by type (absolute/percentage) 28 | * Sorting 29 | * Limitation 30 | * Key labels (Line type (line/inline), size, Alias) 31 | * Coloring (Opacity, pallet, thresholds, key determination) 32 | 33 | About [development of the plugin](https://corpglory.com/s/grafana-progress-list/). 34 | 35 | 36 | ## Installation 37 | 38 | ### Linux 39 | - Navigate to either: 40 | - `/data/plugins` (when Grafana is installed from tarball or source) 41 | - or `/var/lib/grafana/plugins` (when Grafana is installed from `.deb`/`.rpm` package) 42 | 43 | - Download Progress List Panel 44 | ``` 45 | wget https://github.com/CorpGlory/grafana-progress-list/archive/v1.0.9.tar.gz 46 | ``` 47 | 48 | - Unpack downloaded files 49 | ``` 50 | tar -zxvf v1.0.9.tar.gz 51 | ``` 52 | 53 | - Restart grafana-server 54 | - For Grafana installed via Standalone Linux Binaries: 55 | - Stop any running instances of grafana-server 56 | - Start grafana-server by: 57 | ```$GRAFANA_PATH/bin/grafana-server``` 58 | - For grafana installed via Package Manager: 59 | - type in ```systemctl restart grafana-server``` 60 | 61 | ### Grafana in Docker 62 | You can install Progress List Panel to Grafana in Docker passing it as environment variable (as described in [Grafana docs](http://docs.grafana.org/installation/docker/#installing-plugins-from-other-sources)) 63 | 64 | ```bash 65 | docker run \ 66 | -p 3000:3000 \ 67 | -e "GF_INSTALL_PLUGINS=https://github.com/CorpGlory/grafana-progress-list/archive/v1.0.9.zip;corpglory-progresslist-panel" \ 68 | grafana/grafana 69 | ``` 70 | 71 | ## See Also 72 | * Dicussion on [Grafana Community Forum](https://community.grafana.com/t/progress-list-panel/3286) 73 | * Based on [Webpack Typescript Template](https://github.com/CorpGlory/grafana-plugin-template-webpack-typescript) 74 | * more about Grafana from CorpGlory: https://corpglory.com/t/grafana/ 75 | * https://github.com/chartwerk/grafana-chartwerk-app -- advanced Grafana plugins from CorpGlory 76 | 77 | ## About CorpGlory Inc. 78 | The plugin is developed by [CorpGlory Inc.](https://corpglory.com/), a company which provides software development, data visualization, Grafana and monitoring consulting services. 79 | -------------------------------------------------------------------------------- /src/progress_bar.ts: -------------------------------------------------------------------------------- 1 | import { ColoringType, PanelConfig, TitleViewOptions, ValueLabelType, DEFAULT_FONT_SIZE } from './panel_config'; 2 | import { getFormattedValue } from './value_formatter'; 3 | 4 | import * as _ from 'lodash'; 5 | 6 | 7 | type ProgressTitle = { 8 | barHeight: number, 9 | titleHeight: number, 10 | position: Position 11 | }; 12 | 13 | enum Position { 14 | STATIC = 'static', 15 | ABSOLUTE = 'absolute' 16 | } 17 | 18 | /** 19 | * It's model for rendering bars in view (partial) and tooltip 20 | */ 21 | export type Bar = { 22 | name: string, 23 | value: number, 24 | color: string 25 | } 26 | 27 | /** 28 | * Model for the main component of the app -- bars, but it's not just a Bar, 29 | * it also keeps all small "bars", title and metainfo 30 | */ 31 | export class ProgressBar { 32 | 33 | private _bars: Bar[]; 34 | private _active: boolean; 35 | 36 | constructor( 37 | private _panelConfig: PanelConfig, 38 | private _title: string, 39 | private _keys: string[], // maybe "_names" is better than "_keys" 40 | private _values: number[], 41 | private _maxTotalValue: number 42 | ) { 43 | if(this._keys.length !== this._values.length) { 44 | throw new Error('keys amount should be equal to values amount'); 45 | } 46 | this._bars = []; 47 | for(let i = 0; i < _keys.length; ++i) { 48 | this._bars.push({ 49 | name: this._keys[i], 50 | value: this._values[i], 51 | color: mapValue2Color(this._values[i], this._keys[i], i, this._panelConfig) 52 | }); 53 | } 54 | 55 | // bad code starts: 56 | 57 | } 58 | 59 | get active(): boolean { return this._active; } 60 | set active(value: boolean) { this._active = value;} 61 | 62 | get title(): string { return this._title; } 63 | 64 | get keys(): string[] { return this._keys; } 65 | 66 | get values(): number[] { return this._values; } 67 | 68 | get bars(): Bar[] { return this._bars; } 69 | 70 | get sumOfValues(): number { return _.sum(this.values); } 71 | 72 | get percentValues(): number[] { 73 | return this.values.map( 74 | value => value / this.sumOfValues * 100 75 | ); 76 | } 77 | 78 | get aggregatedProgress(): number { 79 | return (this.sumOfValues / this._maxTotalValue) * 100; 80 | } 81 | 82 | get totalValue(): number { 83 | const valueLabelType = this._panelConfig.getValue('valueLabelType'); 84 | switch(valueLabelType) { 85 | case ValueLabelType.ABSOLUTE: 86 | return this.sumOfValues; 87 | case ValueLabelType.PERCENTAGE: 88 | return this.aggregatedProgress; 89 | default: 90 | throw new Error(`Unknown value label type: ${valueLabelType}`); 91 | } 92 | } 93 | 94 | get formattedTotalValue(): string { 95 | return getFormattedValue( 96 | this.totalValue, 97 | this._panelConfig.getValue('prefix'), 98 | this._panelConfig.getValue('postfix'), 99 | this._panelConfig.getValue('decimals') 100 | ); 101 | } 102 | 103 | get valueFontSize(): number { 104 | const configSize = this._panelConfig.getValue('valueSize'); 105 | if(configSize === undefined || configSize === null) { 106 | return DEFAULT_FONT_SIZE; 107 | } 108 | return configSize; 109 | } 110 | 111 | get titleFontSize(): number { 112 | const configSize = this._panelConfig.getValue('titleSize'); 113 | if(configSize === undefined || configSize === null) { 114 | return DEFAULT_FONT_SIZE; 115 | } 116 | return configSize; 117 | } 118 | 119 | get colors(): string[] { 120 | return _.map(this._bars, bar => bar.color); 121 | } 122 | 123 | // it should go somewhere to view 124 | get titleParams(): ProgressTitle { 125 | const titleType = this._panelConfig.getValue('titleViewType'); 126 | switch(titleType) { 127 | case TitleViewOptions.SEPARATE_TITLE_LINE: 128 | return { 129 | barHeight: 8, 130 | titleHeight: 16, 131 | position: Position.STATIC 132 | }; 133 | case TitleViewOptions.INLINE: 134 | return { 135 | barHeight: 24, 136 | titleHeight: 24, 137 | position: Position.ABSOLUTE 138 | }; 139 | default: 140 | throw new Error(`Wrong titleType: ${titleType}`); 141 | } 142 | } 143 | 144 | get opacity(): string { 145 | return this._panelConfig.getValue('opacity'); 146 | } 147 | 148 | } 149 | 150 | /** VIEW **/ 151 | 152 | function mapValue2Color(value: number, key: string, index: number, _panelConfig: any): string { 153 | const colorType: ColoringType = _panelConfig.getValue('coloringType'); 154 | const colors: string[] = _panelConfig.getValue('colors'); 155 | 156 | switch(colorType) { 157 | case ColoringType.PALLETE: 158 | return colors[index % colors.length]; 159 | case ColoringType.THRESHOLDS: 160 | // TODO: parse only once 161 | const thresholds = _panelConfig.getValue('thresholds').split(',').map(parseFloat); 162 | if(colors.length <= thresholds.length) { 163 | // we add one because a threshold is a cut of the range of values 164 | throw new Error('Number of colors must be at least as number as threasholds + 1'); 165 | } 166 | for(let i = thresholds.length; i > 0; i--) { 167 | if(value >= thresholds[i - 1]) { 168 | return colors[i]; 169 | } 170 | } 171 | return colors[0]; 172 | case ColoringType.KEY_MAPPING: 173 | const colorKeyMappings = _panelConfig.getValue('colorKeyMappings') as any[]; 174 | const keyColorMapping = _.find(colorKeyMappings, k => k.key === key); 175 | if(keyColorMapping === undefined) { 176 | return _panelConfig.getValue('colorsKeyMappingDefault'); 177 | } 178 | return keyColorMapping.color; 179 | default: 180 | throw new Error('Unknown color type ' + colorType); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/mapper.ts: -------------------------------------------------------------------------------- 1 | import { ProgressBar } from './progress_bar'; 2 | import { PanelConfig, StatType } from './panel_config'; 3 | 4 | 5 | import * as _ from 'lodash'; 6 | 7 | 8 | type KeyValue = [string, number]; 9 | 10 | 11 | export class Mapper { 12 | 13 | private _panelConfig: PanelConfig; 14 | private _templateSrv: any; 15 | 16 | constructor(panelConfig: PanelConfig, templateSrv: any) { 17 | this._panelConfig = panelConfig; 18 | this._templateSrv = templateSrv; 19 | } 20 | 21 | mapMetricData(seriesList: any): ProgressBar[] { 22 | const alias = this._panelConfig.getValue('alias'); 23 | const nullMapping = this._panelConfig.getValue('nullMapping'); 24 | 25 | if(seriesList === undefined || seriesList.length == 0) { 26 | return []; 27 | } 28 | 29 | if(seriesList[0].columns === undefined) { 30 | throw new Error('"Time Series" queries are not supported, please make sure to select "Table" and query at least 2 metrics'); 31 | } 32 | 33 | let keys = seriesList[0].columns.map(col => col.text); 34 | 35 | const keyColumn = this._panelConfig.getValue('keyColumn'); 36 | let keyIndex = 0; 37 | if(keyColumn !== '') { 38 | keyIndex = keys.findIndex(key => key === keyColumn); 39 | } 40 | 41 | let skipIndexes: number[] = [keyIndex]; 42 | const skipColumns = this._panelConfig.getValue('skipColumns'); 43 | skipColumns.forEach(column => { 44 | const index = keys.findIndex(key => key === column); 45 | if(index >= 0) { 46 | skipIndexes.push(index); 47 | } 48 | }); 49 | 50 | const rowsMaxes = seriesList[0].rows.map(row => { 51 | const values = this._rowToValues(row, skipIndexes, nullMapping); 52 | return _.sum(values); 53 | }); 54 | const totalMaxValue = _.max(rowsMaxes); 55 | 56 | const filteredKeys = keys.filter((key, idx) => !_.includes(skipIndexes, idx)); 57 | // TODO: it's wrong, we return a bad type here 58 | return seriesList[0].rows.map( 59 | row => { 60 | let title = row[keyIndex]; 61 | if(alias !== '') { 62 | const scopedVars = { 63 | __key: { value: title } 64 | }; 65 | title = this._templateSrv.replace(alias, scopedVars); 66 | } 67 | const values = this._rowToValues(row, skipIndexes, nullMapping); 68 | 69 | return new ProgressBar( 70 | this._panelConfig, 71 | title, 72 | filteredKeys, 73 | values, 74 | totalMaxValue as number 75 | ) 76 | } 77 | ); 78 | 79 | } 80 | 81 | private _rowToValues(row: number[], skipIndexes: number[], nullMapping: number | undefined): number[] { 82 | const values = row.filter((value, idx) => !_.includes(skipIndexes, idx)); 83 | return this._mapNullValues(values, nullMapping); 84 | } 85 | 86 | private _mapNullValues(values: (number | null)[], nullMapping: number | undefined): number[] { 87 | return values.map(value => { 88 | if(value !== null) { 89 | return value; 90 | } 91 | if(nullMapping === undefined || nullMapping === null) { 92 | throw new Error('Got null value. Set null value mapping in Options -> Value Labels -> Null Value'); 93 | } 94 | return nullMapping; 95 | }); 96 | } 97 | 98 | _mapKeysTotal(seriesList): KeyValue[] { 99 | if(seriesList.length !== 1) { 100 | throw new Error('Expecting list of keys: got more than one timeseries'); 101 | } 102 | var kv = {}; 103 | var datapointsLength = seriesList[0].datapoints.length; 104 | for(let i = 0; i < datapointsLength; i++) { 105 | let k = seriesList[0].datapoints[i][0].toString(); 106 | if(kv[k] === undefined) { 107 | kv[k] = 0; 108 | } 109 | kv[k]++; 110 | } 111 | 112 | var res: KeyValue[] = []; 113 | for(let k in kv) { 114 | res.push([k, kv[k]]); 115 | } 116 | 117 | return res; 118 | 119 | } 120 | 121 | _mapNumeric(seriesList, statType: StatType, nullMapping): KeyValue[] { 122 | if(seriesList.length != 2) { 123 | throw new Error('Expecting timeseries in format (key, value). You can use keys only in total mode'); 124 | } 125 | if(seriesList[0].datapoints.length !== seriesList[1].datapoints.length) { 126 | throw new Error('Timeseries has different length'); 127 | } 128 | 129 | var kv = {}; 130 | var datapointsLength = seriesList[0].datapoints.length; 131 | 132 | for(let i = 0; i < datapointsLength; i++) { 133 | let k = seriesList[0].datapoints[i][0].toString(); 134 | let v = seriesList[1].datapoints[i][0]; 135 | let vn = parseFloat(v); 136 | if(v === null) { 137 | if(nullMapping === undefined || nullMapping === null) { 138 | throw new Error('Got null value. You set null value mapping in Options -> Mapping -> Null'); 139 | } 140 | console.log('nullMapping ->' + nullMapping); 141 | vn = nullMapping; 142 | } 143 | if(isNaN(vn)) { 144 | throw new Error('Got non-numberic value: ' + v); 145 | } 146 | if(kv[k] === undefined) { 147 | kv[k] = []; 148 | } 149 | kv[k].push(vn); 150 | } 151 | 152 | var res: KeyValue[] = []; 153 | for(let k in kv) { 154 | res.push([k, this._flatSeries(kv[k], statType)]); 155 | } 156 | 157 | return res; 158 | } 159 | 160 | _mapTargetToDatapoints(seriesList, statType: StatType): KeyValue[] { 161 | return seriesList.map(serie => [ 162 | serie.target, 163 | this._flatSeries(serie.datapoints.map(datapoint => datapoint[0]), statType) 164 | ]); 165 | } 166 | 167 | _flatSeries(values: number[], statType: StatType): number { 168 | if(values.length === 0) { 169 | return 0; 170 | } 171 | 172 | if(statType === StatType.TOTAL) { 173 | return _.sum(values); 174 | } 175 | 176 | if(statType === StatType.MAX) { 177 | return _.max(values) as number; 178 | } 179 | 180 | if(statType === StatType.MIN) { 181 | return _.min(values) as number; 182 | } 183 | 184 | if(statType === StatType.CURRENT) { 185 | return _.last(values) as number; 186 | } 187 | 188 | return 0; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { GraphTooltip } from './graph_tooltip'; 2 | import * as PanelConfig from './panel_config'; 3 | import { Mapper } from './mapper'; 4 | import { ProgressBar } from './progress_bar'; 5 | 6 | import { MetricsPanelCtrl, loadPluginCss } from 'grafana/app/plugins/sdk'; 7 | 8 | import * as _ from 'lodash'; 9 | 10 | export type HoverEvent = { 11 | index: number, 12 | event: any 13 | } 14 | 15 | const ERROR_MAPPING = ` 16 | Can't map the received metrics, see 17 | 18 | wiki 19 | 20 | `; 21 | const ERROR_NO_DATA = "no data"; 22 | 23 | class Ctrl extends MetricsPanelCtrl { 24 | static templateUrl = 'partials/template.html'; 25 | 26 | public mapper: Mapper; 27 | 28 | // TODO: rename progressBars 29 | public progressBars: ProgressBar[]; 30 | 31 | private _panelConfig: PanelConfig.PanelConfig; 32 | 33 | private _seriesList: any; 34 | 35 | private _tooltip: GraphTooltip; 36 | 37 | private statNameOptions = _.values(PanelConfig.StatType); 38 | // TODO: review these ooptions and make types in PanelConfig 39 | private statProgressTypeOptions = [ 'max value', 'shared' ]; 40 | 41 | private coloringTypeOptions = _.values(PanelConfig.ColoringType); 42 | 43 | private titleViewTypeOptions = _.values(PanelConfig.TitleViewOptions); 44 | private sortingOrderOptions = [ 'none', 'increasing', 'decreasing' ]; 45 | private valueLabelTypeOptions = _.values(PanelConfig.ValueLabelType); 46 | private tooltipModeOptions = _.values(PanelConfig.TooltipMode); 47 | 48 | // field for updating tooltip on rendering and storing previous state 49 | private _lastHoverEvent?: HoverEvent; 50 | 51 | // used to show status messages replacing rendered graphics 52 | // see isPanelAlert and panelAlertMessage 53 | private _panelAlert = { 54 | active: true, 55 | // message prop can be formatted with html, 56 | message: 'loading...' // loading will be showed only once at the beginning 57 | 58 | // would be nice to add `type` property with values ['info', 'warning', 'error'] 59 | // and then move it https://github.com/chartwerk/grafana-panel-base/issues/1 60 | }; 61 | 62 | constructor($scope: any, $injector: any, public templateSrv: any) { 63 | super($scope, $injector); 64 | 65 | _.defaults(this.panel, PanelConfig.DEFAULTS); 66 | 67 | this._panelConfig = new PanelConfig.PanelConfig(this.panel); 68 | this._initStyles(); 69 | 70 | this.mapper = new Mapper(this._panelConfig, this.templateSrv); 71 | this.progressBars = []; 72 | this._tooltip = new GraphTooltip(); 73 | 74 | this.events.on('init-edit-mode', this._onInitEditMode.bind(this)); 75 | this.events.on('data-received', this._onDataReceived.bind(this)); 76 | this.events.on('render', this._onRender.bind(this)); 77 | } 78 | 79 | _initStyles() { 80 | // small hack to load base styles 81 | const cssPath = this._panelConfig.pluginDirName.replace('public/', ''); 82 | loadPluginCss({ 83 | light: cssPath + 'css/panel.base.css', 84 | dark: cssPath + 'css/panel.base.css' 85 | }); 86 | loadPluginCss({ 87 | light: cssPath + 'css/panel.light.css', 88 | dark: cssPath + 'css/panel.dark.css' 89 | }); 90 | } 91 | 92 | _onRender() { 93 | // maybe we want to make a config "last value" instead of ERROR_NO_DATA 94 | // see https://github.com/chartwerk/grafana-panel-base/issues/3 95 | if(this._seriesList === undefined || this._seriesList.length === 0) { 96 | this._panelAlert.active = true; 97 | this._panelAlert.message = ERROR_NO_DATA; 98 | return; 99 | } 100 | try { 101 | // TODO: set this.items also 102 | this.progressBars = this.mapper.mapMetricData(this._seriesList); 103 | } catch(e) { 104 | this._panelAlert.active = true; 105 | this._panelAlert.message = `${ERROR_MAPPING}

${e}

`; 106 | return; 107 | } 108 | if(this._panelConfig.getValue('sortingOrder') === 'increasing') { 109 | this.progressBars = _.sortBy(this.progressBars, i => i.aggregatedProgress); 110 | } 111 | if(this._panelConfig.getValue('sortingOrder') === 'decreasing') { 112 | this.progressBars = _.sortBy(this.progressBars, i => -i.aggregatedProgress); 113 | } 114 | 115 | this.progressBars = _.take(this.progressBars, this._panelConfig.getValue('limit')); 116 | 117 | if(this._tooltip.visible) { 118 | if(this._lastHoverEvent === undefined) { 119 | throw new Error( 120 | 'Need to show tooltip because it`s visible, but don`t have previous state' 121 | ); 122 | } 123 | this.onHover(this._lastHoverEvent); 124 | } 125 | this._panelAlert.active = false; 126 | } 127 | 128 | onValueLabelTypeChange(): void { 129 | this.updatePostfix(); 130 | this._onRender(); 131 | } 132 | 133 | onAddSkipColumnClick(): void { 134 | this.panel.skipColumns.push(''); 135 | } 136 | 137 | onRemoveSkipColumnClick(index: number): void { 138 | this.panel.skipColumns.splice(index, 1); 139 | this.render(); 140 | } 141 | 142 | updatePostfix(): void { 143 | const valueLabelType = this._panelConfig.getValue('valueLabelType'); 144 | const postfixValue = this.panel.postfix; 145 | switch(valueLabelType) { 146 | case PanelConfig.ValueLabelType.ABSOLUTE: 147 | if(postfixValue === '%') { 148 | this.panel.postfix = ''; 149 | } 150 | break; 151 | case PanelConfig.ValueLabelType.PERCENTAGE: 152 | if(postfixValue === '') { 153 | this.panel.postfix = '%'; 154 | } 155 | break; 156 | default: 157 | throw new Error(`Unknown value label type: ${valueLabelType}`); 158 | } 159 | } 160 | 161 | onHover(event: HoverEvent) { 162 | this._clearActiveProgressBar(); 163 | this._lastHoverEvent = event; // TODO: use it to unset active previous progressbar 164 | this.progressBars[event.index].active = true; 165 | this._tooltip.show(event.event, this.progressBars, this.panel.tooltipMode); 166 | } 167 | 168 | private _clearActiveProgressBar() { 169 | if( 170 | this._lastHoverEvent !== undefined && 171 | this._lastHoverEvent.index < this.progressBars.length 172 | ) { 173 | this.progressBars[this._lastHoverEvent.index].active = false; 174 | } 175 | } 176 | 177 | onMouseLeave() { 178 | this._clearActiveProgressBar(); 179 | this._tooltip.hide(); 180 | } 181 | 182 | _onDataReceived(seriesList: any) { 183 | this._seriesList = seriesList; 184 | this.render(); 185 | } 186 | 187 | _onInitEditMode() { 188 | var thisPartialPath = this._panelConfig.pluginDirName + 'partials/'; 189 | this.addEditorTab('Options', thisPartialPath + 'options.html', 2); 190 | } 191 | 192 | invertColorOrder() { 193 | var tmp = this.panel.colors[0]; 194 | this.panel.colors[0] = this.panel.colors[2]; 195 | this.panel.colors[2] = tmp; 196 | this.render(); 197 | } 198 | 199 | addColorKeyMapping() { 200 | this.panel.colorKeyMappings.push({ 201 | key: 'KEY_NAME', 202 | color: 'rgba(50, 172, 45, 0.97)' 203 | }); 204 | } 205 | 206 | removeColorKeyMapping(index) { 207 | this.panel.colorKeyMappings.splice(index, 1); 208 | this.render(); 209 | } 210 | 211 | _dataError(err) { 212 | console.log('got data error'); 213 | console.log(err); 214 | // TODO: reveiew this logic 215 | this.$scope.data = []; 216 | this.$scope.dataError = err; 217 | } 218 | 219 | get columns(): string[] { 220 | if( 221 | this._seriesList === undefined || 222 | this._seriesList.length === 0 || 223 | this._seriesList[0].columns === undefined 224 | ) { 225 | return []; 226 | } 227 | return this._seriesList[0].columns.map(col => col.text); 228 | } 229 | 230 | get isPanelAlert(): boolean { 231 | return this._panelAlert.active; 232 | } 233 | 234 | // the field will be rendered as html 235 | get panelAlertMessage(): string { 236 | return this._panelAlert.message; 237 | } 238 | 239 | } 240 | 241 | export { Ctrl as PanelCtrl } 242 | -------------------------------------------------------------------------------- /src/partials/options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 11 |
12 |
13 | 14 |
15 | 16 | 19 |
20 | 21 |
22 | 23 |
24 | 30 |
31 | 34 |
35 | 36 |
37 |
Tooltip
38 |
39 | 40 |
41 | 47 |
48 |
49 | 50 |
51 |
Appearance
52 |
53 | 54 |
55 | 61 |
62 |
63 |
64 | 68 | 76 |
77 | 78 |
79 |
Value Labels
80 |
81 | 82 |
83 | 89 |
90 |
91 | 92 |
93 | 94 | 100 |
101 | 102 |
103 | 104 | 110 |
111 | 112 |
113 | 117 | 124 |
125 | 126 |
127 | 131 | 139 |
140 | 141 |
142 | 143 | 151 |
152 | 153 |
154 |
Key Labels
155 |
156 | 157 |
158 | 164 |
165 |
166 |
167 | 171 | 178 |
179 |
180 |
181 | 187 | 188 | 194 |
195 |
196 | 197 |
198 |
Coloring
199 |
200 | 201 | 210 |
211 | 212 |
213 | 214 |
215 | 221 |
222 |
223 | 224 |
225 |
226 | 231 | 238 |
239 |
240 | 241 |
242 |
243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | Invert 255 | 256 |
257 |
258 | 259 |
260 |
261 |
262 | 267 | 268 | 272 | 273 | 278 |
279 |
280 | 281 |
282 | 286 | 287 | 291 | 292 |
293 |
294 |
295 | 299 |
300 |
301 |
302 |
303 | -------------------------------------------------------------------------------- /dist/partials/options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 11 |
12 |
13 | 14 |
15 | 16 | 19 |
20 | 21 |
22 | 23 |
24 | 30 |
31 | 34 |
35 | 36 |
37 |
Tooltip
38 |
39 | 40 |
41 | 47 |
48 |
49 | 50 |
51 |
Appearance
52 |
53 | 54 |
55 | 61 |
62 |
63 |
64 | 68 | 76 |
77 | 78 |
79 |
Value Labels
80 |
81 | 82 |
83 | 89 |
90 |
91 | 92 |
93 | 94 | 100 |
101 | 102 |
103 | 104 | 110 |
111 | 112 |
113 | 117 | 124 |
125 | 126 |
127 | 131 | 139 |
140 | 141 |
142 | 143 | 151 |
152 | 153 |
154 |
Key Labels
155 |
156 | 157 |
158 | 164 |
165 |
166 |
167 | 171 | 178 |
179 |
180 |
181 | 187 | 188 | 194 |
195 |
196 | 197 |
198 |
Coloring
199 |
200 | 201 | 210 |
211 | 212 |
213 | 214 |
215 | 221 |
222 |
223 | 224 |
225 |
226 | 231 | 238 |
239 |
240 | 241 |
242 |
243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | Invert 255 | 256 |
257 |
258 | 259 |
260 |
261 |
262 | 267 | 268 | 272 | 273 | 278 |
279 |
280 | 281 |
282 | 286 | 287 | 291 | 292 |
293 |
294 |
295 | 299 |
300 |
301 |
302 |
303 | -------------------------------------------------------------------------------- /dist/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./graph_tooltip.ts","webpack:///./mapper.ts","webpack:///./module.ts","webpack:///./panel_config.ts","webpack:///./progress_bar.ts","webpack:///./value_formatter.ts","webpack:///external \"app/plugins/sdk\"","webpack:///external \"lodash\""],"names":[],"mappings":"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","file":"module.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading wasm modules\n \tvar installedWasmModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// object with all compiled WebAssembly.Modules\n \t__webpack_require__.w = {};\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./module.ts\");\n","import * as _ from 'lodash';\n\nimport { Bar, ProgressBar } from './progress_bar'\nimport { TooltipMode } from './panel_config';\n\n\nexport type Position = {\n pageX: number,\n pageY: number\n};\n\n// TODO: check if we need this\nexport type Serie = {\n datapoints: [number, number][],\n target: string,\n alias?: string\n};\n\nexport class GraphTooltip {\n\n private $tooltip: JQuery;\n private _visible = false;\n\n constructor() {\n this.$tooltip = $('
');\n }\n\n show(pos: Position, progressBars: ProgressBar[], mode: TooltipMode): void {\n if(mode == TooltipMode.NONE) {\n return;\n }\n this._visible = true;\n // TODO: use more vue/react approach here\n // TODO: maybe wrap this rendering logic into classes\n if(mode == TooltipMode.SINGLE) {\n let activeItem = _.find(progressBars, item => item.active);\n var html = `
Current value
`;\n if(activeItem === undefined) {\n throw new Error(\n 'Can`t find any active item to show current value in tooltip'\n );\n }\n html += progressBar2Html(activeItem);\n } else if (mode == TooltipMode.ALL_SERIES) {\n // TODO: build this string faster\n var html = progressBars.map(progressBar2Html).join('');\n } else {\n throw new Error('unknown tooltip type');\n }\n\n // TODO: move this \"20\" to a constant\n // TODO: check how this work when `pos` is close to the page bottom edge\n (this.$tooltip.html(html) as any).place_tt(pos.pageX + 20, pos.pageY).show();\n }\n\n hide(): void {\n this._visible = false;\n this.$tooltip.hide();\n }\n\n get visible(): boolean { return this._visible; }\n\n}\n\n/** VIEW **/\n\nfunction progressBar2Html(progressBar: ProgressBar): string {\n return `\n
\n
\n ${progressBar.active ? '' : ''}\n ${progressBar.title}\n ${progressBar.active ? '' : ''}\n
\n ${progressBarBars2Html(progressBar.bars)}\n
\n `;\n}\n\nfunction progressBarBars2Html(bars: Bar[]): string {\n return bars.map(bar => `\n
\n ${bar.value}\n
\n `).join('');\n}\n","import { ProgressBar } from './progress_bar';\nimport { PanelConfig, StatType } from './panel_config';\n\n\nimport * as _ from 'lodash';\n\n\ntype KeyValue = [string, number];\n\n\nexport class Mapper {\n\n private _panelConfig: PanelConfig;\n private _templateSrv: any;\n\n constructor(panelConfig: PanelConfig, templateSrv: any) {\n this._panelConfig = panelConfig;\n this._templateSrv = templateSrv;\n }\n\n mapMetricData(seriesList: any): ProgressBar[] {\n const alias = this._panelConfig.getValue('alias');\n const nullMapping = this._panelConfig.getValue('nullMapping');\n\n if(seriesList === undefined || seriesList.length == 0) {\n return [];\n }\n\n if(seriesList[0].columns === undefined) {\n throw new Error('\"Time Series\" queries are not supported, please make sure to select \"Table\" and query at least 2 metrics');\n }\n\n let keys = seriesList[0].columns.map(col => col.text);\n\n const keyColumn = this._panelConfig.getValue('keyColumn');\n let keyIndex = 0;\n if(keyColumn !== '') {\n keyIndex = keys.findIndex(key => key === keyColumn);\n }\n\n let skipIndexes: number[] = [keyIndex];\n const skipColumns = this._panelConfig.getValue('skipColumns');\n skipColumns.forEach(column => {\n const index = keys.findIndex(key => key === column);\n if(index >= 0) {\n skipIndexes.push(index);\n }\n });\n\n const rowsMaxes = seriesList[0].rows.map(\n row => _.sum(\n row.filter((value, idx) => !_.includes(skipIndexes, idx))\n )\n );\n const totalMaxValue = _.max(rowsMaxes);\n\n const filteredKeys = keys.filter((key, idx) => !_.includes(skipIndexes, idx));\n // TODO: it's wrong, we return a bad type here\n return seriesList[0].rows.map(\n row => {\n let title = row[keyIndex];\n if(alias !== '') {\n const scopedVars = {\n __key: { value: title }\n };\n title = this._templateSrv.replace(alias, scopedVars);\n }\n const values = row.filter((value, idx) => !_.includes(skipIndexes, idx));\n const mappedValues = this._mapNullValues(values, nullMapping);\n\n return new ProgressBar(\n this._panelConfig,\n title,\n filteredKeys,\n mappedValues,\n totalMaxValue as number\n )\n }\n );\n\n }\n\n private _mapNullValues(values: (number | null)[], nullMapping: number | undefined): number[] {\n const mappedValues = values.map(value => {\n if(value == null) {\n if(nullMapping === undefined || nullMapping === null) {\n throw new Error('Got null value. Set null value mapping in Options -> Value Labels -> Null Value');\n }\n return nullMapping;\n } else {\n return value\n }\n });\n return mappedValues;\n }\n\n _mapKeysTotal(seriesList): KeyValue[] {\n if(seriesList.length !== 1) {\n throw new Error('Expecting list of keys: got more than one timeseries');\n }\n var kv = {};\n var datapointsLength = seriesList[0].datapoints.length;\n for(let i = 0; i < datapointsLength; i++) {\n let k = seriesList[0].datapoints[i][0].toString();\n if(kv[k] === undefined) {\n kv[k] = 0;\n }\n kv[k]++;\n }\n\n var res: KeyValue[] = [];\n for(let k in kv) {\n res.push([k, kv[k]]);\n }\n\n return res;\n\n }\n\n _mapNumeric(seriesList, statType: StatType, nullMapping): KeyValue[] {\n if(seriesList.length != 2) {\n throw new Error('Expecting timeseries in format (key, value). You can use keys only in total mode');\n }\n if(seriesList[0].datapoints.length !== seriesList[1].datapoints.length) {\n throw new Error('Timeseries has different length');\n }\n\n var kv = {};\n var datapointsLength = seriesList[0].datapoints.length;\n\n for(let i = 0; i < datapointsLength; i++) {\n let k = seriesList[0].datapoints[i][0].toString();\n let v = seriesList[1].datapoints[i][0];\n let vn = parseFloat(v);\n if(v === null) {\n if(nullMapping === undefined || nullMapping === null) {\n throw new Error('Got null value. You set null value mapping in Options -> Mapping -> Null');\n }\n console.log('nullMapping ->' + nullMapping);\n vn = nullMapping;\n }\n if(isNaN(vn)) {\n throw new Error('Got non-numberic value: ' + v);\n }\n if(kv[k] === undefined) {\n kv[k] = [];\n }\n kv[k].push(vn);\n }\n\n var res: KeyValue[] = [];\n for(let k in kv) {\n res.push([k, this._flatSeries(kv[k], statType)]);\n }\n\n return res;\n }\n\n _mapTargetToDatapoints(seriesList, statType: StatType): KeyValue[] {\n return seriesList.map(serie => [\n serie.target,\n this._flatSeries(serie.datapoints.map(datapoint => datapoint[0]), statType)\n ]);\n }\n\n _flatSeries(values: number[], statType: StatType): number {\n if(values.length === 0) {\n return 0;\n }\n\n if(statType === StatType.TOTAL) {\n return _.sum(values);\n }\n\n if(statType === StatType.MAX) {\n return _.max(values) as number;\n }\n\n if(statType === StatType.MIN) {\n return _.min(values) as number;\n }\n\n if(statType === StatType.CURRENT) {\n return _.last(values) as number;\n }\n\n return 0;\n }\n}\n","import { GraphTooltip } from './graph_tooltip';\nimport * as PanelConfig from './panel_config';\nimport { Mapper } from './mapper';\nimport { ProgressBar } from './progress_bar';\n\nimport { MetricsPanelCtrl, loadPluginCss } from 'grafana/app/plugins/sdk';\n\nimport * as _ from 'lodash';\n\nexport type HoverEvent = {\n index: number,\n event: any\n}\n\nconst ERROR_MAPPING = `\n Can't map the received metrics, see \n \n wiki\n \n`;\nconst ERROR_NO_DATA = \"no data\";\n\nclass Ctrl extends MetricsPanelCtrl {\n static templateUrl = 'partials/template.html';\n\n public mapper: Mapper;\n\n // TODO: rename progressBars\n public progressBars: ProgressBar[];\n\n private _panelConfig: PanelConfig.PanelConfig;\n\n private _seriesList: any;\n\n private _tooltip: GraphTooltip;\n\n private statNameOptions = _.values(PanelConfig.StatType);\n // TODO: review these ooptions and make types in PanelConfig\n private statProgressTypeOptions = [ 'max value', 'shared' ];\n \n private coloringTypeOptions = _.values(PanelConfig.ColoringType);\n\n private titleViewTypeOptions = _.values(PanelConfig.TitleViewOptions);\n private sortingOrderOptions = [ 'none', 'increasing', 'decreasing' ];\n private valueLabelTypeOptions = _.values(PanelConfig.ValueLabelType);\n private tooltipModeOptions = _.values(PanelConfig.TooltipMode);\n\n // field for updating tooltip on rendering and storing previous state\n private _lastHoverEvent?: HoverEvent;\n\n // used to show status messages replacing rendered graphics\n // see isPanelAlert and panelAlertMessage\n private _panelAlert = {\n active: true,\n // message prop can be formatted with html,\n message: 'loading...' // loading will be showed only once at the beginning\n\n // would be nice to add `type` property with values ['info', 'warning', 'error']\n // and then move it https://github.com/chartwerk/grafana-panel-base/issues/1\n };\n\n constructor($scope: any, $injector: any, public templateSrv: any) {\n super($scope, $injector);\n\n _.defaults(this.panel, PanelConfig.DEFAULTS);\n\n this._panelConfig = new PanelConfig.PanelConfig(this.panel);\n this._initStyles();\n\n this.mapper = new Mapper(this._panelConfig, this.templateSrv);\n this.progressBars = [];\n this._tooltip = new GraphTooltip();\n\n this.events.on('init-edit-mode', this._onInitEditMode.bind(this));\n this.events.on('data-received', this._onDataReceived.bind(this));\n this.events.on('render', this._onRender.bind(this));\n }\n\n _initStyles() {\n // small hack to load base styles\n const cssPath = this._panelConfig.pluginDirName.replace('public/', '');\n loadPluginCss({\n light: cssPath + 'css/panel.base.css',\n dark: cssPath + 'css/panel.base.css'\n });\n loadPluginCss({\n light: cssPath + 'css/panel.light.css',\n dark: cssPath + 'css/panel.dark.css'\n });\n }\n\n _onRender() {\n // maybe we want to make a config \"last value\" instead of ERROR_NO_DATA\n // see https://github.com/chartwerk/grafana-panel-base/issues/3\n if(this._seriesList === undefined || this._seriesList.length === 0) {\n this._panelAlert.active = true;\n this._panelAlert.message = ERROR_NO_DATA;\n return;\n }\n try {\n // TODO: set this.items also\n this.progressBars = this.mapper.mapMetricData(this._seriesList);\n } catch(e) {\n this._panelAlert.active = true;\n this._panelAlert.message = `${ERROR_MAPPING}

${e}

`;\n return;\n }\n if(this._panelConfig.getValue('sortingOrder') === 'increasing') {\n this.progressBars = _.sortBy(this.progressBars, i => i.aggregatedProgress);\n }\n if(this._panelConfig.getValue('sortingOrder') === 'decreasing') {\n this.progressBars = _.sortBy(this.progressBars, i => -i.aggregatedProgress);\n }\n\n this.progressBars = _.take(this.progressBars, this._panelConfig.getValue('limit'));\n\n if(this._tooltip.visible) {\n if(this._lastHoverEvent === undefined) {\n throw new Error(\n 'Need to show tooltip because it`s visible, but don`t have previous state'\n );\n }\n this.onHover(this._lastHoverEvent);\n }\n this._panelAlert.active = false;\n }\n\n onValueLabelTypeChange(): void {\n this.updatePostfix();\n this._onRender();\n }\n\n onAddSkipColumnClick(): void {\n this.panel.skipColumns.push('');\n }\n\n onRemoveSkipColumnClick(index: number): void {\n this.panel.skipColumns.splice(index, 1);\n this.render();\n }\n\n updatePostfix(): void {\n const valueLabelType = this._panelConfig.getValue('valueLabelType');\n const postfixValue = this.panel.postfix;\n switch(valueLabelType) {\n case PanelConfig.ValueLabelType.ABSOLUTE:\n if(postfixValue === '%') {\n this.panel.postfix = '';\n }\n break;\n case PanelConfig.ValueLabelType.PERCENTAGE:\n if(postfixValue === '') {\n this.panel.postfix = '%';\n }\n break;\n default:\n throw new Error(`Unknown value label type: ${valueLabelType}`);\n }\n }\n\n onHover(event: HoverEvent) {\n this._clearActiveProgressBar();\n this._lastHoverEvent = event; // TODO: use it to unset active previous progressbar\n this.progressBars[event.index].active = true;\n this._tooltip.show(event.event, this.progressBars, this.panel.tooltipMode);\n }\n\n private _clearActiveProgressBar() {\n if(\n this._lastHoverEvent !== undefined &&\n this._lastHoverEvent.index < this.progressBars.length\n ) {\n this.progressBars[this._lastHoverEvent.index].active = false;\n }\n }\n\n onMouseLeave() {\n this._clearActiveProgressBar();\n this._tooltip.hide();\n }\n\n _onDataReceived(seriesList: any) {\n this._seriesList = seriesList;\n this.render();\n }\n\n _onInitEditMode() {\n var thisPartialPath = this._panelConfig.pluginDirName + 'partials/';\n this.addEditorTab('Options', thisPartialPath + 'options.html', 2);\n }\n\n invertColorOrder() {\n var tmp = this.panel.colors[0];\n this.panel.colors[0] = this.panel.colors[2];\n this.panel.colors[2] = tmp;\n this.render();\n }\n\n addColorKeyMapping() {\n this.panel.colorKeyMappings.push({\n key: 'KEY_NAME',\n color: 'rgba(50, 172, 45, 0.97)'\n });\n }\n\n removeColorKeyMapping(index) {\n this.panel.colorKeyMappings.splice(index, 1);\n this.render();\n }\n\n _dataError(err) {\n console.log('got data error');\n console.log(err);\n // TODO: reveiew this logic\n this.$scope.data = [];\n this.$scope.dataError = err;\n }\n\n get columns(): string[] {\n if(\n this._seriesList === undefined ||\n this._seriesList.length === 0 ||\n this._seriesList[0].columns === undefined\n ) {\n return [];\n }\n return this._seriesList[0].columns.map(col => col.text);\n }\n\n get isPanelAlert(): boolean {\n return this._panelAlert.active;\n }\n\n // the field will be rendered as html\n get panelAlertMessage(): string {\n return this._panelAlert.message;\n }\n\n}\n\nexport { Ctrl as PanelCtrl }\n","export enum StatType {\n CURRENT = 'current',\n MIN = 'min',\n MAX = 'max',\n TOTAL = 'total'\n};\n\nexport enum TitleViewOptions {\n SEPARATE_TITLE_LINE = 'Separate title line',\n INLINE = 'Inline'\n};\n\nexport enum ColoringType {\n PALLETE = 'pallete',\n THRESHOLDS = 'thresholds',\n KEY_MAPPING = 'key mapping'\n}\n\nexport enum ValueLabelType {\n PERCENTAGE = 'percentage',\n ABSOLUTE = 'absolute'\n}\n\nexport enum TooltipMode {\n NONE = 'none',\n SINGLE = 'single',\n ALL_SERIES = 'all series'\n};\n\nexport const DEFAULT_FONT_SIZE = 14;\n\nexport const DEFAULTS = {\n keyColumn: '',\n skipColumns: [],\n statNameOptionValue: StatType.CURRENT,\n statProgressMaxValue: null,\n coloringType: ColoringType.PALLETE,\n titleViewType: TitleViewOptions.SEPARATE_TITLE_LINE,\n sortingOrder: 'none',\n valueLabelType: ValueLabelType.ABSOLUTE,\n alias: '',\n prefix: '',\n postfix: '',\n valueSize: DEFAULT_FONT_SIZE,\n titleSize: DEFAULT_FONT_SIZE,\n thresholds: '10, 30',\n // https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts#L57\n colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],\n colorsKeyMappingDefault: 'rgba(245, 54, 54, 0.9)',\n colorKeyMappings: [],\n nullMapping: undefined,\n tooltipMode: TooltipMode.ALL_SERIES,\n opacity: 0.5,\n limit: 50\n};\n\n\nexport class PanelConfig {\n private _panel: any;\n public constructor(panel: any) {\n this._panel = panel;\n\n // migrations\n if(this.getValue('coloringType') === 'auto') {\n this.setValue('coloringType', ColoringType.PALLETE);\n }\n\n const skipColumn = this.getValue('skipColumn');\n if(skipColumn !== undefined && skipColumn !== '') {\n this.setValue('skipColumn', undefined);\n this.setValue('skipColumns', [skipColumn]);\n }\n }\n\n public getValue(key: string): any {\n return this._panel[key];\n }\n\n public setValue(key: string, value: any): void {\n this._panel[key] = value;\n }\n\n private _pluginDirName: string;\n public get pluginDirName(): string {\n if(!this._pluginDirName) {\n var panels = window['grafanaBootData'].settings.panels;\n var thisPanel = panels[this._panel.type];\n // the system loader preprends publib to the url,\n // add a .. to go back one level\n this._pluginDirName = thisPanel.baseUrl + '/';\n }\n return this._pluginDirName;\n }\n}\n","import { ColoringType, PanelConfig, TitleViewOptions, ValueLabelType, DEFAULT_FONT_SIZE } from './panel_config';\nimport { getFormattedValue } from './value_formatter';\n\nimport * as _ from 'lodash';\n\n\ntype ProgressTitle = {\n barHeight: number,\n titleHeight: number,\n position: Position\n};\n\nenum Position {\n STATIC = 'static',\n ABSOLUTE = 'absolute'\n}\n\n/**\n * It's model for rendering bars in view (partial) and tooltip\n */\nexport type Bar = {\n name: string,\n value: number,\n color: string\n}\n\n/**\n * Model for the main component of the app -- bars, but it's not just a Bar,\n * it also keeps all small \"bars\", title and metainfo\n */\nexport class ProgressBar {\n\n private _bars: Bar[];\n private _active: boolean;\n\n constructor(\n private _panelConfig: PanelConfig,\n private _title: string,\n private _keys: string[], // maybe \"_names\" is better than \"_keys\"\n private _values: number[],\n private _maxTotalValue: number\n ) {\n if(this._keys.length !== this._values.length) {\n throw new Error('keys amount should be equal to values amount');\n }\n this._bars = [];\n for(let i = 0; i < _keys.length; ++i) {\n this._bars.push({\n name: this._keys[i],\n value: this._values[i],\n color: mapValue2Color(this._values[i], this._keys[i], i, this._panelConfig)\n });\n }\n\n // bad code starts:\n\n }\n\n get active(): boolean { return this._active; }\n set active(value: boolean) { this._active = value;}\n\n get title(): string { return this._title; }\n\n get keys(): string[] { return this._keys; }\n\n get values(): number[] { return this._values; }\n\n get bars(): Bar[] { return this._bars; }\n\n get sumOfValues(): number { return _.sum(this.values); }\n\n get percentValues(): number[] {\n return this.values.map(\n value => value / this.sumOfValues * 100\n );\n }\n\n get aggregatedProgress(): number {\n return (this.sumOfValues / this._maxTotalValue) * 100;\n }\n\n get totalValue(): number {\n const valueLabelType = this._panelConfig.getValue('valueLabelType');\n switch(valueLabelType) {\n case ValueLabelType.ABSOLUTE:\n return this.sumOfValues;\n case ValueLabelType.PERCENTAGE:\n return this.aggregatedProgress;\n default:\n throw new Error(`Unknown value label type: ${valueLabelType}`);\n }\n }\n\n get formattedTotalValue(): string {\n return getFormattedValue(\n this.totalValue,\n this._panelConfig.getValue('prefix'),\n this._panelConfig.getValue('postfix'),\n this._panelConfig.getValue('decimals')\n );\n }\n\n get valueFontSize(): number {\n const configSize = this._panelConfig.getValue('valueSize');\n if(configSize === undefined || configSize === null) {\n return DEFAULT_FONT_SIZE;\n }\n return configSize;\n }\n\n get titleFontSize(): number {\n const configSize = this._panelConfig.getValue('titleSize');\n if(configSize === undefined || configSize === null) {\n return DEFAULT_FONT_SIZE;\n }\n return configSize;\n }\n\n get colors(): string[] {\n return _.map(this._bars, bar => bar.color);\n }\n\n // it should go somewhere to view\n get titleParams(): ProgressTitle {\n const titleType = this._panelConfig.getValue('titleViewType');\n switch(titleType) {\n case TitleViewOptions.SEPARATE_TITLE_LINE:\n return {\n barHeight: 8,\n titleHeight: 16,\n position: Position.STATIC\n };\n case TitleViewOptions.INLINE:\n return {\n barHeight: 24,\n titleHeight: 24,\n position: Position.ABSOLUTE\n };\n default:\n throw new Error(`Wrong titleType: ${titleType}`);\n }\n }\n\n get opacity(): string {\n return this._panelConfig.getValue('opacity');\n }\n\n}\n\n/** VIEW **/\n\nfunction mapValue2Color(value: number, key: string, index: number, _panelConfig: any): string {\n const colorType: ColoringType = _panelConfig.getValue('coloringType');\n const colors: string[] = _panelConfig.getValue('colors');\n\n switch(colorType) {\n case ColoringType.PALLETE:\n return colors[index % colors.length];\n case ColoringType.THRESHOLDS:\n // TODO: parse only once\n const thresholds = _panelConfig.getValue('thresholds').split(',').map(parseFloat);\n if(colors.length <= thresholds.length) {\n // we add one because a threshold is a cut of the range of values\n throw new Error('Number of colors must be at least as number as threasholds + 1');\n }\n for(let i = thresholds.length; i > 0; i--) {\n if(value >= thresholds[i - 1]) {\n return colors[i];\n }\n }\n return colors[0];\n case ColoringType.KEY_MAPPING:\n const colorKeyMappings = _panelConfig.getValue('colorKeyMappings') as any[];\n const keyColorMapping = _.find(colorKeyMappings, k => k.key === key);\n if(keyColorMapping === undefined) {\n return _panelConfig.getValue('colorsKeyMappingDefault');\n }\n return keyColorMapping.color;\n default:\n throw new Error('Unknown color type ' + colorType);\n }\n}\n","import * as _ from 'lodash';\n\n\nexport function getFormattedValue(\n value: number,\n prefix: string,\n postfix: string,\n decimals: number\n): string {\n return `${prefix}${getFormattedFloat(value, decimals)}${postfix}`;\n}\n\nfunction getFormattedFloat(value: number, decimals: number): string {\n let dm = getDecimalsForValue(value, decimals).decimals;\n\n if(dm === 0) {\n return Math.round(value).toString();\n }\n\n let fv = value;\n for(let i = 0; i < dm; i++) {\n fv *= 10;\n };\n let fvs = Math.round(fv).toString();\n return fvs.substr(0, fvs.length - dm) + '.' + fvs.substr(fvs.length - dm);\n}\n\nfunction getDecimalsForValue(value: number, decimals?: number) {\n // based on https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts\n if(_.isNumber(decimals)) {\n return {\n decimals,\n scaledDecimals: null\n };\n }\n\n let delta = value / 2;\n let dec = -Math.floor(Math.log(delta) / Math.LN10);\n\n let magn = Math.pow(10, -dec),\n norm = delta / magn, // norm is between 1.0 and 10.0\n size;\n\n if(norm < 1.5) {\n size = 1;\n } else if (norm < 3) {\n size = 2;\n // special case for 2.5, requires an extra decimal\n if (norm > 2.25) {\n size = 2.5;\n ++dec;\n }\n } else if(norm < 7.5) {\n size = 5;\n } else {\n size = 10;\n }\n\n size *= magn;\n\n // reduce starting decimals if not needed\n if(Math.floor(value) === value) {\n dec = 0;\n }\n\n let result: any = {};\n result.decimals = Math.max(0, dec);\n result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;\n\n return result;\n}\n","module.exports = __WEBPACK_EXTERNAL_MODULE_grafana_app_plugins_sdk__;","module.exports = __WEBPACK_EXTERNAL_MODULE_lodash__;"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/module.js: -------------------------------------------------------------------------------- 1 | define(["app/plugins/sdk","lodash"], function(__WEBPACK_EXTERNAL_MODULE_grafana_app_plugins_sdk__, __WEBPACK_EXTERNAL_MODULE_lodash__) { return /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // object to store loaded and loading wasm modules 6 | /******/ var installedWasmModules = {}; 7 | /******/ 8 | /******/ // The require function 9 | /******/ function __webpack_require__(moduleId) { 10 | /******/ 11 | /******/ // Check if module is in cache 12 | /******/ if(installedModules[moduleId]) { 13 | /******/ return installedModules[moduleId].exports; 14 | /******/ } 15 | /******/ // Create a new module (and put it into the cache) 16 | /******/ var module = installedModules[moduleId] = { 17 | /******/ i: moduleId, 18 | /******/ l: false, 19 | /******/ exports: {} 20 | /******/ }; 21 | /******/ 22 | /******/ // Execute the module function 23 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 24 | /******/ 25 | /******/ // Flag the module as loaded 26 | /******/ module.l = true; 27 | /******/ 28 | /******/ // Return the exports of the module 29 | /******/ return module.exports; 30 | /******/ } 31 | /******/ 32 | /******/ 33 | /******/ // expose the modules object (__webpack_modules__) 34 | /******/ __webpack_require__.m = modules; 35 | /******/ 36 | /******/ // expose the module cache 37 | /******/ __webpack_require__.c = installedModules; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // define __esModule on exports 51 | /******/ __webpack_require__.r = function(exports) { 52 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 53 | /******/ }; 54 | /******/ 55 | /******/ // getDefaultExport function for compatibility with non-harmony modules 56 | /******/ __webpack_require__.n = function(module) { 57 | /******/ var getter = module && module.__esModule ? 58 | /******/ function getDefault() { return module['default']; } : 59 | /******/ function getModuleExports() { return module; }; 60 | /******/ __webpack_require__.d(getter, 'a', getter); 61 | /******/ return getter; 62 | /******/ }; 63 | /******/ 64 | /******/ // Object.prototype.hasOwnProperty.call 65 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 66 | /******/ 67 | /******/ // __webpack_public_path__ 68 | /******/ __webpack_require__.p = ""; 69 | /******/ 70 | /******/ // object with all compiled WebAssembly.Modules 71 | /******/ __webpack_require__.w = {}; 72 | /******/ 73 | /******/ 74 | /******/ // Load entry module and return exports 75 | /******/ return __webpack_require__(__webpack_require__.s = "./module.ts"); 76 | /******/ }) 77 | /************************************************************************/ 78 | /******/ ({ 79 | 80 | /***/ "./graph_tooltip.ts": 81 | /*!**************************!*\ 82 | !*** ./graph_tooltip.ts ***! 83 | \**************************/ 84 | /*! no static exports found */ 85 | /***/ (function(module, exports, __webpack_require__) { 86 | 87 | "use strict"; 88 | eval("\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar _ = __webpack_require__(/*! lodash */ \"lodash\");\nvar panel_config_1 = __webpack_require__(/*! ./panel_config */ \"./panel_config.ts\");\n\nvar GraphTooltip = function () {\n function GraphTooltip() {\n _classCallCheck(this, GraphTooltip);\n\n this._visible = false;\n this.$tooltip = $('
');\n }\n\n _createClass(GraphTooltip, [{\n key: \"show\",\n value: function show(pos, progressBars, mode) {\n if (mode == panel_config_1.TooltipMode.NONE) {\n return;\n }\n this._visible = true;\n // TODO: use more vue/react approach here\n // TODO: maybe wrap this rendering logic into classes\n if (mode == panel_config_1.TooltipMode.SINGLE) {\n var activeItem = _.find(progressBars, function (item) {\n return item.active;\n });\n var html = \"
Current value
\";\n if (activeItem === undefined) {\n throw new Error('Can`t find any active item to show current value in tooltip');\n }\n html += progressBar2Html(activeItem);\n } else if (mode == panel_config_1.TooltipMode.ALL_SERIES) {\n // TODO: build this string faster\n var html = progressBars.map(progressBar2Html).join('');\n } else {\n throw new Error('unknown tooltip type');\n }\n // TODO: move this \"20\" to a constant\n // TODO: check how this work when `pos` is close to the page bottom edge\n this.$tooltip.html(html).place_tt(pos.pageX + 20, pos.pageY).show();\n }\n }, {\n key: \"hide\",\n value: function hide() {\n this._visible = false;\n this.$tooltip.hide();\n }\n }, {\n key: \"visible\",\n get: function get() {\n return this._visible;\n }\n }]);\n\n return GraphTooltip;\n}();\n\nexports.GraphTooltip = GraphTooltip;\n/** VIEW **/\nfunction progressBar2Html(progressBar) {\n return \"\\n
\\n
\\n \" + (progressBar.active ? '' : '') + \"\\n \" + progressBar.title + \"\\n \" + (progressBar.active ? '' : '') + \"\\n
\\n \" + progressBarBars2Html(progressBar.bars) + \"\\n
\\n \";\n}\nfunction progressBarBars2Html(bars) {\n return bars.map(function (bar) {\n return \"\\n
\\n \" + bar.value + \"\\n
\\n \";\n }).join('');\n}\n\n//# sourceURL=webpack:///./graph_tooltip.ts?"); 89 | 90 | /***/ }), 91 | 92 | /***/ "./mapper.ts": 93 | /*!*******************!*\ 94 | !*** ./mapper.ts ***! 95 | \*******************/ 96 | /*! no static exports found */ 97 | /***/ (function(module, exports, __webpack_require__) { 98 | 99 | "use strict"; 100 | eval("\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar progress_bar_1 = __webpack_require__(/*! ./progress_bar */ \"./progress_bar.ts\");\nvar panel_config_1 = __webpack_require__(/*! ./panel_config */ \"./panel_config.ts\");\nvar _ = __webpack_require__(/*! lodash */ \"lodash\");\n\nvar Mapper = function () {\n function Mapper(panelConfig, templateSrv) {\n _classCallCheck(this, Mapper);\n\n this._panelConfig = panelConfig;\n this._templateSrv = templateSrv;\n }\n\n _createClass(Mapper, [{\n key: \"mapMetricData\",\n value: function mapMetricData(seriesList) {\n var _this = this;\n\n var alias = this._panelConfig.getValue('alias');\n var nullMapping = this._panelConfig.getValue('nullMapping');\n if (seriesList === undefined || seriesList.length == 0) {\n return [];\n }\n if (seriesList[0].columns === undefined) {\n throw new Error('\"Time Series\" queries are not supported, please make sure to select \"Table\" and query at least 2 metrics');\n }\n var keys = seriesList[0].columns.map(function (col) {\n return col.text;\n });\n var keyColumn = this._panelConfig.getValue('keyColumn');\n var keyIndex = 0;\n if (keyColumn !== '') {\n keyIndex = keys.findIndex(function (key) {\n return key === keyColumn;\n });\n }\n var skipIndexes = [keyIndex];\n var skipColumns = this._panelConfig.getValue('skipColumns');\n skipColumns.forEach(function (column) {\n var index = keys.findIndex(function (key) {\n return key === column;\n });\n if (index >= 0) {\n skipIndexes.push(index);\n }\n });\n var rowsMaxes = seriesList[0].rows.map(function (row) {\n var values = _this._rowToValues(row, skipIndexes, nullMapping);\n return _.sum(values);\n });\n var totalMaxValue = _.max(rowsMaxes);\n var filteredKeys = keys.filter(function (key, idx) {\n return !_.includes(skipIndexes, idx);\n });\n // TODO: it's wrong, we return a bad type here\n return seriesList[0].rows.map(function (row) {\n var title = row[keyIndex];\n if (alias !== '') {\n var scopedVars = {\n __key: { value: title }\n };\n title = _this._templateSrv.replace(alias, scopedVars);\n }\n var values = _this._rowToValues(row, skipIndexes, nullMapping);\n return new progress_bar_1.ProgressBar(_this._panelConfig, title, filteredKeys, values, totalMaxValue);\n });\n }\n }, {\n key: \"_rowToValues\",\n value: function _rowToValues(row, skipIndexes, nullMapping) {\n var values = row.filter(function (value, idx) {\n return !_.includes(skipIndexes, idx);\n });\n return this._mapNullValues(values, nullMapping);\n }\n }, {\n key: \"_mapNullValues\",\n value: function _mapNullValues(values, nullMapping) {\n return values.map(function (value) {\n if (value !== null) {\n return value;\n }\n if (nullMapping === undefined || nullMapping === null) {\n throw new Error('Got null value. Set null value mapping in Options -> Value Labels -> Null Value');\n }\n return nullMapping;\n });\n }\n }, {\n key: \"_mapKeysTotal\",\n value: function _mapKeysTotal(seriesList) {\n if (seriesList.length !== 1) {\n throw new Error('Expecting list of keys: got more than one timeseries');\n }\n var kv = {};\n var datapointsLength = seriesList[0].datapoints.length;\n for (var i = 0; i < datapointsLength; i++) {\n var k = seriesList[0].datapoints[i][0].toString();\n if (kv[k] === undefined) {\n kv[k] = 0;\n }\n kv[k]++;\n }\n var res = [];\n for (var _k in kv) {\n res.push([_k, kv[_k]]);\n }\n return res;\n }\n }, {\n key: \"_mapNumeric\",\n value: function _mapNumeric(seriesList, statType, nullMapping) {\n if (seriesList.length != 2) {\n throw new Error('Expecting timeseries in format (key, value). You can use keys only in total mode');\n }\n if (seriesList[0].datapoints.length !== seriesList[1].datapoints.length) {\n throw new Error('Timeseries has different length');\n }\n var kv = {};\n var datapointsLength = seriesList[0].datapoints.length;\n for (var i = 0; i < datapointsLength; i++) {\n var k = seriesList[0].datapoints[i][0].toString();\n var v = seriesList[1].datapoints[i][0];\n var vn = parseFloat(v);\n if (v === null) {\n if (nullMapping === undefined || nullMapping === null) {\n throw new Error('Got null value. You set null value mapping in Options -> Mapping -> Null');\n }\n console.log('nullMapping ->' + nullMapping);\n vn = nullMapping;\n }\n if (isNaN(vn)) {\n throw new Error('Got non-numberic value: ' + v);\n }\n if (kv[k] === undefined) {\n kv[k] = [];\n }\n kv[k].push(vn);\n }\n var res = [];\n for (var _k2 in kv) {\n res.push([_k2, this._flatSeries(kv[_k2], statType)]);\n }\n return res;\n }\n }, {\n key: \"_mapTargetToDatapoints\",\n value: function _mapTargetToDatapoints(seriesList, statType) {\n var _this2 = this;\n\n return seriesList.map(function (serie) {\n return [serie.target, _this2._flatSeries(serie.datapoints.map(function (datapoint) {\n return datapoint[0];\n }), statType)];\n });\n }\n }, {\n key: \"_flatSeries\",\n value: function _flatSeries(values, statType) {\n if (values.length === 0) {\n return 0;\n }\n if (statType === panel_config_1.StatType.TOTAL) {\n return _.sum(values);\n }\n if (statType === panel_config_1.StatType.MAX) {\n return _.max(values);\n }\n if (statType === panel_config_1.StatType.MIN) {\n return _.min(values);\n }\n if (statType === panel_config_1.StatType.CURRENT) {\n return _.last(values);\n }\n return 0;\n }\n }]);\n\n return Mapper;\n}();\n\nexports.Mapper = Mapper;\n\n//# sourceURL=webpack:///./mapper.ts?"); 101 | 102 | /***/ }), 103 | 104 | /***/ "./module.ts": 105 | /*!*******************!*\ 106 | !*** ./module.ts ***! 107 | \*******************/ 108 | /*! no static exports found */ 109 | /***/ (function(module, exports, __webpack_require__) { 110 | 111 | "use strict"; 112 | eval("\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar graph_tooltip_1 = __webpack_require__(/*! ./graph_tooltip */ \"./graph_tooltip.ts\");\nvar PanelConfig = __webpack_require__(/*! ./panel_config */ \"./panel_config.ts\");\nvar mapper_1 = __webpack_require__(/*! ./mapper */ \"./mapper.ts\");\nvar sdk_1 = __webpack_require__(/*! grafana/app/plugins/sdk */ \"grafana/app/plugins/sdk\");\nvar _ = __webpack_require__(/*! lodash */ \"lodash\");\nvar ERROR_MAPPING = \"\\n Can't map the received metrics, see \\n \\n wiki\\n \\n\";\nvar ERROR_NO_DATA = \"no data\";\n\nvar Ctrl = function (_sdk_1$MetricsPanelCt) {\n _inherits(Ctrl, _sdk_1$MetricsPanelCt);\n\n function Ctrl($scope, $injector, templateSrv) {\n _classCallCheck(this, Ctrl);\n\n var _this = _possibleConstructorReturn(this, (Ctrl.__proto__ || Object.getPrototypeOf(Ctrl)).call(this, $scope, $injector));\n\n _this.templateSrv = templateSrv;\n _this.statNameOptions = _.values(PanelConfig.StatType);\n // TODO: review these ooptions and make types in PanelConfig\n _this.statProgressTypeOptions = ['max value', 'shared'];\n _this.coloringTypeOptions = _.values(PanelConfig.ColoringType);\n _this.titleViewTypeOptions = _.values(PanelConfig.TitleViewOptions);\n _this.sortingOrderOptions = ['none', 'increasing', 'decreasing'];\n _this.valueLabelTypeOptions = _.values(PanelConfig.ValueLabelType);\n _this.tooltipModeOptions = _.values(PanelConfig.TooltipMode);\n // used to show status messages replacing rendered graphics\n // see isPanelAlert and panelAlertMessage\n _this._panelAlert = {\n active: true,\n // message prop can be formatted with html,\n message: 'loading...' // loading will be showed only once at the beginning\n // would be nice to add `type` property with values ['info', 'warning', 'error']\n // and then move it https://github.com/chartwerk/grafana-panel-base/issues/1\n };\n _.defaults(_this.panel, PanelConfig.DEFAULTS);\n _this._panelConfig = new PanelConfig.PanelConfig(_this.panel);\n _this._initStyles();\n _this.mapper = new mapper_1.Mapper(_this._panelConfig, _this.templateSrv);\n _this.progressBars = [];\n _this._tooltip = new graph_tooltip_1.GraphTooltip();\n _this.events.on('init-edit-mode', _this._onInitEditMode.bind(_this));\n _this.events.on('data-received', _this._onDataReceived.bind(_this));\n _this.events.on('render', _this._onRender.bind(_this));\n return _this;\n }\n\n _createClass(Ctrl, [{\n key: \"_initStyles\",\n value: function _initStyles() {\n // small hack to load base styles\n var cssPath = this._panelConfig.pluginDirName.replace('public/', '');\n sdk_1.loadPluginCss({\n light: cssPath + 'css/panel.base.css',\n dark: cssPath + 'css/panel.base.css'\n });\n sdk_1.loadPluginCss({\n light: cssPath + 'css/panel.light.css',\n dark: cssPath + 'css/panel.dark.css'\n });\n }\n }, {\n key: \"_onRender\",\n value: function _onRender() {\n // maybe we want to make a config \"last value\" instead of ERROR_NO_DATA\n // see https://github.com/chartwerk/grafana-panel-base/issues/3\n if (this._seriesList === undefined || this._seriesList.length === 0) {\n this._panelAlert.active = true;\n this._panelAlert.message = ERROR_NO_DATA;\n return;\n }\n try {\n // TODO: set this.items also\n this.progressBars = this.mapper.mapMetricData(this._seriesList);\n } catch (e) {\n this._panelAlert.active = true;\n this._panelAlert.message = ERROR_MAPPING + \"

\" + e + \"

\";\n return;\n }\n if (this._panelConfig.getValue('sortingOrder') === 'increasing') {\n this.progressBars = _.sortBy(this.progressBars, function (i) {\n return i.aggregatedProgress;\n });\n }\n if (this._panelConfig.getValue('sortingOrder') === 'decreasing') {\n this.progressBars = _.sortBy(this.progressBars, function (i) {\n return -i.aggregatedProgress;\n });\n }\n this.progressBars = _.take(this.progressBars, this._panelConfig.getValue('limit'));\n if (this._tooltip.visible) {\n if (this._lastHoverEvent === undefined) {\n throw new Error('Need to show tooltip because it`s visible, but don`t have previous state');\n }\n this.onHover(this._lastHoverEvent);\n }\n this._panelAlert.active = false;\n }\n }, {\n key: \"onValueLabelTypeChange\",\n value: function onValueLabelTypeChange() {\n this.updatePostfix();\n this._onRender();\n }\n }, {\n key: \"onAddSkipColumnClick\",\n value: function onAddSkipColumnClick() {\n this.panel.skipColumns.push('');\n }\n }, {\n key: \"onRemoveSkipColumnClick\",\n value: function onRemoveSkipColumnClick(index) {\n this.panel.skipColumns.splice(index, 1);\n this.render();\n }\n }, {\n key: \"updatePostfix\",\n value: function updatePostfix() {\n var valueLabelType = this._panelConfig.getValue('valueLabelType');\n var postfixValue = this.panel.postfix;\n switch (valueLabelType) {\n case PanelConfig.ValueLabelType.ABSOLUTE:\n if (postfixValue === '%') {\n this.panel.postfix = '';\n }\n break;\n case PanelConfig.ValueLabelType.PERCENTAGE:\n if (postfixValue === '') {\n this.panel.postfix = '%';\n }\n break;\n default:\n throw new Error(\"Unknown value label type: \" + valueLabelType);\n }\n }\n }, {\n key: \"onHover\",\n value: function onHover(event) {\n this._clearActiveProgressBar();\n this._lastHoverEvent = event; // TODO: use it to unset active previous progressbar\n this.progressBars[event.index].active = true;\n this._tooltip.show(event.event, this.progressBars, this.panel.tooltipMode);\n }\n }, {\n key: \"_clearActiveProgressBar\",\n value: function _clearActiveProgressBar() {\n if (this._lastHoverEvent !== undefined && this._lastHoverEvent.index < this.progressBars.length) {\n this.progressBars[this._lastHoverEvent.index].active = false;\n }\n }\n }, {\n key: \"onMouseLeave\",\n value: function onMouseLeave() {\n this._clearActiveProgressBar();\n this._tooltip.hide();\n }\n }, {\n key: \"_onDataReceived\",\n value: function _onDataReceived(seriesList) {\n this._seriesList = seriesList;\n this.render();\n }\n }, {\n key: \"_onInitEditMode\",\n value: function _onInitEditMode() {\n var thisPartialPath = this._panelConfig.pluginDirName + 'partials/';\n this.addEditorTab('Options', thisPartialPath + 'options.html', 2);\n }\n }, {\n key: \"invertColorOrder\",\n value: function invertColorOrder() {\n var tmp = this.panel.colors[0];\n this.panel.colors[0] = this.panel.colors[2];\n this.panel.colors[2] = tmp;\n this.render();\n }\n }, {\n key: \"addColorKeyMapping\",\n value: function addColorKeyMapping() {\n this.panel.colorKeyMappings.push({\n key: 'KEY_NAME',\n color: 'rgba(50, 172, 45, 0.97)'\n });\n }\n }, {\n key: \"removeColorKeyMapping\",\n value: function removeColorKeyMapping(index) {\n this.panel.colorKeyMappings.splice(index, 1);\n this.render();\n }\n }, {\n key: \"_dataError\",\n value: function _dataError(err) {\n console.log('got data error');\n console.log(err);\n // TODO: reveiew this logic\n this.$scope.data = [];\n this.$scope.dataError = err;\n }\n }, {\n key: \"columns\",\n get: function get() {\n if (this._seriesList === undefined || this._seriesList.length === 0 || this._seriesList[0].columns === undefined) {\n return [];\n }\n return this._seriesList[0].columns.map(function (col) {\n return col.text;\n });\n }\n }, {\n key: \"isPanelAlert\",\n get: function get() {\n return this._panelAlert.active;\n }\n // the field will be rendered as html\n\n }, {\n key: \"panelAlertMessage\",\n get: function get() {\n return this._panelAlert.message;\n }\n }]);\n\n return Ctrl;\n}(sdk_1.MetricsPanelCtrl);\n\nCtrl.templateUrl = 'partials/template.html';\nexports.PanelCtrl = Ctrl;\n\n//# sourceURL=webpack:///./module.ts?"); 113 | 114 | /***/ }), 115 | 116 | /***/ "./panel_config.ts": 117 | /*!*************************!*\ 118 | !*** ./panel_config.ts ***! 119 | \*************************/ 120 | /*! no static exports found */ 121 | /***/ (function(module, exports, __webpack_require__) { 122 | 123 | "use strict"; 124 | eval("\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar StatType;\n(function (StatType) {\n StatType[\"CURRENT\"] = \"current\";\n StatType[\"MIN\"] = \"min\";\n StatType[\"MAX\"] = \"max\";\n StatType[\"TOTAL\"] = \"total\";\n})(StatType = exports.StatType || (exports.StatType = {}));\n;\nvar TitleViewOptions;\n(function (TitleViewOptions) {\n TitleViewOptions[\"SEPARATE_TITLE_LINE\"] = \"Separate title line\";\n TitleViewOptions[\"INLINE\"] = \"Inline\";\n})(TitleViewOptions = exports.TitleViewOptions || (exports.TitleViewOptions = {}));\n;\nvar ColoringType;\n(function (ColoringType) {\n ColoringType[\"PALLETE\"] = \"pallete\";\n ColoringType[\"THRESHOLDS\"] = \"thresholds\";\n ColoringType[\"KEY_MAPPING\"] = \"key mapping\";\n})(ColoringType = exports.ColoringType || (exports.ColoringType = {}));\nvar ValueLabelType;\n(function (ValueLabelType) {\n ValueLabelType[\"PERCENTAGE\"] = \"percentage\";\n ValueLabelType[\"ABSOLUTE\"] = \"absolute\";\n})(ValueLabelType = exports.ValueLabelType || (exports.ValueLabelType = {}));\nvar TooltipMode;\n(function (TooltipMode) {\n TooltipMode[\"NONE\"] = \"none\";\n TooltipMode[\"SINGLE\"] = \"single\";\n TooltipMode[\"ALL_SERIES\"] = \"all series\";\n})(TooltipMode = exports.TooltipMode || (exports.TooltipMode = {}));\n;\nexports.DEFAULT_FONT_SIZE = 14;\nexports.DEFAULTS = {\n keyColumn: '',\n skipColumns: [],\n statNameOptionValue: StatType.CURRENT,\n statProgressMaxValue: null,\n coloringType: ColoringType.PALLETE,\n titleViewType: TitleViewOptions.SEPARATE_TITLE_LINE,\n sortingOrder: 'none',\n valueLabelType: ValueLabelType.ABSOLUTE,\n alias: '',\n prefix: '',\n postfix: '',\n valueSize: exports.DEFAULT_FONT_SIZE,\n titleSize: exports.DEFAULT_FONT_SIZE,\n thresholds: '10, 30',\n // https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts#L57\n colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],\n colorsKeyMappingDefault: 'rgba(245, 54, 54, 0.9)',\n colorKeyMappings: [],\n nullMapping: undefined,\n tooltipMode: TooltipMode.ALL_SERIES,\n opacity: 0.5,\n limit: 50\n};\n\nvar PanelConfig = function () {\n function PanelConfig(panel) {\n _classCallCheck(this, PanelConfig);\n\n this._panel = panel;\n // migrations\n if (this.getValue('coloringType') === 'auto') {\n this.setValue('coloringType', ColoringType.PALLETE);\n }\n var skipColumn = this.getValue('skipColumn');\n if (skipColumn !== undefined && skipColumn !== '') {\n this.setValue('skipColumn', undefined);\n this.setValue('skipColumns', [skipColumn]);\n }\n }\n\n _createClass(PanelConfig, [{\n key: \"getValue\",\n value: function getValue(key) {\n return this._panel[key];\n }\n }, {\n key: \"setValue\",\n value: function setValue(key, value) {\n this._panel[key] = value;\n }\n }, {\n key: \"pluginDirName\",\n get: function get() {\n if (!this._pluginDirName) {\n var panels = window['grafanaBootData'].settings.panels;\n var thisPanel = panels[this._panel.type];\n // the system loader preprends publib to the url,\n // add a .. to go back one level\n this._pluginDirName = thisPanel.baseUrl + '/';\n }\n return this._pluginDirName;\n }\n }]);\n\n return PanelConfig;\n}();\n\nexports.PanelConfig = PanelConfig;\n\n//# sourceURL=webpack:///./panel_config.ts?"); 125 | 126 | /***/ }), 127 | 128 | /***/ "./progress_bar.ts": 129 | /*!*************************!*\ 130 | !*** ./progress_bar.ts ***! 131 | \*************************/ 132 | /*! no static exports found */ 133 | /***/ (function(module, exports, __webpack_require__) { 134 | 135 | "use strict"; 136 | eval("\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar panel_config_1 = __webpack_require__(/*! ./panel_config */ \"./panel_config.ts\");\nvar value_formatter_1 = __webpack_require__(/*! ./value_formatter */ \"./value_formatter.ts\");\nvar _ = __webpack_require__(/*! lodash */ \"lodash\");\nvar Position;\n(function (Position) {\n Position[\"STATIC\"] = \"static\";\n Position[\"ABSOLUTE\"] = \"absolute\";\n})(Position || (Position = {}));\n/**\n * Model for the main component of the app -- bars, but it's not just a Bar,\n * it also keeps all small \"bars\", title and metainfo\n */\n\nvar ProgressBar = function () {\n function ProgressBar(_panelConfig, _title, _keys, // maybe \"_names\" is better than \"_keys\"\n _values, _maxTotalValue) {\n _classCallCheck(this, ProgressBar);\n\n this._panelConfig = _panelConfig;\n this._title = _title;\n this._keys = _keys;\n this._values = _values;\n this._maxTotalValue = _maxTotalValue;\n if (this._keys.length !== this._values.length) {\n throw new Error('keys amount should be equal to values amount');\n }\n this._bars = [];\n for (var i = 0; i < _keys.length; ++i) {\n this._bars.push({\n name: this._keys[i],\n value: this._values[i],\n color: mapValue2Color(this._values[i], this._keys[i], i, this._panelConfig)\n });\n }\n // bad code starts:\n }\n\n _createClass(ProgressBar, [{\n key: \"active\",\n get: function get() {\n return this._active;\n },\n set: function set(value) {\n this._active = value;\n }\n }, {\n key: \"title\",\n get: function get() {\n return this._title;\n }\n }, {\n key: \"keys\",\n get: function get() {\n return this._keys;\n }\n }, {\n key: \"values\",\n get: function get() {\n return this._values;\n }\n }, {\n key: \"bars\",\n get: function get() {\n return this._bars;\n }\n }, {\n key: \"sumOfValues\",\n get: function get() {\n return _.sum(this.values);\n }\n }, {\n key: \"percentValues\",\n get: function get() {\n var _this = this;\n\n return this.values.map(function (value) {\n return value / _this.sumOfValues * 100;\n });\n }\n }, {\n key: \"aggregatedProgress\",\n get: function get() {\n return this.sumOfValues / this._maxTotalValue * 100;\n }\n }, {\n key: \"totalValue\",\n get: function get() {\n var valueLabelType = this._panelConfig.getValue('valueLabelType');\n switch (valueLabelType) {\n case panel_config_1.ValueLabelType.ABSOLUTE:\n return this.sumOfValues;\n case panel_config_1.ValueLabelType.PERCENTAGE:\n return this.aggregatedProgress;\n default:\n throw new Error(\"Unknown value label type: \" + valueLabelType);\n }\n }\n }, {\n key: \"formattedTotalValue\",\n get: function get() {\n return value_formatter_1.getFormattedValue(this.totalValue, this._panelConfig.getValue('prefix'), this._panelConfig.getValue('postfix'), this._panelConfig.getValue('decimals'));\n }\n }, {\n key: \"valueFontSize\",\n get: function get() {\n var configSize = this._panelConfig.getValue('valueSize');\n if (configSize === undefined || configSize === null) {\n return panel_config_1.DEFAULT_FONT_SIZE;\n }\n return configSize;\n }\n }, {\n key: \"titleFontSize\",\n get: function get() {\n var configSize = this._panelConfig.getValue('titleSize');\n if (configSize === undefined || configSize === null) {\n return panel_config_1.DEFAULT_FONT_SIZE;\n }\n return configSize;\n }\n }, {\n key: \"colors\",\n get: function get() {\n return _.map(this._bars, function (bar) {\n return bar.color;\n });\n }\n // it should go somewhere to view\n\n }, {\n key: \"titleParams\",\n get: function get() {\n var titleType = this._panelConfig.getValue('titleViewType');\n switch (titleType) {\n case panel_config_1.TitleViewOptions.SEPARATE_TITLE_LINE:\n return {\n barHeight: 8,\n titleHeight: 16,\n position: Position.STATIC\n };\n case panel_config_1.TitleViewOptions.INLINE:\n return {\n barHeight: 24,\n titleHeight: 24,\n position: Position.ABSOLUTE\n };\n default:\n throw new Error(\"Wrong titleType: \" + titleType);\n }\n }\n }, {\n key: \"opacity\",\n get: function get() {\n return this._panelConfig.getValue('opacity');\n }\n }]);\n\n return ProgressBar;\n}();\n\nexports.ProgressBar = ProgressBar;\n/** VIEW **/\nfunction mapValue2Color(value, key, index, _panelConfig) {\n var colorType = _panelConfig.getValue('coloringType');\n var colors = _panelConfig.getValue('colors');\n switch (colorType) {\n case panel_config_1.ColoringType.PALLETE:\n return colors[index % colors.length];\n case panel_config_1.ColoringType.THRESHOLDS:\n // TODO: parse only once\n var thresholds = _panelConfig.getValue('thresholds').split(',').map(parseFloat);\n if (colors.length <= thresholds.length) {\n // we add one because a threshold is a cut of the range of values\n throw new Error('Number of colors must be at least as number as threasholds + 1');\n }\n for (var i = thresholds.length; i > 0; i--) {\n if (value >= thresholds[i - 1]) {\n return colors[i];\n }\n }\n return colors[0];\n case panel_config_1.ColoringType.KEY_MAPPING:\n var colorKeyMappings = _panelConfig.getValue('colorKeyMappings');\n var keyColorMapping = _.find(colorKeyMappings, function (k) {\n return k.key === key;\n });\n if (keyColorMapping === undefined) {\n return _panelConfig.getValue('colorsKeyMappingDefault');\n }\n return keyColorMapping.color;\n default:\n throw new Error('Unknown color type ' + colorType);\n }\n}\n\n//# sourceURL=webpack:///./progress_bar.ts?"); 137 | 138 | /***/ }), 139 | 140 | /***/ "./value_formatter.ts": 141 | /*!****************************!*\ 142 | !*** ./value_formatter.ts ***! 143 | \****************************/ 144 | /*! no static exports found */ 145 | /***/ (function(module, exports, __webpack_require__) { 146 | 147 | "use strict"; 148 | eval("\n\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar _ = __webpack_require__(/*! lodash */ \"lodash\");\nfunction getFormattedValue(value, prefix, postfix, decimals) {\n return \"\" + prefix + getFormattedFloat(value, decimals) + postfix;\n}\nexports.getFormattedValue = getFormattedValue;\nfunction getFormattedFloat(value, decimals) {\n var dm = getDecimalsForValue(value, decimals).decimals;\n if (dm === 0) {\n return Math.round(value).toString();\n }\n var fv = value;\n for (var i = 0; i < dm; i++) {\n fv *= 10;\n }\n ;\n var fvs = Math.round(fv).toString();\n return fvs.substr(0, fvs.length - dm) + '.' + fvs.substr(fvs.length - dm);\n}\nfunction getDecimalsForValue(value, decimals) {\n // based on https://github.com/grafana/grafana/blob/v4.1.1/public/app/plugins/panel/singlestat/module.ts\n if (_.isNumber(decimals)) {\n return {\n decimals: decimals,\n scaledDecimals: null\n };\n }\n var delta = value / 2;\n var dec = -Math.floor(Math.log(delta) / Math.LN10);\n var magn = Math.pow(10, -dec),\n norm = delta / magn,\n // norm is between 1.0 and 10.0\n size = void 0;\n if (norm < 1.5) {\n size = 1;\n } else if (norm < 3) {\n size = 2;\n // special case for 2.5, requires an extra decimal\n if (norm > 2.25) {\n size = 2.5;\n ++dec;\n }\n } else if (norm < 7.5) {\n size = 5;\n } else {\n size = 10;\n }\n size *= magn;\n // reduce starting decimals if not needed\n if (Math.floor(value) === value) {\n dec = 0;\n }\n var result = {};\n result.decimals = Math.max(0, dec);\n result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;\n return result;\n}\n\n//# sourceURL=webpack:///./value_formatter.ts?"); 149 | 150 | /***/ }), 151 | 152 | /***/ "grafana/app/plugins/sdk": 153 | /*!**********************************!*\ 154 | !*** external "app/plugins/sdk" ***! 155 | \**********************************/ 156 | /*! no static exports found */ 157 | /***/ (function(module, exports) { 158 | 159 | eval("module.exports = __WEBPACK_EXTERNAL_MODULE_grafana_app_plugins_sdk__;\n\n//# sourceURL=webpack:///external_%22app/plugins/sdk%22?"); 160 | 161 | /***/ }), 162 | 163 | /***/ "lodash": 164 | /*!*************************!*\ 165 | !*** external "lodash" ***! 166 | \*************************/ 167 | /*! no static exports found */ 168 | /***/ (function(module, exports) { 169 | 170 | eval("module.exports = __WEBPACK_EXTERNAL_MODULE_lodash__;\n\n//# sourceURL=webpack:///external_%22lodash%22?"); 171 | 172 | /***/ }) 173 | 174 | /******/ })});; --------------------------------------------------------------------------------