3
0
mirror of https://github.com/grafana/grafana.git synced 2025-02-25 18:55:37 -06:00

feat(data source variable): progress on data source as variable

This commit is contained in:
Torkel Ödegaard 2016-04-28 19:08:35 +02:00
commit 7349427189
103 changed files with 2690 additions and 1121 deletions
CHANGELOG.mdMakefileREADME.mdbuild.gocircle.yml
docs/sources
http_api
plugins
package.json
pkg
public

View File

@ -1,3 +1,28 @@
# 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)
* **Singlestat**: Fixes prefix an postfix for gauges, fixes [#4812](https://github.com/grafana/grafana/issues/4812)
* **Singlestat**: Fixes auto-refresh on change for some options, fixes [#4809](https://github.com/grafana/grafana/issues/4809)
### Breaking changes
**Data Source Query Editors**: Issue [#3900](https://github.com/grafana/grafana/issues/3900)
Query editors have been updated to use the new form styles. External data source plugins needs to be
updated to work. Sorry to introduce breaking change this late in beta phase. We wanted to get this change
in before 3.0 stable is released so we don't have to break data sources in next release (3.1). If you are
a data source plugin author and want help for how the new form styles work please ask for help in
slack channel (link to slack channel in readme).
# 3.0.0-beta5 (2016-04-15)
### Bug fixes

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
all: deps build
deps:
go run build.go setup
godep restore
npm install
build:
go run build.go build
npm run build
test:
godep go test -v ./pkg/...
npm test
run:
./bin/grafana-server

View File

@ -103,8 +103,7 @@ npm (v2.5.0) and grunt (v0.4.5). Run the following:
```bash
npm install
npm install -g grunt-cli
grunt
npm run build
```
### Recompile backend on source change
@ -145,4 +144,3 @@ please [sign the CLA](http://docs.grafana.org/project/cla/)
Grafana is distributed under Apache 2.0 License.
Work in progress Grafana 2.0 (with included Grafana backend)

View File

@ -306,7 +306,7 @@ func ChangeWorkingDir(dir string) {
}
func grunt(params ...string) {
runPrint("./node_modules/grunt-cli/bin/grunt", params...)
runPrint("./node_modules/.bin/grunt", params...)
}
func setup() {

View File

@ -25,12 +25,12 @@ test:
# Go test
- godep go test -v ./pkg/...
# js tests
- ./node_modules/grunt-cli/bin/grunt test
- npm test
- npm run coveralls
deployment:
master:
branch: master
owner: grafana
commands:
commands:
- ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}

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": {
@ -62,6 +62,7 @@
"npm": "2.14.x"
},
"scripts": {
"build": "grunt",
"test": "grunt test",
"coveralls": "grunt karma:coveralls && rm -rf ./coverage"
},

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

@ -48,6 +48,6 @@ func (slice PluginList) Swap(i, j int) {
type ImportDashboardCommand struct {
PluginId string `json:"pluginId"`
Path string `json:"path"`
Reinstall bool `json:"reinstall"`
Overwrite bool `json:"overwrite"`
Inputs []plugins.ImportDashboardInput `json:"inputs"`
}

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,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)
}
}
}

View File

@ -156,11 +156,12 @@ func GetPluginReadme(c *middleware.Context) Response {
func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
cmd := plugins.ImportDashboardCommand{
OrgId: c.OrgId,
UserId: c.UserId,
PluginId: apiCmd.PluginId,
Path: apiCmd.Path,
Inputs: apiCmd.Inputs,
OrgId: c.OrgId,
UserId: c.UserId,
PluginId: apiCmd.PluginId,
Path: apiCmd.Path,
Inputs: apiCmd.Inputs,
Overwrite: apiCmd.Overwrite,
}
if err := bus.Dispatch(&cmd); err != nil {

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

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

@ -11,12 +11,13 @@ import (
)
type ImportDashboardCommand struct {
Path string `json:"string"`
Inputs []ImportDashboardInput `json:"inputs"`
Path string
Inputs []ImportDashboardInput
Overwrite bool
OrgId int64 `json:"-"`
UserId int64 `json:"-"`
PluginId string `json:"-"`
OrgId int64
UserId int64
PluginId string
Result *PluginDashboardInfoDTO
}
@ -67,6 +68,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
Dashboard: generatedDash,
OrgId: cmd.OrgId,
UserId: cmd.UserId,
Overwrite: cmd.Overwrite,
}
if err := bus.Dispatch(&saveCmd); err != nil {

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
@ -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:"-"`
}

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

@ -61,7 +61,6 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
for key, data := range cmd.SecureJsonData {
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
}
pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
pluginSetting.Updated = time.Now()
pluginSetting.Enabled = cmd.Enabled
pluginSetting.JsonData = cmd.JsonData

View File

@ -9,10 +9,10 @@ function (_, $, coreModule) {
coreModule.default.directive('dropdownTypeahead', function($compile) {
var inputTemplate = '<input type="text"'+
' class="tight-form-input input-medium tight-form-input"' +
' class="gf-form-input input-medium tight-form-input"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item tight-form-func dropdown-toggle"' +
var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' data-placement="top"><i class="fa fa-plus"></i></a>';

View File

@ -8,10 +8,13 @@ function (_, $, coreModule) {
coreModule.default.directive('metricSegment', function($compile, $sce) {
var inputTemplate = '<input type="text" data-provide="typeahead" ' +
' class="tight-form-clear-input input-medium"' +
' class="gf-form-input input-medium"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
var linkTemplate = '<a class="gf-form-label" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
var selectTemplate = '<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
return {
@ -20,9 +23,9 @@ function (_, $, coreModule) {
getOptions: "&",
onChange: "&",
},
link: function($scope, elem) {
link: function($scope, elem, attrs) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
var $button = $(attrs.styleMode === 'select' ? selectTemplate : linkTemplate);
var segment = $scope.segment;
var options = null;
var cancelBlur = null;

View File

@ -206,9 +206,15 @@ 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);
scope.$apply(function() {
scope.$broadcast('refresh');
});
});
}
function registerPluginComponent(scope, elem, attrs, componentInfo) {

View File

@ -25,7 +25,6 @@ export class ContextSrv {
isGrafanaAdmin: any;
isEditor: any;
sidemenu: any;
lightTheme: any;
constructor() {
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
@ -41,7 +40,6 @@ export class ContextSrv {
}
this.version = config.buildInfo.version;
this.lightTheme = false;
this.user = new User();
this.isSignedIn = this.user.isSignedIn;
this.isGrafanaAdmin = this.user.isGrafanaAdmin;

View File

@ -32,6 +32,8 @@ export default class TableModel {
if (options.desc) {
this.rows.reverse();
this.columns[options.col].desc = true;
} else {
this.columns[options.col].desc = false;
}
}
}

View File

@ -23,7 +23,7 @@ export class Emitter {
this.emitter.on(name, handler);
if (scope) {
scope.$on('$destroy', function() {
scope.$on('$destroy', () => {
this.emitter.off(name, handler);
});
}

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

@ -5,4 +5,5 @@ define([
'./query_ctrl',
'./panel_editor_tab',
'./query_editor_row',
'./metrics_ds_selector',
], function () {});

View File

@ -0,0 +1,110 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
var module = angular.module('grafana.directives');
var template = `
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">
<i class="icon-gf icon-gf-datasource"></i>
</label>
<label class="gf-form-label">
Panel data source
</label>
<metric-segment segment="ctrl.dsSegment" style-mode="select"
get-options="ctrl.getOptions()"
on-change="ctrl.datasourceChanged()"></metric-segment>
</div>
<div class="gf-form gf-form--offset-1">
<button class="btn btn-inverse gf-form-btn" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.current.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Add query
</button>
<div class="dropdown" ng-if="ctrl.current.meta.mixed">
<button class="btn btn-inverse dropdown-toggle gf-form-btn" data-toggle="dropdown">
Add Query&nbsp;<span class="fa fa-caret-down"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
`;
export class MetricsDsSelectorCtrl {
dsSegment: any;
dsName: string;
panelCtrl: any;
datasources: any[];
current: any;
/** @ngInject */
constructor(private uiSegmentSrv, datasourceSrv) {
this.datasources = datasourceSrv.getMetricSources();
var dsValue = this.panelCtrl.panel.datasource || null;
for (let ds of this.datasources) {
if (ds.value === dsValue) {
this.current = ds;
}
}
if (!this.current) {
this.current = {name: dsValue + ' not found', value: null};
}
this.dsSegment = uiSegmentSrv.newSegment(this.current.name);
}
getOptions() {
return Promise.resolve(this.datasources.map(value => {
return this.uiSegmentSrv.newSegment(value.name);
}));
}
datasourceChanged() {
var ds = _.findWhere(this.datasources, {name: this.dsSegment.value});
if (ds) {
this.current = ds;
this.panelCtrl.setDatasource(ds);
}
}
addDataQuery(datasource) {
var target: any = {isNew: true};
if (datasource) {
target.datasource = datasource.name;
}
this.panelCtrl.panel.targets.push(target);
}
}
module.directive('metricsDsSelector', function() {
return {
restrict: 'E',
template: template,
controller: MetricsDsSelectorCtrl,
bindToController: true,
controllerAs: 'ctrl',
transclude: true,
scope: {
panelCtrl: "="
}
};
});

View File

@ -28,7 +28,6 @@ class MetricsPanelCtrl extends PanelCtrl {
resolution: any;
timeInfo: any;
skipDataOnInit: boolean;
datasources: any[];
dataStream: any;
dataSubscription: any;
@ -53,13 +52,6 @@ class MetricsPanelCtrl extends PanelCtrl {
private onInitMetricsPanelEditMode() {
this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
this.datasources = this.datasourceSrv.getMetricSources();
// find current
var current = _.findWhere(this.datasources, {value: this.panel.datasource});
if (current) {
this.datasourceName = current.name;
}
}
private onMetricsPanelRefresh() {
@ -257,16 +249,6 @@ class MetricsPanelCtrl extends PanelCtrl {
this.datasource = null;
this.refresh();
}
addDataQuery(datasource) {
var target: any = {};
if (datasource) {
target.datasource = datasource.name;
}
this.panel.targets.push(target);
}
}
export {MetricsPanelCtrl};

View File

@ -44,15 +44,15 @@ export class PanelCtrl {
this.pluginName = plugin.name;
}
$scope.$on("refresh", () => this.refresh());
$scope.$on("render", () => this.render());
$scope.$on("refresh", this.refresh.bind(this));
$scope.$on("render", this.render.bind(this));
$scope.$on("$destroy", () => this.events.emit('panel-teardown'));
}
init() {
this.publishAppEvent('panel-instantiated', {scope: this.$scope});
this.calculatePanelHeight();
this.refresh();
this.publishAppEvent('panel-initialized', {scope: this.$scope});
this.events.emit('panel-initialized');
}
renderingCompleted() {

View File

@ -1,4 +1,63 @@
<div class="tight-form">
<div class="gf-form-query">
<div class="gf-form">
<label class="gf-form-label gf-form-query-letter-cell">
<a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
<i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
</span>
<span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
</a>
</label>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ctrl.collapsedText}}
</label>
</div>
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
</div>
<div class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
</li>
</ul>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</label>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="tight-form" ng-if="false">
<ul class="tight-form-list pull-right">
<li ng-show="ctrl.error" class="tight-form-item">
<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">

View File

@ -13,45 +13,11 @@ export class QueryCtrl {
constructor(public $scope, private $injector) {
this.panel = this.panelCtrl.panel;
if (!this.target.refId) {
this.target.refId = this.getNextQueryLetter();
}
}
getNextQueryLetter() {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
return _.find(letters, refId => {
return _.every(this.panel.targets, function(other) {
return other.refId !== refId;
});
});
}
removeQuery() {
this.panel.targets = _.without(this.panel.targets, this.target);
this.panelCtrl.refresh();
};
duplicateQuery() {
var clone = angular.copy(this.target);
clone.refId = this.getNextQueryLetter();
this.panel.targets.push(clone);
}
moveQuery(direction) {
var index = _.indexOf(this.panel.targets, this.target);
_.move(this.panel.targets, index, index + direction);
}
refresh() {
this.panelCtrl.refresh();
}
toggleHideQuery() {
this.target.hide = !this.target.hide;
this.panelCtrl.refresh();
}
}

View File

@ -1,17 +1,115 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import $ from 'jquery';
import _ from 'lodash';
var module = angular.module('grafana.directives');
export class QueryRowCtrl {
collapsedText: string;
canCollapse: boolean;
getCollapsedText: any;
target: any;
queryCtrl: any;
panelCtrl: any;
panel: any;
collapsed: any;
constructor() {
this.panelCtrl = this.queryCtrl.panelCtrl;
this.target = this.queryCtrl.target;
this.panel = this.panelCtrl.panel;
if (!this.target.refId) {
this.target.refId = this.getNextQueryLetter();
}
this.toggleCollapse(true);
if (this.target.isNew) {
delete this.target.isNew;
this.toggleCollapse(false);
}
}
toggleHideQuery() {
this.target.hide = !this.target.hide;
this.panelCtrl.refresh();
}
getNextQueryLetter() {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
return _.find(letters, refId => {
return _.every(this.panel.targets, function(other) {
return other.refId !== refId;
});
});
}
toggleCollapse(init) {
if (!this.canCollapse) {
return;
}
if (!this.panelCtrl.__collapsedQueryCache) {
this.panelCtrl.__collapsedQueryCache = {};
}
if (init) {
this.collapsed = this.panelCtrl.__collapsedQueryCache[this.target.refId] !== false;
} else {
this.collapsed = !this.collapsed;
this.panelCtrl.__collapsedQueryCache[this.target.refId] = this.collapsed;
}
try {
this.collapsedText = this.queryCtrl.getCollapsedText();
} catch (e) {
var err = e.message || e.toString();
this.collapsedText = 'Error: ' + err;
}
}
toggleEditorMode() {
if (this.canCollapse && this.collapsed) {
this.collapsed = false;
}
this.queryCtrl.toggleEditorMode();
}
removeQuery() {
delete this.panelCtrl.__collapsedQueryCache[this.target.refId];
this.panel.targets = _.without(this.panel.targets, this.target);
this.panelCtrl.refresh();
}
duplicateQuery() {
var clone = angular.copy(this.target);
clone.refId = this.getNextQueryLetter();
this.panel.targets.push(clone);
}
moveQuery(direction) {
var index = _.indexOf(this.panel.targets, this.target);
_.move(this.panel.targets, index, index + direction);
}
}
/** @ngInject **/
function queryEditorRowDirective() {
return {
restrict: 'E',
controller: QueryRowCtrl,
bindToController: true,
controllerAs: "ctrl",
templateUrl: 'public/app/features/panel/partials/query_editor_row.html',
transclude: true,
scope: {ctrl: "="},
scope: {
queryCtrl: "=",
canCollapse: "=",
hasTextEditMode: "=",
},
};
}

View File

@ -98,9 +98,7 @@ export class DataSourceEditCtrl {
this.datasourceSrv.get(this.current.name).then(datasource => {
if (!datasource.testDatasource) {
this.testing.message = 'Data source does not support test connection feature.';
this.testing.status = 'warning';
this.testing.title = 'Unknown';
delete this.testing;
return;
}
@ -118,7 +116,9 @@ export class DataSourceEditCtrl {
}
});
}).finally(() => {
this.testing.done = true;
if (this.testing) {
this.testing.done = true;
}
});
}

View File

@ -15,16 +15,16 @@
</td>
<td>
v{{dash.revision}}
</td>
<td ng-if="dash.installed">
Imported v{{dash.installedRevision}}
<span ng-if="dash.installed">
&nbsp;(Imported v{{dash.installedRevision}})
<span>
</td>
<td style="text-align: right">
<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
Import
</button>
<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
Re-Import
Update
</button>
<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
Delete

View File

@ -43,11 +43,11 @@ export class DashImportListCtrl {
});
}
import(dash, reinstall) {
import(dash, overwrite) {
var installCmd = {
pluginId: this.plugin.id,
path: dash.path,
reinstall: reinstall,
overwrite: overwrite,
inputs: []
};

View File

@ -53,7 +53,7 @@
</plugin-component>
</rebuild-on-change>
<div ng-if="ctrl.testing" style="margin-top: 25px">
<div ng-if="ctrl.testing" class="gf-form-group">
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<div class="alert-{{ctrl.testing.status}} alert">
<div class="alert-title">{{ctrl.testing.title}}</div>

View File

@ -14,8 +14,8 @@
<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 code--line"><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>
<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>
</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

@ -323,17 +323,10 @@ function (angular, _, kbn) {
options[value] = {text: text, value: value};
}
return _.map(_.keys(options).sort(), function(key) {
return options[key];
});
return _.sortBy(options, 'text');
};
this.addAllOption = function(variable) {
if (variable.allValue) {
variable.options.unshift({text: 'All', value: variable.allValue});
return;
}
variable.options.unshift({text: 'All', value: "$__all"});
};

View File

@ -1,56 +1,19 @@
<div class="editor-row">
<div class="tight-form-container">
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'tight-form-disabled': target.hide}">
<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
<plugin-component type="query-ctrl">
</plugin-component>
</rebuild-on-change>
</div>
</div>
<div style="margin: 20px 0 0 0">
<button class="btn btn-inverse" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.datasource.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Query
</button>
<div class="dropdown" ng-if="ctrl.datasource.meta.mixed">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>&nbsp;
Query &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>
<rebuild-on-change property="ctrl.panel.datasource" show-null="true">
<plugin-component type="query-options-ctrl">
</plugin-component>
</rebuild-on-change>
<div class="query-editor-rows gf-form-group">
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
<plugin-component type="query-ctrl">
</plugin-component>
</rebuild-on-change>
</div>
</div>
<div class="editor-row">
<metrics-ds-selector panel-ctrl="ctrl"></metrics-ds-selector>
<div class="pull-right dropdown" style="margin-right: 10px;">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
<i class="fa fa-database"></i>&nbsp;
{{ctrl.datasourceName}} &nbsp; <span class="fa fa-caret-down"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem">
<a ng-click="ctrl.setDatasource(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
<div class="clearfix"></div>
<div class="gf-form-group">
<rebuild-on-change property="ctrl.panel.datasource" show-null="true">
<plugin-component type="query-options-ctrl">
</plugin-component>
</rebuild-on-change>
</div>

View File

@ -1,4 +1,4 @@
<query-editor-row ctrl="ctrl">
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>
</query-editor-row>
<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>

View File

@ -1,58 +1,59 @@
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
Metric
</li>
<li>
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
</li>
<li class="tight-form-item query-keyword">
Stats
</li>
<li ng-repeat="segment in statSegments">
<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
</li>
</ul>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Metric</label>
<div class="clearfix"></div>
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Stats</label>
</div>
<div class="gf-form" ng-repeat="segment in statSegments">
<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
Dimensions
</li>
<li ng-repeat="segment in dimSegments">
<metric-segment segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
</li>
</ul>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Dimensions</label>
<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
</div>
<div class="clearfix"></div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
Period
<tip>Interval between points in seconds</tip>
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
</li>
<li class="tight-form-item query-keyword">
Alias
<tip>{{metric}} {{stat}} {{namespace}} {{region}} {{DIMENSION_NAME}}</tip>
</li>
<li>
<input type="text" class="input-xlarge tight-form-input" ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()">
</li>
</ul>
<div class="clearfix"></div>
<info-popover mode="right-normal">Interval between points in seconds</info-popover>
</label>
<input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
</div>
<div class="gf-form max-width-30">
<label class="gf-form-label query-keyword width-7">Alias</label>
<input type="text" class="gf-form-input" ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()">
<info-popover mode="right-absolute">
Alias replacement variables:
<ul ng-non-bindable>
<li>{{metric}}</li>
<li>{{stat}}</li>
<li>{{namespace}}</li>
<li>{{region}}</li>
<li>{{DIMENSION_NAME}}</li>
</ul>
</info-popover>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>

View File

@ -60,6 +60,10 @@ function (angular, _, queryDef) {
$scope.agg.query = '*';
break;
}
case 'geohash_grid': {
$scope.agg.settings.precision = 3;
break;
}
}
$scope.validateModel();
@ -121,6 +125,13 @@ function (angular, _, queryDef) {
if (settings.trimEdges && settings.trimEdges > 0) {
settingsLinkText += ', Trim edges: ' + settings.trimEdges;
}
break;
}
case 'geohash_grid': {
// limit precision to 7
settings.precision = Math.max(Math.min(settings.precision, 7), 1);
settingsLinkText = 'Precision: ' + settings.precision;
break;
}
}

Binary file not shown.

Before

(image error) Size: 40 KiB

After

(image error) Size: 38 KiB

View File

@ -1,131 +1,96 @@
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
<span ng-show="isFirst">Group by</span>
<span ng-hide="isFirst">Then by</span>
</li>
<li>
<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
</li>
<li ng-if="agg.field">
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
</li>
<li ng-if="!agg.field">
<span class="tight-form-item tight-form-item-xxlarge">&nbsp;</span>
</li>
<li class="tight-form-item last" ng-if="settingsLinkText">
</label>
<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="width-10"></metric-segment-model>
<metric-segment-model ng-if="agg.field" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment>
</div>
<div class="gf-form gf-form--grow">
<label class="gf-form-label gf-form-label--grow">
<a ng-click="toggleOptions()">
<i class="fa fa-caret-down" ng-show="showOptions"></i>
<i class="fa fa-caret-right" ng-hide="showOptions"></i>
{{settingsLinkText}}
</a>
</li>
</ul>
</label>
</div>
<ul class="tight-form-list pull-right">
<li class="tight-form-item last" ng-if="isFirst">
<div class="gf-form">
<label class="gf-form-label" ng-if="isFirst">
<a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a>
</li>
<li class="tight-form-item last">
</label>
<label class="gf-form-label">
<a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a>
</li>
</ul>
<div class="clearfix"></div>
</label>
</div>
</div>
<div class="tight-form" ng-if="showOptions">
<div class="tight-form-inner-box" ng-if="agg.type === 'date_histogram'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px">
Interval
</li>
<li>
<metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="last" custom="true"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form-group" ng-if="showOptions">
<div ng-if="agg.type === 'date_histogram'">
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">Interval</label>
<metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="width-12" custom="true"></metric-segment-model>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px">
Min Doc Count
</li>
<li>
<input type="number" class="tight-form-input" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">Min Doc Count</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px">
Trim edges points
</li>
<li>
<input class="tight-form-input" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
</li>
<li class="tight-form-item last">
<i class="fa fa-question-circle" bs-tooltip="'Trim the edges on the timeseries x datapoints'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">
Trim edges
<info-popover mode="right-normal">
Trim the edges on the timeseries datapoints
</info-popover>
</label>
<input class="gf-form-input max-width-12" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
</div>
</div>
<div class="tight-form-inner-box" ng-if="agg.type === 'terms'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 60px">
Order
</li>
<li>
<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
<div ng-if="agg.type === 'terms'">
<div class="gf-form offset-width-7">
<label class="gf-form-label">Order</label>
<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 60px">
Size
</li>
<li>
<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">Size</label>
<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 60px">
Order By
</li>
<li>
<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">Order By</label>
<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</div>
</div>
<div class="tight-form-inner-box" ng-if="agg.type === 'filters'">
<div class="tight-form" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Query {{$index + 1}}
</li>
<li>
<input type="text" class="tight-form-input input-large" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
</li>
<li class="tight-form-item last" ng-if="$first">
<div ng-if="agg.type === 'filters'">
<div class="gf-form-inline" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
<div class="gf-form">
<label class="gf-form-item width-10">Query {{$index + 1}}</label>
<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
</div>
<div class="gf-form">
<label class="gf-form-label" ng-if="$first">
<a class="pointer" ng-click="addFiltersQuery()"><i class="fa fa-plus"></i></a>
</li>
<li class="tight-form-item last" ng-if="!$first">
</label>
<label class="gf-form-label" ng-if="!$first">
<a class="pointer" ng-click="removeFiltersQuery(filter)"><i class="fa fa-minus"></i></a>
</li>
</ul>
<div class="clearfix"></div>
</label>
</div>
</div>
</div>
<div ng-if="agg.type === 'geohash_grid'">
<div class="gf-form offset-width-7">
<label class="gf-form-label">Precision</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
</div>
</div>
</div>

View File

@ -1,138 +1,82 @@
<div class="tight-form" ng-class="{'tight-form-disabled': agg.hide}">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<div class="gf-form-inline" ng-class="{'gf-form-disabled': agg.hide}">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
Metric
&nbsp;
<a ng-click="toggleShowMetric()" bs-tooltip="'Click to toggle show / hide metric'">
<i class="fa fa-eye" ng-hide="agg.hide"></i>
<i class="fa fa-eye-slash" ng-show="agg.hide"></i>
</a>
</li>
<li>
<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
</li>
<li ng-if="aggDef.requiresField">
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment-model>
</li>
<li ng-if="aggDef.isPipelineAgg">
<metric-segment-model property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="tight-form-item-xxlarge"></metric-segment-model>
</li>
<li class="tight-form-item last" ng-if="settingsLinkText">
<a ng-click="toggleOptions()">
</label>
</div>
<div class="gf-form">
<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="width-10"></metric-segment-model>
<metric-segment-model ng-if="aggDef.requiresField" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment-model>
<metric-segment-model ng-if="aggDef.isPipelineAgg" property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="width-12"></metric-segment-model>
</div>
<div class="gf-form gf-form--grow">
<label class="gf-form-label gf-form-label--grow">
<a ng-click="toggleOptions()" ng-if="settingsLinkText">
<i class="fa fa-caret-down" ng-show="showOptions"></i>
<i class="fa fa-caret-right" ng-hide="showOptions"></i>
{{settingsLinkText}}
{{settingsLinkText}}
</a>
</li>
</ul>
</label>
</div>
<ul class="tight-form-list pull-right">
<li class="tight-form-item last" ng-if="isFirst">
<div class="gf-form">
<label class="gf-form-label" ng-if="isFirst">
<a class="pointer" ng-click="addMetricAgg()"><i class="fa fa-plus"></i></a>
</li>
<li class="tight-form-item last" ng-if="!isSingle">
</label>
<label class="gf-form-label" ng-if="!isSingle">
<a class="pointer" ng-click="removeMetricAgg()"><i class="fa fa-minus"></i></a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="showOptions">
<div class="tight-form-inner-box tight-form-container">
<div class="tight-form" ng-if="agg.type === 'derivative'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 75px;">
Unit
</li>
<li>
<input type="text" class="input-medium tight-form-input last" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="agg.type === 'moving_avg'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 75px;">
Window
</li>
<li>
<input type="number" class="input-medium tight-form-input last" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="agg.type === 'moving_avg'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 75px;">
Model
</li>
<li>
<input type="text" class="input-medium tight-form-input last" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last" ng-if="agg.type === 'percentiles'">
<ul class="tight-form-list">
<li class="tight-form-item">
Percentiles
</li>
<li>
<input type="text" class="input-xlarge tight-form-input last" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div ng-if="agg.type === 'extended_stats'">
<div class="tight-form" ng-repeat="stat in extendedStats">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
{{stat.text}}
</li>
<li class="tight-form-item last">
<editor-checkbox text="" model="agg.meta.{{stat.value}}" change="onChange()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="tight-form" ng-if="agg.type === 'extended_stats'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Sigma
</li>
<li>
<input type="number" class="input-mini tight-form-input last" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="aggDef.supportsInlineScript">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
Script
</li>
<li>
<input type="text" class="input-medium tight-form-input last" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="aggDef.supportsMissing">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
Missing
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
</li>
<li>
<input type="number" class="input-medium tight-form-input last" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
</label>
</div>
</div>
<div class="gf-form-group" ng-if="showOptions">
<div class="gf-form offset-width-7" ng-if="agg.type === 'derivative'">
<label class="gf-form-label width-10">Unit</label>
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
</div>
<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
<label class="gf-form-label width-10">Window</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</div>
<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
<label class="gf-form-label width-10">Model</label>
<input type="text" class="gf-form-input max-width-12" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
</div>
<div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
<label class="gf-form-label width-10">Percentiles</label>
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
</div>
<div ng-if="agg.type === 'extended_stats'">
<gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
<div class="gf-form offset-width-7">
<label class="gf-form-label width-10">Sigma</label>
<input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
</div>
</div>
<div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
<label class="gf-form-label width-10">Script</label>
<input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
</div>
<div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
<label class="gf-form-label width-10">
Missing
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
</label>
<input type="number" class="gf-form-input max-width-12" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
</div>
</div>

View File

@ -1,32 +1,31 @@
<query-editor-row ctrl="ctrl">
<li class="tight-form-item query-keyword" style="width: 75px">
Query
</li>
<li>
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()">
</li>
<li class="tight-form-item query-keyword">
Alias
</li>
<li>
<input type="text" class="tight-form-input" style="width: 200px;" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="ctrl.refresh()">
</li>
<query-editor-row query-ctrl="ctrl" can-collapse="true">
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<label class="gf-form-label query-keyword width-7">Query</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()">
</div>
<div class="gf-form max-width-15">
<label class="gf-form-label query-keyword">Alias</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns" ng-blur="ctrl.refresh()">
</div>
</div>
<div ng-repeat="agg in ctrl.target.metrics">
<elastic-metric-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()"
es-version="ctrl.esVersion">
</elastic-metric-agg>
</div>
<div ng-repeat="agg in ctrl.target.bucketAggs">
<elastic-bucket-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()">
</elastic-bucket-agg>
</div>
</query-editor-row>
<div ng-repeat="agg in ctrl.target.metrics">
<elastic-metric-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()"
es-version="ctrl.esVersion">
</elastic-metric-agg>
</div>
<div ng-repeat="agg in ctrl.target.bucketAggs">
<elastic-bucket-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()">
</elastic-bucket-agg>
</div>

View File

@ -153,6 +153,10 @@ function (queryDef) {
this.buildTermsAgg(aggDef, esAgg, target);
break;
}
case 'geohash_grid': {
esAgg['geohash_grid'] = {field: aggDef.field, precision: aggDef.settings.precision};
break;
}
}
nestedAggs.aggs = nestedAggs.aggs || {};

View File

@ -5,6 +5,7 @@ import './metric_agg';
import angular from 'angular';
import _ from 'lodash';
import queryDef from './query_def';
import {QueryCtrl} from 'app/plugins/sdk';
export class ElasticQueryCtrl extends QueryCtrl {
@ -38,6 +39,48 @@ export class ElasticQueryCtrl extends QueryCtrl {
this.$rootScope.appEvent('elastic-query-updated');
}
getCollapsedText() {
var metricAggs = this.target.metrics;
var bucketAggs = this.target.bucketAggs;
var metricAggTypes = queryDef.getMetricAggTypes(this.esVersion);
var bucketAggTypes = queryDef.bucketAggTypes;
var text = '';
if (this.target.query) {
text += 'Query: ' + this.target.query + ', ';
}
text += 'Metrics: ';
_.each(metricAggs, (metric, index) => {
var aggDef = _.findWhere(metricAggTypes, {value: metric.type});
text += aggDef.text + '(';
if (aggDef.requiresField) {
text += metric.field;
}
text += '), ';
});
_.each(bucketAggs, (bucketAgg, index) => {
if (index === 0) {
text += ' Group by: ';
}
var aggDef = _.findWhere(bucketAggTypes, {value: bucketAgg.type});
text += aggDef.text + '(';
if (aggDef.requiresField) {
text += bucketAgg.field;
}
text += '), ';
});
if (this.target.alias) {
text += 'Alias: ' + this.target.alias;
}
return text;
}
handleQueryError(err) {
this.error = err.message || 'Failed to issue metric query';
return [];

View File

@ -20,9 +20,10 @@ function (_) {
],
bucketAggTypes: [
{text: "Terms", value: 'terms' },
{text: "Terms", value: 'terms', requiresField: true},
{text: "Filters", value: 'filters' },
{text: "Date Histogram", value: 'date_histogram' },
{text: "Geo Hash Grid", value: 'geohash_grid', requiresField: true},
{text: "Date Histogram", value: 'date_histogram', requiresField: true},
],
orderByOptions: [

View File

@ -1,5 +1,7 @@
<query-editor-row ctrl="ctrl">
<li class="tight-form-item">
Test metric (fake data source)
</li>
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Test metric (fake data source)</label>
</div>
</div>
</query-editor-row>

View File

@ -11,10 +11,10 @@ function (angular, _, $, gfunc) {
.module('grafana.directives')
.directive('graphiteAddFunc', function($compile) {
var inputTemplate = '<input type="text"'+
' class="tight-form-input input-medium tight-form-input"' +
' class="gf-form-input"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item tight-form-func dropdown-toggle"' +
var buttonTemplate = '<a class="gf-form-label query-part dropdown-toggle"' +
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
'<i class="fa fa-plus"></i></a>';

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

@ -1,21 +1,27 @@
<query-editor-row ctrl="ctrl">
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
<li class="tight-form-flex-wrapper" ng-show="ctrl.target.textEditor">
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="ctrl.target.target" give-focus="ctrl.target.textEditor" spellcheck='false' ng-model-onblur ng-change="ctrl.targetTextChanged()"></input>
</li>
<div class="gf-form" ng-show="ctrl.target.textEditor">
<input type="text" class="gf-form-input" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.refresh()"></input>
</div>
<li ng-hide-start="ctrl.target.textEditor"></li>
<div ng-hide="ctrl.target.textEditor">
<div class="gf-form-inline">
<div ng-repeat="segment in ctrl.segments" role="menuitem" class="gf-form">
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
</div>
<li ng-repeat="segment in ctrl.segments" role="menuitem">
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
</li>
<li ng-repeat="func in ctrl.functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>
</li>
<li class="dropdown" graphite-add-func>
</li>
<div ng-repeat="func in ctrl.functions" class="gf-form">
<span graphite-func-editor class="gf-form-label query-part"></span>
</div>
<li ng-hide-end></li>
<div class="gf-form dropdown">
<span graphite-add-func></span>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
</query-editor-row>

View File

@ -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/)
[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)

View File

@ -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');
}
@ -160,12 +162,14 @@ export default class InfluxQuery {
return str + '"' + tag.key + '" ' + operator + ' ' + value;
}
getMeasurementAndPolicy() {
getMeasurementAndPolicy(interpolate) {
var policy = this.target.policy;
var measurement = this.target.measurement;
var measurement = this.target.measurement || 'measurement';
if (!measurement.match('^/.*/')) {
measurement = '"' + measurement+ '"';
} else if (interpolate) {
measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
}
if (policy !== 'default') {
@ -188,10 +192,6 @@ export default class InfluxQuery {
}
}
if (!target.measurement) {
throw {message: "Metric measurement is missing"};
}
var query = 'SELECT ';
var i, y;
for (i = 0; i < this.selectModels.length; i++) {
@ -208,7 +208,7 @@ export default class InfluxQuery {
query += selectText;
}
query += ' FROM ' + this.getMeasurementAndPolicy() + ' WHERE ';
query += ' FROM ' + this.getMeasurementAndPolicy(interpolate) + ' WHERE ';
var conditions = _.map(target.tags, (tag, index) => {
return this.renderTagCondition(tag, index, interpolate);
});

View File

@ -1,73 +1,96 @@
<query-editor-row ctrl="ctrl">
<ul class="tight-form-list" ng-hide="ctrl.target.rawQuery">
<li class="tight-form-item query-keyword" style="width: 75px">
FROM
</li>
<li>
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<div class="gf-form" ng-if="ctrl.target.rawQuery">
<input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" ng-blur="ctrl.refresh()"></input>
</div>
<div ng-if="!ctrl.target.rawQuery">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">FROM</label>
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements()" on-change="ctrl.measurementChanged()"></metric-segment>
</li>
<li class="tight-form-item query-keyword" style="padding-left: 15px; padding-right: 15px;">
WHERE
</li>
<li ng-repeat="segment in ctrl.tagSegments">
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">WHERE</label>
</div>
<div class="gf-form" ng-repeat="segment in ctrl.tagSegments">
<metric-segment segment="segment" get-options="ctrl.getTagsOrValues(segment, $index)" on-change="ctrl.tagSegmentUpdated(segment, $index)"></metric-segment>
</li>
</ul>
</div>
<div class="tight-form-flex-wrapper" ng-show="ctrl.target.rawQuery">
<input type="text" class="tight-form-clear-input" ng-model="ctrl.target.query" spellcheck="false" style="width: 100%;" ng-blur="ctrl.refresh()"></input>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-repeat="selectParts in ctrl.queryModel.selectModels">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
<span ng-show="$index === 0">SELECT</span>
</label>
</div>
<div class="gf-form" ng-repeat="part in selectParts">
<influx-query-part-editor
class="gf-form-label query-part"
part="part"
remove-action="ctrl.removeSelectPart(selectParts, part)"
part-updated="ctrl.selectPartUpdated(selectParts, part)"
get-options="ctrl.getPartOptions(part)">
</influx-query-part-editor>
</div>
<div class="gf-form">
<label class="dropdown"
dropdown-typeahead="ctrl.selectMenu"
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
<span>GROUP BY</span>
</label>
<influx-query-part-editor
ng-repeat="part in ctrl.queryModel.groupByParts"
part="part"
class="gf-form-label query-part"
remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)">
</influx-query-part-editor>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<label class="gf-form-label query-keyword width-7">ALIAS BY</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
</div>
<div class="gf-form">
<label class="gf-form-label">Format as</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
<div ng-hide="ctrl.target.rawQuery">
<div class="tight-form" ng-repeat="selectParts in ctrl.queryModel.selectModels">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<span ng-show="$index === 0">SELECT</span>
</li>
<li ng-repeat="part in selectParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeSelectPart(selectParts, part)" part-updated="ctrl.selectPartUpdated(selectParts, part)" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
</li>
<li class="dropdown" dropdown-typeahead="ctrl.selectMenu" dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<span>GROUP BY</span>
</li>
<li ng-repeat="part in ctrl.queryModel.groupByParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
</li>
<li>
<metric-segment segment="ctrl.groupBySegment" get-options="ctrl.getGroupByOptions()" on-change="ctrl.groupByAction(part, $index)"></metric-segment>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
ALIAS BY
</li>
<li>
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
</li>
<li class="tight-form-item">
Format as
</li>
<li>
<select class="input-small tight-form-input" style="width: 104px" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>

View File

@ -38,7 +38,7 @@
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="pull-left">
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
<h5>Alias patterns</h5>

View File

@ -2,4 +2,4 @@
<span class="pointer fa fa-remove" ng-click="removeActionInternal()" ></span>
</div>
<a ng-click="toggleControls()">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>
<a ng-click="toggleControls()" class="query-part-name">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>

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

@ -23,6 +23,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
measurementSegment: any;
removeTagFilterSegment: any;
/** @ngInject **/
constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
super($scope, $injector);
@ -154,7 +155,11 @@ export class InfluxQueryCtrl extends QueryCtrl {
}
toggleEditorMode() {
this.target.query = this.queryModel.render(false);
try {
this.target.query = this.queryModel.render(false);
} catch (err) {
console.log('query render error');
}
this.target.rawQuery = !this.target.rawQuery;
}
@ -316,5 +321,9 @@ export class InfluxQueryCtrl extends QueryCtrl {
return '=';
}
}
getCollapsedText() {
return this.queryModel.render(false);
}
}

View File

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

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

@ -1,223 +1,253 @@
<query-editor-row ctrl="ctrl">
<li class="tight-form-item query-keyword" style="width: 100px">
Metric
</li>
<li>
<input type="text" class="input-large tight-form-input" ng-model="ctrl.target.metric"
spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()">
</input>
<a bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item query-keyword">
Aggregator
</li>
<li>
<select ng-model="ctrl.target.aggregator" class="tight-form-input input-small"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</li>
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">
Metric
<label class="gf-form-label" bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
<i class="fa fa-warning"></i>
</label>
</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric"
spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">
Aggregator
<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
<i class="fa fa-warning"></i>
</a>
</label>
<div class="gf-form-select-wrapper max-width-15">
<select ng-model="ctrl.target.aggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-6">
Alias:
<info-popover mode="right-normal">
Use patterns like $tag_tagname to replace part of the alias for a tag value
</info-popover>
</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</div>
<li class="tight-form-item query-keyword">
Alias:
<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
</li>
<li>
<input type="text" class="tight-form-input input-large"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</li>
</query-editor-row>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
Down sample
</li>
<div class="gf-form-inline">
<div class="gf-form max-width-25">
<label class="gf-form-label query-keyword width-8">Down sample</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.downsampleInterval"
ng-model-onblur
ng-change="ctrl.targetBlur()"
placeholder="interval"></input>
<info-popover mode="right-absolute">
blank for auto, or for example <code>1m</code>
</info-popover>
</div>
<li>
<input type="text" class="input-large tight-form-input"
ng-model="ctrl.target.downsampleInterval"
ng-model-onblur
ng-change="ctrl.targetBlur()"
placeholder="interval (empty = auto)"></input>
</li>
<div class="gf-form">
<label class="gf-form-label query-keyword">Aggregator</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleAggregator" class="gf-form-input"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<li class="tight-form-item query-keyword">
Aggregator
</li>
<div class="gf-form" ng-if="ctrl.tsdbVersion == 2">
<label class="gf-form-label query-keyword width-6">Fill</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<li>
<select ng-model="ctrl.target.downsampleAggregator" class="tight-form-input input-small"
ng-options="agg for agg in ctrl.aggregators"
ng-change="ctrl.targetBlur()">
</select>
</li>
<gf-form-switch class="gf-form"
label="Disable downsampling"
checked="ctrl.target.disableDownsampling"
on-change="ctrl.targetBlur()">
</gf-form-switch>
<li class="tight-form-item query-keyword" style="width: 59px" ng-if="ctrl.tsdbVersion == 2">
Fill
</li>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<li ng-if="ctrl.tsdbVersion == 2">
<select ng-model="ctrl.target.downsampleFillPolicy" class="tight-form-input input-small"
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</li>
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion == 2">
<div class="gf-form">
<li class="tight-form-item query-keyword">
Disable downsampling <editor-checkbox text="" model="ctrl.target.disableDownsampling" change="ctrl.targetBlur()"></editor-checkbox>
</li>
<label class="gf-form-label query-keyword width-8">
Filters
<info-popover mode="right-normal">
Filters does not work with tags, either of the two will work but not both.
</info-popover>
</label>
</ul>
<div class="clearfix"></div>
</div>
<div ng-repeat="fil in ctrl.target.filters track by $index" class="gf-form-label">
{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
<a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</div>
<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</label>
</div>
<div class="tight-form" ng-if="ctrl.tsdbVersion == 2">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
Filters
<tip ng-if="ctrl.tsdbVersion == 2">Filters does not work with tags, either of the two will work but not both.</tip>
</li>
<li ng-repeat="fil in ctrl.target.filters track by $index" class="tight-form-item">
{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
<a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</li>
<div class="gf-form-inline" ng-show="ctrl.addFilterMode">
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentFilterKey" placeholder="key">
</input>
</div>
<li class="query-keyword" ng-show="ctrl.addFilterMode">
<input type="text" class="input-small tight-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentFilterKey" placeholder="key"></input>
<div class="gf-form">
<label class="gf-form-label">Type</label>
<div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.currentFilterType" class="gf-form-input" ng-options="filType for filType in ctrl.filterTypes">
</select>
</div>
</div>
Type <select ng-model="ctrl.target.currentFilterType"
class="tight-form-input input-small"
ng-options="filType for filType in ctrl.filterTypes">
</select>
<input type="text" class="input-small tight-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
</input>
<div class="gf-form">
<input type="text" class="gf-form-input" spellcheck='false' bs-typeahead="ctrl.suggestTagValues" data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
</input>
</div>
groupBy <editor-checkbox text="" model="ctrl.target.currentFilterGroupBy"></editor-checkbox>
<gf-form-switch class="gf-form" label="Group by" checked="ctrl.target.currentFilterGroupBy" on-change="ctrl.targetBlur()">
</gf-form-switch>
<a bs-tooltip="ctrl.errors.filters"
style="color: rgb(229, 189, 28)"
ng-show="ctrl.errors.filters">
<i class="fa fa-warning"></i>
</a>
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">
add filter
</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
</a>
<div class="gf-form" ng-show="ctrl.addFilterMode">
<label class="gf-form-label" ng-show="ctrl.errors.filters">
<a bs-tooltip="ctrl.errors.filters" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
</li>
</ul>
<div class="clearfix"></div>
</div>
<label class="gf-form-label">
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
Tags
<tip ng-if="ctrl.tsdbVersion == 2">Please use filters, tags are deprecated in opentsdb 2.2</tip>
</li>
<li ng-repeat="(key, value) in ctrl.target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
</div>
<li class="tight-form-item query-keyword" ng-hide="ctrl.addTagMode">
<a ng-click="ctrl.addTag()">
<i class="fa fa-plus"></i>
</a>
</li>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<li ng-show="ctrl.addTagMode">
<input type="text" class="input-small tight-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentTagKey" placeholder="key"></input>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-8">
Tags
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion == 2">
Please use filters, tags are deprecated in opentsdb 2.2
</info-popover>
</label>
</div>
<input type="text" class="input-small tight-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
<div class="gf-form" ng-repeat="(key, value) in ctrl.target.tags track by $index" class="gf-form">
<label class="gf-form-label">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="gf-form" ng-hide="ctrl.addTagMode">
<label class="gf-form-label query-keyword">
<a ng-click="ctrl.addTag()"><i class="fa fa-plus"></i></a>
</label>
</div>
<div class="gf-form" ng-show="ctrl.addTagMode">
<input type="text"
class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentTagKey" placeholder="key">
</input>
<a bs-tooltip="ctrl.errors.tags"
style="color: rgb(229, 189, 28)"
ng-show="ctrl.errors.tags">
<i class="fa fa-warning"></i>
</a>
<input type="text" class="gf-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
</input>
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">
add tag
</a>
<a ng-click="ctrl.closeAddTagMode()">
<i class="fa fa-remove"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<label class="gf-form-label" ng-show="ctrl.errors.tags">
<a bs-tooltip="ctrl.errors.tags" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i>
</a>
</label>
<label class="gf-form-label" >
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
<a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
</label>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
Rate <editor-checkbox text="" model="ctrl.target.shouldComputeRate" change="ctrl.targetBlur()"></editor-checkbox>
</li>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.shouldComputeRate">
Counter <editor-checkbox text="" model="ctrl.target.isCounter" change="ctrl.targetBlur()"></editor-checkbox>
</li>
<div class="gf-form-inline">
<gf-form-switch class="gf-form" label="Rate" label-class="width-8 query-keyword" checked="ctrl.target.shouldComputeRate" on-change="ctrl.targetBlur()">
</gf-form-switch>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
Counter Max:
</li>
<gf-form-switch ng-hide="!ctrl.target.shouldComputeRate"
class="gf-form" label="Counter" checked="ctrl.target.isCounter" on-change="ctrl.targetBlur()">
</gf-form-switch>
<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()"></input>
</li>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
Reset Value:
</li>
<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="gf-form" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<label class="gf-form-label">Counter Max</label>
<input type="text" class="gf-form-input"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
<label class="gf-form-label">Reset Value</label>
<input type="text" class="tight-form-input input-small"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>

View File

@ -1,80 +1,54 @@
<query-editor-row ctrl="ctrl">
<li class="tight-form-item" style="width: 94px">
Query
</li>
<li>
<input type="text"
class="input-xxlarge tight-form-input"
ng-model="ctrl.target.expr"
spellcheck='false'
placeholder="query expression"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="ctrl.refreshMetricData()">
</li>
<li class="tight-form-item">
Metric
</li>
<li>
<input type="text"
class="input-medium tight-form-input"
ng-model="ctrl.target.metric"
spellcheck='false'
bs-typeahead="ctrl.suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100>
</li>
</query-editor-row>
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<label class="gf-form-label width-8">Query</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck='false' placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()">
</div>
<div class="gf-form max-width-22">
<label class="gf-form-label">Metric lookup</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align" style="width: 94px">
Legend format
</li>
<li>
<input type="text" class="tight-form-input input-xxlarge" ng-model="ctrl.target.legendFormat"
<div class="gf-form-inline">
<div class="gf-form max-width-26">
<label class="gf-form-label width-8">Legend format</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.legendFormat"
spellcheck='false' placeholder="legend format" data-min-length=0 data-items=1000
ng-model-onblur ng-change="ctrl.refreshMetricData()">
</input>
</li>
</ul>
</div>
<div class="gf-form">
<label class="gf-form-label width-5">Step</label>
<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
data-placement="right"
spellcheck='false'
placeholder="{{ctrl.panelCtrl.interval}}"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="ctrl.refreshMetricData()"/>
<info-popover mode="right-absolute">
Leave blank for auto handling based on time range and panel width
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label">Resolution</label>
<div class="gf-form-select-wrapper max-width-15">
<select ng-model="ctrl.target.intervalFactor" class="gf-form-input"
ng-options="r.factor as r.label for r in ctrl.resolutions"
ng-change="ctrl.refreshMetricData()">
</select>
</div>
<label class="gf-form-label">
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
<i class="fa fa-share-square-o"></i>
</a>
</label>
</div>
<div class="clearfix"></div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align" style="width: 94px">
Step
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="ctrl.target.interval"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
data-placement="right"
spellcheck='false'
placeholder="{{ctrl.panelCtrl.interval}}"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="ctrl.refreshMetricData()"
/>
</input>
</li>
<li class="tight-form-item">
Resolution
</li>
<li>
<select ng-model="ctrl.target.intervalFactor" class="tight-form-input input-mini"
ng-options="r.factor as r.label for r in ctrl.resolutions"
ng-change="ctrl.refreshMetricData()">
</select>
</li>
<li class="tight-form-item">
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
<i class="fa fa-share-square-o"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</query-editor-row>

View File

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

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

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,55 @@
</div>
</div>
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form">
<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">
Min
</li>
<li>
<input type="number" 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="number" class="input-small tight-form-input last" ng-model="ctrl.panel.gauge.maxValue" ng-blur="ctrl.render()" placeholder="100"></input>
<span class="alert-state-critical" ng-show="ctrl.invalidGaugeRange">
&nbsp;
<i class="fa fa-warning"></i>
Min value is bigger than max.
</span>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<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">
Threshold markers&nbsp;
<input class="cr1" id="panel.gauge.thresholdMarkers" type="checkbox" ng-model="ctrl.panel.gauge.thresholdMarkers" ng-checked="ctrl.panel.gauge.thresholdMarkers" ng-change="ctrl.render()">
<label for="panel.gauge.thresholdMarkers" class="cr1"></label>
</li>
<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,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';
@ -48,11 +18,51 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data: any;
fontSizes: any[];
unitFormats: any[];
invalidGaugeRange: boolean;
// 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,
thresholdMarkers: true,
thresholdLabels: false
}
};
/** @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 +280,109 @@ class SingleStatCtrl extends MetricsPanelCtrl {
return body;
}
function getValueText() {
var result = panel.prefix ? panel.prefix : '';
result += data.valueFormated;
result += panel.postfix ? panel.postfix : '';
return result;
}
function addGauge() {
ctrl.invalidGaugeRange = false;
if (panel.gauge.minValue > panel.gauge.maxValue) {
ctrl.invalidGaugeRange = true;
return;
}
var plotCanvas = $('<div></div>');
var width = elem.width();
var height = elem.height();
var plotCss = {
top: '10px',
margin: 'auto',
position: 'relative',
height: (height * 0.9) + 'px',
width: 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 dimension = Math.min(width, height);
var fontSize = Math.min(dimension/4, 100);
var gaugeWidth = Math.min(dimension/6, 60);
var thresholdMarkersWidth = gaugeWidth/5;
var options = {
series: {
gauges: {
gauge: {
min: panel.gauge.minValue,
max: panel.gauge.maxValue,
background: { color: bgColor },
border: { color: null },
shadow: { show: false },
width: gaugeWidth,
},
frame: { show: false },
label: { show: false },
layout: { margin: 0, thresholdWidth: 0 },
cell: { border: { width: 0 } },
threshold: {
values: thresholds,
label: {
show: panel.gauge.thresholdLabels,
margin: 8,
font: { size: 18 }
},
show: panel.gauge.thresholdMarkers,
width: thresholdMarkersWidth,
},
value: {
color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null,
formatter: function() { return getValueText(); },
font: { size: fontSize, family: 'Helvetica Neue", Helvetica, Arial, sans-serif' }
},
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)) + 15;
} else {
return 30;
}
}
function addSparkline() {
var width = elem.width() + 20;
if (width < 30) {
@ -331,11 +444,11 @@ class SingleStatCtrl extends MetricsPanelCtrl {
function render() {
if (!ctrl.data) { return; }
ctrl.setValues(ctrl.data);
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 +471,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,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));
@ -120,6 +120,11 @@ class TablePanelCtrl extends MetricsPanelCtrl {
}
toggleColumnSort(col, colIndex) {
// remove sort flag from current column
if (this.table.columns[this.panel.sort.col]) {
this.table.columns[this.panel.sort.col].sort = false;
}
if (this.panel.sort.col === colIndex) {
if (this.panel.sort.desc) {
this.panel.sort.desc = false;

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

@ -70,6 +70,7 @@
@import "components/drop";
@import "components/query_editor";
@import "components/tabbed_view";
@import "components/query_part";
// PAGES
@import "pages/login";

View File

@ -67,8 +67,8 @@ $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98%
// Links
// -------------------------
$link-color: darken($white,11%);
$link-color-disabled: darken($link-color,30%);
$link-color: darken($white, 11%);
$link-color-disabled: darken($link-color, 30%);
$link-hover-color: $white;
$external-link-color: $blue;
@ -76,7 +76,7 @@ $external-link-color: $blue;
// -------------------------
$headings-color: darken($white,11%);
$abbr-border-color: $gray-3 !default;
$text-muted: darken($link-color,30%);
$text-muted: $text-color-weak;
$blockquote-small-color: $gray-3 !default;
$blockquote-border-color: $gray-4 !default;

View File

@ -82,7 +82,7 @@ $external-link-color: $blue;
// -------------------------
$headings-color: $text-color;
$abbr-border-color: $gray-2 !default;
$text-muted: darken($link-color,30%);
$text-muted: $text-color-weak;
$blockquote-small-color: $gray-2 !default;
$blockquote-border-color: $gray-3 !default;

View File

@ -18,6 +18,8 @@ pre {
code {
color: $text-color;
white-space: nowrap;
padding: 2px 5px;
margin: 0 2px;
}
code.code--small {
@ -26,10 +28,6 @@ code.code--small {
margin: 0 2px;
}
p.code--line {
line-height: 1.8;
}
// Blocks of code
pre {
display: block;

View File

@ -27,9 +27,9 @@ em { font-style: italic; color: $headings-color; }
cite { font-style: normal; }
// Utility classes
.muted { color: $gray-2; }
.muted { color: $text-muted; }
a.muted:hover,
a.muted:focus { color: darken($gray-2, 10%); }
a.muted:focus { color: darken($text-muted, 10%); }
.text-warning { color: $state-warning-text; }
a.text-warning:hover,
@ -118,6 +118,10 @@ small,
font-weight: normal;
}
.small-xs {
font-size: $font-size-xs;
}
mark,
.mark {
padding: .2em;

View File

@ -1,4 +1,4 @@
$popover-arrow-size: 1rem;
$popover-arrow-size: 0.7rem;
$color: inherit;
$backgroundColor: $btn-secondary-bg;
$color: $text-color;

View File

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

View File

@ -8,8 +8,22 @@ $gf-form-margin: 0.25rem;
text-align: left;
position: relative;
.cr1 {
margin-left: 8px;
&--offset-1 {
margin-left: $spacer;
}
&--grow {
flex-grow: 1;
}
}
.gf-form-disabled {
color: $text-color-weak;
.query-keyword,
a,
.gf-form-input {
color: $text-color-weak;
}
}
@ -22,10 +36,6 @@ $gf-form-margin: 0.25rem;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
.gf-form-flex {
flex-grow: 1;
}
}
.gf-form-button-row {
@ -48,6 +58,12 @@ $gf-form-margin: 0.25rem;
border: $input-btn-border-width solid transparent;
@include border-radius($label-border-radius-sm);
&--grow {
flex-grow: 1;
min-height: 2.70rem;
}
}
.gf-form-checkbox {
@ -107,6 +123,21 @@ $gf-form-margin: 0.25rem;
}
&.gf-size-auto { width: auto; }
&--dropdown {
padding-right: $input-padding-x*2;
&:after {
position: absolute;
top: 35%;
right: $input-padding-x/2;
background-color: transparent;
color: $input-color;
font: normal normal normal $font-size-sm/1 FontAwesome;
content: '\f0d7';
pointer-events: none;
}
}
}
.gf-form-select-wrapper {
@ -152,9 +183,11 @@ $gf-form-margin: 0.25rem;
}
.gf-form-btn {
margin-right: $gf-form-margin;
padding: $input-padding-y $input-padding-x;
margin-right: $gf-form-margin;
line-height: $input-line-height;
font-size: $font-size-sm;
flex-shrink: 0;
flex-grow: 0;
}
@ -209,4 +242,3 @@ $gf-form-margin: 0.25rem;
select.gf-form-input ~ .gf-form-help-icon {
right: 10px;
}

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

@ -48,4 +48,8 @@
}
}
#flotGagueValue0 {
font-weight: bold; //please dont hurt me for this!
}

View File

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

View File

@ -4,13 +4,66 @@
}
.query-segment-key {
border-right: none;
padding-right: 1px;
//border-right: none;
//padding-right: 1px;
}
.query-segment-operator {
padding-right: 1px;
border-right: none;
//padding-right: 1px;
//border-right: none;
color: $orange;
}
.gf-form-query {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: flex-start;
align-items: flex-start;
.gf-form,
.gf-form-filler {
margin-bottom: 2px;
}
.gf-form-switch,
.gf-form-switch label,
.gf-form-input,
.gf-form-select-wrapper,
.gf-form-filler,
.gf-form-label {
margin-right: 2px;
}
}
.gf-form-query-content {
flex-grow: 2;
&--collapsed {
overflow: hidden;
.gf-form-label {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
white-space: nowrap;
}
}
}
.gf-form-query-letter-cell {
.gf-form-query-letter-cell-carret {
display: inline-block;
width: 0.7rem;
position: relative;
left: -2px;
}
.gf-form-query-letter-cell-letter {
font-weight: bold;
color: $blue;
}
.gf-form-query-letter-cell-ds {
color: $text-color-weak;
}
}

View File

@ -0,0 +1,11 @@
.query-part {
background-color: $input-bg !important;
&.show-function-controls {
padding-top: 5px;
min-width: 100px;
text-align: center;
}
}

View File

@ -10,7 +10,7 @@
font-family: inherit;
background: $theme-bg;
color: $theme-color;
padding: $spacer;
padding: 0.65rem;
font-size: $font-size-sm;
max-width: 20rem;

View File

@ -61,13 +61,14 @@ button.close {
.hide {
display: none;
}
.show {
display: block;
}
// Visibility
.invisible {
visibility: hidden;
visibility: hidden !important;
}
// For Affix plugin

View File

@ -17,3 +17,9 @@
}
}
@for $i from 1 through 30 {
.offset-width-#{$i} {
margin-left: ($spacer * $i) !important;
}
}

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

Some files were not shown because too many files have changed in this diff Show More