Commit 1a433de0 by Torkel Ödegaard

Merge branch 'master' into query-editor-style

parents 39cdaf51 4d802df0
# 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
......
......@@ -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**:
......
......@@ -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)
......@@ -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": {
......
......@@ -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"},
......
......@@ -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 == "" {
......
......@@ -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")
......
......@@ -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,7 +129,9 @@ 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"})
}
data.MainNavLinks = append(data.MainNavLinks, appLink)
if len(appLink.Children) > 0 {
data.MainNavLinks = append(data.MainNavLinks, appLink)
}
}
}
......
......@@ -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()
......
......@@ -126,11 +126,16 @@ 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 {
panic(r)
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)
}
}
}
}()
......@@ -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()
}
}
......
......@@ -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())
}
......@@ -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() {
......
......@@ -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
......
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
......
......@@ -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
......@@ -80,14 +86,14 @@ type PluginDependencies struct {
}
type PluginInclude struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
Component string `json:"component"`
Role models.RoleType `json:"role"`
AddToNav bool `json:"addToNav"`
DefaultNav bool `json:"defaultNav"`
Slug string `json:"slug"`
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
Component string `json:"component"`
Role m.RoleType `json:"role"`
AddToNav bool `json:"addToNav"`
DefaultNav bool `json:"defaultNav"`
Slug string `json:"slug"`
Id string `json:"-"`
}
......
......@@ -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 {
......
......@@ -206,9 +206,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
});
$compile(child)(scope);
elem.empty();
elem.append(child);
// let a binding digest cycle complete before adding to dom
setTimeout(function() {
elem.append(child);
});
}
function registerPluginComponent(scope, elem, attrs, componentInfo) {
......
......@@ -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);
});
......
......@@ -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();
}
......
......@@ -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>
......
......@@ -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);
......
......@@ -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"});
};
......
......@@ -81,6 +81,13 @@ function (_, $) {
});
addFuncDef({
name: 'stddevSeries',
params: optionalSeriesRefArgs,
defaultParams: [''],
category: categories.Calculate,
});
addFuncDef({
name: 'divideSeries',
params: optionalSeriesRefArgs,
defaultParams: ['#A'],
......
......@@ -6,8 +6,8 @@ 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:
[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
\ No newline at end of file
[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
......@@ -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');
}
......@@ -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') {
......
......@@ -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 + "'";
}
......
......@@ -12,17 +12,29 @@ 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 influxdb11format = query.toLowerCase().indexOf('show tag values') >= 0;
var res = {};
_.each(influxResults.series, serie => {
_.each(serie.values, value => {
if (_.isArray(value)) {
if (influxdb11format) {
addUnique(res, value[1] || value[0]);
} else {
addUnique(res, value[0]);
}
} else {
return { text: value[0] };
addUnique(res, value);
}
} else {
return { text: value };
}
});
});
return _.map(res, value => {
return { text: value};
});
}
}
function addUnique(arr, value) {
arr[value] = value;
}
......@@ -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", () => {
......
......@@ -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];
......
......@@ -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;
......
......@@ -13,85 +13,6 @@ import TimeSeries from 'app/core/time_series2';
import * as fileExport from 'app/core/utils/file_export';
import {MetricsPanelCtrl} from 'app/plugins/sdk';
var panelDefaults = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
yaxes: [
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short'
},
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short'
}
],
xaxis: {
show: true
},
grid : {
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
msResolution: false,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
......@@ -105,14 +26,93 @@ class GraphCtrl extends MetricsPanelCtrl {
datapointsWarning: boolean;
colors: any = [];
panelDefaults = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
yaxes: [
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short'
},
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short'
}
],
xaxis: {
show: true
},
grid : {
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
msResolution: false,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
/** @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;
......
......@@ -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>
......
......@@ -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 = [];
......
......@@ -160,6 +160,43 @@
<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">
<ul class="tight-form-list">
<li class="tight-form-item">
<strong>Value to text mapping</strong>
</li>
......
......@@ -4,43 +4,13 @@ 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';
// Set and populate defaults
var panelDefaults = {
links: [],
datasource: null,
maxDataPoints: 100,
interval: null,
targets: [{}],
cacheTimeout: null,
format: 'none',
prefix: '',
postfix: '',
nullText: null,
valueMaps: [
{ value: 'null', op: '=', text: 'N/A' }
],
nullPointMode: 'connected',
valueName: 'avg',
prefixFontSize: '50%',
valueFontSize: '80%',
postfixFontSize: '50%',
thresholds: '',
colorBackground: false,
colorValue: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
sparkline: {
show: false,
full: false,
lineColor: 'rgb(31, 120, 193)',
fillColor: 'rgba(31, 118, 189, 0.18)',
}
};
class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
......@@ -49,10 +19,48 @@ class SingleStatCtrl extends MetricsPanelCtrl {
fontSizes: any[];
unitFormats: any[];
// Set and populate defaults
panelDefaults = {
links: [],
datasource: null,
maxDataPoints: 100,
interval: null,
targets: [{}],
cacheTimeout: null,
format: 'none',
prefix: '',
postfix: '',
nullText: null,
valueMaps: [
{ value: 'null', op: '=', text: 'N/A' }
],
nullPointMode: 'connected',
valueName: 'avg',
prefixFontSize: '50%',
valueFontSize: '80%',
postfixFontSize: '50%',
thresholds: '',
colorBackground: false,
colorValue: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
sparkline: {
show: false,
full: false,
lineColor: 'rgb(31, 120, 193)',
fillColor: 'rgba(31, 118, 189, 0.18)',
},
gauge: {
show: false,
minValue: 0,
maxValue: 100,
thresholdLabels: true
}
};
/** @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) {
......
......@@ -10,33 +10,6 @@ import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor';
import {TableRenderer} from './renderer';
var panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
......@@ -44,6 +17,33 @@ class TablePanelCtrl extends MetricsPanelCtrl {
dataRaw: any;
table: any;
panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
/** @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));
......
......@@ -30,7 +30,7 @@ export class TableRenderer {
}
if (_.isArray(v)) {
v = v.join(',&nbsp;');
v = v.join(', ');
}
return v;
......
......@@ -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));
......
......@@ -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: {
......
......@@ -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;
i {
padding-right: 5px;
color: $link-color-disabled;
> 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;
}
}
}
}
......
......@@ -85,7 +85,8 @@
}
.graph-legend-table {
overflow-y: scroll;
overflow-y: auto;
overflow-x: hidden;
.graph-legend-series {
display: table-row;
......
......@@ -4,4 +4,5 @@
margin: 0 0 $spacer $spacer * 1.5;
}
li {line-height: 2;}
a { color: $external-link-color; }
}
......@@ -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;
......
......@@ -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() {
......
......@@ -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');
});
});
......
......@@ -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: {
......
......@@ -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)
}
});
});
});
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment