From b6de656cf1d783b1a3de83b3da70247d0e898020 Mon Sep 17 00:00:00 2001 From: Nathan LaFreniere Date: Thu, 14 Apr 2016 17:08:54 -0700 Subject: [PATCH 01/32] dont quote numbers for influxdb, closes #4163 --- public/app/plugins/datasource/influxdb/influx_query.ts | 4 +++- public/app/plugins/datasource/influxdb/query_builder.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 85457c7a8b3..8143e187b76 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -152,7 +152,9 @@ export default class InfluxQuery { if (interpolate) { value = this.templateSrv.replace(value, this.scopedVars); } - value = "'" + value.replace('\\', '\\\\') + "'"; + if (isNaN(+value)) { + value = "'" + value.replace('\\', '\\\\') + "'"; + } } else if (interpolate){ value = this.templateSrv.replace(value, this.scopedVars, 'regex'); } diff --git a/public/app/plugins/datasource/influxdb/query_builder.js b/public/app/plugins/datasource/influxdb/query_builder.js index 1921c672ba6..07d920350d7 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.js +++ b/public/app/plugins/datasource/influxdb/query_builder.js @@ -25,8 +25,8 @@ function (_) { } } - // quote value unless regex - if (operator !== '=~' && operator !== '!~') { + // quote value unless regex or number + if (operator !== '=~' && operator !== '!~' && isNaN(+value)) { value = "'" + value + "'"; } From 7e66d0bcc1b2a5049e1f5229f23770dff758851e Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 18 Apr 2016 15:25:06 +0200 Subject: [PATCH 02/32] fix(): remove only usage in tests --- public/test/core/utils/emitter_specs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/test/core/utils/emitter_specs.ts b/public/test/core/utils/emitter_specs.ts index f7076c46719..fec4d02a649 100644 --- a/public/test/core/utils/emitter_specs.ts +++ b/public/test/core/utils/emitter_specs.ts @@ -24,7 +24,7 @@ describe("Emitter", () => { expect(sub2Called).to.be(true); }); - it.only('should handle errors', () => { + it('should handle errors', () => { var events = new Emitter(); var sub1Called = 0; var sub2Called = 0; From fde6eee4f4ac9326ccad44059ef9ffff2927204c Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 18 Apr 2016 16:17:03 +0200 Subject: [PATCH 03/32] fix(influxdb): adds support for multi table values When quering for tag values without measurement all tags and values should be shown closes #4726 --- .../datasource/influxdb/response_parser.ts | 28 +++++++++++++------ .../influxdb/specs/response_parser_specs.ts | 26 +++++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index 2e33b398a88..ec0b1a4c249 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -12,17 +12,27 @@ export default class ResponseParser { return []; } - var series = influxResults.series[0]; - return _.map(series.values, (value) => { - if (_.isArray(value)) { - if (query.toLowerCase().indexOf('show tag values') >= 0) { - return { text: (value[1] || value[0]) }; + var res = []; + _.each(influxResults.series, (serie) => { + _.each(serie.values, (value) => { + if (_.isArray(value)) { + if (query.toLowerCase().indexOf('show tag values') >= 0) { + addUnique(res, { text: (value[1] || value[0])}); + } else { + addUnique(res, { text: value[0]}); + } } else { - return { text: value[0] }; + addUnique(res, {text: value}); } - } else { - return { text: value }; - } + }); }); + + return res; + } +} + +function addUnique(arr, value) { + if (!_.any(arr, value)) { + arr.push(value); } } diff --git a/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts b/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts index e58fe32dd1b..f545753d10e 100644 --- a/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts @@ -38,7 +38,7 @@ describe("influxdb response parser", () => { { "name": "hostnameTagValues", "columns": ["hostname"], - "values": [ ["server1"], ["server2"] ] + "values": [ ["server1"], ["server2"], ["server2"] ] } ] } @@ -54,7 +54,7 @@ describe("influxdb response parser", () => { }); }); - describe("response from 0.11.0", () => { + describe("response from 0.12.0", () => { var response = { "results": [ { @@ -62,8 +62,19 @@ describe("influxdb response parser", () => { { "name": "cpu", "columns": [ "key", "value"], - "values": [ [ "source", "site" ], [ "source", "api" ] ] - } + "values": [ + [ "source", "site" ], + [ "source", "api" ] + ] + }, + { + "name": "logins", + "columns": [ "key", "value"], + "values": [ + [ "source", "site" ], + [ "source", "webapi"] + ] + }, ] } ] @@ -72,15 +83,12 @@ describe("influxdb response parser", () => { var result = this.parser.parse(query, response); it("should get two responses", () => { - expect(_.size(result)).to.be(2); + expect(_.size(result)).to.be(3); expect(result[0].text).to.be('site'); expect(result[1].text).to.be('api'); + expect(result[2].text).to.be('webapi'); }); }); - - - - }); describe("SHOW FIELD response", () => { From 80818f80a99ab89fd4fbf85182200e5aa4109573 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 18 Apr 2016 17:12:53 +0200 Subject: [PATCH 04/32] tech(grunt): add check for not including "only" in tests --- public/test/core/utils/emitter_specs.ts | 2 +- tasks/default_task.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/public/test/core/utils/emitter_specs.ts b/public/test/core/utils/emitter_specs.ts index f7076c46719..fec4d02a649 100644 --- a/public/test/core/utils/emitter_specs.ts +++ b/public/test/core/utils/emitter_specs.ts @@ -24,7 +24,7 @@ describe("Emitter", () => { expect(sub2Called).to.be(true); }); - it.only('should handle errors', () => { + it('should handle errors', () => { var events = new Emitter(); var sub1Called = 0; var sub2Called = 0; diff --git a/tasks/default_task.js b/tasks/default_task.js index d2bebb89789..2aa0a7d8826 100644 --- a/tasks/default_task.js +++ b/tasks/default_task.js @@ -25,6 +25,19 @@ module.exports = function(grunt) { 'typescript:build' ]); - grunt.registerTask('test', ['default', 'karma:test']); + grunt.registerTask('test', ['default', 'karma:test', 'no-only-tests']); + grunt.registerTask('no-only-tests', function() { + var files = grunt.file.expand('public/**/*_specs\.ts', 'public/**/*_specs\.js'); + + files.forEach(function(spec) { + var rows = grunt.file.read(spec).split('\n'); + rows.forEach(function(row) { + if (row.indexOf('.only(') > 0) { + grunt.log.errorlns(row); + grunt.fail.warn('found only statement in test: ' + spec) + } + }); + }); + }); }; From 267ab822c2650f19f86e1c46460bf027cec98ef7 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 18 Apr 2016 18:46:50 +0200 Subject: [PATCH 05/32] tech(influxdb): uses hashmap for uniqueness --- .../datasource/influxdb/response_parser.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index ec0b1a4c249..cb1efea6b33 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -12,27 +12,29 @@ export default class ResponseParser { return []; } - var res = []; - _.each(influxResults.series, (serie) => { - _.each(serie.values, (value) => { + var newInfluxdbFormat = query.toLowerCase().indexOf('show tag values') >= 0; + + var res = {}; + _.each(influxResults.series, serie => { + _.each(serie.values, value => { if (_.isArray(value)) { - if (query.toLowerCase().indexOf('show tag values') >= 0) { - addUnique(res, { text: (value[1] || value[0])}); + if (newInfluxdbFormat) { + addUnique(res, value[1] || value[0]); } else { - addUnique(res, { text: value[0]}); + addUnique(res, value[0]); } } else { - addUnique(res, {text: value}); + addUnique(res, value); } }); }); - return res; + return _.map(res, value => { + return { text: value}; + }); } } function addUnique(arr, value) { - if (!_.any(arr, value)) { - arr.push(value); - } + arr[value] = value; } From c148e54bbd7347c911f6220a8aa2368ccd9fb195 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 18 Apr 2016 18:52:09 +0200 Subject: [PATCH 06/32] style(influxdb): improve naming --- public/app/plugins/datasource/influxdb/response_parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index cb1efea6b33..23173674361 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -12,13 +12,13 @@ export default class ResponseParser { return []; } - var newInfluxdbFormat = query.toLowerCase().indexOf('show tag values') >= 0; + var influxdb11format = query.toLowerCase().indexOf('show tag values') >= 0; var res = {}; _.each(influxResults.series, serie => { _.each(serie.values, value => { if (_.isArray(value)) { - if (newInfluxdbFormat) { + if (influxdb11format) { addUnique(res, value[1] || value[0]); } else { addUnique(res, value[0]); From bd21a08bc9c25dc7c03643ec0d91a22c0deab932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Apr 2016 17:33:51 -0400 Subject: [PATCH 07/32] fix(templating): fixed encoding of all value for regex, and custom all value, fixes #4755, fixes #4736 --- CHANGELOG.md | 7 +++++++ public/app/features/templating/templateSrv.js | 6 +++++- public/test/specs/templateSrv-specs.js | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8239bd437..0c57bfcfee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 3.0.0-beta6 (unreleased) + +### Bug fixes +* **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726) +* **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) +* **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736) + # 3.0.0-beta5 (2016-04-15) ### Bug fixes diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index 26dc77d36a9..f60414eac43 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -57,7 +57,7 @@ function (angular, _) { } var escapedValues = _.map(value, regexEscape); - return escapedValues.join('|'); + return '(' + escapedValues.join('|') + ')'; } case "lucene": { if (typeof value === 'string') { @@ -152,6 +152,10 @@ function (angular, _) { value = variable.current.value; if (self.isAllValue(value)) { value = self.getAllValue(variable); + // skip formating of custom all values + if (variable.allValue) { + return value; + } } var res = self.formatValue(value, format, variable); diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js index 4948e1272ce..3334d7a7bfe 100644 --- a/public/test/specs/templateSrv-specs.js +++ b/public/test/specs/templateSrv-specs.js @@ -99,6 +99,11 @@ define([ var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); expect(target).to.be('this.*.filters'); }); + + it('should not escape custom all value', function() { + var target = _templateSrv.replace('this.$test', {}, 'regex'); + expect(target).to.be('this.*'); + }); }); describe('lucene format', function() { @@ -127,7 +132,7 @@ define([ it('multi value and regex format should render regex string', function() { var result = _templateSrv.formatValue(['test.','test2'], 'regex'); - expect(result).to.be('test\\.|test2'); + expect(result).to.be('(test\\.|test2)'); }); it('multi value and pipe should render pipe string', function() { From 485a3778009fdffe974273f7f19b3471cb37f1f0 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 08:27:26 +0200 Subject: [PATCH 08/32] fix(cli): adds support for local plugin folder closes #4572 --- pkg/cmd/grafana-cli/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go index 5cc3fb6f306..b9549e00c1a 100644 --- a/pkg/cmd/grafana-cli/main.go +++ b/pkg/cmd/grafana-cli/main.go @@ -8,7 +8,6 @@ import ( "github.com/codegangsta/cli" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands" "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" - "strings" ) var version = "master" @@ -18,7 +17,7 @@ func getGrafanaPluginDir() string { defaultNix := "/var/lib/grafana/plugins" if currentOS == "windows" { - return "C:\\opt\\grafana\\plugins" + return "../data/plugins" } pwd, err := os.Getwd() @@ -29,16 +28,17 @@ func getGrafanaPluginDir() string { } if isDevenvironment(pwd) { - return "../../../data/plugins" + return "../data/plugins" } return defaultNix } func isDevenvironment(pwd string) bool { - // if grafana-cli is executed from the cmd folder we can assume + // if ../conf/defaults.ini exists, grafana is not installed as package // that its in development environment. - return strings.HasSuffix(pwd, "/pkg/cmd/grafana-cli") + _, err := os.Stat("../conf/defaults.ini") + return err == nil } func main() { From 5abaf26b5f529fe196e3fa112b3fd34b0614e159 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 08:32:23 +0200 Subject: [PATCH 09/32] style(cli): remove some logging --- pkg/cmd/grafana-cli/commands/remove_command.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/remove_command.go b/pkg/cmd/grafana-cli/commands/remove_command.go index e0ecbb2b788..9792ed9d095 100644 --- a/pkg/cmd/grafana-cli/commands/remove_command.go +++ b/pkg/cmd/grafana-cli/commands/remove_command.go @@ -3,7 +3,7 @@ package commands import ( "errors" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" + "fmt" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" ) @@ -15,22 +15,17 @@ func removeCommand(c CommandLine) error { pluginPath := c.GlobalString("pluginsDir") localPlugins := getPluginss(pluginPath) - log.Info("remove!\n") - plugin := c.Args().First() - log.Info("plugin: " + plugin + "\n") if plugin == "" { return errors.New("Missing plugin parameter") } - log.Infof("plugins : \n%v\n", localPlugins) - for _, p := range localPlugins { if p.Id == c.Args().First() { - log.Infof("removing plugin %s", p.Id) removePlugin(pluginPath, p.Id) + return nil } } - return nil + return fmt.Errorf("Could not find plugin named %s", c.Args().First()) } From 903d1b77970a08ab3f527b355c60c3388f34c6a6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 09:13:58 +0200 Subject: [PATCH 10/32] tech(cli): dont use defer statements in loops --- pkg/cmd/grafana-cli/commands/install_command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 81e7a15233b..b837c1990c1 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -164,14 +164,14 @@ func downloadFile(pluginName, filePath, url string) (err error) { return fmt.Errorf(permissionsDeniedMessage, newFile) } - defer dst.Close() src, err := zf.Open() if err != nil { log.Errorf("%v", err) } - defer src.Close() io.Copy(dst, src) + dst.Close() + src.Close() } } From a8c68e33db48dfd01b03a11c165c94b6a56aaf57 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 10:22:02 +0200 Subject: [PATCH 11/32] feat(cli): add more logging for failed install --- pkg/cmd/grafana-cli/commands/install_command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index b837c1990c1..409604a7c2e 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -126,8 +126,8 @@ func downloadFile(pluginName, filePath, url string) (err error) { defer func() { if r := recover(); r != nil { retryCount++ - if retryCount == 1 { - log.Debug("\nFailed downloading. Will retry once.\n") + if retryCount < 3 { + fmt.Printf("\nFailed downloading. Will retry once.\n%v\n", r) downloadFile(pluginName, filePath, url) } else { panic(r) From d38d4efc18d67dd8ab4b466dcaf65d46a403f725 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 10:39:55 +0200 Subject: [PATCH 12/32] style(cli): improve logging to find install crash --- pkg/cmd/grafana-cli/commands/install_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 409604a7c2e..c528cbfc5bd 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -166,7 +166,7 @@ func downloadFile(pluginName, filePath, url string) (err error) { src, err := zf.Open() if err != nil { - log.Errorf("%v", err) + log.Errorf("%Failed to open zipfile: v", err) } io.Copy(dst, src) From 4e8396dbcea9907f9b819f529b146dfbd9f1cc7d Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 13:41:35 +0200 Subject: [PATCH 13/32] fix(graph): sets legend table scroll to auto closes #4760 --- public/sass/components/_panel_graph.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index ca79034a0bd..b830561f816 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -85,7 +85,8 @@ } .graph-legend-table { - overflow-y: scroll; + overflow-y: auto; + overflow-x: hidden; .graph-legend-series { display: table-row; From ff22f430024d87dacbfbd75ad86fd9f0475b955d Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 14:46:03 +0200 Subject: [PATCH 14/32] fix(cli): fixes missplaced % --- pkg/cmd/grafana-cli/commands/install_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index c528cbfc5bd..addcf9a8b7e 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -166,7 +166,7 @@ func downloadFile(pluginName, filePath, url string) (err error) { src, err := zf.Open() if err != nil { - log.Errorf("%Failed to open zipfile: v", err) + log.Errorf("Failed to extract file: %v", err) } io.Copy(dst, src) From 44c1d8c1e54a9ca0338edaa65825b96798e802cf Mon Sep 17 00:00:00 2001 From: David Keijser Date: Fri, 8 Jan 2016 18:55:49 +0100 Subject: [PATCH 15/32] Add jquery.flot.gauge --- public/vendor/flot/jquery.flot.gauge.js | 955 ++++++++++++++++++++++++ 1 file changed, 955 insertions(+) create mode 100644 public/vendor/flot/jquery.flot.gauge.js diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js new file mode 100644 index 00000000000..02aa553b7fa --- /dev/null +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -0,0 +1,955 @@ +/*! + * jquery.flot.gauge v1.1.0 * + * + * Flot plugin for rendering gauge charts. + * + * Copyright (c) 2015 @toyoty99. + * Licensed under the MIT license. + */ + +/** + * @module flot.gauge + */ +(function($) { + + + /** + * Gauge class + * + * @class Gauge + */ + var Gauge = (function() { + /** + * context of canvas + * + * @property context + * @type Object + */ + var context; + /** + * placeholder of canvas + * + * @property placeholder + * @type Object + */ + var placeholder; + /** + * options of plot + * + * @property options + * @type Object + */ + var options; + /** + * options of gauge + * + * @property gaugeOptions + * @type Object + */ + var gaugeOptions; + /** + * data series + * + * @property series + * @type Array + */ + var series; + /** + * logger + * + * @property logger + * @type Object + */ + var logger; + + /** + * constructor + * + * @class Gauge + * @constructor + * @param {Object} gaugeOptions gauge options + */ + var Gauge = function(plot, ctx) { + context = ctx; + placeholder = plot.getPlaceholder(); + options = plot.getOptions(); + gaugeOptions = options.series.gauges; + series = plot.getData(); + logger = getLogger(gaugeOptions.debug); + } + + /** + * calculate layout + * + * @method calculateLayout + * @return the calculated layout properties + */ + Gauge.prototype.calculateLayout = function() { + + var canvasWidth = placeholder.width(); + var canvasHeight = placeholder.height(); + + + + // calculate cell size + var columns = Math.min(series.length, gaugeOptions.layout.columns); + var rows = Math.ceil(series.length / columns); + + + + var margin = gaugeOptions.layout.margin; + var hMargin = gaugeOptions.layout.hMargin; + var vMargin = gaugeOptions.layout.vMargin; + var cellWidth = (canvasWidth - (margin * 2) - (hMargin * (columns - 1))) / columns; + var cellHeight = (canvasHeight - (margin * 2) - (vMargin * (rows - 1))) / rows; + if (gaugeOptions.layout.square) { + var cell = Math.min(cellWidth, cellHeight); + cellWidth = cell; + cellHeight = cell; + } + + + + // calculate 'auto' values + calculateAutoValues(gaugeOptions, cellWidth); + + // calculate maximum radius + var cellMargin = gaugeOptions.cell.margin; + var labelMargin = 0; + var labelFontSize = 0; + if (gaugeOptions.label.show) { + labelMargin = gaugeOptions.label.margin; + labelFontSize = gaugeOptions.label.font.size; + } + var valueMargin = 0; + var valueFontSize = 0; + if (gaugeOptions.value.show) { + valueMargin = gaugeOptions.value.margin; + valueFontSize = gaugeOptions.value.font.size; + } + var thresholdWidth = 0; + if (gaugeOptions.threshold.show) { + thresholdWidth = gaugeOptions.threshold.width; + } + var thresholdLabelMargin = 0; + var thresholdLabelFontSize = 0; + if (gaugeOptions.threshold.label.show) { + thresholdLabelMargin = gaugeOptions.threshold.label.margin; + thresholdLabelFontSize = gaugeOptions.threshold.label.font.size; + } + + var maxRadiusH = (cellWidth / 2) - cellMargin - thresholdWidth - (thresholdLabelMargin * 2) - thresholdLabelFontSize; + + var startAngle = gaugeOptions.gauge.startAngle; + var endAngle = gaugeOptions.gauge.endAngle; + var dAngle = (endAngle - startAngle) / 100; + var heightRatioV = -1; + for (var a = startAngle; a < endAngle; a += dAngle) { + heightRatioV = Math.max(heightRatioV, Math.sin(toRad(a))); + } + heightRatioV = Math.max(heightRatioV, Math.sin(toRad(endAngle))); + var outerRadiusV = (cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize) / (1 + heightRatioV); + if (outerRadiusV * heightRatioV < valueMargin + (valueFontSize / 2)) { + outerRadiusV = cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize - valueMargin - (valueFontSize / 2); + } + var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth; + + var radius = Math.min(maxRadiusH, maxRadiusV); + + + var width = gaugeOptions.gauge.width; + if (width >= radius) { + width = Math.max(3, radius / 3); + } + + + var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius; + var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2)); + + return { + canvasWidth: canvasWidth, + canvasHeight: canvasHeight, + margin: margin, + hMargin: hMargin, + vMargin: vMargin, + columns: columns, + rows: rows, + cellWidth: cellWidth, + cellHeight: cellHeight, + cellMargin: cellMargin, + labelMargin: labelMargin, + labelFontSize: labelFontSize, + valueMargin: valueMargin, + valueFontSize: valueFontSize, + width: width, + radius: radius, + thresholdWidth: thresholdWidth, + thresholdLabelMargin: thresholdLabelMargin, + thresholdLabelFontSize: thresholdLabelFontSize, + gaugeOuterHeight: gaugeOuterHeight + }; + } + + /** + * calculate the values which are set as 'auto' + * + * @method calculateAutoValues + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} cellWidth the width of cell + */ + function calculateAutoValues(gaugeOptionsi, cellWidth) { + + if (gaugeOptionsi.gauge.width === "auto") { + gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8); + } + if (gaugeOptionsi.label.margin === "auto") { + gaugeOptionsi.label.margin = Math.max(1, cellWidth / 20); + } + if (gaugeOptionsi.label.font.size === "auto") { + gaugeOptionsi.label.font.size = Math.max(5, cellWidth / 8); + } + if (gaugeOptionsi.value.margin === "auto") { + gaugeOptionsi.value.margin = Math.max(1, cellWidth / 30); + } + if (gaugeOptionsi.value.font.size === "auto") { + gaugeOptionsi.value.font.size = Math.max(5, cellWidth / 9); + } + if (gaugeOptionsi.threshold.width === "auto") { + gaugeOptionsi.threshold.width = Math.max(3, cellWidth / 100); + } + if (gaugeOptionsi.threshold.label.margin === "auto") { + gaugeOptionsi.threshold.label.margin = Math.max(3, cellWidth / 40); + } + if (gaugeOptionsi.threshold.label.font.size === "auto") { + gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15); + } + + } + Gauge.prototype.calculateAutoValues = calculateAutoValues; + + /** + * calculate the layout of the cell inside + * + * @method calculateCellLayout + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} cellWidth the width of cell + * @param {Number} i the index of the series + * @return the calculated cell layout properties + */ + Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) { + + // calculate top, left and center + var c = col(layout.columns, i); + var r = row(layout.columns, i); + var x = layout.margin + (layout.cellWidth + layout.hMargin) * c; + var y = layout.margin + (layout.cellHeight + layout.vMargin) * r; + var cx = x + (layout.cellWidth / 2); + var cy = y + layout.cellMargin + (layout.labelMargin * 2) + layout.labelFontSize + layout.thresholdWidth + + layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2) + layout.radius; + var blank = layout.cellHeight - (layout.cellMargin * 2) - (layout.labelMargin * 2) - layout.labelFontSize - layout.gaugeOuterHeight; + var offsetY = 0; + if (gaugeOptionsi.cell.vAlign === "middle") { + offsetY = (blank / 2); + } else if (gaugeOptionsi.cell.vAlign === "bottom") { + offsetY = blank; + } + cy += offsetY; + + return { + col: c, + row: r, + x: x, + y: y, + offsetY: offsetY, + cellWidth: layout.cellWidth, + cellHeight: layout.cellHeight, + cellMargin: layout.cellMargin, + cx: cx, + cy: cy + } + } + + /** + * draw the background of chart + * + * @method drawBackground + * @param {Object} layout the layout properties + */ + Gauge.prototype.drawBackground = function(layout) { + + if (!options.grid.show) { + return; + } + context.save(); + context.strokeStyle = options.grid.borderColor; + context.lineWidth = options.grid.borderWidth; + context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight); + if (options.grid.backgroundColor) { + context.fillStyle = options.grid.backgroundColor; + context.fillRect(0, 0, layout.canvasWidth, layout.canvasHeight); + } + context.restore(); + } + + /** + * draw the background of cell + * + * @method drawCellBackground + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} cellLayout the cell layout properties + */ + Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) { + + context.save(); + if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { + context.strokeStyle = gaugeOptionsi.cell.border.color; + context.lineWidth = gaugeOptionsi.cell.border.width; + context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); + } + if (gaugeOptionsi.cell.background && gaugeOptionsi.cell.background.color) { + context.fillStyle = gaugeOptionsi.cell.background.color; + context.fillRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); + } + context.restore(); + } + + /** + * draw the gauge + * + * @method drawGauge + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {String} label the label of data + * @param {Number} data the value of the gauge + */ + Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) { + + + var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0; + + + // draw gauge frame + drawArcWithShadow( + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius, + layout.width, + toRad(gaugeOptionsi.gauge.startAngle), + toRad(gaugeOptionsi.gauge.endAngle), + gaugeOptionsi.gauge.stroke.color, // line color + gaugeOptionsi.gauge.stroke.width, // line width + gaugeOptionsi.gauge.frameColor, // fill color + blur); + + // draw gauge + var c1 = getColor(gaugeOptionsi, data); + var a2 = calculateAngle(gaugeOptionsi, layout, data); + drawArcWithShadow( + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius - 1, + layout.width - 2, + toRad(gaugeOptionsi.gauge.startAngle), + toRad(a2), + c1, // line color + 1, // line width + c1, // fill color + blur); + } + + /** + * decide the color of the data from the threshold options + * + * @method getColor + * @private + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} data the value of the gauge + */ + function getColor(gaugeOptionsi, data) { + var color; + for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { + var threshold = gaugeOptionsi.threshold.values[i]; + color = threshold.color; + if (data <= threshold.value) { + break; + } + } + return color; + } + + /** + * calculate the angle of the data + * + * @method calculateAngle + * @private + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Number} data the value of the gauge + */ + function calculateAngle(gaugeOptionsi, layout, data) { + var a = + gaugeOptionsi.gauge.startAngle + + (gaugeOptionsi.gauge.endAngle - gaugeOptionsi.gauge.startAngle) + * ((data - gaugeOptionsi.gauge.min) / (gaugeOptionsi.gauge.max - gaugeOptionsi.gauge.min)); + + if (a < gaugeOptionsi.gauge.startAngle) { + a = gaugeOptionsi.gauge.startAngle; + } else if (a > gaugeOptionsi.gauge.endAngle) { + a = gaugeOptionsi.gauge.endAngle; + } + return a; + } + + /** + * draw the arc of the threshold + * + * @method drawThreshold + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + */ + Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) { + + var a1 = gaugeOptionsi.gauge.startAngle; + for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { + var threshold = gaugeOptionsi.threshold.values[i]; + c1 = threshold.color; + a2 = calculateAngle(gaugeOptionsi, layout, threshold.value); + drawArc( + context, + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius + layout.thresholdWidth, + layout.thresholdWidth - 2, + toRad(a1), + toRad(a2), + c1, // line color + 1, // line width + c1); // fill color + a1 = a2; + } + } + + /** + * draw an arc with a shadow + * + * @method drawArcWithShadow + * @private + * @param {Number} cx the x position of the center + * @param {Number} cy the y position of the center + * @param {Number} r the radius of an arc + * @param {Number} w the width of an arc + * @param {Number} rd1 the start angle of an arc in radians + * @param {Number} rd2 the end angle of an arc in radians + * @param {String} lc the color of a line + * @param {Number} lw the widht of a line + * @param {String} fc the fill color of an arc + * @param {Number} blur the shdow blur + */ + function drawArcWithShadow(cx, cy, r, w, rd1, rd2, lc, lw, fc, blur) { + if (rd1 === rd2) { + return; + } + context.save(); + + drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc); + + if (blur) { + drawArc(context, cx, cy, r, w, rd1, rd2); + context.clip(); + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 10; + context.shadowColor = "gray"; + drawArc(context, cx, cy, r + 1, w + 2, rd1, rd2, lc, 1); + } + context.restore(); + } + + /** + * draw the label of the gauge + * + * @method drawLable + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Object} item the item of the series + */ + Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) { + + drawText( + cellLayout.cx, + cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY, + "flotGagueLabel" + i, + gaugeOptionsi.label.formatter ? gaugeOptionsi.label.formatter(item.label, item.data[0][1]) : text, + gaugeOptionsi.label); + } + + /** + * draw the value of the gauge + * + * @method drawValue + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Object} item the item of the series + */ + Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) { + + drawText( + cellLayout.cx, + cellLayout.cy - (gaugeOptionsi.value.font.size / 2), + "flotGagueValue" + i, + gaugeOptionsi.value.formatter ? gaugeOptionsi.value.formatter(item.label, item.data[0][1]) : text, + gaugeOptionsi.value); + } + + /** + * draw the values of the threshold + * + * @method drawThresholdValues + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + */ + Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) { + + // min, max + drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Min" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle); + drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Max" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle); + // threshold values + for (var j = 0; j < gaugeOptionsi.threshold.values.length; j++) { + var threshold = gaugeOptionsi.threshold.values[j]; + if (threshold.value > gaugeOptionsi.gauge.min && threshold.value < gaugeOptionsi.gauge.max) { + var a = calculateAngle(gaugeOptionsi, layout, threshold.value); + drawThresholdValue(gaugeOptionsi, layout, cellLayout, i + "_" + j, threshold.value, a); + } + } + } + + /** + * draw the value of the threshold + * + * @method drawThresholdValue + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Number} value the value of the threshold + * @param {Number} a the angle of the value drawn + */ + function drawThresholdValue(gaugeOptionsi, layout, cellLayout, i, value, a) { + drawText( + cellLayout.cx + + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) + * Math.cos(toRad(a))), + cellLayout.cy + + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) + * Math.sin(toRad(a))), + "flotGagueThresholdValue" + i, + gaugeOptionsi.threshold.label.formatter ? gaugeOptionsi.threshold.label.formatter(value) : value, + gaugeOptionsi.threshold.label, + a); + } + + /** + * draw a text + * + * the textOptions is assumed as follows: + * + * textOptions: { + * background: { + * color: null, + * opacity: 0 + * }, + * font: { + * size: "auto" + * family: "\"MS ゴシック\",sans-serif" + * }, + * color: null + * } + * + * @method drawText + * @private + * @param {Number} x the x position of the text drawn (left top) + * @param {Number} y the y position of the text drawn (left top) + * @param {String} id the id of the dom element + * @param {String} text the text drawn + * @param {Object} textOptions the option of the text + * @param {Number} [a] the angle of the value drawn + */ + function drawText(x, y, id, text, textOptions, a) { + var span = $("#" + id); + var exists = span.length; + if (!exists) { + span = $("") + span.attr("id", id); + span.css("position", "absolute"); + span.css("top", y + "px"); + if (textOptions.font.size) { + span.css("font-size", textOptions.font.size + "px"); + } + if (textOptions.font.family) { + span.css("font-family", textOptions.font.family); + } + if (textOptions.color) { + span.css("color", textOptions.color); + } + if (textOptions.background.color) { + span.css("background-color", textOptions.background.color); + } + if (textOptions.background.opacity) { + span.css("opacity", textOptions.background.opacity); + } + placeholder.append(span); + } + span.text(text); + // after append, readjust the left position + span.css("left", x + "px"); // for redraw, resetting the left position is needed here + span.css("left", (parseInt(span.css("left")) - (span.width()/ 2)) + "px"); + + // at last, set angle + if (!exists && a) { + span.css("top", (parseInt(span.css("top")) - (span.height()/ 2)) + "px"); + span.css("transform", "rotate(" + ((180 * a) + 90) + "deg)"); // not supported for ie8 + } + } + + return Gauge; + })(); + + /** + * get a instance of Logger + * + * @method getLogger + * @for flot.gauge + * @private + * @param {Object} debugOptions the options of debug + */ + function getLogger(debugOptions) { + return typeof Logger !== "undefined" ? new Logger(debugOptions) : null; + } + + /** + * calculate the index of columns for the specified data + * + * @method col + * @for flot.gauge + * @param {Number} columns the number of columns + * @param {Number} i the index of the series + * @return the index of columns + */ + function col(columns, i) { + return i % columns; + } + + /** + * calculate the index of rows for the specified data + * + * @method row + * @for flot.gauge + * @param {Number} columns the number of rows + * @param {Number} i the index of the series + * @return the index of rows + */ + function row(columns, i) { + return Math.floor(i / columns); + } + + /** + * calculate the angle in radians + * + * internally, use a number without PI (0 - 2). + * so, in this function, multiply PI + * + * @method toRad + * @for flot.gauge + * @param {Number} a the number of angle without PI + * @return the angle in radians + */ + function toRad(a) { + return a * Math.PI; + } + + /** + * draw an arc + * + * @method drawArc + * @for flot.gauge + * @param {Object} context the context of canvas + * @param {Number} cx the x position of the center + * @param {Number} cy the y position of the center + * @param {Number} r the radius of an arc + * @param {Number} w the width of an arc + * @param {Number} rd1 the start angle of an arc in radians + * @param {Number} rd2 the end angle of an arc in radians + * @param {String} lc the color of a line + * @param {Number} lw the widht of a line + * @param {String} fc the fill color of an arc + */ + function drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc) { + if (rd1 === rd2) { + return; + } + var counterClockwise = false; + context.save(); + context.beginPath(); + context.arc(cx, cy, r, rd1, rd2, counterClockwise); + context.lineTo(cx + (r - w) * Math.cos(rd2), + cy + (r - w) * Math.sin(rd2)); + context.arc(cx, cy, r - w, rd2, rd1, !counterClockwise); + context.closePath(); + if (lw) { + context.lineWidth = lw; + } + if (lc) { + context.strokeStyle = lc; + context.stroke(); + } + if (fc) { + context.fillStyle = fc; + context.fill(); + } + context.restore(); + } + + /** + * initialize plugin + * + * @method init + * @for flot.gauge + * @private + * @param {Object} plot a instance of plot + */ + function init (plot) { + // add processOptions hook + plot.hooks.processOptions.push(function(plot, options) { + var logger = getLogger(options.series.gauges.debug); + + + + + // turn 'grid' and 'legend' off + if (options.series.gauges.show) { + options.grid.show = false; + options.legend.show = false; + } + + // sort threshold + var thresholds = options.series.gauges.threshold.values; + + thresholds.sort(function(a, b) { + if (a.value < b.value) { + return -1; + } else if (a.value > b.value) { + return 1; + } else { + return 0; + } + }); + + + + }); + + // add draw hook + plot.hooks.draw.push(function(plot, context) { + var options = plot.getOptions(); + var gaugeOptions = options.series.gauges; + + var logger = getLogger(gaugeOptions.debug); + + + if (!gaugeOptions.show) { + return; + } + + var series = plot.getData(); + + if (!series || !series.length) { + return; // if no series were passed + } + + var gauge = new Gauge(plot, context); + + // calculate layout + var layout = gauge.calculateLayout(); + + // debug layout + if (gaugeOptions.debug.layout) { + + } + + // draw background + gauge.drawBackground(layout) + + // draw cells (label, gauge, value, threshold) + for (var i = 0; i < series.length; i++) { + var item = series[i]; + + var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges); + if (item.gauges) { + // re-calculate 'auto' values + gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth); + } + + // calculate cell layout + var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i); + + // draw cell background + gauge.drawCellBackground(gaugeOptionsi, cellLayout) + // debug layout + if (gaugeOptionsi.debug.layout) { + + } + // draw label + if (gaugeOptionsi.label.show) { + gauge.drawLable(gaugeOptionsi, layout, cellLayout, i, item); + } + // draw gauge + gauge.drawGauge(gaugeOptionsi, layout, cellLayout, item.label, item.data[0][1]); + // draw threshold + if (gaugeOptionsi.threshold.show) { + gauge.drawThreshold(gaugeOptionsi, layout, cellLayout); + } + if (gaugeOptionsi.threshold.label.show) { + gauge.drawThresholdValues(gaugeOptionsi, layout, cellLayout, i) + } + // draw value + if (gaugeOptionsi.value.show) { + gauge.drawValue(gaugeOptionsi, layout, cellLayout, i, item); + } + } + }); + } + + /** + * [defaults description] + * + * @property defaults + * @type {Object} + */ + var defaults = { + series: { + gauges: { + debug: { + log: false, + layout: false, + alert: false + }, + show: false, + layout: { + margin: 5, + columns: 3, + hMargin: 5, + vMargin: 5, + square: false + }, + cell: { + background: { + color: null + }, + border: { + color: "black", + width: 1 + }, + margin: 5, + vAlign: "middle" // 'top' or 'middle' or 'bottom' + }, + gauge: { + width: "auto", // a specified number, or 'auto' + startAngle: 0.9, // 0 - 2 factor of the radians + endAngle: 2.1, // 0 - 2 factor of the radians + min: 0, + max: 100, + shadow: { + show: true, + blur: 5 + }, + stroke: { + color: "lightgray", + width: 2 + }, + frameColor: "white" + }, + label: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: "sans-serif" + }, + color: null, + formatter: function(label, value) { + return label; + } + }, + value: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: "sans-serif" + }, + color: null, + formatter: function(label, value) { + return parseInt(value); + } + }, + threshold: { + show: true, + width: "auto", // a specified number, or 'auto' + label: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: ",sans-serif" + }, + color: null, + formatter: function(value) { + return value; + } + }, + values: [ + { + value: 50, + color: "lightgreen" + }, { + value: 80, + color: "yellow" + }, { + value: 100, + color: "red" + } + ] + } + } + } + }; + + // register the gauge plugin + $.plot.plugins.push({ + init: init, + options: defaults, + name: "gauge", + version: "1.1.0" + }); + +})(jQuery); From c455e501ac89a9e87eb977c81b45344eb2491266 Mon Sep 17 00:00:00 2001 From: David Keijser Date: Wed, 3 Feb 2016 17:47:46 +0100 Subject: [PATCH 16/32] Add gauge option to singlestat --- .../app/plugins/panel/singlestat/editor.html | 37 +++++++++ public/app/plugins/panel/singlestat/module.ts | 80 ++++++++++++++++++- public/app/system.conf.js | 3 +- public/test/test-main.js | 3 +- public/vendor/flot/jquery.flot.gauge.js | 29 ++++--- 5 files changed, 137 insertions(+), 15 deletions(-) diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index 6b0806133b8..437445a2400 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -156,6 +156,43 @@ +
+
+
+
    +
  • + Gauge +
  • +
  • + Show  + + +
  • +
  • + Threshold labels  + + +
  • +
  • + Min +
  • +
  • + +
  • +
  • + Max +
  • +
  • + +
  • +
+
+
+
+
+
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 8af684c9d68..19909f08441 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -4,6 +4,7 @@ import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; import 'jquery.flot'; +import 'jquery.flot.gauge'; import kbn from 'app/core/utils/kbn'; import TimeSeries from 'app/core/time_series2'; @@ -38,6 +39,12 @@ var panelDefaults = { full: false, lineColor: 'rgb(31, 120, 193)', fillColor: 'rgba(31, 118, 189, 0.18)', + }, + gauge: { + show: false, + minValue: 0, + maxValue: 100, + thresholdLabels: true } }; @@ -270,6 +277,73 @@ class SingleStatCtrl extends MetricsPanelCtrl { return body; } + function addGauge() { + var plotCanvas = $('
'); + var plotCss = { + top: '10px', + margin: 'auto', + position: 'relative', + height: elem.height() + 'px', + width: elem.width() + 'px' + }; + + plotCanvas.css(plotCss); + + var thresholds = []; + for (var i = 0; i < data.thresholds.length; i++) { + thresholds.push({ + value: data.thresholds[i], + color: data.colorMap[i] + }); + } + thresholds.push({ + value: panel.gauge.maxValue, + color: data.colorMap[data.colorMap.length - 1] + }); + + var options = { + series: { + gauges: { + gauge: { + min: panel.gauge.minValue, + max: panel.gauge.maxValue, + background: { color: 'rgb(38,38,38)'}, + border: { color: null }, + shadow: { show: false }, + width: 38 + }, + frame: { show: false }, + label: { show: false }, + layout: { margin: 0 }, + cell: { border: { width: 0 } }, + threshold: { + values: thresholds, + label: { + show: panel.gauge.thresholdLabels, + margin: 8, + font: { size: 18 } + }, + width: 8 + }, + value: { + color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null, + formatter: function () { return data.valueFormated; }, + font: { size: 30 } + }, + show: true + } + } + }; + + elem.append(plotCanvas); + + var plotSeries = { + data: [[0, data.valueRounded]] + }; + + $.plot(plotCanvas, [plotSeries], options); + } + function addSparkline() { var width = elem.width() + 20; if (width < 30) { @@ -335,7 +409,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { data = ctrl.data; setElementHeight(); - var body = getBigValueHtml(); + var body = panel.gauge.show ? '' : getBigValueHtml(); if (panel.colorBackground && !isNaN(data.valueRounded)) { var color = getColorForValue(data, data.valueRounded); @@ -358,6 +432,10 @@ class SingleStatCtrl extends MetricsPanelCtrl { addSparkline(); } + if (panel.gauge.show) { + addGauge(); + } + elem.toggleClass('pointer', panel.links.length > 0); if (panel.links.length > 0) { diff --git a/public/app/system.conf.js b/public/app/system.conf.js index 276988e5c34..40b70c0e30e 100644 --- a/public/app/system.conf.js +++ b/public/app/system.conf.js @@ -27,7 +27,8 @@ System.config({ "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent", "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", - "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow" + "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", + "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" }, packages: { diff --git a/public/test/test-main.js b/public/test/test-main.js index d40955022fe..dbee086c250 100644 --- a/public/test/test-main.js +++ b/public/test/test-main.js @@ -35,7 +35,8 @@ "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent", "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", - "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow" + "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", + "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" }, packages: { diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js index 02aa553b7fa..d8ed958e990 100644 --- a/public/vendor/flot/jquery.flot.gauge.js +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -277,7 +277,7 @@ */ Gauge.prototype.drawBackground = function(layout) { - if (!options.grid.show) { + if (!gaugeOptions.frame.show) { return; } context.save(); @@ -301,7 +301,7 @@ Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) { context.save(); - if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { + if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { context.strokeStyle = gaugeOptionsi.cell.border.color; context.lineWidth = gaugeOptionsi.cell.border.width; context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); @@ -337,9 +337,9 @@ layout.width, toRad(gaugeOptionsi.gauge.startAngle), toRad(gaugeOptionsi.gauge.endAngle), - gaugeOptionsi.gauge.stroke.color, // line color - gaugeOptionsi.gauge.stroke.width, // line width - gaugeOptionsi.gauge.frameColor, // fill color + gaugeOptionsi.gauge.border.color, // line color + gaugeOptionsi.gauge.border.width, // line width + gaugeOptionsi.gauge.background.color, // fill color blur); // draw gauge @@ -583,7 +583,7 @@ * @param {Number} [a] the angle of the value drawn */ function drawText(x, y, id, text, textOptions, a) { - var span = $("#" + id); + var span = $("." + id, placeholder); var exists = span.length; if (!exists) { span = $("") @@ -621,7 +621,6 @@ return Gauge; })(); - /** * get a instance of Logger * @@ -849,11 +848,15 @@ vMargin: 5, square: false }, + frame: { + show: true + }, cell: { background: { color: null }, border: { + show: true, color: "black", width: 1 }, @@ -866,15 +869,17 @@ endAngle: 2.1, // 0 - 2 factor of the radians min: 0, max: 100, - shadow: { - show: true, - blur: 5 + background: { + color: "white" }, - stroke: { + border: { color: "lightgray", width: 2 }, - frameColor: "white" + shadow: { + show: true, + blur: 5 + } }, label: { show: true, From 0c6841bdc77639d7a8bc25071269be648bb0a75c Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 14:44:29 +0200 Subject: [PATCH 17/32] fix(singlestat): adds support for fontsizes in gagues --- public/app/plugins/panel/singlestat/module.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 19909f08441..c8ebd0b0561 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -328,7 +328,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { value: { color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null, formatter: function () { return data.valueFormated; }, - font: { size: 30 } + font: { size: getGaugeFontSize() } }, show: true } @@ -344,6 +344,15 @@ class SingleStatCtrl extends MetricsPanelCtrl { $.plot(plotCanvas, [plotSeries], options); } + function getGaugeFontSize() { + if (panel.valueFontSize) { + var num = parseInt(panel.valueFontSize.substring(0, panel.valueFontSize.length - 1)); + return 30 * (num / 100); + } else { + return 30; + } + } + function addSparkline() { var width = elem.width() + 20; if (width < 30) { @@ -405,7 +414,6 @@ class SingleStatCtrl extends MetricsPanelCtrl { function render() { if (!ctrl.data) { return; } - data = ctrl.data; setElementHeight(); From 0108b5eb883841b78d9b7986f941905065007dd9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Apr 2016 14:23:51 +0200 Subject: [PATCH 18/32] fix(singlestat): adds support for lighttheme --- public/app/plugins/panel/singlestat/module.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index c8ebd0b0561..6d60a70269e 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -7,6 +7,7 @@ import 'jquery.flot'; import 'jquery.flot.gauge'; import kbn from 'app/core/utils/kbn'; +import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; @@ -283,7 +284,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { top: '10px', margin: 'auto', position: 'relative', - height: elem.height() + 'px', + height: (elem.height() * 0.9) + 'px', width: elem.width() + 'px' }; @@ -301,13 +302,17 @@ class SingleStatCtrl extends MetricsPanelCtrl { color: data.colorMap[data.colorMap.length - 1] }); + var bgColor = config.bootData.user.lightTheme + ? 'rgb(230,230,230)' + : 'rgb(38,38,38)'; + var options = { series: { gauges: { gauge: { min: panel.gauge.minValue, max: panel.gauge.maxValue, - background: { color: 'rgb(38,38,38)'}, + background: { color: bgColor }, border: { color: null }, shadow: { show: false }, width: 38 From 8d1ac8c7f585e8174b53d3d9afe9c3d3018d055c Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 20 Apr 2016 14:33:08 +0900 Subject: [PATCH 19/32] (cloudwatch) add kinesis metrics --- pkg/api/cloudwatch/metrics.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index f5e5274a202..5ecdaabf516 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -56,7 +56,7 @@ func init() { "HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"}, "AWS/ES": {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueLength", "ReadIOPS", "WriteIOPS"}, "AWS/Events": {"Invocations", "FailedInvocations", "TriggeredRules", "MatchedEvents", "ThrottledRules"}, - "AWS/Kinesis": {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"}, + "AWS/Kinesis": {"GetRecords.Bytes", "GetRecords.IteratorAge", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Records", "GetRecords.Success", "IncomingBytes", "IncomingRecords", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "ReadProvisionedThroughputExceeded", "WriteProvisionedThroughputExceeded", "IteratorAgeMilliseconds", "OutgoingBytes", "OutgoingRecords"}, "AWS/Lambda": {"Invocations", "Errors", "Duration", "Throttles"}, "AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"}, "AWS/ML": {"PredictCount", "PredictFailureCount"}, @@ -88,7 +88,7 @@ func init() { "AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"}, "AWS/ES": {}, "AWS/Events": {"RuleName"}, - "AWS/Kinesis": {"StreamName"}, + "AWS/Kinesis": {"StreamName", "ShardID"}, "AWS/Lambda": {"FunctionName"}, "AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"}, "AWS/ML": {"MLModelId", "RequestMode"}, From 3925bcb94cae91181259907fcc65c95062baaf33 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 20 Apr 2016 08:47:16 -0700 Subject: [PATCH 20/32] Updating Elasticsearch logo --- .../elasticsearch/img/logo_large.png | Bin 40972 -> 38427 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/img/logo_large.png b/public/app/plugins/datasource/elasticsearch/img/logo_large.png index 429dedac1a7c94faa89a84d907e93c637975a18a..b76d01e5d74cf26415174edce6852366edf7c42e 100644 GIT binary patch literal 38427 zcmdSB1y>zGlrD-K+}+`DaQEPD2X}|y7TkgdcY=EeF2SAP!QI{6U4z3TGjr$Och|Z< zV0x`yUA_COy{oEscUSHDsw0#YrBM(G5g{NTP-MUost^#6asPIB*bj;SpHHJ72Y{uh zf+z$;O)S!jG0ev~&=jny00H4e{ecLEfOz_cJcNL7V}*b?HiCfQOM`&GbIfc}5%{=( z;0V@nfq+25`nN+uWMtxhXrHrI*LKxbkmoaXuwynhb1*Sy_ONsOKtn(Xc<_BB?aW<` zfgX0Y_AY!Lf}sCE@O`BJiCI9v|A4sK2!gZ~l!4+7&gMWaW=>{SkPspe2o!KOv*1&e zkovFeA7_FfD_2)XJ{A^tcXwuY4rT{uOBOa>US1Yfb{2MarVj*@i>JM-u?LgA3&npb z`Cob@%w0^KtsPyh9qfVs^ctHuxVZ{~K>rN=_x7LVbhWnl-1&uyb@acX9d9FT^JBAISfo?0?(&PrQ<|wfRR$|CQ&z@&Bjn|Hdm@ zIk-A}w3@TEsf@j=x$}py|H{q&U)KKLi2u?h!1Ax+|3^doXDk1Q^rM-D5CvHN`<4kI zLM{4#c!&sujD)DV2jr{uoAp|6zyWNPk9~^Gsd}Whg)ry6R@^ zV)RZ0&)1#B2vcNfL?&1LG9~xZ#oSmMtt0N+$3tQMuTK{h1}A?g0P6&U{juAN-?{mm zRzLE*`5vVjxvKh%!Lvd|Lr^H&1H!?-n2mqgb*sy!2?};kAGj)+BS@wr$~?lE&+Lgk z*6uNSFAW0_EcGbhS>aSEP;`{$CIJCW5pp3l(5uk+kZ5=Jh@z+W01)Qp4CDGR5kV=E1 zaS=iTTu%bhw}|{)MySmZ=~{pP!d)EjAGf&XlQIO?q%@`ds*K`OGq^77yT=burRW3{ z%!a#sOB90$YQQ#lsK90aq&G+_A%U;I<`Y5dND_3^%%MM`Zw9Vej1P>Fn;gP3&B+R@ zx7cbYXtEz*i*_xK3lUD~WO=4TPK@&5-E9tAAeKgeqZ-~kZ0@Flf>xcegr7s|c9xgD zV+Z#@mh>VG-oT(|L#McYUiEWTyvX>jRD4rFA1*$1Cm6|{-G!FNN=^S5x9z5kt1yMNYway(skjS(O7ZYxz5A6cH1r5zIB#sz!k!q&?S7benc zB1Oq4zNOsgy_3_K&x60*Wetxfjm9!5$am;>eJdbk#;Xpyq|5-G>Zyj;dLc68u! z>aIRB^JGiE*@11gqE33smH+{(p6O~ZABt)HX~%sHjGWnW z6k(_nj4NiU(SCaqTY@+%aT`K%2(v*}j|8`6p}(Q=Un)IiRpa{SgKwl(%^jr9`3y z14oFd9wNvl4CzsJ+(k9kp^h(M9SKNqdq2@3hbl8a1A_Lq3vtC&#*^Q<9snUth@cT5 zt+Af$VL&h(qClL#$>;C^0!Yf@nvh?ybiBu`mq

Omjk$)=+h|7g9Z3&w#>yDrX?G*GS=KdS^II7sD$ zlZBi9Y)?=!!Wq?6!Z*xrKJN<>RguvGrNsdpFP#G6Ss~Mb%EV{bP}+-@tC8t8m1(8F zEzcj(hc=>7?;7W&n#0W)BFNi~V%4`K*wbcsV2p>xMRp+t;kjL@Hw73S1GNu-MGTD? zX|))4|K0n07v_tWK$}cWnSZNE#lUE(?)G7!q=lNnFaSD+BGoZ-wZpOfy3M_(=r2ZGZZ!>5?FAuY>6YF zVhq2nd1HA`!qjp>|!O^HqOyMfHpscbakMfJ}hj&OLi~(aIm;y+q1C90=eaP~0`zTgfUCBJ3pDZ64y91R zw@}S@vw>|w0f<@4)@;p_si5v5XguztN3$D(q5`+LgZUMylnxjIC|IA2;O z)Wv@K^u_*9<894F(+&2*)oF@LrDoO@8WT$YG5Oh+hFM0&jD_AnoN=0VJ2GazC85CD zuYq&++VPIL6r$Qf5(ETHyEn`z4B_R*w@YYjmDYqml01{S7+qV+JMfor$~!0mEnb2K zDmDgM@)r;8Mk}Qu5%n(vapvn_qA$pW`n4_HsVoX;mRqpW^%MA*Uk9%u;O|qW9OrMi z=$2uypIkp{2~Yl#o4GFbSp+1F@Hk@uzGBbHGGC}+Puqb?{$;hU(aRo+AlR5BgB z)SL_{HFE0KJ?$`)+9VoGyCB1D3&@YmNEGC_nQ=pgl$8LMkkar5>)r&>Z$+=!O0LG zW%RC`$@zHfu?iV#kPz{UJ0;Yt95Ih={n00$genX6eK-rlZxBBWYV6G@)f?QB-6wxl z(9gTusViY4{qd>XX|6a}N1mFs)FT>0btGY}nttgeSc_3++!o5vQv*_pLtuZ>X|hl( z=y;w#TsCFxN|H#kV+svUc%`~5BeT_W_dx2~feFu_D*bLl4Q!Z-DV__Qck7jy1^NyI z*hN|Ns*U}Y6zjiZrd(Jw6bS0_lds&V@2%wqvrJEB27npJM@5>)1+5sHj_=;U_LTV@ z1?q>;Bqx7=Qi8g_HGC=4yF4(|J1enCETi>%>(C%#k8!fJNo>e-n56m)l^G^LvA~AQ z7~tG)swt+OGBSKu17rMVMgK;D$PVscc!f zsGk=09MI*ju3c`v%lz|I|0Xxwu9Am$GocdN__#M{{gXVEs*D`5@HFMLHIlbn98Xl1 zqY*#~C_>C!fj89Yx7>_xMauhl8HhF`2qHB*mpdRXC4$@+DQ$fXm5UYCk1S7~bJ5cp zOUSzFD++&{4|tn=BhL(HWC=3IdB>bAI32nA&)mn_>FA27MYsxQ*$o? zKITU7%Z1i3n? z-{^*x7{K-)Q^JRZH34%vDYquCf=CYIe+{bO5Wh;5KA{_-7CbH6HuV-G%%ND*S$@k0ip`)`-3AundKNV_ca;=K_u){(TtR#Ger-ZKD7GCRko0O&{ z{qYnNpb4{K0j1jYo#XQq)b`a&BaWRtwX1_mua_K|W|Gpv@Rv4$0dHK|>4(MGbekeR`bV(Vh zf5E*asS+S?CjloJVZqdBq>G72@a13;>`VM&Tpbyn1>s~d5px8(-0A8eBYDNsBo}}C zW7@OziYOt$j!|;ie-owE*i}S7SyC;QP>6FR0Jd#-wmHzJRK30`s(zGpB(;v7QnpNu z^*~pr?9X%D))v>K$-9@kcepl`GU9|TB&fzUX)fw{(`=5CAsEO_%G-cEh<1C@-NnQ>*-H zCwKrm!Es(wU8gjUVKS7oml!|~1aTbnO7e7{NKgkEkPM8)iT9f!Hu@H|l$=fX=(%pd z0nI8j2Ab4u=hFSvZD$@}^3iA?JdBE-{ZOn|_q+1e?__PPxp30L!zcOT)9ZFX*!zN# zMS;d^?a&lrBbc~nO3)5d7dCqCha2$Fw^Y!-)56uY!;DD4AzWa4C+Ck>XhoS2;is{c zL73JP41^LNEp*C$=Nj0Kd+kJjZ-=km2`f!LS!9qrJe^|N13}NhVC`>jy*)_pnIOz7 zASmITszZ+%&?Gt>@6p6+dj_Kn+Y@pkC&Z0X9M%TwPV0JH+%9NB5U4?>VjAOc@*=|#-?G|V zcw;}F;2UlXqFxG_UU8#jqdP~^{4yyCN`l`Kh7J^@xzq)|rXY4g>JHKfh-P6YI8534 zz;J)8M?J4{UW`LnX{o(3XJF@dWz^Y$L99Awx|SDXH*Wo!F!>X#Af0h&)3c=eOMHsf zgbnHtFCSJ)*e60(_)ZjoWd8{8&`Rzw`)veR-vva*KUH@LE&6zOkEwrI!}!q;q(eF_ zwv;7-BhpmOrnmPS?^xCqqEPmv*4G?~XCt4&5x^>2Kee|59Lfyz zCfX6p8sDQnT{7#Qn>?k2Cnrq%%Bk=ddSG5SYb^!SUqi}LF` zWL)E9H1D4-A_9ZIapdt@twWl3=008JM9ST^$uDEmcoP|AAScMw^3R7QHFIl9{!Q6| zry&kh$Wu2wInjCGyZ$Np*{3Y;mP->%4K%l%^!GS@I@V$MSufgA(Y>1mFtr|YCT(`K z87U8DdQy6lb+ZU_ncBKx!{{^-KlM(uh999$Gw~^_vYgV{h040uA;YT46EuQS<+K`5 z;<+q6xi~QPEu%9T4KnRTUN0?6$Hs{7>WBmP_H4?s60_p5e;e2;hJ^a7EZY@Tl?i9J zwczbnpLg~#JQe?;3WM>W?E}C+-8S{nMa8GwTA}!FX&%cn6SZriCMSmWp#~tNLt6U?~A~>yk8-DAW)ttQ;bN=c0auM}vk(}3_4owF$ z%T&z0(B@&$Q(U$GGt+!k|c2iNcdSG=4wssJcAoQRp6*JYFIWqnZzuNhul7U0j zi%2&=jEu?`KBE3h%er|bu?vvui@%6fyax1P{km`c}adH(WK&YUFUe%z`oXQJfdBi>LkYTL7{xvxT z)`jjb+kkw@w>whPF2(Z=YhU>Ezh6hAV{yJ;a6oxtT)r?BFDDx}9Kx^NF%gaNj}4#?6|=Mf(K)Ej^Nu@8IW{*1L~F=XV@Q*#OO1^aaVRq!4<0=bZziuTIoD5BsnpB!D;2?g;A^-*M?JNG^;y;^@=qx6ufud8-%lB?PZL3spGgJh4bzn zs!vMea*s3k86AWr3W2Dya$P)m7Y>I&# zh(x)J!ci(k8(VZd+%N_?gez`YtF7}v=4Q_AraQ(@Za5w2Dr9E2y3b`xbnmYyqBhNz zq#G&WI*d|0EB(`BTz2=M{SsfpWSs6OM5yHXyS|G5R*BQP6$i(1Tcu^b>dD|-7H+QB z1(diJCG?;cmPtv!aNLiN-?dI!jYdvZPfQ&I0Smn)mLz$qUqH@r+U(A5?YO!u24O&s z;?S1MnWkF)>_5IUX&@ocYvGE;I9*Y(tohZ>LrQ=1@0FyMPU2d$CsBFjvay@wiQw_P z#*iyHHBHo2KwLPdg#=F~!B=*fWt2fDK6`4_%)S)E1)1e*0J@bygvM=HDZx0X=Flb#THdmaw-hNhRZn)$p_DdlxwJGlt#X;lR(@v?e4P4w2X|5Y6gMc# zzM$fyFtV}1`6C$n;H1T5yrU}1_cOccIV(zjmmAJdmB;rn&Ki;vw{5ZTxY0}^%ewU% zkGgw~@iX0;Kr^!18;)a1PikKm4%@g6b&v zFtVSu-<4il@tS1%!-tVp`^m49lMVEFhB$e=hA{kJ8*nw~oW-ZX!O+j?_b8w(0dao+{&Ur5s62$jE2giYFX%#yvy|Y8 z*ax6Qqs40B8ucx2!LVOuA;j;N${6*Phi?&+oQ^^YV~m&W9z(du{Y>B1?NYCBt%62vlThuL_oXA_Yc!J!vv5-s`;1pi8ms5Kvsiu$6jmHvJ>FQ-ZcAF- zeWiJPJ-g60(gsc|V>(pIjBnq8nAoP+^k|4Y-e^g?5-}_Wva0(`tcNHER2~MZ+>F!a z0Ap@U!E~uK!Lbd&){E76OMXxi4n_<uHAp7$yAY)t&ZHZsiy z7Bg#PbpYF+v6BH3FF|ohH>vML3=!h^NCrLN5*hr8B z5iwlBT8uSvqs~{2ThB;SIXcq1G=~|=dU^`vFW#Q|nS&wqM|vXATa*x+o2U2y`y<8Y z%5vYn$-6dz@^}tsz1yL50k}UB@W9QSVdJjY6u2jeOeB&jjwj;wz$Fg4Kgkt}% ziN7e?k3LM}`nC4s5d2ci3jZJg<5?cZ+R)cg1H|4WU>tE^JHB~!Iurc~-|NsB{DM4? zZ4@$|h%`^am!b!+z?0`yNAtBg3rQX)n*)x0s2`~*w(Bh=c0Aj{O`T9%eLo0#UR|D@FJEBSeOL8Az7E zJU&GG{F(c-kyR;X5iUnXgYX^z1_Tq(luQBhtKZ$K!h^_oK+qq+?&YbPZ6H1_1z|ue zn__)^fuzd_`h55%>hHnX99S;@MTF;<2uft@nqNxD#=isP-@ zdmbIgUS7d-wQ7S6zeTFHqFSyccf7d*AwHavlX9&5(kZ%6Po7-zTL{K4Apkg2&f<5? zl9-fCao4l6x*>&PU9Y@I5j@aWpo7OI65-M&M0!*8BOI>z;CjL+DaI=6X0LM+iw4dA zGL)c1iQO23v#r@a8B3!Qj0N{UY9m_GM9hVU26$*nNh=_Njmf z>6uweE8oew3-gq2bP8on^cFoH^`VsLdM zsLN6qjcOv`O}gOSC_@AgDi3$aZKM#Vag12U5?z~VBza|o*j^`9mZduP@lOyCr-E(O ziae;P`zlo2S3PDJG5>sS{TNsc*{zH#s=2zij(m21B*T9lILFRfVJS3I(PWmIbF56d z?Ec>UFsEexB5nvK`E!Xl;5zL_IgrbI;wd)B124`(|CSJEBK(cN5PL%Rv&VfWKQ1xR zRFdE@rgUraV$&3fQ#K2JfD?Lkzo({OY|9m-3K&)2jwa@JOXW%=TJ@XbufK$*r=0Dm z(Wzp=F>SyCYlM(&2;(6#+WkYz$MZcu{`a+^HetT=!;=@@Xz$b~rZHbfbyvt~$oai4^!sWt%U&YRo@UB}( zC)XD3H$6-h!oH4S7#D(i1h^eMvEhtafhwY=My0dn~d$NS@;gqD^cA*!)+EMj1yqCX;8C^*Y5Elgs%Fuvg zlWtMrq>KPi55qiG024M}R@K+kv@E$7;i15E4=eT3%h+xYyf4_bXkqzq=^+Ah!L!9f zpb^**u8&zC4#XT3kk4wtF)#;OZJ| zO0{^U5fMQ5d9{uKf^}aGRTLPP6^Z$k%o60RqNlRe+V4Vj%UBcu`;7=L7c3IAQ!Lnw z@!;aot!Q{7{kRlIVh|+(hP42m{tz?}ls!)jPd9(zfeB(oy^DdsLO+JANfR#Zg=1k2 zk?sw?C5uKeRvUu^NN|p0Iv;R!pZ8$evYYR*Isw3Kk}V08u9UrFT)%y3eg3Y)Wi+1s zr1@l!W(;C?qP5|o_EX7Nn#83O<2ynML3$12T-+|e^=gBHtN|@!b_+QHw#yhDhV%z1 z_3&21p6HnN4YSy0z-*Y4)?+eMNb?xEHij7a+qWbu7Efru!bv?rnz?Q}H zexvWp!i@gcj>#Z2bjFU-_&0^5?qXbOc~1qJz7vt6nObd>zy)zoIylPFwDo}Zp4-;< z%_JMkm}BEeM6cpvUJx?E0_Skc^;#fZromi6kV~axUTV+zBMd8*u=w%Df5e7Ru{goIoeGdqDf_mU|0o{Ot zNWQB@NlA_baIJ9)E9#rR_9e6=7ND`0kf;VhIG*)h0VO^}v#xlSo5KA*=DXELynx9-$}*wP)i~u7z&XI)hl`tz%tW`(#k2M>=84ItHEoYeaY)Pg#ju zF001x#jiQW9$m&;&Ra?J_yJxtK+cQ@T&HI)C7rJDbW} z!a0(r zQDXG3ItkHU4yrSJCQ)TpoI=@cN6~bz^xnMU; zf_c{^{z4egLJgcMhx^PCuS+{4Zrm3&VN+L!1qUT&a(j;$R9+kI&kC)&*hn1~o$e~d zSwD{3c#p>Cw44+vsT0!I+^t$>1I5nIAN@<&>U;fQD;E>g){%9{-IO&hTrZS0=~2#MLF^(*FNHaPd4Uu3$&R4d*1Ya7;EK!2*~I_G>bkeYsZf z+KZX8#;H9~;`e7~v(2l`K{V!)L9J~F3v7^AOx3mQ%gOSB!z&n38T>~gqj4#^1g`_W zM>tsVCZecj#tr^=>Z&EebLnvhpXWockuGYB^dc31yd{y1;*+@vVCePU-qm!OgK|JJ*O#q1$wHfcG zk#?^izGWG=)u_koi3CpEu(CyQZUGG|TvkXKoQpDj=rDeHLzz&*W%#&n*e0tD$7oHr zxz2P)*2BDw!^R*^dp_#nrPgELr4kSn#f7no~$;;SH zuSCWuTA4-?7s)Wo+&lEzJsD|`#g+u&VfI0g@DxumF&??b<9E#}^F&n9{8@w z2K8|lP1xjRYBVHvq8m>?0R^Zkwqtzy^JXZ?FC+Sxe5INYaRaK041gh4p9CRs}?lxb*OOxz0{0w`>g(+Tt4_g4l;bFdphneCf=$-B`ooCenbKDhtGnOVV70HON zpaZokfEm7~(v=lEvXnx$zg1^ptALkk8hT0fLI&oiPg6GK4S{c&_b_zir-g&1=Lldr zk$nMr2*|O3oYo*nW7GoVGtq||vkK%+JnoSe8ep#>9-fI}I|zL;6tg!(*;+eLwe}N2 z&in%hv?}|mT$0Kf7CNJI;P2meHynx|MKF%Z1*0(4xh3aB zxt?@5mo79j#L_`@2kqy7`j|^qOZhz48j?6xihDiK>cVMd*fQUqC7b_Nt&+!V%xmXX1pCp0zxHKj4NP<5a# zMf+p^x|vxKhp#X2SBehnUV|@`YNA_3nrt&@8ayeS`E6iH=|t77_My~7w^#(G#1LwF zts+8lXEPwxobQ`_2EO`7oI1x|qj|_<3*IS1C&4+gTHQXPtk&$-PNzPZDL6#W6{W>% zvn!!bOJgZx2fO7PLipiy^=ZfZTm1qjMWr>|qs+E)5WK&#*~)wf=9-otYn@JgXg~8I zXcYo3Qv45r^V9pt1kEs+))WMbVfRN??1}m9{7Z9SdIOv=Or7d^38un!C zBeUELEJRiW6W$q=d{kC%hns+Vo~dR~szE<t}CFTg)j%1TQ;48WsC-ZFJ8k`H6WMJ#d&^#Ap1Z+2Q zkH>sax6*~8_S>f|Ol(3(-TQbR#@V);U@ZGdfJ8u%BP|b9jFlDaJ;uAtH3>tVm{|IMm**slbQ%Y{@w`#s&>HPMV5m9>}%F^1^OAs{rDt ze$FBwjGZM6Te9_{iPa<*@=gy}&w6cu?}#BAF@u0clMa5C3l8&#pxy}kQ)-1k(a%(p@%*h9j2XtfSv)btH>qGk+xSC%D8B-~UpV{aJY>+N%dG|%0Eu#gD4aTMfrHdQ&HS6J zrG~kY2#+QL1DfKJhQ?#fzst06fTjL#WM+wuzW;{65&T*8k_&wE-bw9tvTFVxZ*e9j z6~n8kPcEs_d8jZ;Om(2bBj3Wo5bVRFCS{Hq>;gN*lxJGWey*%AY*dLLTmo8=LvOX~ zr(`ti#5D!Hr`eYNvWdBye;;a0+G&&-AHvVNiI|X+)u49j4s=Yz0Doi>jP<+)B)IWc zD-?L9q zaJVV+Pa}8tn>LdT*n6 z(Y}k5yln+wekc7dbr-(P#dUT$7)2XO=!3&}^6M{}n4VD=9PV-akw4VWIlV+X6_GG5 zQfk}@&U$Uo`aMOD%jZ;|f$KA`SC-5OQ1WeZc;P6fU$YN~F?N@!g8?{X!WeOf#9 z3>c`oJ-HGE2%4H05io(mons3H8#Svmr1jvqhyfn-`2~u7k(a-kDvKw*)#?!51TXTG z<=hA?JroKJen^zvvZ5Y;E^Y0)Z9F(jD*wvjw7nD%)3nHXp~^*-!WZZERwX56%Nzc= zF5{C15kqCUdBz)c5#?lHnL#DkwFflV6fV|?d&KW&e-R9qHGcY%Fd7t=`Q?0z*4yIT z*)Sk0K#1~0yJonIb8TDP9rB_%V<wg;J75`%A^RU)G3!g=(=}=`bj##HZ~l#Ee?Jejz-W`y?+Lcer7QNS@1Duu_?X@BX=!f zwH7xDc8RLBKj=L(6k?)MNfgs4H|5Xv+(yka>Yq8PR(JBtWE`K}E8+8*<{v&3tJ+RV z$uF}y9rsFt6`6J0DT%jQn%cu}zQaH6={;{>-{}(0TtZ}4mQk@Vp-h{GT;g)QMX#J= zc~x$`nJ z^r%SU4QQ;dt8x87(|4q#E!KNTqLIr~5#eI`eJSBRX_qpe0cp^@Fj0YmRUs%v#8tU_ z(!*}+k-qgc?SxDB3#9z zERBlJRwQ?IE#aWO?URGN^JB5TW3@?NxlG&`#CG8IqQ%^1b|2cjSH5%KFJ1CJps;*D zK;nbCOQ-b}>ft~lRE+xn!5v73 z4$Z}EEPH~LbkSesWclXxC}QOuOjn(pu}E8TaiF>|lYc`ZWrPjob}9M2^eUYNA#tl@ zH9g-2St>^s9ms6jkdmL2|u5 z{-z$Om4vQ5<|z@_&ar7hZr`5#lSGk7Tk(I?Dx`pI@=hXI1)LST6?v!aU~)~8{WYZG zNaLyXR`qhbrHGDpT;9{p*cjrX;LaG5Jo7nvuIEFQhN3z(H<(tp>8}&S$D|*DSho!r zdf5vDc??bkxegi3y=Y?eb5wOGm&h zbY004?DvldPTYS!zgfNUaiVc%;r>396{{%hAlQ%HWh!1J^vlj8+OY$m(Er?(bhP;juMCtiNPSuj`<23=J$o1VQq z?nlO;R<+j6^hiVzee~bw{ERjHaE8@_baF2(r(dBjzy!`6^2H=cfz+ME!@&?!cwYdB zXSvnNCH1a?gms4qVRRG_F9pWItTKpOi=i7(nN=ycE&P{v>@kq82`Ylwdu2;|JPKTFKj#=p8J5tZn+Km;gS!#5bzWE9Y zG1cW5Ozgl|7*T)TIP1=)oQ9)~`=6(+RMvV?=KZ+{j<1`%>z0Nl+oB4;qi_pDmisS5|XpBe>rlR@gX4 zxo4G+yUrQ7Nl_4S&yH$LR(^Zul||iYK+mqh2~iaBezB#plNr=BHh?JZ57%tfs?7kR4&^&C;$!zLF4;dI$sgGC-sxqhvhex~Y~ zz1*?2Or~rd9xjm&bt0oJEastZVg$v$rYTB;mqy35=rzzM9hwl!m-gEI6fA^FxWa>t z0eRe}yb(#gf>{N)H{O9iA=?*<4EMxHpf*6jz)XaXNbP?aaqBpz%|D?z*?8jIE54(k z+}>7p`bT4wNOb+0k=ObI7jK;nzt#c6DeaCOPZi#5Kp`mjwBGSjfRQkh-zD_YaXhcg ziM(t#43fO2eBWZL7cOivE&GeEncp>>rC~Z~X5-v4OU1TALq}n-Zda^c+Ok;D++OaP zMofGtHQ78KJ__eJr1NVur`7(Bh|W4TVgxGf1T@DwTi(stu(W@MIt!%}EF8x)D^Yn- zf8|5K=uWE8?U~%|ws_jp7b@?)g+hDT4~87Nf&zn$@qFv^y*s>1(V*V6_#4H8q8390y9TMb_p$E5i61l^pZ z<4x%+=hEK|0u&70sF4qj5K`r2mOv&qNSDn5dKAdBEyyPf^mtBO(0Fiu z(nY7!j-eWFL|iqv%!jETP;j|u6L?ihH1Qk;roY=LB^T32&@p$S1wuvfFE<<_a;Sp) zkA*{&YFc)3E(!ZeT}Tcnsd$ z#D+rKoX+BRae#ZL@2HI}wq?&j@_C!t%TT)q;V;d-GUoF0g0*E6p4T8Wsm!+%(zfj) z9t?~#%tHDy^`9XYs0H|EDLCfsw~cyPRyE~gQj}sAMVzxvX|PZk4M9-}Asovg5ejXb z1@Zz#0akMzh<@p+e9%Dz4|6R7*1z8#5~RW+e!Old$yIl zrAb~*fTYe$r>^&6pIFzP_RSPB>IbLjwAPH!U-P{zjSqY%2^${XHt#qs?D4v@VsYB! zs219%Q)u(ShU)9i_{%-0Yd&Ue-9eS9c*iia3lpl=`6CS(4$#x@727=Nt85gi*rR;l zH)vf_-xX0XwqMlnb^2|J>fT`{6CNvm+7;zPO>eq{4iM!1?LGEuL#X)0??J7^3yP>d zoy1N9xe^hPaj2{C;F>Cc3t6H_6?0zb$0CO>*3BYPVjD15Rv+P@9}l>H&hCEeyx1P| z!1)D`L*^`ZI6wRSUDq<&Lpf|Rvu+&MFEmw7 zhFI&UGl3CY543~#LRBuBXX@UM+yrynx&_Z=e?DrualM?IobP$A zo037Eaoc*|HaguY$+sx~SgE!jOx-Qm(U5>sdl4B?B|jF@fv0lD5-*Dsc8_~`yn5ar zb)z`nrhr<+yT`%pf3sjUjm)btpMBzf8~k;5Bb4wl#fJ=!D0DO)XHciVTasQdcs9>% zi9R1!q$cgb$2s_z+Hi6tVVE=0$ELCOnlR*|nc0X}P)K8^!!_RnO4$g3T^oq=%03|@ zw(V2E5etn4`5zcz(erb#KJ=WOR6Qa^PEzGOZ${{lmLqycA%Kmq1YEwDVIx+UZrL$n6xQ&RmE0>!D&CN$wdniZaS5tv{n9y!>Sruv8!fH(o`A^KMw>CmKX@miP$$TQE+l=8upG}lMGp)EAIL|%w2K+aCanr`W zUW$jOCNo8&f!=={r)tz+2CV6NFo|l&L39fz%mDAqs^DoKjK92Eq=N|elPxE3Smo8w zQ4C1@c-{5gJ~R0zRZnb>T_7!LET8Or9EgvVK8O(8Nr@0wK*iljl#3@B>4GgZJ1kVF zLTXaC-`Op=23^NrEX0G-q&2m2Na#mmafe+DSq(t8b6qfPy73i z21jgUKl8Cy_)uzU{y*xzu|3l!*f!QA6Wg|J+qRv_#C&2W6HknZZQHhO=ZS5dyw}

_;J&)M`mXA#uC-RL|MR@Uzq&v@y;C3wxDw4ET!IWJRsb?0CGIV1{oV>zKW=!< z6xYmehci^w>{U#UMwpW(6MP50mJ7}oCeDiAA!wwhbObF7f~Y&GQKsag{9C`5bbJq$ z|5`fr^oxuoP4!w1!OqJX6hf-~MhjCc{EHYq;8DUKEV>dlW~DwM4;A=MH%_-2Jy~`2 zl1fp(0}<*qXTS~8)JH;c{@!}CF z0Lm~^#Gfo3E<-9!fzKCu!c|Z%wykC>$VdMlSm}Z#V1~xED03SNCeD*l+lts_!{_b7 zr|8CRqCt-h1^G4o7i}4uBQ8GEsy@POl6hLkQ4mgczZFEdhlj?m`jemVTi|<+sNy+E zM-7y(z!7hKp6^PQ8h^=hDncOi90!?>&08^Je+yb+V1NhaRm%IvGHbC4Iy2i1(K4A* za2C6JQ+GG_8Gl*cg^b>a@m@SOR!eMzWW=nX=>KO)juN6GpPbHFbWqy)Uh%HYrB(MH zMug;QjQhzNWRaLjRU{UL5)pN?TXG~G6+993r>C3N{qZ-@CqF@rZNiwpAZ&U1_TsJ} zo9)unyOKnMZ^n^|4oNef{z~{M8nUOZ?$8U4#(KIoiORzt*Hf2+vIE=HGdH719Yosc zG>-!}5P@QbK*E=1mVciC^y{2@UZlAqM{S#b2Q`Al$sxX(C^K*`V{x)#-wwR4qkk+I zMIkR{m2S`|7MytWGkmx&8G{+IN@hh~x@)NV&TGaU1XLx}bo#EBKDMI!2udWF3(>&b zK-(t>R?56_cw{b$h~&zL(}m<~+SV!#%cXR)Ou-Wi2~mfx#deC$*gw?@tnY7m{Dr}` zW}G>kweZvNJ>sXA5a0LGCKP%h3Ru<;)k?#|GgT_StTWDh`)q8<@B<&;9cfI=vy~-D zr}~@ds3wnV<~r&!%JmAJiN@Ki<8yeCzRn*XG`qv#M3yurJQK^Mho)1GefRfOxfLVA z&EzpGw?D4@)+jrs3t6AHM%*zrc+?bx6&WKeQbzXR@P^kQ?`qnO5)9sY0kQM7W&6Bq z&FCXxN+%1FP1W6x^TCiAry6dt-(h|Ag0;?NB?@lLmBmQqu6e`rAR=eo$mZ`W$!+J(rh7;i*eF|9W}|`Z~$e6|x5&4U7)nRfu!eM}^-+!gdCR&^Z z_Ru7m{COxc4Z2w03c?C}n%BN|poo{1w(BKHUGnv<8@XBZxZGo-*IiO9*w-9U#|3h) z7H&!7ZueM*b|S)W&p`9Bsnj0~$?-aeu6d*P**TQSx7zYUH=DxTlMaU*BMAWaO~B3} zfl7xPF1h^!y-!X;Z~*5!?aFzjC-7?sS2uIw={`_&TcrG4_W;69%b*zF_ev`1zJc~J8fhw7e3+Riw&?dNZmKjivr%4lU-$kh zgkxN(J*`OAu)?~B$$SvFqV&Iip~~(S1@mCH{<7?p9L>l>VV#f~uanlkBv1G*NctQ7 zBti}*b7*Qyr}QX}gr=gD&4+X3OY6#)yzITF!+7Q1ifWLg(SIU;v%OSQl!b)&FpNW9 zOe!9X_LNsQi-QYA1qVJ4(7L{))uNSy8ZmDD{nfvMbo2QzVdhSQ9a7M@-99+>zWQ)qkRMU}~ zrY)0-jF8NF+|dmdg4&~5hY1VjOnfi&NU_jILcSyBzspT&px(3{jngFKf6TR+lL+2}X8DBMBbtQw6ORLB4 zUUl)tlgmd;{Ju%)ulBFK!t(Bi5615M@vkQ0sICEoRSH{M)>Qr(2osqE7yvcVw?WY_ zQ0UOuzJDTRXlid|_GM7Z@d7HzFBChP2gb?K>jy#Kd39)M<*uu7LyhT7KLN&{bbR;b zvW_xAlB+V~*op`Nc=1blZ=b9HlB9P(QKcHMn}NiZ_?-C}xVt2Le3RYtv^B95^dr?@ zS?M0Ku+Xe9l@Lvu3}P4_%YpK2b#Zl8YNnQymUrkB#p)8zWuUp2mE7S0dT=ZEuhg zCBjc3Sftu9L7GlrAz4RE%sXL6`Q%qTIt*k9w72dgO8xqJqZbMVLB4-ktSpLn>m1AP z@QPs8)aQ`<0qAB!H&;aU?ADl9AcUOIY5qUSB^6ifJg-Kwr)zx&AE&oY_xC{|iVx0F zhZ1>msnMT~!Dys<0~tW-pG{flTI)A5RO1gC4RNYS%%Eq?EIwUjt`H-A)0mor``}vU z_lU!>#b1Xf(YW&iu1bgP^}2`)JV*})f}gi%t+b@o`oL+(fLS9ML$kRg0vx68K4-2$Pn&0=3OFO0-%TL9J z@wAC0r?pwDw;SzHtS7PXAtK|+fmU?3_RhBRf$0N~qJ5hda(KUhpf0)&L8SWqO}4hr z5Pitov8O(-E4BBSQhJa#g;7H$9h28-%yAef{GJ&@~$*m-&MXGFYR!wx@Qq^nlu0-V*0!oMR3HJB!5 z_3V*a2IpBLsoJ*HbaS$2){TIz-iM@{^E~{yF?|Oc8o2;5F|FaH;^H#15NQnnoFId^ ztPY+84?KvHC~GZn@t~RC>E-GwkEYgD{ej(Q3D#Mb)r&%$OBgmLVbszY<*)*B)1wpf%$#cGH|YFt{o50t9+hq+n|;hR zBV;vgzNsdOI5#v?-weEeH8&}^(hI~Kd=0L45*%CMt7nA9NILTE6Ri$QRA}^4WV%ML z)R(47b4NtjMw|^Mc;a=FLr@nQJ@P*b900kJ^%J+3QY4SeR z)!(pg$9ScqNg}*uquy0pIZsIH&7@=NaRxGpCEbmyA+bgyMDI-~W_&2T7_pjJW-O8O zNk=elZ_EtWjXSOTQ^KHvI4W7%VM{{(vQ!5&_^&ppaKVE%t0NLk6b;>3Sw?(eDTdG4 zCi1zO>g2g+@F-(@5KWR!IjBtObO>rPu>3B`Hk8XCkX)gF@Bm3Cnj9OE3aaQh`8EWh zs;q&@-(#%ZH!J4$v#$ta2@{U_vn9)e>th%tu&1=xpB`uBosqh_baZEEn-Wh)mf!Y< zXKBCBweK>(I%>pP-LhF*;>KDrf`T^*taL*Ln+gtx{os=O2}**aLm8599xNhaPx!;O z_vJ!a#g|aSdAoz|wOle(^UoQif5zmB8&XE5Te@BoUIy~cD&bQA39%61FF&OM7Ozti zLnpBIs$m7&5m|%FFiiQ1g=#Y;fzLqKQFtOmn*|w_PorV&9|b30FL$R84B-sqb5|iq zWoX35Q11E+#Oh?hQ@`D=v2n>)UVo%)Qof9558|~+J8ziLv zuPP5J*lMrsJ>By|to^T=Fp+KqV&Dt=#;QELR^Y`TzI&fBQ(5$;L~9~$ z%7FX*9vC#d??EBM5>Yc@!1^)tJS~PA_Vhi6%4>yQNy4oRqHpXGGG7|wZL^z1@9~h( zaHRARE&Ag2ReP-dPy;k>3%QeJF4qA{VPtTN^BPJ6`bgO!esgTLx-*C!P?h(Tfz1BJ z)4RKOC@z5Ew&CS&lLaD zO5XlFdsSJ3_n=z?VGs%WBoftlY}BI<_7OqN%t~3Ca-=rGkEu7EfB`w(=oPc;qJW&5 zGYvhwO9ja{%j!!5$s#?Cz+hz+Zh|-PjSBiGM!JGc zbM!S|>E6N}=V^r>D_!0Psb_a6*+NIk+aV^z4nQGNnusld7(Ax4`3YKgaC*t{Sixmn zu`<7VsGD;AI_$^QFZYvvn5qK`|JgfgDM!Er`#Bb}Ctp;T1ld^FKStb{UQ7$9hl(X{ z5{f>{wz8knZsMDl9a4nqq3?`*uW~s}lqVz>=sO@k!wv)1Lg|3ML#ISQx0MhU|Kz*z z#XjY?Pkps)Ftq;mjJ}L73?L2Ukb`kJIfn?iUv`KJAHa+ff<;aRrl|ChIBG-*{RBgr zbg43+mo6g2x9kKvQN{^9hs3sK=tJs6$-i@gKaOOkV(fDSl@f!V(j_9YA&Df%h6l2Z z%=c8KVH`y62ZM(PeENq|K?MJafdk;~h2ml2PX6GcNF+kr8aJXh5L$zVCx0^LKC~l` z4hrRT;nMVr&*0=@{l@bwq~Z^Pbt@Omw%$BvB2!_4eQOqk@E!BAL-cUDL`>=&h+Fj0 zmur-6BwfFtguK`cT*pk^!~YN)uyapJkMQ|xA#FYZqBs{5n}gu|!UW}2^8-aU$3v+p zinE>4PTV5reA>U>LCAS7N8|&k@E?Tq33?Gvk)9{slj0hSt9Oa;{pURb?k<__Inu>R z9hA<|IQlPMKKj34*d{+sZ9AT&&56E?X_mDBS!R9+GK_)IDc#=E0gE$%O8QP3#R*d4 zP?qqF+wyjO;GlQ2Tl|(OO2zbj&t5ZjfC%yThe}dTJV{|7E$B3AJ+(kEkrQvW$EZwv z{YtT?5fXQI6UYyXjrNBn*q=ukxz)rEvI-*!pEw2U=mq0|NW7Zo#Q#Jk1J*>1$dtJ{ zeJugLuejb}|M!F@|BvZn{x7Fnh6hSY6CA>G@M)H`91%JFEs8lPEF>WrgU)7m?V*fU zK`XF$gX7y_iEda6$C!hB7+ceq5gwTW9dI)ge3Nx3T?VjQv>hy3g@D?6n$l@HcjYq3 zO^lK)r3>-ON;F(;cfT5Msos}kCG_?A7G5F*b>3pys_$UDmY2v8g8|kWE!LSI17!5W zZu*o%<6eu5H<~S{voMun((xF>2ltrZzVR>1>AY^|*L;e41a*}R!>%D>?lYvR37%N9 z5&ld!_#x|TjO-nj^xAb$MuvZ`=%Zuf1bEE3r=GHT9d?v3?rLEs*y@j$u>(qpk;E;d zXAe|L8$+!>I{SJ81kNdsxD@veLaf+qzOxg)lFA+kv-d~2Rv^4gDVZ;_*aLjO!?0f= zL-w3dHm+4%_JR;0dS4XmE=+&R2GfKe@##f^EisFVLFoNYgyCLDXo;LXB&I0%2i?M% z*d$Ap7bXL)w#?d8frT|f3Znf+E2hAgbIL)pDpEm%7YmK_{1HN*%yyZ=k60yhp@vew zGJmV#AJ6e}}=;VGBM3(-pYG<)+eEs8%AE`A;zm_Vj z6$baO`kPsQEK3us{}3pEemvjy6RdL0lK1dLPU)V zKsYnA;pM;RF!o=<%@SzHsx6y~9=gN&{#jro$o`TOjfz*;Myhb+i>fvfq8IhoI-RrH z@O)je*tSQA>RVKb2IBklMxR5{gC`8|d3z*xl;~F79mG`pcZW^d1EU829foO&sBM~v z64VP+z4wfy?hCn*@J7deiR3X2e4qYIVB{h>8Zc5!GZ2!2u~3=T2t2Cs{c<^*PUERw z1c~fi3Ph#S;<+78cljwn9fA91D49TB)M(5D`I|`MGPqL$JZizQUv-E1PeX9|%v18b zPu>+z#Q?tw@5q0YNt~F9ZAE;MX^e-=8ZcultM5Tifux0m1;}{AW6nHJ)ZMe57_8kV ziR@SH;y9#tN9w=~C=GWMP_h@^00qJHQYgVTj(W!;wQ282+_)n#Fju&-`KZ2XRE?%} z?n9Wd^y&^0nmmloAa`e83=a|_;=B(jPU8G@22O#mi{(Gp83-O&n* zK6x+JbalUFucC&yM&^gHw18a>G}*OG5-R(l-mbX%jJx|)j!PHJc7I+7RRr#raTTgo%SHj)1`ddqVDZDSUNLwQ;6tEkKYPg z_LImn7EZ#xeVRRaR@F7{%-eI!+Qc1jB;LWAG(>OWNRi0KPL$*|K$nEWohBDrPhF7h z`+9O;vT)nQNfXmm(6BQ`R&TOe7ppV(EUAT|tg}|32Ity)`SF(B$L@niI+kOYwL>^& z>fpd+^6*H*Y#AfuIXV~4qh^FOh6S86}|peW5t;I;B5D>^0vlk=4ckx*d6 zNc&((?%<5H^&~1KJ1XzP)RW1e#z@#yQZkIdj3i#_&#SUs9wgVaZZnbL-3s%AAY*st zFGx%?=_^QZZwLFe;SGVs<>g}nJ`V>gwY*@`52~w^;Moh|hg_yTT)ie+ZL)I7yX>q_ z_X$U%%Z*m~i;Ih)Z;qN5a3*))r242E6;e4om$5>x=^U{p?z;T>XGv8X;!c9W*Zb!O ze{U)aPY7(&dN@yvLVo5l=mWhaHbtg8+q%+yUm4^#;3ankDzgie|EwcnIA(g#6YJQl zJwdzGDG>HRTrS8BEI#C!K3ZeF)Boq@>!Sc<6THH}IC1(x|A&|KsQVbb){(Tf#6MzM9 z#~k;DRi8C^zudNl9(VRCiWXc?*%rjMSsln{?xQyzRUD2phAHMyJVWH+l7d&RB+d2z zy^}QX*bPDTkg`@)Do>`<-i5sbGd3azJa%Ej@i#diPnmjDjpvvieTh!Pf`9b(RxjB+ zXSIH}2G>1Y4+A>itZGeH&`WIzwU;8#`bRKYa2cgk&xgm~%&8iVTaUAFd+NPklwS=d zC<%tf$Yl*;c_fsVGy4vjk)Ad=-HhFZxGaMYFZXx@AorS7yJ+TeW}f0HPstX(WTrjjf+sJy6G9^H zi+2IlIyE-QsE$Xn)4CBN3`sH!68cB^49>My4d~?dVqJ+8b+vp8&4|-k!}a)BdcG~r z+g+A7;iv9J-mY0}GmlS?61C68=#T4S-u)on$5WkNej(0qJdAj*xcj_63Q;4LbF1~? zr=EBYOQCPhtFPxTf=%tP82bO9)2_iwb|QbAliYteD{qBZ6;n{AvEZD?L7Omz*Y3ky z&U1Q#ps&bY6w#f5WRild?_QB`e#}qJe%)({qDGL*CcC8ebC_LaXa8A+EDCMh*hg(F13GO!vhC_K^3S^N#pWFtW#+FDrWJ9rW zaq|W~yJzfP7uSK$wjiKPU{H((0DpQd^@T(xn*03>;U?^Ud2Te)5XiH==ibdHbW~g> z(Z$q>3quML{D&y;)%SzfLBP(l9u~omi8h5nKQYkRt~V(Q-dp{5|YU(I7GYT{4c~-3B{4vF2w>n(igh#}4zZ!qd(qaMSDY z;tg?6t7NLz882)dluM5?pVXdoxzXT zqlWct5YYQ>S~8+OlJZ`!+#gedxe0eFaoBrB4gdNT1#10+Ulgk<%#VAodIn?50XxQaldg8fE`mx=A2&pAluwV3VqO<2tVgvUQ5sgszxDy@^4rV<&7s%#Y zF;S^%w|?jwd(Xn3@5S}W7cX5s?L@^B3&iF^O1yv=%6T$DX$D28_vV^ben!}q6v!&a zL;7ns?<$b^?>#0Ae~U^HBv~D~p~c@KQ3mJ9Xs5NOU&>!QEU1;vXNSXAi)Y8ra$)5e z`LGBZelt7h=Zi3B(x(r6)v_rQAoFs+*pitGg9JFyi$2-Y*LGGSnm><3-lGLn7GQHz z4$CJTEZlbx@|-nx(J#^CfC{7P${uDyZNng(Ms&3AxK+_fOcDDKfSm3nB%uR>55x}G z-$(5!;oRrf5y^ra3g#cXD7e!nis}@tCOdZC-SgAf(-kQyGeSR*_>YP+d&cw+;5xf^ zE1-p)N+=KYCEw;f%J9aB9+Mm$SKjo~O^fHLVPRUUwy9+j5$GTN;86tluRcM?KUrES z6~BUeVE0K2iPNRA~G3KuXTaCM--r1?sy`rms$p_7;7K`2lbj(e`-8 z!o&hvPf(X!ur9 z69oFULXKjgv^`0m>@}jOx=#+LPRxzojEujcet*#&PhOSYm4zw6_F8a!b{b>KPbdL( zpG#EyGbEcw!u;=YA7XKpQ<#U}Im0YV^ix~uLix0Q%k`M0p?5sjTJ7y3H9S^m-7cd1 z6NsEioIxrT4}XF7yLKdI^*#LAbYbxpF+zP1OR<|om@L)uAc=-&`ZGg1{?6!TTloOS zj(fnRshUoA$3k};T9S6PF$u%JSB^v3>G$kJ1geNkK|fz82#Drkcu)$U*R7jNe7$@> zoAQv+1PNZy)$A25@M*OcemM@@=ndvRwwW~G$Fz3a{h#Bp<$Y5nB1tdmD7tHavDl0?)fC8OXE31{N?!KmENf=IU>Vz%^gaL^=U8$) zh4GsP+^+0GhxY^jHEsuFOah#+j;#cb_`R>CNE(qdw<{3@fylv(mih`Ni8zCEaIh4Deg+G< z7S3{CY-XB%15uSsv^1aVnHc9GRtgclulbjXr>M(7&@RhP=7Fk*nNr(}uQ3=z+F1wH zEO6xM1OsJ)^mR`0_H*_lH5?k{6Mj(WLzt^j6f*ynNV3oWy13;Pyx5s7!4Q!!h)G5P zQF6Td1pgH%j}%+I+wY4Dn_$Luq##-moZyLNB(W`W2=YRuGaU39iC7SA+0W^4UuiI??Ny@u7u^`GI$sXXkI}K(iylXwgMsy578>9vE|+ zopn=vxl9ESp=0fI&1{~kXae?PaIh`>yq{9QAvC~rDrqQkR-*QB;Cwii6e-N-8-8m( zj3`I*F0F9wv-7sFO|UEPI=2mdd((}Gg!oT@-~ib8ej7c8lmf~?G3Lvl4-Wl}j@gbD z#CYBZDHJA@V@eLGTk-UugtdFOouKQEn>1lrF*l^X%*6t?B9^TX+%PxdodwgKK|ap! zpBWZ4TIUKogLlO|THA)!?PfK_dhJJ~UGztOvE}pov3B$i2pgUAIRG2?w;9>78;mw@ zdC2YRQc_1aEX}Uf)v`|2ZR1B_wur&xGsu&X$Q=vMDKb3T@GPw&$ror8zaxBJ(DRD* zV~o|GqK@(FV}Q#F=e~ez50cMwIOi}rhZuRFn92(eozKY60S?Xm!l#`1-M?Je;#j$5 zTheCNh8{P?B|ziz@Wo#N!$KVz2l#zz<>0H610{kfX6qjI%v)DIckEx?ovZ#7kr#cQ{%R!-}Iyf<XV6t)RDU>n`nv2~TwGM&T+CR2zjEsa zTx}k%U%Gd%6`|Wg-U3eYrcMzs?QG-U6f!+I{)6 zgooz({Su`+pdvZ2viAZbYxMscRb@$@>GJz zV;Kz}W`HBUxm|f%ZqF%5B@Vh~f@Joh5}9vNeQ5DXtXO0|Cmqtg3}fh#SI^V9D?j65 zL{vAR{;zll!G$Vt2^{dG;HXAKg?uR3FJT`{xr&HweVPY0@2MuB{a`oN)7@iO?E>x% zT?-t~@uOaABatZ^D_^u))IPWp4O|XIwY#Yv1Lad66K{$>3)}I4Spg5mM`HNtpw;(A z?a=G3b1z}m_|2$6jjV*mA5JOtetLY`PA6&qd!~%){k}OZ3fZSEJL@%a9ytO5$53gZ zBM2M^|AzS|cXf4XV6TOF*qf5P{8&k3OrT~YQ^nG)NDOebFfX9Xf0xu71GYMhU0bau ztUod-Iv=8VQj6c-}eZ>RG)4xXnN$~d*$vS87g|| zAY!G;(GR5dz2jFr@MG_H`0KRKPf&Rc z#bwy01O43IS`u9RP7HiD;~-Ibwfj=QxI?28=}RT-OOw94`x8yu5GA5!eX$S8v?N0C zf$~x&Vc-{x4@ju>hK*3a?xX;m;nmgEcIiu*MDa~hwv$hM*0?LoUIHKHEP^`8pSR1b ztgP@6#+_H9>RlJmO85>mm+cMG9(x7b0XWU}) zQie_Zk!<2!?EJz9ZYp1PL!M?d%enp|RF*wGw@(TeMZf%Yt>ceYuv(3=p=ex137spk z)W0zFX(d5{4VC$NUAMIX0iC6|x5~5{RTK+qP-OCpf4t-5Jn>D3w<$?Akd4J^{GAa# zIlb~Xw8G(2>4xR>L=ST(FsL^3)dUf$26$0}YuknzW(35@|IV!g$l-pL4yhpOhdPJ5 z8J@!brt%NeHI8ZstLhnKDEspd0J_=~Gc{OUoza12mmk2kuQxg>9}Zuoh)RQ4SUTFs zkn5ls>*C__tu5Ec+d{6`s$s2fHE$U3dp#}2cD3bZ5Q)l@I`=*5r%s>!2|nKC=5w;!wcv6dsJ0Wy7 zk3PCi$wK<*=(QUL&jRgttH*|F21bzteqwS*Jr)gJF_OwN$I!rOO_peru^wxORgHue z8}5a0MA8FT!g`*hzTJ0sMdQS(snTE2yo^IBpm(484il+TC7~%2s@5QWf@ppw7<2_{ zwS4n=S}N1WI9o>DJ*O!{7T zCieweL;5;xoUF)FwbN%<=&M``>$W|P!WT`|0XgrT8;)u-yI8h= zxfp~!c+)KKsQ?K^biiy1ay`vu=2OL!Znsv0&*NIs?XB(GbjH(-IQPOS3r=OSaEQoueDIl6M2=Dut_mV7se>AMiB1p1t z$&@Sk{?TO89$qyZF=fF<-GJ``iIAEtJeI&7S9}qPq<*8{^rQ*OPPTfO&9!+!Jjhp< zE+dm+s5)fcYLk5$r>1wv8hoBC@Phb^`7#$>@q@^1jAeYR+3qr|!kjV+Rk_|$et~oznG1`z z^(qm+wKn%*@8x?S*;YYzY3#19{E+0f9RTOIKm2i1x$4E~t>5Z05=UBLY&lVn!IoW& zv_f?PxlFlQ7VQNN-t0H+vwh%RHGA9*?OTzSKoW$jkh$);! z+;U?l@|P0Py;Z19u0VA^fVz|Z_Lr4do7M11bJ`vrrOHq)d)oq_=Sig4n^@H(+Fj=? zay=2-@OH>_cCIiMNt?GQNe{cU&m-%St8I-k1jk@m6I4zdO=L`*L9dH zUrH2RBCQ%<2kRtkY>K|mBv6YQt6S$Vx0FX-0y|g#LTB?~lLn1vrvLRh{gS4l!^6-a_uL_I3`N(Bt8Pnn! zN$7b{<6bLgl4u;F`MbDTqJLRbi+94(Z|T zPvG*c8#(9mBGqa^%3ph>6Hgq5>k{~Rb*1W&vs(3vl_?4SB@atom*x2Y*q_lkfi%c{ zMK);R9GH|-6^m2t9!oY<7*=gr$~=*LU`ri5ZL~TuJ~Jov>nQ_0Ol<=D@9CI;b=u0i zz&3Uzv{|sU60ZKXOnC0yT)S%|!63K|pDFI520j^S%Fa|>7`PD@LFdop%6&buXz+1Q z!Kb`!ZWIOnf1m|$@o0J}jRk5<9ks*FWs4dY?X!ERiR1iRTqSWv$9snB8=F4$8q%O8 zdq1+^;vL}pIw!=IAwF+(dl=Z)j3h$JepP@S(nmJy=8X4Q*%g+Uoy_vm_aUFGcdOC&r^)SvzsSNA#zgtZ6z|5R% z><#MtYZ&goi!!r_yGHFAy)`s`xQ0|^a-puQf~)k+82?863HCBRGrn% zUh;t09o&T7UR*SDau>Yg2<;i;@(PHQSo<)&K!rPCXiaBL@@z$?DSdDVN5?tIU|#wZhbbQG;qufc(Dhcw zyojF+=U1aA63sSlDBNKAYPSO}?L+tQZ31*MzQD$@I|=7pl9D4P6Ih_N1LWIEyIY|F z%N*NRK6zDgwrL#;K5^i!QBy~19=tjYqSI0WMcy=2idk`oc>(du{9wp)n>?(fhpYT6D~L z@iv4>Setwh87?sZ*R$66;N$Jd-I3%@g#)h2Tp13Knc4G94Zy3UP_4Re5n;9&thX#* ztX$>OEpIupXHkolI?g^J=gNB0;N>00K0;~|x}?ZI&OeT&i@-46j{;CO4``T2$aUhz z4rd%eY6k@0$UgSiIHzPv$7dTqQqmPlIs$>S`LbHVYsro~2io#v1RyaQWTV)8o6K9e z?k1qlHwi3c>Q1y=B>$RVbB}|ry+h%rik@v+%pZ_MooB5)>B(iGkr{G*G#65g)s1Zo zH6|UNEmVKBkr6b))2_@Y#vLt3ObN)W+ZLho+O%d(7(KCrbcC8;i=K;ZjmBfd2Hq)a zJoCxzNg~EZ*?Rq*m-IPAE{ypcjnez!bkv6%(XcBc@UwOiBo4J6B@+Vc5v(O{^+RSD z&gP|F?|EKLBilm1&_$xtM81f0&x7?&Nyp}Mszt>oMx+rOTM&vmcGmurI>U*;pv&#z z=L6V!T!=hw`!t8Il2!uEaNjOoL4>J_^Y3`iP`#mK_VGh_?> zvXHy3bzV51>S~TRw(b&mKxU9y5o!#tHR=I#_Ga6 z^1+&9+Yq-C*t@4$Oiwy+la=GbVHXezt=;mb{AFfdZUE`4hYHSsrT^ca%)t z23#itott~*ur!8B`x|zKD1H2+`aQzI<14Fk9b4s{VSKiGmXpgva}?JCREe$X<`OtN zs9f8R3$NQH>+ueC|LpY0Mvl-%I0rYCdh20$t64_=m7oSgL<96I??7Nv|87aTFjrP= zgPtE=yP>2Q*T6`){u2lidOua+V?ywy0Ju3p{+^fhwXe$cT(*U z>5jj-97WvyqtVW3EA<12)&+>-IES*iC*E=^FSdv6p#9f=WsZ-=3eg|XeUQ2`$P50G z=H3-@x4w-S&rUnR+`N&T{|K|M@~N_2{0Z!bxV$DI-`RC$3ZHVp{053Na8bw+&6cU8{wAvNG9-Az_*)5>(*+Pvy-{A_Q|{Zg|ZGwi5S5Z@1(w24k?6gEG(?)dy8u1rxmHjFqokPyg<*e=EL zrB`osA~&_sG~-W%>uX~Ax$g-(cFRulSMbDK6z@djQ(mGuIw_u$)gq)}PFWw{*4i?7 z;%FSAnxG_$)#DAECMJU`Qd%+;I3tB$QsOCf!zT1cxMXW`-HxwiZ&R&yg5TUbjFLwR zp)Mh)%+6@!3cTP6Az4S9By--O5zYn2x`Z3+ih$m2B_N#)z7FBZx;Ne8ATE1ejsv4} zVE-s|7MUjD)f*?$8!sEf4KmP;r(UZ(OcEHl9m3!co>6&{70)tbpdy?7@NiQ#wl}zI zo4(qhlT=GXhPR5hDYvU;-pdx&c**|G0n*7ic(~V`cI}JuCGF^F`$1CyIa_o{7D@MT zhv}oZf^Cer`&7Jno|F^O5uuNgVf~T?`i0ZCS0jeoZb;D&*6CqABKw#-#%vl=j(EBG zhUH7Rlg_~m<`$(l13Ft`sqi{wizsJ(2^$yZA{UN1 zPq4@E8f=?SVSd(a4{sfPrFi{J1upAc*mM5%0aN%5THSMJ0cNj1^BiZ_mfa>Hwg&eK zRIs?@IypuLIJ04;Q2E|Gu!v+-hkXj<(i_PmhDDA|8!rd-_~fAo2+;{>;fu{@>NwyC zH(wN;0Ct+^`0bQ3V;r4Z55Zx;!wv1o=;Wjr5&9SIa%7lYSW?cI3*rK|SA|MF)W!v5 z%&F!UC_Zcz1Ga4oYfjq!a*C9)LZyRKz4d+i1r(0sCCO0Mmu)TpQj`qeDYB~7#Z4Na zL*zMP#twc@9D2*pdcYVeb;h9u%IG0^h7W=O{_<^7jLC598xw%0_nDXRg`*Zv0e^U! z569r2il4p;q;S*c0KeA}L_C-xoT?z(l?h5{oBr@*{JFkdr0j&FL6oXB^i+1U70ux5;W0%+(!{}79(H(L$UdophVj(f- zp=untnxk3Tk*G<(s>gJT!SAZzECE1YrQlpL33<4^v&NGf+EJTUZ3(jN=U*D*08k6= zy|RVj7zkJVD&aFrMBlzxj>x+M002{m`U)=xsS&0RMxx*{#xiIcnJ=1mS>=SgRVBu~ zai`T(Uye8&s#o_yjd4~b^tXVDmbQT~5*a99#yYgfAu+)T9FM_0A1`M;F&#x-F6mYn z2`&J6tg)?p?GTd^>!Y6Z0+X*X(WsZZ(L@YQP%#51zrr9_GYSKal8VlCtXy*N-y}I! zy<=Y`7zA?KQ13%Qat+*;aN9fii&Dt~iV7b#cl9{mWH&0qe+C&fnZ}weY-@Ks@Jz7ue5JEY?1uDPL)SYh} zZK`RDZdh8+MiOYQ-sF-bdL{6^7YXiz`pg=H)Nrfy)(k!!59{rWv%p`aZ4BqM-YeIt za3@JJIF||CI~W=oZs!o>$VMuOy_o^FVq?_{m zs+hvuzzGbwHA?SL3Ws%}Bjmj*oz}#|u}M(2Sm9W=y_sVZ(+tR%MBkBXnHJ*84Y88& zC;6~l9vPV@sJYAVKWy;~Cs^m#U$4w0b5}k=AXP|crZUy9&i22sGOo?#UvBuJ4sqTfo%8HpM;JE zw{;R?<6(|GODr1tLUF_ftEgMszX@ViWtB^9;|)*2k9H~EyOHdmIGjEYRp5robT(s1 ze&K2TIh!UNvy}=VChRy`);_T#46%ZToUrB_zx+wBjiQr(A<8qv+;3+tfd9k?k4Io1 zRmoy45s!}YSe#@{80u&dIovL9a>o)BxX>hnW6PmuEpQHtfU%buA71BtAC2a@Ba+q9 zynl~tHqq7P18h+HA#qa5bE_9}=E~!W4%2(nFTwD}(IH%of5LBQ%QVX(Y%qg$>n^gw zc9Q!0wQ07{Ak-z7;6XGq)8cH3$<*kQ1e9h{N-)VuhSS}93cr9lB1<~BN%l<6f0`s3 z8#=lE5!93ybskI{o8?OLEERgIaKUDf9u3;D@dLVR`X9YHyQ)=mvpl7#A?mZqg6Wnz zZ21x_>g&(;%;6NPk-S1&Y;4c0qFZCaRTY*6lg?y2{}gr*5Y$v@F=4gj2nqzN_B9St z+wrIuu@dER6*Ctx%E>B=>o;_f{_9x?ZG}I+Szz;2?8wZWh3fV+?jvvj`IU%_Gbz>n zPrZu_#zknk*-j}0Ec!o$tnk0u0?+Y2919f1s1O%g=6$L*xn3sWo;QTk(XlI0ro*mv zcGF|J2a>JwQbZp0Sq!Yr@$MZ9>8BaBO=y+b?*FzeN?cGC?gh(wD_Uz65#xmkj`tb6 z`c6>@gb?T2Ipj{CnC+lxm<6hcN}k$S+mCTA<{AUNSjfe#kKiTXG8cTLq~1pvuQ%wT z``|zsO@kSGrCh)HNRW}}3LFq1A2GwsR1(>T%6L~#o@fA&V&fK3#scPlL=freA<@RE zdlB!=d=Mv3bY-spm^fAhbtaehQ>>mxriCH2*+dW;sPwKAp(}wZcn7@v*tEXC4H#n# zFTFf$Kwt^>wbXUhpEsQCE@Ne8Z`d>S^>CRgN)mC#g2uiJxYqZU0n;7T8yDCdbd^i` zf5yMDb$%dK7O6F~5Il78BuEqX%mK>E4=F>EiRZOETBI;rT2IbIAjg+vX)H3|_@6mjf`%lo{}cwZPl?;Rs~XW~17 za9k|Z5l7<;Myv;xXjL-7x^d$CUk~5g!jf<1| z8|5n&4kpO=C;7m*t6o9;S@930BoDoO>e}p)nq!m zqLZl<((p_gt=Fpfxr|?HM@fUtEABm({r{`(uK(e195{|+42PNeaJrkBp6+&H&S7%$ zV@&sriP>~BZHG-wcXu=0&N0WGZl}MWf8+c9@%2BvA5Y}(8OXF(_W5R}=bm014c$NQ z#&;}cKcE)NREEB@>g_p9J@XLe|BtAwJC+1yS!!FZ5MI5$Ds4AuHK5Jky}Y$CxG|u$ zNY1WY3m_b(5zv5~0S8X$H&g?Ttsxb6YLdVxVegvxt$oafQHjyV<*R;*HfxwtR%4DF za%4(-aWYq04EqCM-niE|X~swlcYvicTj|jfhBi_e%~NocupVZ##+$SXQjXm4cN7>& zN)jjv;2G~ohhf~lkpYT<_M?dX#ysB@F4I6h%ka0@Mm-alqf0uT>Xr6e{f6X&(dgxm z+uj9LReHNhra6fvkCn=2OVww*SO59PTW(>&-xh!X!mv^~N^0pNFckdx5`9s@_&Jub zjh1oya)No9903q$S1Q);h`PLTi`e}Xhu(vOP3RDNV7TiWs+|ZZy#jS7U}CY3!Z7&O zH`Xl6ZTFz$_tHjoOkv?QN@O@f4B_Q}aItDXmOjYFHb!J&@n_GgI|BC^wH=h~X!o--*^-&vmeR1K^Pu>;3u6P{IIccjri253v6{u&nbLcTo$JJ+{ z3H!q_Kf21I8rcLJO>)p&nXf^?1?U1FPc#BBAOUJleqOYrb4E_UUTJv(B>?>N=`c~r zv*y^9c`OJR(%Ow*GV}fg=k4|a37uuz@1~|#>`v`|bv9`YBd^$l%iM~msM@<-qiS<+ zP^z%F#>M0#xPr(Z#jkWd6U)+cL0#QHho5j2MqZMcsX}>W)+d%7Tw-G%Uofmx3JD_r zO8$yu>gX%poFz>y9f}fSfj6skF~1^e#4&hYTwkJ9|AO25xXQRTl>s276?*Pp!7=@> zx`4_l2|d#pGs&OBano|DGV70&mpbK|FpkvOf$b|TsopVzgUYk1VWRH_e6xF2iENg`!EA8kOaF9H>xFY>f5_C8UY554Go}xqa(G=d67i&ci0hC5>zo? ztak626by9A$<+NEgBo29;Ck!~KWYwqcNLf(<6*_)}?;L&?-nv;Mr-!rv#!zX8Ju=jAq6cRjbH zC1~1wbrZDcizFk*dSN!kz*I>&_QYs@iMbo}ILpI5&#tq3dviid?kgd+Eg~@)O~oWR%O(;@{rf8k_XNH0 zT@mYJor@0q2MIhc`N0$fEKNaoOMS5%AO32jGThb{1b-eG&#}S7;xVoCd(Rb&_9l8duqK$Pksj8 z0~;;v3avAA~fz&w9w63Hf9k$DkrUZB3r zYWn@O+<49NKoq>FT_YU$Tboz8Pd%w)rqA~0`9%FI8u zu9SH+D!1d|+4bKIlK=wq34@VNQuw4=XO&_nD_G4U0(DR;Qpz&vJv?+KHN1*phplV! zfCg+E4B;lq2n}lUw5Fot(;=qrg5U~$?#?!)e05z~`ZSF*oXU-qYRYAQ_tw2EhA@y! zj`M3HFoR+FNpqa^M zg6R6#b)!i9MFCR6xFF!T#r5;~%hy70NDFp}i&D9c7?y4VB5u?w*SYfnApA@fZC2jG z>OO%=G9dZCqM$6-Xavvx)|j31>n3l63A=6d>}!;#@ZN7b&M#V$Wp|)djYu6p6M<{3 zF@3XYA&3V|>YVCmaZzEh%v>2;v2`?fT}M+U(X+4oMF~dnt$3_=!Y@uCMU~BGBU}i} z$y4I_bDA0_4nT+`SgUtzUPle4o~Dk?*1zl*#k(7hthg17T>FUiD+l8WCfIgERwRPm zA{u*?4E2jEM9VI+Hs=EzS}A*suTzPGwa9g-ZuabK@b$|sD;QQI`v$6=;^ZALLiJ5J zdyuHExCKrs55-zG(i0zHH_mVar_GHT0;Sga(4sv;$3_cOVs zA{ArC-l|bYqzh%caE^D#AtSGp`qfT#oR;=sv9r>!^8Sr`Nr=wVK!zQ^tn*;TJwe_5 zZqnmRUsGscM2dKxlT15)=VP=K7LXHFoGW?YZVyTz%YyrqdIOiIezs>+%0=&nC`%si zeEPnkVsG63ZHLXF^C6;@<3fJyHA)3-D2GT##jnpnqjCaThZ;k4BWdLrc`~YC=AS0* zH00bR=)#OjII~Bmwz)DlbHb+hrceAt4rooWI6L_H>VCEriLJ-M>P?f=gQH36v~wg2 z1=m@ab9$$R*K1xR_L!kSK&}Bz(S+{m*$(cjOO12h`^Kq3n`Tu2eC=i&(v!scZ_73# zWj0vZz^XxiZmA*X?Tc&r8xjlW>tmbdEi4bel>eS~8g28vZpc_;u&?ZXQ1J65(wC=( z!d6sikfNF*Slm80L>_MszXX2hQr7HcJ(YVour_=+pLEvd6i{+K6X)q$mTdiDpWhx^9R9yz9Wgt_)k zdFGvEl0>V|@$YEZLhug%%5OeM6i}^A`tuof^sV{--M{nWd`iaX<2C0;qf8ma@P9xj z5}DX^#0TXC^S2?pDvn{BZ87j(Rbpj}m{PlX7Aey27UsHiL|azzZ5aPRBU$bpiS416 zxi|tiRKq{)b1j%)o?W;dO9*5IqsCW$sgty#Qi>PbK{R7G6lZ4(%_bP_5a0g9=@&hfZ6MH9gtj6gK$0t$F9Dl9!rz) z+v*9#%ke3Yu+AitlIb<%?;gCw%s*W*&-IQPUc8>i(oo5pKkg6G1|iVTH8&I@bzo#(!QN$pJhRNEA$*h~Lhm^t;h?5_w+|G30i0Bu8S^ z4fJl7%R!26F$P~dgl;L7puD_(2bKXdqmD9yTXUGIkU?oJ6X*#J@t#!H3YRiUvs)_E zqu;*UwAhl;-6vL3jcY~yZw*t-xk$Q<{Lg;4E@W`j5&0EKNK$%f+5%-1mdkxZz$-jN zeG5w9l_Hq5!AO0cJm@Pq-1JATkGR){oDTOqBRz&Q(1JqK9j>!1J3fDcjJ34}10_?N zQ0%u0&V;0e@^@4DjzF6g#hz6(^|QG?@&wPBw@6osa|LS8K@{h91EE>hyma$99qzpQ zDSh2)FcqCq%m|qjuZ6|wRyck_qh{Td&JL};KkM%rFGMIF|JGPw-riyNJMV6ccJew@ z#0g?7AomV58bo{9z?f-e+=0_kEn~PO>eIxSv)vF~J4Lpw(D$L}56QcPn2atRJ)AFd z4LZLvoZq_uzl^LIZKf2*&7fz$JDIX86mMfU<$-w`{~DQ4g$xS%G%NkcC!{YXssF?N zU|y_gc*QnOB}A$YCEPi%J3wXe#7g=EL3fk}54-i19a=s4Z$&R<0BU^D-}fC$yZ8Ut zndoigu#I2~LJ8SYYdp8gsYx)M2$*jBy@c;CuZuWbB2Oos5wpxt|#F(`7lS~|6h2kjd;RdC#Q{bP;j=z P`fr-5AeBlb>#+X;Yv(*z literal 40972 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 From 7079b5d770eddfa5207973b16d74cd7a5fc29f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Apr 2016 09:15:45 -0700 Subject: [PATCH 21/32] fix(dashboard list): fixed minor issue with dashboard list panel, fixes #4768 --- CHANGELOG.md | 1 + public/app/plugins/panel/dashlist/module.ts | 23 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c57bfcfee2..36f644196fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726) * **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) * **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736) +* **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768) # 3.0.0-beta5 (2016-04-15) diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 77029bd7ceb..b6407cf103e 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -5,27 +5,26 @@ import config from 'app/core/config'; import {PanelCtrl} from 'app/plugins/sdk'; import {impressions} from 'app/features/dashboard/impression_store'; - // Set and populate defaults -var panelDefaults = { - query: '', - limit: 10, - tags: [], - recent: false, - search: false, - starred: true, - headings: true, -}; - class DashListCtrl extends PanelCtrl { static templateUrl = 'module.html'; groups: any[]; modes: any[]; + panelDefaults = { + query: '', + limit: 10, + tags: [], + recent: false, + search: false, + starred: true, + headings: true, + }; + /** @ngInject */ constructor($scope, $injector, private backendSrv) { super($scope, $injector); - _.defaults(this.panel, panelDefaults); + _.defaults(this.panel, this.panelDefaults); if (this.panel.tag) { this.panel.tags = [this.panel.tag]; From 51de894692ed38d9e5917077397ab2c110555ce1 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Apr 2016 09:59:48 +0200 Subject: [PATCH 22/32] fix(snapshots): sets default value for snapshot name make it possible for < 3.0 instances to publish snapshots ref #4778 --- pkg/api/dashboard_snapshot.go | 4 ++++ pkg/models/dashboard_snapshot.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 3c369ca5b7f..cdcb871314d 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -21,6 +21,10 @@ func GetSharingOptions(c *middleware.Context) { } func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) { + if cmd.Name == "" { + cmd.Name = "Unnamed snapshot" + } + if cmd.External { // external snapshot ref requires key and delete key if cmd.Key == "" || cmd.DeleteKey == "" { diff --git a/pkg/models/dashboard_snapshot.go b/pkg/models/dashboard_snapshot.go index f920e91f2e4..4a4d1290b6d 100644 --- a/pkg/models/dashboard_snapshot.go +++ b/pkg/models/dashboard_snapshot.go @@ -45,7 +45,7 @@ type DashboardSnapshotDTO struct { type CreateDashboardSnapshotCommand struct { Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` - Name string `json:"name" binding:"Required"` + Name string `json:"name"` Expires int64 `json:"expires"` // these are passed when storing an external snapshot ref From 0fb4141ce4efe2b603b374d64d2b7514d0d34726 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Apr 2016 10:23:09 +0200 Subject: [PATCH 23/32] docs(changelog): add note about scrollbar fix #4769 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f644196fc..d5b7dbf495c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) * **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736) * **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768) +* **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760) # 3.0.0-beta5 (2016-04-15) From 297c829bdc3247d1dbdc7e1c441f31059ed3f3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 09:47:30 +0200 Subject: [PATCH 24/32] tech(): updatad sass-lint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f454a42ba7a..e71a9049dde 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "phantomjs-prebuilt": "^2.1.3", "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.4", - "sass-lint": "^1.5.0", + "sass-lint": "^1.6.0", "systemjs": "0.19.24" }, "engines": { From dfe36fb702cf730d6aff344c4bfbb4e18a83a124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 11:41:56 +0200 Subject: [PATCH 25/32] feat(panel): added new panel event panel-initialized --- public/app/features/dashboard/viewStateSrv.js | 2 +- public/app/features/panel/panel_ctrl.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 2738105553a..cda5e69f006 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -36,7 +36,7 @@ function (angular, _, $) { self.update(payload); }); - $scope.onAppEvent('panel-instantiated', function(evt, payload) { + $scope.onAppEvent('panel-initialized', function(evt, payload) { self.registerPanel(payload.scope); }); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 4f3df7e6e8a..48746e56ff4 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -50,8 +50,11 @@ export class PanelCtrl { } init() { - this.publishAppEvent('panel-instantiated', {scope: this.$scope}); this.calculatePanelHeight(); + + this.publishAppEvent('panel-initialized', {scope: this.$scope}); + this.events.emit('panel-initialized'); + this.refresh(); } From de52ca47855658f8f41402ce76443c0d41738303 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 22 Apr 2016 11:54:09 +0200 Subject: [PATCH 26/32] docs(changelog): adds gauges to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b7dbf495c..90217553c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # 3.0.0-beta6 (unreleased) +### Enhancements +* **Singlestat**: Support for gauges in singlestat panel. closes [#3688](https://github.com/grafana/grafana/pull/3688) + ### Bug fixes * **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726) * **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) From db15bf23b83fb3cee2fe5e811c32642099ea4e67 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 22 Apr 2016 12:09:02 +0200 Subject: [PATCH 27/32] fix(sass): fixes sass lint issue --- public/sass/components/_dropdown.scss | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/public/sass/components/_dropdown.scss b/public/sass/components/_dropdown.scss index 339284eb471..c4fbb20f1f3 100644 --- a/public/sass/components/_dropdown.scss +++ b/public/sass/components/_dropdown.scss @@ -67,18 +67,20 @@ } // Links within the dropdown menu - > li > a { - display: block; - padding: 3px 20px 3px 15px; - clear: both; - font-weight: normal; - line-height: $line-height-base; - color: $dropdownLinkColor; - white-space: nowrap; + > li { + > a { + display: block; + padding: 3px 20px 3px 15px; + clear: both; + font-weight: normal; + line-height: $line-height-base; + color: $dropdownLinkColor; + white-space: nowrap; - i { - padding-right: 5px; - color: $link-color-disabled; + i { + padding-right: 5px; + color: $link-color-disabled; + } } } } From 24fbcd8dea5e80cb3678579ecf5e38008a9b4ac9 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Fri, 22 Apr 2016 13:05:43 +0200 Subject: [PATCH 28/32] Revert "Gauges" --- .../app/plugins/panel/singlestat/editor.html | 37 - public/app/plugins/panel/singlestat/module.ts | 95 +- public/app/system.conf.js | 3 +- public/test/test-main.js | 3 +- public/vendor/flot/jquery.flot.gauge.js | 960 ------------------ 5 files changed, 4 insertions(+), 1094 deletions(-) delete mode 100644 public/vendor/flot/jquery.flot.gauge.js diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index 437445a2400..6b0806133b8 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -156,43 +156,6 @@

-
-
-
-
    -
  • - Gauge -
  • -
  • - Show  - - -
  • -
  • - Threshold labels  - - -
  • -
  • - Min -
  • -
  • - -
  • -
  • - Max -
  • -
  • - -
  • -
-
-
-
-
-
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 6d60a70269e..8af684c9d68 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -4,10 +4,8 @@ import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; import 'jquery.flot'; -import 'jquery.flot.gauge'; import kbn from 'app/core/utils/kbn'; -import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; @@ -40,12 +38,6 @@ var panelDefaults = { full: false, lineColor: 'rgb(31, 120, 193)', fillColor: 'rgba(31, 118, 189, 0.18)', - }, - gauge: { - show: false, - minValue: 0, - maxValue: 100, - thresholdLabels: true } }; @@ -278,86 +270,6 @@ class SingleStatCtrl extends MetricsPanelCtrl { return body; } - function addGauge() { - var plotCanvas = $('
'); - var plotCss = { - top: '10px', - margin: 'auto', - position: 'relative', - height: (elem.height() * 0.9) + 'px', - width: elem.width() + 'px' - }; - - plotCanvas.css(plotCss); - - var thresholds = []; - for (var i = 0; i < data.thresholds.length; i++) { - thresholds.push({ - value: data.thresholds[i], - color: data.colorMap[i] - }); - } - thresholds.push({ - value: panel.gauge.maxValue, - color: data.colorMap[data.colorMap.length - 1] - }); - - var bgColor = config.bootData.user.lightTheme - ? 'rgb(230,230,230)' - : 'rgb(38,38,38)'; - - var options = { - series: { - gauges: { - gauge: { - min: panel.gauge.minValue, - max: panel.gauge.maxValue, - background: { color: bgColor }, - border: { color: null }, - shadow: { show: false }, - width: 38 - }, - frame: { show: false }, - label: { show: false }, - layout: { margin: 0 }, - cell: { border: { width: 0 } }, - threshold: { - values: thresholds, - label: { - show: panel.gauge.thresholdLabels, - margin: 8, - font: { size: 18 } - }, - width: 8 - }, - value: { - color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null, - formatter: function () { return data.valueFormated; }, - font: { size: getGaugeFontSize() } - }, - show: true - } - } - }; - - elem.append(plotCanvas); - - var plotSeries = { - data: [[0, data.valueRounded]] - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function getGaugeFontSize() { - if (panel.valueFontSize) { - var num = parseInt(panel.valueFontSize.substring(0, panel.valueFontSize.length - 1)); - return 30 * (num / 100); - } else { - return 30; - } - } - function addSparkline() { var width = elem.width() + 20; if (width < 30) { @@ -419,10 +331,11 @@ class SingleStatCtrl extends MetricsPanelCtrl { function render() { if (!ctrl.data) { return; } + data = ctrl.data; setElementHeight(); - var body = panel.gauge.show ? '' : getBigValueHtml(); + var body = getBigValueHtml(); if (panel.colorBackground && !isNaN(data.valueRounded)) { var color = getColorForValue(data, data.valueRounded); @@ -445,10 +358,6 @@ class SingleStatCtrl extends MetricsPanelCtrl { addSparkline(); } - if (panel.gauge.show) { - addGauge(); - } - elem.toggleClass('pointer', panel.links.length > 0); if (panel.links.length > 0) { diff --git a/public/app/system.conf.js b/public/app/system.conf.js index 40b70c0e30e..276988e5c34 100644 --- a/public/app/system.conf.js +++ b/public/app/system.conf.js @@ -27,8 +27,7 @@ System.config({ "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent", "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", - "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", - "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" + "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow" }, packages: { diff --git a/public/test/test-main.js b/public/test/test-main.js index dbee086c250..d40955022fe 100644 --- a/public/test/test-main.js +++ b/public/test/test-main.js @@ -35,8 +35,7 @@ "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent", "jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", - "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", - "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge" + "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow" }, packages: { diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js deleted file mode 100644 index d8ed958e990..00000000000 --- a/public/vendor/flot/jquery.flot.gauge.js +++ /dev/null @@ -1,960 +0,0 @@ -/*! - * jquery.flot.gauge v1.1.0 * - * - * Flot plugin for rendering gauge charts. - * - * Copyright (c) 2015 @toyoty99. - * Licensed under the MIT license. - */ - -/** - * @module flot.gauge - */ -(function($) { - - - /** - * Gauge class - * - * @class Gauge - */ - var Gauge = (function() { - /** - * context of canvas - * - * @property context - * @type Object - */ - var context; - /** - * placeholder of canvas - * - * @property placeholder - * @type Object - */ - var placeholder; - /** - * options of plot - * - * @property options - * @type Object - */ - var options; - /** - * options of gauge - * - * @property gaugeOptions - * @type Object - */ - var gaugeOptions; - /** - * data series - * - * @property series - * @type Array - */ - var series; - /** - * logger - * - * @property logger - * @type Object - */ - var logger; - - /** - * constructor - * - * @class Gauge - * @constructor - * @param {Object} gaugeOptions gauge options - */ - var Gauge = function(plot, ctx) { - context = ctx; - placeholder = plot.getPlaceholder(); - options = plot.getOptions(); - gaugeOptions = options.series.gauges; - series = plot.getData(); - logger = getLogger(gaugeOptions.debug); - } - - /** - * calculate layout - * - * @method calculateLayout - * @return the calculated layout properties - */ - Gauge.prototype.calculateLayout = function() { - - var canvasWidth = placeholder.width(); - var canvasHeight = placeholder.height(); - - - - // calculate cell size - var columns = Math.min(series.length, gaugeOptions.layout.columns); - var rows = Math.ceil(series.length / columns); - - - - var margin = gaugeOptions.layout.margin; - var hMargin = gaugeOptions.layout.hMargin; - var vMargin = gaugeOptions.layout.vMargin; - var cellWidth = (canvasWidth - (margin * 2) - (hMargin * (columns - 1))) / columns; - var cellHeight = (canvasHeight - (margin * 2) - (vMargin * (rows - 1))) / rows; - if (gaugeOptions.layout.square) { - var cell = Math.min(cellWidth, cellHeight); - cellWidth = cell; - cellHeight = cell; - } - - - - // calculate 'auto' values - calculateAutoValues(gaugeOptions, cellWidth); - - // calculate maximum radius - var cellMargin = gaugeOptions.cell.margin; - var labelMargin = 0; - var labelFontSize = 0; - if (gaugeOptions.label.show) { - labelMargin = gaugeOptions.label.margin; - labelFontSize = gaugeOptions.label.font.size; - } - var valueMargin = 0; - var valueFontSize = 0; - if (gaugeOptions.value.show) { - valueMargin = gaugeOptions.value.margin; - valueFontSize = gaugeOptions.value.font.size; - } - var thresholdWidth = 0; - if (gaugeOptions.threshold.show) { - thresholdWidth = gaugeOptions.threshold.width; - } - var thresholdLabelMargin = 0; - var thresholdLabelFontSize = 0; - if (gaugeOptions.threshold.label.show) { - thresholdLabelMargin = gaugeOptions.threshold.label.margin; - thresholdLabelFontSize = gaugeOptions.threshold.label.font.size; - } - - var maxRadiusH = (cellWidth / 2) - cellMargin - thresholdWidth - (thresholdLabelMargin * 2) - thresholdLabelFontSize; - - var startAngle = gaugeOptions.gauge.startAngle; - var endAngle = gaugeOptions.gauge.endAngle; - var dAngle = (endAngle - startAngle) / 100; - var heightRatioV = -1; - for (var a = startAngle; a < endAngle; a += dAngle) { - heightRatioV = Math.max(heightRatioV, Math.sin(toRad(a))); - } - heightRatioV = Math.max(heightRatioV, Math.sin(toRad(endAngle))); - var outerRadiusV = (cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize) / (1 + heightRatioV); - if (outerRadiusV * heightRatioV < valueMargin + (valueFontSize / 2)) { - outerRadiusV = cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize - valueMargin - (valueFontSize / 2); - } - var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth; - - var radius = Math.min(maxRadiusH, maxRadiusV); - - - var width = gaugeOptions.gauge.width; - if (width >= radius) { - width = Math.max(3, radius / 3); - } - - - var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius; - var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2)); - - return { - canvasWidth: canvasWidth, - canvasHeight: canvasHeight, - margin: margin, - hMargin: hMargin, - vMargin: vMargin, - columns: columns, - rows: rows, - cellWidth: cellWidth, - cellHeight: cellHeight, - cellMargin: cellMargin, - labelMargin: labelMargin, - labelFontSize: labelFontSize, - valueMargin: valueMargin, - valueFontSize: valueFontSize, - width: width, - radius: radius, - thresholdWidth: thresholdWidth, - thresholdLabelMargin: thresholdLabelMargin, - thresholdLabelFontSize: thresholdLabelFontSize, - gaugeOuterHeight: gaugeOuterHeight - }; - } - - /** - * calculate the values which are set as 'auto' - * - * @method calculateAutoValues - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Number} cellWidth the width of cell - */ - function calculateAutoValues(gaugeOptionsi, cellWidth) { - - if (gaugeOptionsi.gauge.width === "auto") { - gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8); - } - if (gaugeOptionsi.label.margin === "auto") { - gaugeOptionsi.label.margin = Math.max(1, cellWidth / 20); - } - if (gaugeOptionsi.label.font.size === "auto") { - gaugeOptionsi.label.font.size = Math.max(5, cellWidth / 8); - } - if (gaugeOptionsi.value.margin === "auto") { - gaugeOptionsi.value.margin = Math.max(1, cellWidth / 30); - } - if (gaugeOptionsi.value.font.size === "auto") { - gaugeOptionsi.value.font.size = Math.max(5, cellWidth / 9); - } - if (gaugeOptionsi.threshold.width === "auto") { - gaugeOptionsi.threshold.width = Math.max(3, cellWidth / 100); - } - if (gaugeOptionsi.threshold.label.margin === "auto") { - gaugeOptionsi.threshold.label.margin = Math.max(3, cellWidth / 40); - } - if (gaugeOptionsi.threshold.label.font.size === "auto") { - gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15); - } - - } - Gauge.prototype.calculateAutoValues = calculateAutoValues; - - /** - * calculate the layout of the cell inside - * - * @method calculateCellLayout - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Number} cellWidth the width of cell - * @param {Number} i the index of the series - * @return the calculated cell layout properties - */ - Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) { - - // calculate top, left and center - var c = col(layout.columns, i); - var r = row(layout.columns, i); - var x = layout.margin + (layout.cellWidth + layout.hMargin) * c; - var y = layout.margin + (layout.cellHeight + layout.vMargin) * r; - var cx = x + (layout.cellWidth / 2); - var cy = y + layout.cellMargin + (layout.labelMargin * 2) + layout.labelFontSize + layout.thresholdWidth - + layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2) + layout.radius; - var blank = layout.cellHeight - (layout.cellMargin * 2) - (layout.labelMargin * 2) - layout.labelFontSize - layout.gaugeOuterHeight; - var offsetY = 0; - if (gaugeOptionsi.cell.vAlign === "middle") { - offsetY = (blank / 2); - } else if (gaugeOptionsi.cell.vAlign === "bottom") { - offsetY = blank; - } - cy += offsetY; - - return { - col: c, - row: r, - x: x, - y: y, - offsetY: offsetY, - cellWidth: layout.cellWidth, - cellHeight: layout.cellHeight, - cellMargin: layout.cellMargin, - cx: cx, - cy: cy - } - } - - /** - * draw the background of chart - * - * @method drawBackground - * @param {Object} layout the layout properties - */ - Gauge.prototype.drawBackground = function(layout) { - - if (!gaugeOptions.frame.show) { - return; - } - context.save(); - context.strokeStyle = options.grid.borderColor; - context.lineWidth = options.grid.borderWidth; - context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight); - if (options.grid.backgroundColor) { - context.fillStyle = options.grid.backgroundColor; - context.fillRect(0, 0, layout.canvasWidth, layout.canvasHeight); - } - context.restore(); - } - - /** - * draw the background of cell - * - * @method drawCellBackground - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} cellLayout the cell layout properties - */ - Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) { - - context.save(); - if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { - context.strokeStyle = gaugeOptionsi.cell.border.color; - context.lineWidth = gaugeOptionsi.cell.border.width; - context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); - } - if (gaugeOptionsi.cell.background && gaugeOptionsi.cell.background.color) { - context.fillStyle = gaugeOptionsi.cell.background.color; - context.fillRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); - } - context.restore(); - } - - /** - * draw the gauge - * - * @method drawGauge - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - * @param {String} label the label of data - * @param {Number} data the value of the gauge - */ - Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) { - - - var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0; - - - // draw gauge frame - drawArcWithShadow( - cellLayout.cx, // center x - cellLayout.cy, // center y - layout.radius, - layout.width, - toRad(gaugeOptionsi.gauge.startAngle), - toRad(gaugeOptionsi.gauge.endAngle), - gaugeOptionsi.gauge.border.color, // line color - gaugeOptionsi.gauge.border.width, // line width - gaugeOptionsi.gauge.background.color, // fill color - blur); - - // draw gauge - var c1 = getColor(gaugeOptionsi, data); - var a2 = calculateAngle(gaugeOptionsi, layout, data); - drawArcWithShadow( - cellLayout.cx, // center x - cellLayout.cy, // center y - layout.radius - 1, - layout.width - 2, - toRad(gaugeOptionsi.gauge.startAngle), - toRad(a2), - c1, // line color - 1, // line width - c1, // fill color - blur); - } - - /** - * decide the color of the data from the threshold options - * - * @method getColor - * @private - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Number} data the value of the gauge - */ - function getColor(gaugeOptionsi, data) { - var color; - for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { - var threshold = gaugeOptionsi.threshold.values[i]; - color = threshold.color; - if (data <= threshold.value) { - break; - } - } - return color; - } - - /** - * calculate the angle of the data - * - * @method calculateAngle - * @private - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Number} data the value of the gauge - */ - function calculateAngle(gaugeOptionsi, layout, data) { - var a = - gaugeOptionsi.gauge.startAngle - + (gaugeOptionsi.gauge.endAngle - gaugeOptionsi.gauge.startAngle) - * ((data - gaugeOptionsi.gauge.min) / (gaugeOptionsi.gauge.max - gaugeOptionsi.gauge.min)); - - if (a < gaugeOptionsi.gauge.startAngle) { - a = gaugeOptionsi.gauge.startAngle; - } else if (a > gaugeOptionsi.gauge.endAngle) { - a = gaugeOptionsi.gauge.endAngle; - } - return a; - } - - /** - * draw the arc of the threshold - * - * @method drawThreshold - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - */ - Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) { - - var a1 = gaugeOptionsi.gauge.startAngle; - for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { - var threshold = gaugeOptionsi.threshold.values[i]; - c1 = threshold.color; - a2 = calculateAngle(gaugeOptionsi, layout, threshold.value); - drawArc( - context, - cellLayout.cx, // center x - cellLayout.cy, // center y - layout.radius + layout.thresholdWidth, - layout.thresholdWidth - 2, - toRad(a1), - toRad(a2), - c1, // line color - 1, // line width - c1); // fill color - a1 = a2; - } - } - - /** - * draw an arc with a shadow - * - * @method drawArcWithShadow - * @private - * @param {Number} cx the x position of the center - * @param {Number} cy the y position of the center - * @param {Number} r the radius of an arc - * @param {Number} w the width of an arc - * @param {Number} rd1 the start angle of an arc in radians - * @param {Number} rd2 the end angle of an arc in radians - * @param {String} lc the color of a line - * @param {Number} lw the widht of a line - * @param {String} fc the fill color of an arc - * @param {Number} blur the shdow blur - */ - function drawArcWithShadow(cx, cy, r, w, rd1, rd2, lc, lw, fc, blur) { - if (rd1 === rd2) { - return; - } - context.save(); - - drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc); - - if (blur) { - drawArc(context, cx, cy, r, w, rd1, rd2); - context.clip(); - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 10; - context.shadowColor = "gray"; - drawArc(context, cx, cy, r + 1, w + 2, rd1, rd2, lc, 1); - } - context.restore(); - } - - /** - * draw the label of the gauge - * - * @method drawLable - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - * @param {Number} i the index of the series - * @param {Object} item the item of the series - */ - Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) { - - drawText( - cellLayout.cx, - cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY, - "flotGagueLabel" + i, - gaugeOptionsi.label.formatter ? gaugeOptionsi.label.formatter(item.label, item.data[0][1]) : text, - gaugeOptionsi.label); - } - - /** - * draw the value of the gauge - * - * @method drawValue - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - * @param {Number} i the index of the series - * @param {Object} item the item of the series - */ - Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) { - - drawText( - cellLayout.cx, - cellLayout.cy - (gaugeOptionsi.value.font.size / 2), - "flotGagueValue" + i, - gaugeOptionsi.value.formatter ? gaugeOptionsi.value.formatter(item.label, item.data[0][1]) : text, - gaugeOptionsi.value); - } - - /** - * draw the values of the threshold - * - * @method drawThresholdValues - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - * @param {Number} i the index of the series - */ - Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) { - - // min, max - drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Min" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle); - drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Max" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle); - // threshold values - for (var j = 0; j < gaugeOptionsi.threshold.values.length; j++) { - var threshold = gaugeOptionsi.threshold.values[j]; - if (threshold.value > gaugeOptionsi.gauge.min && threshold.value < gaugeOptionsi.gauge.max) { - var a = calculateAngle(gaugeOptionsi, layout, threshold.value); - drawThresholdValue(gaugeOptionsi, layout, cellLayout, i + "_" + j, threshold.value, a); - } - } - } - - /** - * draw the value of the threshold - * - * @method drawThresholdValue - * @param {Object} gaugeOptionsi the options of the gauge - * @param {Object} layout the layout properties - * @param {Object} cellLayout the cell layout properties - * @param {Number} i the index of the series - * @param {Number} value the value of the threshold - * @param {Number} a the angle of the value drawn - */ - function drawThresholdValue(gaugeOptionsi, layout, cellLayout, i, value, a) { - drawText( - cellLayout.cx - + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) - * Math.cos(toRad(a))), - cellLayout.cy - + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) - * Math.sin(toRad(a))), - "flotGagueThresholdValue" + i, - gaugeOptionsi.threshold.label.formatter ? gaugeOptionsi.threshold.label.formatter(value) : value, - gaugeOptionsi.threshold.label, - a); - } - - /** - * draw a text - * - * the textOptions is assumed as follows: - * - * textOptions: { - * background: { - * color: null, - * opacity: 0 - * }, - * font: { - * size: "auto" - * family: "\"MS ゴシック\",sans-serif" - * }, - * color: null - * } - * - * @method drawText - * @private - * @param {Number} x the x position of the text drawn (left top) - * @param {Number} y the y position of the text drawn (left top) - * @param {String} id the id of the dom element - * @param {String} text the text drawn - * @param {Object} textOptions the option of the text - * @param {Number} [a] the angle of the value drawn - */ - function drawText(x, y, id, text, textOptions, a) { - var span = $("." + id, placeholder); - var exists = span.length; - if (!exists) { - span = $("") - span.attr("id", id); - span.css("position", "absolute"); - span.css("top", y + "px"); - if (textOptions.font.size) { - span.css("font-size", textOptions.font.size + "px"); - } - if (textOptions.font.family) { - span.css("font-family", textOptions.font.family); - } - if (textOptions.color) { - span.css("color", textOptions.color); - } - if (textOptions.background.color) { - span.css("background-color", textOptions.background.color); - } - if (textOptions.background.opacity) { - span.css("opacity", textOptions.background.opacity); - } - placeholder.append(span); - } - span.text(text); - // after append, readjust the left position - span.css("left", x + "px"); // for redraw, resetting the left position is needed here - span.css("left", (parseInt(span.css("left")) - (span.width()/ 2)) + "px"); - - // at last, set angle - if (!exists && a) { - span.css("top", (parseInt(span.css("top")) - (span.height()/ 2)) + "px"); - span.css("transform", "rotate(" + ((180 * a) + 90) + "deg)"); // not supported for ie8 - } - } - - return Gauge; - })(); - /** - * get a instance of Logger - * - * @method getLogger - * @for flot.gauge - * @private - * @param {Object} debugOptions the options of debug - */ - function getLogger(debugOptions) { - return typeof Logger !== "undefined" ? new Logger(debugOptions) : null; - } - - /** - * calculate the index of columns for the specified data - * - * @method col - * @for flot.gauge - * @param {Number} columns the number of columns - * @param {Number} i the index of the series - * @return the index of columns - */ - function col(columns, i) { - return i % columns; - } - - /** - * calculate the index of rows for the specified data - * - * @method row - * @for flot.gauge - * @param {Number} columns the number of rows - * @param {Number} i the index of the series - * @return the index of rows - */ - function row(columns, i) { - return Math.floor(i / columns); - } - - /** - * calculate the angle in radians - * - * internally, use a number without PI (0 - 2). - * so, in this function, multiply PI - * - * @method toRad - * @for flot.gauge - * @param {Number} a the number of angle without PI - * @return the angle in radians - */ - function toRad(a) { - return a * Math.PI; - } - - /** - * draw an arc - * - * @method drawArc - * @for flot.gauge - * @param {Object} context the context of canvas - * @param {Number} cx the x position of the center - * @param {Number} cy the y position of the center - * @param {Number} r the radius of an arc - * @param {Number} w the width of an arc - * @param {Number} rd1 the start angle of an arc in radians - * @param {Number} rd2 the end angle of an arc in radians - * @param {String} lc the color of a line - * @param {Number} lw the widht of a line - * @param {String} fc the fill color of an arc - */ - function drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc) { - if (rd1 === rd2) { - return; - } - var counterClockwise = false; - context.save(); - context.beginPath(); - context.arc(cx, cy, r, rd1, rd2, counterClockwise); - context.lineTo(cx + (r - w) * Math.cos(rd2), - cy + (r - w) * Math.sin(rd2)); - context.arc(cx, cy, r - w, rd2, rd1, !counterClockwise); - context.closePath(); - if (lw) { - context.lineWidth = lw; - } - if (lc) { - context.strokeStyle = lc; - context.stroke(); - } - if (fc) { - context.fillStyle = fc; - context.fill(); - } - context.restore(); - } - - /** - * initialize plugin - * - * @method init - * @for flot.gauge - * @private - * @param {Object} plot a instance of plot - */ - function init (plot) { - // add processOptions hook - plot.hooks.processOptions.push(function(plot, options) { - var logger = getLogger(options.series.gauges.debug); - - - - - // turn 'grid' and 'legend' off - if (options.series.gauges.show) { - options.grid.show = false; - options.legend.show = false; - } - - // sort threshold - var thresholds = options.series.gauges.threshold.values; - - thresholds.sort(function(a, b) { - if (a.value < b.value) { - return -1; - } else if (a.value > b.value) { - return 1; - } else { - return 0; - } - }); - - - - }); - - // add draw hook - plot.hooks.draw.push(function(plot, context) { - var options = plot.getOptions(); - var gaugeOptions = options.series.gauges; - - var logger = getLogger(gaugeOptions.debug); - - - if (!gaugeOptions.show) { - return; - } - - var series = plot.getData(); - - if (!series || !series.length) { - return; // if no series were passed - } - - var gauge = new Gauge(plot, context); - - // calculate layout - var layout = gauge.calculateLayout(); - - // debug layout - if (gaugeOptions.debug.layout) { - - } - - // draw background - gauge.drawBackground(layout) - - // draw cells (label, gauge, value, threshold) - for (var i = 0; i < series.length; i++) { - var item = series[i]; - - var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges); - if (item.gauges) { - // re-calculate 'auto' values - gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth); - } - - // calculate cell layout - var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i); - - // draw cell background - gauge.drawCellBackground(gaugeOptionsi, cellLayout) - // debug layout - if (gaugeOptionsi.debug.layout) { - - } - // draw label - if (gaugeOptionsi.label.show) { - gauge.drawLable(gaugeOptionsi, layout, cellLayout, i, item); - } - // draw gauge - gauge.drawGauge(gaugeOptionsi, layout, cellLayout, item.label, item.data[0][1]); - // draw threshold - if (gaugeOptionsi.threshold.show) { - gauge.drawThreshold(gaugeOptionsi, layout, cellLayout); - } - if (gaugeOptionsi.threshold.label.show) { - gauge.drawThresholdValues(gaugeOptionsi, layout, cellLayout, i) - } - // draw value - if (gaugeOptionsi.value.show) { - gauge.drawValue(gaugeOptionsi, layout, cellLayout, i, item); - } - } - }); - } - - /** - * [defaults description] - * - * @property defaults - * @type {Object} - */ - var defaults = { - series: { - gauges: { - debug: { - log: false, - layout: false, - alert: false - }, - show: false, - layout: { - margin: 5, - columns: 3, - hMargin: 5, - vMargin: 5, - square: false - }, - frame: { - show: true - }, - cell: { - background: { - color: null - }, - border: { - show: true, - color: "black", - width: 1 - }, - margin: 5, - vAlign: "middle" // 'top' or 'middle' or 'bottom' - }, - gauge: { - width: "auto", // a specified number, or 'auto' - startAngle: 0.9, // 0 - 2 factor of the radians - endAngle: 2.1, // 0 - 2 factor of the radians - min: 0, - max: 100, - background: { - color: "white" - }, - border: { - color: "lightgray", - width: 2 - }, - shadow: { - show: true, - blur: 5 - } - }, - label: { - show: true, - margin: "auto", // a specified number, or 'auto' - background: { - color: null, - opacity: 0 - }, - font: { - size: "auto", // a specified number, or 'auto' - family: "sans-serif" - }, - color: null, - formatter: function(label, value) { - return label; - } - }, - value: { - show: true, - margin: "auto", // a specified number, or 'auto' - background: { - color: null, - opacity: 0 - }, - font: { - size: "auto", // a specified number, or 'auto' - family: "sans-serif" - }, - color: null, - formatter: function(label, value) { - return parseInt(value); - } - }, - threshold: { - show: true, - width: "auto", // a specified number, or 'auto' - label: { - show: true, - margin: "auto", // a specified number, or 'auto' - background: { - color: null, - opacity: 0 - }, - font: { - size: "auto", // a specified number, or 'auto' - family: ",sans-serif" - }, - color: null, - formatter: function(value) { - return value; - } - }, - values: [ - { - value: 50, - color: "lightgreen" - }, { - value: 80, - color: "yellow" - }, { - value: 100, - color: "red" - } - ] - } - } - } - }; - - // register the gauge plugin - $.plot.plugins.push({ - init: init, - options: defaults, - name: "gauge", - version: "1.1.0" - }); - -})(jQuery); From 1dfeb192a31f8a6fcfc9265a4ab99a25da3b8b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 13:59:54 +0200 Subject: [PATCH 29/32] fix(templating): another fix for templating and custom all value escaping, fixes #4787 --- public/app/features/templating/templateValuesSrv.js | 5 ----- public/test/specs/templateValuesSrv-specs.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 799e27583d5..4cda10ac406 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -294,11 +294,6 @@ function (angular, _, kbn) { }; this.addAllOption = function(variable) { - if (variable.allValue) { - variable.options.unshift({text: 'All', value: variable.allValue}); - return; - } - variable.options.unshift({text: 'All', value: "$__all"}); }; diff --git a/public/test/specs/templateValuesSrv-specs.js b/public/test/specs/templateValuesSrv-specs.js index d81ce4fd059..110cb548467 100644 --- a/public/test/specs/templateValuesSrv-specs.js +++ b/public/test/specs/templateValuesSrv-specs.js @@ -280,7 +280,7 @@ define([ }); it('should add All option with custom value', function() { - expect(scenario.variable.options[0].value).to.be('*'); + expect(scenario.variable.options[0].value).to.be('$__all'); }); }); From 45dd9c5795600e2a0ff21ae9cd76a7edffdbd813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 14:36:20 +0200 Subject: [PATCH 30/32] fix(graph panel): fix for graph panel alignment when legend is in table mode, fixes #4772 --- public/app/plugins/panel/graph/graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 9e6633b5551..669fe1fbc91 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -73,7 +73,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { var legendSeries = _.filter(data, function(series) { return series.hideFromLegend(panel.legend) === false; }); - var total = 23 + (22 * legendSeries.length); + var total = 23 + (21 * legendSeries.length); return Math.min(total, Math.floor(panelHeight/2)); } else { return 26; From 723bfa70db64f63286dffd65ff5d40fdf14f910d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 14:46:32 +0200 Subject: [PATCH 31/32] fix(table panel): fixed issue with string array formating, fixes #4791 --- CHANGELOG.md | 1 + public/app/plugins/panel/table/renderer.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90217553c19..ba8e6242243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736) * **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768) * **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760) +* **Table panel**: Fixed issue table panel formating string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791) # 3.0.0-beta5 (2016-04-15) diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index c00656071be..6d75f5c97c9 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -30,7 +30,7 @@ export class TableRenderer { } if (_.isArray(v)) { - v = v.join(', '); + v = v.join(', '); } return v; From 8b656f15b9274365d99c7f7d2dc73fbcc21fc2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Apr 2016 14:57:08 +0200 Subject: [PATCH 32/32] feat(graphite): added stddevSeries func def, closes #4782 --- public/app/plugins/datasource/graphite/gfunc.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/app/plugins/datasource/graphite/gfunc.js b/public/app/plugins/datasource/graphite/gfunc.js index de482598fc5..a7907186811 100644 --- a/public/app/plugins/datasource/graphite/gfunc.js +++ b/public/app/plugins/datasource/graphite/gfunc.js @@ -80,6 +80,13 @@ function (_, $) { category: categories.Calculate, }); + addFuncDef({ + name: 'stddevSeries', + params: optionalSeriesRefArgs, + defaultParams: [''], + category: categories.Calculate, + }); + addFuncDef({ name: 'divideSeries', params: optionalSeriesRefArgs,