Merge branch 'master' into query-editor-style

This commit is contained in:
Torkel Ödegaard 2016-04-25 14:22:36 +02:00
commit 1a433de0b4
49 changed files with 1463 additions and 265 deletions

View File

@ -1,3 +1,17 @@
# 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)
* **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)
* **grafana-cli**: Improve error message when failing to install plugins due to corrupt response, fixes [#4651](https://github.com/grafana/grafana/issues/4651)
# 3.0.0-beta5 (2016-04-15)
### Bug fixes

View File

@ -191,7 +191,7 @@ Will return the home dashboard.
`GET /api/dashboards/tags`
Get all tabs of dashboards
Get all tags of dashboards
**Example Request**:

View File

@ -15,7 +15,7 @@ Grafana already have a strong community of contributors and plugin developers.
By making it easier to develop and install plugins we hope that the community
can grow even stronger and develop new plugins that we would never think about.
You can discover available plugins on [Grafana.net](http://grafana.net)
You can discover available plugins on [Grafana.net](https://grafana.net)

View File

@ -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": {

View File

@ -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"},

View File

@ -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 == "" {

View File

@ -41,7 +41,6 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
req.URL.RawQuery = reqQueryVals.Encode()
} else if ds.Type == m.DS_INFLUXDB {
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
reqQueryVals.Add("db", ds.Database)
req.URL.RawQuery = reqQueryVals.Encode()
if !ds.BasicAuth {
req.Header.Del("Authorization")

View File

@ -103,6 +103,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
}
for _, include := range plugin.Includes {
if !c.HasUserRole(include.Role) {
continue
}
if include.Type == "page" && include.AddToNav {
link := &dtos.NavLink{
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug,
@ -110,6 +114,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
}
appLink.Children = append(appLink.Children, link)
}
if include.Type == "dashboard" && include.AddToNav {
link := &dtos.NavLink{
Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug,
@ -124,9 +129,11 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
}
if len(appLink.Children) > 0 {
data.MainNavLinks = append(data.MainNavLinks, appLink)
}
}
}
if c.IsGrafanaAdmin {
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{

View File

@ -4,6 +4,7 @@ import (
"os"
"github.com/codegangsta/cli"
"github.com/fatih/color"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
)
@ -12,7 +13,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
cmd := &contextCommandLine{context}
if err := command(cmd); err != nil {
log.Error("\nError: ")
log.Errorf("\n%s: ", color.RedString("Error"))
log.Errorf("%s\n\n", err)
cmd.ShowHelp()

View File

@ -126,13 +126,18 @@ 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")
downloadFile(pluginName, filePath, url)
if retryCount < 3 {
fmt.Println("Failed downloading. Will retry once.")
err = downloadFile(pluginName, filePath, url)
} else {
failure := fmt.Sprintf("%v", r)
if failure == "runtime error: makeslice: len out of range" {
err = fmt.Errorf("Corrupt http response from source. Please try again.\n")
} else {
panic(r)
}
}
}
}()
resp, err := http.Get(url)
@ -164,14 +169,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)
log.Errorf("Failed to extract file: %v", err)
}
defer src.Close()
io.Copy(dst, src)
dst.Close()
src.Close()
}
}

View File

@ -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())
}

View File

@ -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() {

View File

@ -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

View File

@ -1,7 +1,9 @@
package models
import (
"encoding/json"
"errors"
"fmt"
"time"
)
@ -37,6 +39,26 @@ func (r RoleType) Includes(other RoleType) bool {
return r == other
}
func (r *RoleType) UnmarshalJSON(data []byte) error {
var str string
err := json.Unmarshal(data, &str)
if err != nil {
return err
}
*r = RoleType(str)
if (*r).IsValid() == false {
if (*r) != "" {
return errors.New(fmt.Sprintf("JSON validation error: invalid role value: %s", *r))
}
*r = ROLE_VIEWER
}
return nil
}
type OrgUser struct {
Id int64
OrgId int64

View File

@ -7,7 +7,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
@ -69,6 +69,12 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error {
pb.Dependencies.GrafanaVersion = "*"
}
for _, include := range pb.Includes {
if include.Role == "" {
include.Role = m.RoleType(m.ROLE_VIEWER)
}
}
pb.PluginDir = pluginDir
Plugins[pb.Id] = pb
return nil
@ -84,7 +90,7 @@ type PluginInclude struct {
Path string `json:"path"`
Type string `json:"type"`
Component string `json:"component"`
Role models.RoleType `json:"role"`
Role m.RoleType `json:"role"`
AddToNav bool `json:"addToNav"`
DefaultNav bool `json:"defaultNav"`
Slug string `json:"slug"`

View File

@ -216,7 +216,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
return inTransaction2(func(sess *session) error {
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
has, err := x.Get(&dashboard)
has, err := sess.Get(&dashboard)
if err != nil {
return err
} else if has == false {

View File

@ -206,9 +206,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
});
$compile(child)(scope);
elem.empty();
// let a binding digest cycle complete before adding to dom
setTimeout(function() {
elem.append(child);
});
}
function registerPluginComponent(scope, elem, attrs, componentInfo) {

View File

@ -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);
});

View File

@ -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();
}

View File

@ -14,7 +14,7 @@
<div class="gf-form-group">
<p>Type the following on the command line to update {{plugin.name}}.</p>
<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
<span class="small">Check out {{plugin.name}} on <a href="https://grafana.net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
</div>
<p class="pluginlist-none-installed"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
</div>

View File

@ -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);

View File

@ -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"});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -80,6 +80,13 @@ function (_, $) {
category: categories.Calculate,
});
addFuncDef({
name: 'stddevSeries',
params: optionalSeriesRefArgs,
defaultParams: [''],
category: categories.Calculate,
});
addFuncDef({
name: 'divideSeries',
params: optionalSeriesRefArgs,

View File

@ -6,7 +6,7 @@ There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0
This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API.
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://www.grafana.net/plugins/grafana-influxdb-08-datasource).
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://grafana.net/plugins/grafana-influxdb-08-datasource).
Read more about InfluxDB here:

View File

@ -152,7 +152,9 @@ export default class InfluxQuery {
if (interpolate) {
value = this.templateSrv.replace(value, this.scopedVars);
}
if (isNaN(+value)) {
value = "'" + value.replace('\\', '\\\\') + "'";
}
} else if (interpolate){
value = this.templateSrv.replace(value, this.scopedVars, 'regex');
}
@ -166,6 +168,8 @@ export default class InfluxQuery {
if (!measurement.match('^/.*/')) {
measurement = '"' + measurement+ '"';
} else {
measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
}
if (policy !== 'default') {

View File

@ -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 + "'";
}

View File

@ -12,17 +12,29 @@ export default class ResponseParser {
return [];
}
var series = influxResults.series[0];
return _.map(series.values, (value) => {
var influxdb11format = 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) {
return { text: (value[1] || value[0]) };
if (influxdb11format) {
addUnique(res, value[1] || value[0]);
} else {
return { text: value[0] };
addUnique(res, value[0]);
}
} else {
addUnique(res, value);
}
});
});
return _.map(res, value => {
return { text: value};
}
});
}
}
function addUnique(arr, value) {
arr[value] = value;
}

View File

@ -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", () => {

View File

@ -5,8 +5,13 @@ 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 = {
class DashListCtrl extends PanelCtrl {
static templateUrl = 'module.html';
groups: any[];
modes: any[];
panelDefaults = {
query: '',
limit: 10,
tags: [],
@ -16,16 +21,10 @@ var panelDefaults = {
headings: true,
};
class DashListCtrl extends PanelCtrl {
static templateUrl = 'module.html';
groups: any[];
modes: any[];
/** @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];

View File

@ -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;

View File

@ -13,7 +13,20 @@ import TimeSeries from 'app/core/time_series2';
import * as fileExport from 'app/core/utils/file_export';
import {MetricsPanelCtrl} from 'app/plugins/sdk';
var panelDefaults = {
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
hiddenSeries: any = {};
seriesList: any = [];
logScales: any;
unitFormats: any;
annotationsPromise: any;
datapointsCount: number;
datapointsOutside: boolean;
datapointsWarning: boolean;
colors: any = [];
panelDefaults = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
@ -92,27 +105,14 @@ var panelDefaults = {
seriesOverrides: [],
};
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
hiddenSeries: any = {};
seriesList: any = [];
logScales: any;
unitFormats: any;
annotationsPromise: any;
datapointsCount: number;
datapointsOutside: boolean;
datapointsWarning: boolean;
colors: any = [];
/** @ngInject */
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
_.defaults(this.panel, angular.copy(panelDefaults));
_.defaults(this.panel.tooltip, panelDefaults.tooltip);
_.defaults(this.panel.grid, panelDefaults.grid);
_.defaults(this.panel.legend, panelDefaults.legend);
_.defaults(this.panel, this.panelDefaults);
_.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
_.defaults(this.panel.grid, this.panelDefaults.grid);
_.defaults(this.panel.legend, this.panelDefaults.legend);
this.colors = $scope.$root.colors;

View File

@ -22,7 +22,7 @@
</a>
</div>
<div class="pluginlist-item" ng-show="category.list.length === 0">
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana.net/plugins/">
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="https://grafana.net/plugins">
<span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span>
</a>
</div>

View File

@ -4,20 +4,20 @@ import _ from 'lodash';
import config from 'app/core/config';
import {PanelCtrl} from '../../../features/panel/panel_ctrl';
// Set and populate defaults
var panelDefaults = {
};
class PluginListCtrl extends PanelCtrl {
static templateUrl = 'module.html';
pluginList: any[];
viewModel: any;
// Set and populate defaults
panelDefaults = {
};
/** @ngInject */
constructor($scope, $injector, private backendSrv, private $location) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel, this.panelDefaults);
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.pluginList = [];

View File

@ -156,6 +156,43 @@
</div>
</div>
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Gauge</strong>
</li>
<li class="tight-form-item">
Show&nbsp;
<input class="cr1" id="panel.gauge.show" type="checkbox"
ng-model="ctrl.panel.gauge.show" ng-checked="ctrl.panel.gauge.show" ng-change="ctrl.render()">
<label for="panel.gauge.show" class="cr1"></label>
</li>
<li class="tight-form-item">
Threshold labels&nbsp;
<input class="cr1" id="panel.gauge.thresholdLabels" type="checkbox"
ng-model="ctrl.panel.gauge.thresholdLabels" ng-checked="ctrl.panel.gauge.thresholdLabels" ng-change="ctrl.render()">
<label for="panel.gauge.thresholdLabels" class="cr1"></label>
</li>
<li class="tight-form-item">
Min
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model="ctrl.panel.gauge.minValue" ng-blur="ctrl.render()" placeholder="0"></input>
</li>
<li class="tight-form-item last">
Max
</li>
<li>
<input type="text" class="input-small tight-form-input last" ng-model="ctrl.panel.gauge.maxValue" ng-blur="ctrl.render()" placeholder="100"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form last">

View File

@ -4,13 +4,23 @@ 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';
class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
series: any[];
data: any;
fontSizes: any[];
unitFormats: any[];
// Set and populate defaults
var panelDefaults = {
panelDefaults = {
links: [],
datasource: null,
maxDataPoints: 100,
@ -38,21 +48,19 @@ 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
}
};
class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
series: any[];
data: any;
fontSizes: any[];
unitFormats: any[];
/** @ngInject */
constructor($scope, $injector, private $location, private linkSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel, this.panelDefaults);
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));
@ -270,6 +278,86 @@ class SingleStatCtrl extends MetricsPanelCtrl {
return body;
}
function addGauge() {
var plotCanvas = $('<div></div>');
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) {
@ -331,11 +419,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
function render() {
if (!ctrl.data) { return; }
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 +445,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
addSparkline();
}
if (panel.gauge.show) {
addGauge();
}
elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) {

View File

@ -10,7 +10,14 @@ import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor';
import {TableRenderer} from './renderer';
var panelDefaults = {
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
pageIndex: number;
dataRaw: any;
table: any;
panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
@ -37,13 +44,6 @@ var panelDefaults = {
sort: {col: 0, desc: true},
};
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
pageIndex: number;
dataRaw: any;
table: any;
/** @ngInject */
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
@ -56,7 +56,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
delete this.panel.fields;
}
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel, this.panelDefaults);
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));

View File

@ -30,7 +30,7 @@ export class TableRenderer {
}
if (_.isArray(v)) {
v = v.join(',&nbsp;');
v = v.join(', ');
}
return v;

View File

@ -3,23 +3,21 @@
import _ from 'lodash';
import {PanelCtrl} from 'app/plugins/sdk';
// Set and populate defaults
var panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
export class TextPanelCtrl extends PanelCtrl {
static templateUrl = `public/app/plugins/panel/text/module.html`;
remarkable: any;
content: string;
// Set and populate defaults
panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
/** @ngInject */
constructor($scope, $injector, private templateSrv, private $sce) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel, this.panelDefaults);
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('refresh', this.onRender.bind(this));

View File

@ -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: {

View File

@ -67,7 +67,8 @@
}
// Links within the dropdown menu
> li > a {
> li {
> a {
display: block;
padding: 3px 20px 3px 15px;
clear: both;
@ -82,6 +83,7 @@
}
}
}
}
// Hover/Focus state
// -----------

View File

@ -85,7 +85,8 @@
}
.graph-legend-table {
overflow-y: scroll;
overflow-y: auto;
overflow-x: hidden;
.graph-legend-series {
display: table-row;

View File

@ -4,4 +4,5 @@
margin: 0 0 $spacer $spacer * 1.5;
}
li {line-height: 2;}
a { color: $external-link-color; }
}

View File

@ -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;

View File

@ -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() {

View File

@ -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');
});
});

View File

@ -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: {

960
public/vendor/flot/jquery.flot.gauge.js vendored Normal file
View File

@ -0,0 +1,960 @@
/*!
* 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: "\" ゴシック\",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></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);

View File

@ -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)
}
});
});
});
};