diff --git a/bower.json b/bower.json index 9e0b307c80c..557ee9c9f8f 100644 --- a/bower.json +++ b/bower.json @@ -14,10 +14,10 @@ ], "dependencies": { "jquery": "~2.1.4", - "angular": "~1.5.1", - "angular-route": "~1.5.1", - "angular-mocks": "~1.5.1", - "angular-sanitize": "~1.5.1", + "angular": "~1.5.3", + "angular-route": "~1.5.3", + "angular-mocks": "~1.5.3", + "angular-sanitize": "~1.5.3", "angular-bindonce": "~0.3.3" } } diff --git a/package.json b/package.json index 3c6729a575c..6a9edec76ae 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "http://github.com/grafana/grafana.git" }, "devDependencies": { - "angular2": "2.0.0-beta.0", + "angular2": "2.0.0-beta.12", "autoprefixer": "^6.3.3", "es6-promise": "^3.0.2", "es6-shim": "^0.35.0", @@ -37,7 +37,7 @@ "grunt-postcss": "^0.8.0", "grunt-sass": "^1.1.0", "grunt-string-replace": "~1.2.1", - "grunt-systemjs-builder": "^0.2.5", + "grunt-systemjs-builder": "^0.2.6", "grunt-tslint": "^3.0.2", "grunt-typescript": "^0.8.0", "grunt-usemin": "3.0.0", @@ -55,8 +55,7 @@ "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.2", "sass-lint": "^1.5.0", - "systemjs": "0.19.20", - "zone.js": "0.5.10" + "systemjs": "0.19.24" }, "engines": { "node": "0.4.x", @@ -76,7 +75,7 @@ "lodash": "^2.4.1", "remarkable": "^1.6.2", "sinon": "1.16.1", - "systemjs-builder": "^0.15.7", + "systemjs-builder": "^0.15.13", "tether": "^1.2.0", "tether-drop": "^1.4.2", "tslint": "^3.4.0", diff --git a/pkg/api/api.go b/pkg/api/api.go index 06aee594d42..23a65c6a972 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -45,6 +45,8 @@ func Register(r *macaron.Macaron) { r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/stats", reqGrafanaAdmin, Index) + r.Get("/styleguide", reqSignedIn, Index) + r.Get("/plugins", reqSignedIn, Index) r.Get("/plugins/:id/edit", reqSignedIn, Index) r.Get("/plugins/:id/page/:page", reqSignedIn, Index) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 57c0c324320..0f783d5d3ff 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -5,10 +5,6 @@ import ( "bytes" "errors" "fmt" - "github.com/fatih/color" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" - m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" - s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" "io" "io/ioutil" "net/http" @@ -16,6 +12,11 @@ import ( "path" "regexp" "strings" + + "github.com/fatih/color" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" + m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" + s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" ) func validateInput(c CommandLine, pluginFolder string) error { @@ -24,17 +25,16 @@ func validateInput(c CommandLine, pluginFolder string) error { return errors.New("please specify plugin to install") } - pluginDir := c.GlobalString("path") - if pluginDir == "" { - return errors.New("missing path flag") + pluginsDir := c.GlobalString("pluginsDir") + if pluginsDir == "" { + return errors.New("missing pluginsDir flag") } - fileInfo, err := os.Stat(pluginDir) + fileInfo, err := os.Stat(pluginsDir) if err != nil { - if err = os.MkdirAll(pluginDir, os.ModePerm); err != nil { - return errors.New("path is not a directory") + if err = os.MkdirAll(pluginsDir, os.ModePerm); err != nil { + return errors.New(fmt.Sprintf("pluginsDir (%s) is not a directory", pluginsDir)) } - return nil } @@ -46,7 +46,7 @@ func validateInput(c CommandLine, pluginFolder string) error { } func installCommand(c CommandLine) error { - pluginFolder := c.GlobalString("path") + pluginFolder := c.GlobalString("pluginsDir") if err := validateInput(c, pluginFolder); err != nil { return err } @@ -59,7 +59,7 @@ func installCommand(c CommandLine) error { func InstallPlugin(pluginName, version string, c CommandLine) error { plugin, err := s.GetPlugin(pluginName, c.GlobalString("repo")) - pluginFolder := c.GlobalString("path") + pluginFolder := c.GlobalString("pluginsDir") if err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/ls_command.go b/pkg/cmd/grafana-cli/commands/ls_command.go index a1481157d61..796f6e500d1 100644 --- a/pkg/cmd/grafana-cli/commands/ls_command.go +++ b/pkg/cmd/grafana-cli/commands/ls_command.go @@ -3,6 +3,7 @@ package commands import ( "errors" "fmt" + "github.com/fatih/color" "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" @@ -31,7 +32,7 @@ var validateLsCommand = func(pluginDir string) error { } func lsCommand(c CommandLine) error { - pluginDir := c.GlobalString("path") + pluginDir := c.GlobalString("pluginsDir") if err := validateLsCommand(pluginDir); err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/remove_command.go b/pkg/cmd/grafana-cli/commands/remove_command.go index 1235d257ab6..e0ecbb2b788 100644 --- a/pkg/cmd/grafana-cli/commands/remove_command.go +++ b/pkg/cmd/grafana-cli/commands/remove_command.go @@ -2,6 +2,7 @@ package commands import ( "errors" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" @@ -11,7 +12,7 @@ var getPluginss func(path string) []m.InstalledPlugin = services.GetLocalPlugins var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin func removeCommand(c CommandLine) error { - pluginPath := c.GlobalString("path") + pluginPath := c.GlobalString("pluginsDir") localPlugins := getPluginss(pluginPath) log.Info("remove!\n") diff --git a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go index d8594182a99..e805e8e6f34 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go @@ -28,9 +28,9 @@ func ShouldUpgrade(installed string, remote m.Plugin) bool { } func upgradeAllCommand(c CommandLine) error { - pluginDir := c.GlobalString("path") + pluginsDir := c.GlobalString("pluginsDir") - localPlugins := s.GetLocalPlugins(pluginDir) + localPlugins := s.GetLocalPlugins(pluginsDir) remotePlugins, err := s.ListAllPlugins(c.GlobalString("repo")) @@ -53,7 +53,7 @@ func upgradeAllCommand(c CommandLine) error { for _, p := range pluginsToUpgrade { log.Infof("Upgrading %v \n", p.Id) - s.RemoveInstalledPlugin(pluginDir, p.Id) + s.RemoveInstalledPlugin(pluginsDir, p.Id) InstallPlugin(p.Id, "", c) } diff --git a/pkg/cmd/grafana-cli/commands/upgrade_command.go b/pkg/cmd/grafana-cli/commands/upgrade_command.go index e4072e5ced9..b9ca834be6d 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_command.go @@ -5,10 +5,10 @@ import ( ) func upgradeCommand(c CommandLine) error { - pluginDir := c.GlobalString("path") + pluginsDir := c.GlobalString("pluginsDir") pluginName := c.Args().First() - localPlugin, err := s.ReadPlugin(pluginDir, pluginName) + localPlugin, err := s.ReadPlugin(pluginsDir, pluginName) if err != nil { return err @@ -23,7 +23,7 @@ func upgradeCommand(c CommandLine) error { for _, v := range remotePlugins.Plugins { if localPlugin.Id == v.Id { if ShouldUpgrade(localPlugin.Info.Version, v) { - s.RemoveInstalledPlugin(pluginDir, pluginName) + s.RemoveInstalledPlugin(pluginsDir, pluginName) return InstallPlugin(localPlugin.Id, "", c) } } diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go index 2b3b94f1751..b5b13ee36e0 100644 --- a/pkg/cmd/grafana-cli/main.go +++ b/pkg/cmd/grafana-cli/main.go @@ -2,16 +2,17 @@ package main import ( "fmt" + "os" + "runtime" + "github.com/codegangsta/cli" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands" "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" - "os" - "runtime" ) var version = "master" -func getGrafanaPluginPath() string { +func getGrafanaPluginDir() string { os := runtime.GOOS if os == "windows" { return "C:\\opt\\grafana\\plugins" @@ -25,20 +26,21 @@ func main() { app := cli.NewApp() app.Name = "Grafana cli" - app.Author = "raintank" + app.Usage = "" + app.Author = "Grafana Project" app.Email = "https://github.com/grafana/grafana" app.Version = version app.Flags = []cli.Flag{ cli.StringFlag{ - Name: "path", - Usage: "path to the grafana installation", - Value: getGrafanaPluginPath(), + Name: "pluginsDir", + Usage: "path to the grafana plugin directory", + Value: getGrafanaPluginDir(), EnvVar: "GF_PLUGIN_DIR", }, cli.StringFlag{ Name: "repo", Usage: "url to the plugin repository", - Value: "https://grafana-net.raintank.io/api/plugins", + Value: "https://grafana.net/api/plugins", EnvVar: "GF_PLUGIN_REPO", }, cli.BoolFlag{ diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index cd03f755075..f0ad460842d 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/pkg/cmd/grafana-cli/services/services.go @@ -4,24 +4,27 @@ import ( "encoding/json" "errors" "fmt" + "path" + "github.com/franela/goreq" "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" - "path" ) var IoHelper m.IoUtil = IoUtilImp{} func ListAllPlugins(repoUrl string) (m.PluginRepo, error) { fullUrl := repoUrl + "/repo" - res, _ := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do() - + res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do() + if err != nil { + return m.PluginRepo{}, err + } if res.StatusCode != 200 { return m.PluginRepo{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode) } var resp m.PluginRepo - err := res.Body.FromJsonTo(&resp) + err = res.Body.FromJsonTo(&resp) if err != nil { return m.PluginRepo{}, errors.New("Could not load plugin data") } @@ -66,9 +69,7 @@ func RemoveInstalledPlugin(pluginPath, id string) error { } func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) { - resp, err := ListAllPlugins(repoUrl) - if err != nil { - } + resp, _ := ListAllPlugins(repoUrl) for _, i := range resp.Plugins { if i.Id == pluginId { diff --git a/public/app/core/components/info_popover.ts b/public/app/core/components/info_popover.ts index 9b5b2877d54..cc49ecbc4ab 100644 --- a/public/app/core/components/info_popover.ts +++ b/public/app/core/components/info_popover.ts @@ -18,7 +18,7 @@ export function infoPopover() { var offset = attrs.offset || '0 -10px'; var position = attrs.position || 'right middle'; - var classes = 'drop-help'; + var classes = 'drop-help drop-hide-out-of-bounds'; if (attrs.wide) { classes += ' drop-wide'; } @@ -40,14 +40,6 @@ export function infoPopover() { } }); - // inputElem.on('focus.popover', function() { - // drop.open(); - // }); - // - // inputElem.on('blur.popover', function() { - // close(); - // }); - scope.$on('$destroy', function() { drop.destroy(); }); diff --git a/public/app/core/components/navbar/navbar.html b/public/app/core/components/navbar/navbar.html index af6534dd63d..e2faea68eaa 100644 --- a/public/app/core/components/navbar/navbar.html +++ b/public/app/core/components/navbar/navbar.html @@ -10,7 +10,7 @@ - + {{ctrl.title}} diff --git a/public/app/core/components/switch.ts b/public/app/core/components/switch.ts new file mode 100644 index 00000000000..1dddae20d49 --- /dev/null +++ b/public/app/core/components/switch.ts @@ -0,0 +1,74 @@ +/// + +import config from 'app/core/config'; +import _ from 'lodash'; +import $ from 'jquery'; +import coreModule from 'app/core/core_module'; +import Drop from 'tether-drop'; + +var template = ` + +
+ + +
+`; + +export class SwitchCtrl { + onChange: any; + checked: any; + show: any; + id: any; + + /** @ngInject */ + constructor($scope) { + this.show = true; + this.id = $scope.$id; + } + + internalOnChange() { + return new Promise(resolve => { + setTimeout(() => { + this.onChange(); + resolve(); + }); + }); + } + +} + +export function switchDirective() { + return { + restrict: 'E', + controller: SwitchCtrl, + controllerAs: 'ctrl', + bindToController: true, + scope: { + checked: "=", + label: "@", + labelClass: "@", + tooltip: "@", + switchClass: "@", + onChange: "&", + }, + template: template, + link: (scope, elem) => { + if (scope.ctrl.tooltip) { + var drop = new Drop({ + target: elem[0], + content: scope.ctrl.tooltip, + position: "right middle", + classes: 'drop-help', + openOn: 'hover', + hoverOpenDelay: 400, + }); + + scope.$on('$destroy', function() { + drop.destroy(); + }); + } + } + }; +} + +coreModule.directive('gfFormSwitch', switchDirective); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 5b220d6a1a4..279f54d4e45 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -31,6 +31,7 @@ import {arrayJoin} from './directives/array_join'; import {liveSrv} from './live/live_srv'; import {Emitter} from './utils/emitter'; import {layoutSelector} from './components/layout_selector/layout_selector'; +import {switchDirective} from './components/switch'; import 'app/core/controllers/all'; import 'app/core/services/all'; import 'app/core/routes/routes'; @@ -49,6 +50,7 @@ export { colorPicker, liveSrv, layoutSelector, + switchDirective, infoPopover, Emitter, appEvents, diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js index ae825c31fca..2dc5111874e 100644 --- a/public/app/core/directives/dropdown_typeahead.js +++ b/public/app/core/directives/dropdown_typeahead.js @@ -119,4 +119,118 @@ function (_, $, coreModule) { } }; }); + + coreModule.default.directive('dropdownTypeahead2', function($compile) { + + var inputTemplate = ''; + + var buttonTemplate = ''; + + return { + scope: { + menuItems: "=dropdownTypeahead2", + dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect", + model: '=ngModel' + }, + link: function($scope, elem, attrs) { + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + $input.appendTo(elem); + $button.appendTo(elem); + + if (attrs.linkText) { + $button.html(attrs.linkText); + } + + if (attrs.ngModel) { + $scope.$watch('model', function(newValue) { + _.each($scope.menuItems, function(item) { + _.each(item.submenu, function(subItem) { + if (subItem.value === newValue) { + $button.html(subItem.text); + } + }); + }); + }); + } + + var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { + if (!value.submenu) { + value.click = 'menuItemSelected(' + index + ')'; + memo.push(value.text); + } else { + _.each(value.submenu, function(item, subIndex) { + item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; + memo.push(value.text + ' ' + item.text); + }); + } + return memo; + }, []); + + $scope.menuItemSelected = function(index, subIndex) { + var menuItem = $scope.menuItems[index]; + var payload = {$item: menuItem}; + if (menuItem.submenu && subIndex !== void 0) { + payload.$subItem = menuItem.submenu[subIndex]; + } + $scope.dropdownTypeaheadOnSelect(payload); + }; + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: typeaheadValues, + minLength: 1, + items: 10, + updater: function (value) { + var result = {}; + _.each($scope.menuItems, function(menuItem) { + _.each(menuItem.submenu, function(submenuItem) { + if (value === (menuItem.text + ' ' + submenuItem.text)) { + result.$subItem = submenuItem; + result.$item = menuItem; + } + }); + }); + + if (result.$item) { + $scope.$apply(function() { + $scope.dropdownTypeaheadOnSelect(result); + }); + } + + $input.trigger('blur'); + return ''; + } + }); + + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); + + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + $input.hide(); + $input.val(''); + $button.show(); + $button.focus(); + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); + } + }; + }); }); diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index fbd950e60b6..b3194868d97 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -212,7 +212,7 @@ function (angular, $, _, moment) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 11; + this.schemaVersion = 12; if (oldVersion === this.schemaVersion) { return; @@ -401,11 +401,61 @@ function (angular, $, _, moment) { }); } - if (oldVersion < 11) { + if (oldVersion < 12) { // update template variables _.each(this.templating.list, function(templateVariable) { if (templateVariable.refresh) { templateVariable.refresh = 1; } if (!templateVariable.refresh) { templateVariable.refresh = 0; } + if (templateVariable.hideVariable) { + templateVariable.hide = 2; + } else if (templateVariable.hideLabel) { + templateVariable.hide = 1; + } else { + templateVariable.hide = 0; + } + }); + } + + if (oldVersion < 12) { + // update graph yaxes changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'graph') { return; } + if (!panel.yaxes) { + panel.yaxes = [ + { + show: panel['y-axis'], + min: panel.grid.leftMin, + max: panel.grid.leftMax, + logBase: panel.grid.leftLogBase, + format: panel.y_formats[0], + label: panel.leftYAxisLabel, + }, + { + show: panel['y-axis'], + min: panel.grid.rightMin, + max: panel.grid.rightMax, + logBase: panel.grid.rightLogBase, + format: panel.y_formats[1], + label: panel.rightYAxisLabel, + } + ]; + + panel.xaxis = { + show: panel['x-axis'], + }; + + delete panel.grid.leftMin; + delete panel.grid.leftMax; + delete panel.grid.leftLogBase; + delete panel.grid.rightMin; + delete panel.grid.rightMax; + delete panel.grid.rightLogBase; + delete panel.y_formats; + delete panel.leftYAxisLabel; + delete panel.rightYAxisLabel; + delete panel['y-axis']; + delete panel['x-axis']; + } }); } diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index deccd22305b..33b1a247235 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -19,10 +19,11 @@
-
+
+
Details
- +
@@ -38,80 +39,89 @@
-
On/Off Toggles
-
-
-
- -
-
- -
-
- -
-
-
-
+
+
Toggles
+
+ + + + + + +
+
+
-
-
Rows settings
+
+
Rows settings
-
-
-
- Title - - -
+
+
+
+ Title + +
+ -
- - - -
-
-
-
+
+ + + +
+
+
+
-
- -
+
+ +
-
- -
+
+ +
-
-
Dashboard info
-
-
- Last updated at: - {{formatDate(dashboardMeta.updated)}} -
-
- Last updated by: - {{dashboardMeta.updatedBy}}  -
-
- Created at: - {{formatDate(dashboardMeta.created)}}  -
-
- Created by: - {{dashboardMeta.createdBy}}  -
-
- Current version: - {{dashboardMeta.version}}  -
-
-
+
+
Dashboard info
+
+
+ Last updated at: + {{formatDate(dashboardMeta.updated)}} +
+
+ Last updated by: + {{dashboardMeta.updatedBy}}  +
+
+ Created at: + {{formatDate(dashboardMeta.created)}}  +
+
+ Created by: + {{dashboardMeta.createdBy}}  +
+
+ Current version: + {{dashboardMeta.version}}  +
+
+
diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/submenu/submenu.html index 21a9744b359..464d8c4cecf 100644 --- a/public/app/features/dashboard/submenu/submenu.html +++ b/public/app/features/dashboard/submenu/submenu.html @@ -1,7 +1,7 @@
@@ -20,14 +20,19 @@ Amount + ng-change="ctrl.refresh()" ng-model-onblur>
-
- - - - +
+
+ + + +
+ +
diff --git a/public/app/features/plugins/import_list/import_list.ts b/public/app/features/plugins/import_list/import_list.ts index 8eb726b81c0..6c82d3faa92 100644 --- a/public/app/features/plugins/import_list/import_list.ts +++ b/public/app/features/plugins/import_list/import_list.ts @@ -10,6 +10,7 @@ export class DashImportListCtrl { plugin: any; datasource: any; + /** @ngInject */ constructor($scope, private $http, private backendSrv, private $rootScope) { this.dashboards = []; diff --git a/public/app/features/plugins/partials/ds_edit.html b/public/app/features/plugins/partials/ds_edit.html index 1866fad50a0..9ff82027abf 100644 --- a/public/app/features/plugins/partials/ds_edit.html +++ b/public/app/features/plugins/partials/ds_edit.html @@ -27,58 +27,59 @@
-
- Name - - - The name is used when you select the data source in panels. - The Default data source is preselected in new - panels. - +
+
+ Name + +
+ + The name is used when you select the data source in panels. + The Default data source is preselected in new + panels. + + +
- -
+
+ Type +
+ +
+
+
-
- Type -
- -
-
- + + + + - - - - +
+
Testing....
+
Test results
+
+
{{ctrl.testing.title}}
+
+
+
-
-
Testing....
-
Test results
-
-
{{ctrl.testing.title}}
-
-
-
- -
- - - +
+ + + - Cancel -
+ + Cancel +
-
- + + -
- -
+
+ +
diff --git a/public/app/features/plugins/partials/ds_http_settings.html b/public/app/features/plugins/partials/ds_http_settings.html index 65c205714bc..c49de6967c9 100644 --- a/public/app/features/plugins/partials/ds_http_settings.html +++ b/public/app/features/plugins/partials/ds_http_settings.html @@ -29,12 +29,18 @@ -
- - Http Auth - - - Tip for with credentials +
+
+ +
+ + + +
diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index b2916e1bcc7..291915ee99f 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -14,6 +14,7 @@ function (angular, _) { datasource: null, refresh: 0, name: '', + hide: 0, options: [], includeAll: false, multi: false, @@ -25,6 +26,12 @@ function (angular, _) { {value: 2, text: "On Time Range Change"}, ]; + $scope.hideOptions = [ + {value: 0, text: ""}, + {value: 1, text: "Label"}, + {value: 2, text: "Variable"}, + ]; + $scope.init = function() { $scope.mode = 'list'; diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index 7bd1fd0940f..d1cab90ac9d 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -74,32 +74,33 @@
Variable
-
+
Name - +
Type -
- +
+
-
+
Data source -
- +
+
-
+
Label - +
Hide - - +
+ +
@@ -176,17 +177,22 @@
-
+
Selection Options
-
- Multi-value - - Enables multiple values to be selected at the same time -
-
- Include All option - -
+ + + + +
Custom all value diff --git a/public/app/headers/common.d.ts b/public/app/headers/common.d.ts index 67c8604685f..219faf195bc 100644 --- a/public/app/headers/common.d.ts +++ b/public/app/headers/common.d.ts @@ -1,5 +1,5 @@ -/// -/// +/// +/// declare var System: any; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index c03e5085cc2..86e085b3f6f 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -21,6 +21,7 @@ describe('CloudWatchDatasource', function() { ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings}); + $httpBackend.when('GET', /\.html$/).respond(''); })); describe('When performing CloudWatch query', function() { diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index bc1da316810..4d630ff9c40 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -12,11 +12,13 @@ describe('ElasticDatasource', function() { beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.providePhase(['templateSrv', 'backendSrv'])); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { ctx.$q = $q; ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.$injector = $injector; + $httpBackend.when('GET', /\.html$/).respond(''); })); function createDatasource(instanceSettings) { diff --git a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts index 2125922b350..237c37cfcc7 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts @@ -15,6 +15,7 @@ describe('graphiteDatasource', function() { ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.$injector = $injector; + $httpBackend.when('GET', /\.html$/).respond(''); })); beforeEach(function() { diff --git a/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts index 1da4268e433..a1dc9baada9 100644 --- a/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts +++ b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts @@ -12,9 +12,10 @@ describe('opentsdb', function() { beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { ctx.$q = $q; - ctx.$httpBackend = $httpBackend; + ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.ds = $injector.instantiate(OpenTsDatasource, {instanceSettings: instanceSettings}); + $httpBackend.when('GET', /\.html$/).respond(''); })); describe('When performing metricFindQuery', function() { diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 1958c6be4cf..5457fc0e16d 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -14,6 +14,7 @@ describe('PrometheusDatasource', function() { ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.ds = $injector.instantiate(PrometheusDatasource, {instanceSettings: instanceSettings}); + $httpBackend.when('GET', /\.html$/).respond(''); })); describe('When querying prometheus with one target using query editor target spec', function() { diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts index 5edf7038cd3..d328f4cb25b 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts @@ -8,6 +8,7 @@ describe('PrometheusMetricFindQuery', function() { var ctx = new helpers.ServiceTestContext(); var instanceSettings = {url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' }; + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { @@ -15,6 +16,7 @@ describe('PrometheusMetricFindQuery', function() { ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; ctx.ds = $injector.instantiate(PrometheusDatasource, {instanceSettings: instanceSettings}); + $httpBackend.when('GET', /\.html$/).respond(''); })); describe('When performing metricFindQuery', function() { diff --git a/public/app/plugins/panel/graph/axisEditor.html b/public/app/plugins/panel/graph/axisEditor.html deleted file mode 100644 index d9b7b4599e0..00000000000 --- a/public/app/plugins/panel/graph/axisEditor.html +++ /dev/null @@ -1,240 +0,0 @@ - -
-
-
-
    -
  • - Left Y -
  • -
  • - Unit -
  • - -
  • - Scale type -
  • -
  • - -
  • -
  • - Label -
  • -
  • - -
  • -
-
-
-
-
    -
  • - -
  • -
  • - Y-Max -
  • -
  • - -
  • -
  • - Y-Min -
  • -
  • - -
  • -
-
-
-
-
    -
  • - Right Y -
  • -
  • - Unit -
  • - -
  • - Scale type -
  • -
  • - -
  • -
  • - Label -
  • -
  • - -
  • -
-
-
-
-
    -
  • - -
  • -
  • - Y-Max -
  • -
  • - -
  • -
  • - Y-Min -
  • -
  • - -
  • -
-
-
- -
- -
-
-
    -
  • - Show Axis -
  • -
  • - X-Axis  - - -
  • -
  • - Y-Axis  - - -
  • -
-
-
-
-
    -
  • - Thresholds -
  • -
  • - Level 1 -
  • -
  • - -
  • -
  • - -
  • -
  • - Level 2 -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
- -
-
-
-
    -
  • - Legend -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - Side width -
  • -
  • - -
  • -
-
-
-
-
    -
  • - Hide series -
  • -
  • - -
  • -
  • - -
  • -
-
-
- -
-
    -
  • - Values -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - Decimals -
  • -
  • - -
  • -
-
-
-
-
diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 0f53852cec0..0443a294eed 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -115,7 +115,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { for (var i = 0; i < data.length; i++) { var series = data[i]; var axis = yaxis[series.yaxis - 1]; - var formater = kbn.valueFormats[panel.y_formats[series.yaxis - 1]]; + var formater = kbn.valueFormats[panel.yaxes[series.yaxis - 1].format]; // decimal override if (_.isNumber(panel.decimals)) { @@ -132,18 +132,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) { } // add left axis labels - if (panel.leftYAxisLabel) { + if (panel.yaxes[0].label) { var yaxisLabel = $("
") - .text(panel.leftYAxisLabel) + .text(panel.yaxes[0].label) .appendTo(elem); yaxisLabel.css("margin-top", yaxisLabel.width() / 2); } // add right axis labels - if (panel.rightYAxisLabel) { + if (panel.yaxes[1].label) { var rightLabel = $("
") - .text(panel.rightYAxisLabel) + .text(panel.yaxes[1].label) .appendTo(elem); rightLabel.css("margin-top", rightLabel.width() / 2); @@ -151,7 +151,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { } function processOffsetHook(plot, gridMargin) { - if (panel.leftYAxisLabel) { gridMargin.left = 20; } + if (panel.yaxis) { gridMargin.left = 20; } if (panel.rightYAxisLabel) { gridMargin.right = 20; } } @@ -217,7 +217,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { for (var i = 0; i < data.length; i++) { var series = data[i]; - series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode, panel.y_formats); + series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode); // if hidden remove points and disable stack if (ctrl.hiddenSeries[series.alias]) { @@ -340,11 +340,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) { function configureAxisOptions(data, options) { var defaults = { position: 'left', - show: panel['y-axis'], - min: panel.grid.leftMin, + show: panel.yaxes[0].show, + min: panel.yaxes[0].min, index: 1, - logBase: panel.grid.leftLogBase || 1, - max: panel.percentage && panel.stack ? 100 : panel.grid.leftMax, + logBase: panel.yaxes[0].logBase || 1, + max: panel.percentage && panel.stack ? 100 : panel.yaxes[0].max, }; options.yaxes.push(defaults); @@ -352,18 +352,19 @@ function (angular, $, moment, _, kbn, GraphTooltip) { if (_.findWhere(data, {yaxis: 2})) { var secondY = _.clone(defaults); secondY.index = 2, - secondY.logBase = panel.grid.rightLogBase || 1, + secondY.show = panel.yaxes[1].show; + secondY.logBase = panel.yaxes[1].logBase || 1, secondY.position = 'right'; - secondY.min = panel.grid.rightMin; - secondY.max = panel.percentage && panel.stack ? 100 : panel.grid.rightMax; + secondY.min = panel.yaxes[1].min; + secondY.max = panel.percentage && panel.stack ? 100 : panel.yaxes[1].max; options.yaxes.push(secondY); applyLogScale(options.yaxes[1], data); - configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.y_formats[1]); + configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format); } applyLogScale(options.yaxes[0], data); - configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.y_formats[0]); + configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format); } function applyLogScale(axis, data) { @@ -456,7 +457,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { url += panel['x-axis'] ? '' : '&hideAxes=true'; url += panel['y-axis'] ? '' : '&hideYAxis=true'; - switch(panel.y_formats[0]) { + switch(panel.yaxes[0].format) { case 'bytes': url += '&yUnitSystem=binary'; break; diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 29c2dccbac2..c99f9070af3 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -17,20 +17,28 @@ var panelDefaults = { datasource: null, // sets client side (flot) or native graphite png renderer (png) renderer: 'flot', - // Show/hide the x-axis - 'x-axis' : true, - // Show/hide y-axis - 'y-axis' : true, - // y axis formats, [left axis,right axis] - y_formats : ['short', 'short'], - // grid options + 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 : { - leftLogBase: 1, - leftMax: null, - rightMax: null, - leftMin: null, - rightMin: null, - rightLogBase: 1, threshold1: null, threshold2: null, threshold1Color: 'rgba(216, 200, 27, 0.27)', @@ -115,8 +123,9 @@ class GraphCtrl extends MetricsPanelCtrl { } onInitEditMode() { - this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html', 2); - this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html', 3); + this.addEditorTab('Axes', 'public/app/plugins/panel/graph/tab_axes.html', 2); + this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3); + this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4); this.logScales = { 'linear': 1, @@ -135,7 +144,7 @@ class GraphCtrl extends MetricsPanelCtrl { } setUnitFormat(axis, subItem) { - this.panel.y_formats[axis] = subItem.value; + axis.format = subItem.value; this.render(); } diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 28b38101004..1a708696680 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -31,7 +31,21 @@ describe('grafanaGraph', function() { panel: { legend: {}, grid: { }, - y_formats: [], + yaxes: [ + { + min: null, + max: null, + format: 'short', + logBase: 1 + }, + { + min: null, + max: null, + format: 'short', + logBase: 1 + } + ], + xaxis: {}, seriesOverrides: [], tooltip: { shared: true @@ -151,13 +165,7 @@ describe('grafanaGraph', function() { graphScenario('when logBase is log 10', function(ctx) { ctx.setup(function(ctrl) { - ctrl.panel.grid = { - leftMax: null, - rightMax: null, - leftMin: null, - rightMin: null, - leftLogBase: 10, - }; + ctrl.panel.yaxes[0].logBase = 10; }); it('should apply axis transform and ticks', function() { diff --git a/public/app/plugins/panel/graph/styleEditor.html b/public/app/plugins/panel/graph/styleEditor.html deleted file mode 100644 index 465e8d4894e..00000000000 --- a/public/app/plugins/panel/graph/styleEditor.html +++ /dev/null @@ -1,123 +0,0 @@ -
-
-
Draw Modes
-
- - -
-
- - -
-
- - -
-
-
-
Mode Options
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- - -
-
-
-
Misc options
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
Multiple Series
-
- - -
-
- - -
-
- -
- -
-
-
- -
- -
-
-
Series specific overrides Regex match example: /server[0-3]/i
-
-
-
    -
  • - -
  • - -
  • - alias or regex -
  • - -
  • - -
  • - -
  • - - - Color: - - - {{option.name}}: {{option.value}} - -
  • - - -
-
-
-
- - -
-
diff --git a/public/app/plugins/panel/graph/tab_axes.html b/public/app/plugins/panel/graph/tab_axes.html new file mode 100644 index 00000000000..81ab3deeb31 --- /dev/null +++ b/public/app/plugins/panel/graph/tab_axes.html @@ -0,0 +1,72 @@ +
+
+ +
Left Y
+
Right Y
+ + + +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ +
+
X-Axis
+ +
+ +
+
Thresholds
+
+
+ + +
+
+ +
+ +
+
+
+
+
+ + +
+
+ +
+ +
+
+
+
+
diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html new file mode 100644 index 00000000000..5c021dae8e1 --- /dev/null +++ b/public/app/plugins/panel/graph/tab_display.html @@ -0,0 +1,123 @@ +
+
+
Draw Modes
+ + + + + + +
+
+
Mode Options
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + +
+
+
Misc options
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
Multiple Series
+ + + + +
+ +
+ +
+
+
+ +
+ +
+
+
Series specific overrides Regex match example: /server[0-3]/i
+
+
+
    +
  • + +
  • + +
  • + alias or regex +
  • + +
  • + +
  • + +
  • + + + Color: + + + {{option.name}}: {{option.value}} + +
  • + + +
+
+
+
+ + +
+
diff --git a/public/app/plugins/panel/graph/tab_legend.html b/public/app/plugins/panel/graph/tab_legend.html new file mode 100644 index 00000000000..ac9e11dcaf0 --- /dev/null +++ b/public/app/plugins/panel/graph/tab_legend.html @@ -0,0 +1,74 @@ +
+
+
Options
+ + + + + + + +
+ + +
+
+ +
+
Values
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + +
+ + +
+
+
+ +
+
Hide series
+ + + + +
+
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 32643962ddc..c7b4567bfb5 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -35,6 +35,7 @@ @import "components/navs"; @import "components/tabs"; @import "components/alerts"; +@import "components/switch"; @import "components/tooltip"; @import "components/tags"; @import "components/panel_graph"; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 26dabd4336f..18b2931373d 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -48,14 +48,17 @@ $critical: #ed2e18; // Scaffolding // ------------------------- -$body-bg: rgb(20,20,20); -$page-bg: $dark-2; -$body-color: $gray-4; -$text-color: $gray-4; -$text-color-strong: $white; -$text-color-weak: $gray-2; +$body-bg: rgb(20,20,20); +$page-bg: $dark-2; +$body-color: $gray-4; +$text-color: $gray-4; +$text-color-strong: $white; +$text-color-weak: $gray-2; +$text-color-faint: $gray-1; $text-color-emphasis: $gray-5; +$text-shadow-strong: 1px 1px 4px $black; +$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45); // gradients $brand-gradient: linear-gradient(to right, rgba(255,213,0,0.7) 0%, rgba(255,68,0,0.7) 99%, rgba(255,68,0,0.7) 100%); @@ -126,7 +129,7 @@ $btn-primary-bg: $brand-primary; $btn-primary-bg-hl: lighten($brand-primary, 8%); $btn-secondary-bg: $blue-dark; -$btn-secondary-bg-hl: lighten($blue-dark, 3%); +$btn-secondary-bg-hl: lighten($blue-dark, 5%); $btn-success-bg: lighten($green, 3%); $btn-success-bg-hl: darken($green, 3%); diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 9071472b9b3..8fef6ae72eb 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -60,8 +60,12 @@ $body-color: $gray-1; $text-color: $gray-1; $text-color-strong: $white; $text-color-weak: $gray-1; +$text-color-faint: $gray-3; $text-color-emphasis: $dark-5; +$text-shadow-strong: none; +$text-shadow-faint: none; + // gradients $brand-gradient: linear-gradient(to right, rgba(255,213,0,1.0) 0%, rgba(255,68,0,1.0) 99%, rgba(255,68,0,1.0) 100%); $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98%); diff --git a/public/sass/base/_forms.scss b/public/sass/base/_forms.scss index 4f1d0778e0b..3891adc4085 100644 --- a/public/sass/base/_forms.scss +++ b/public/sass/base/_forms.scss @@ -174,7 +174,7 @@ label.cr1 { cursor:pointer; } -input[type="checkbox"]:checked+label { +input[type="checkbox"].cr1:checked+label { background: url($checkboxImageUrl) 0px -18px no-repeat; } diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 34c53557d45..0af497f46f2 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -15,11 +15,7 @@ $easing: cubic-bezier(0, 0, 0.265, 1.00); display: block; } - &.drop-open.drop-help.drop-out-of-bounds, - &.drop-open-transitionend.drop-help.drop-out-of-bounds { - display: none; - } -} + } .drop-element, .drop-element * { box-sizing: border-box; @@ -38,6 +34,12 @@ $easing: cubic-bezier(0, 0, 0.265, 1.00); } } +.drop-hide-out-of-bounds { + &.drop-open.drop-help.drop-out-of-bounds, + &.drop-open-transitionend.drop-help.drop-out-of-bounds { + display: none; + } +} @include drop-theme("help", $popover-help-bg, $popover-help-color); @include drop-theme("popover", $popover-bg, $popover-color); diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 0b34cc1fa6a..12e1fd2f32f 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -36,6 +36,7 @@ $gf-form-margin: 0.25rem; .gf-form-label { padding: $input-padding-y $input-padding-x; + margin-right: $gf-form-margin; line-height: $input-line-height; flex-shrink: 0; @@ -108,9 +109,9 @@ $gf-form-margin: 0.25rem; } .gf-form-select-wrapper { + margin-right: $gf-form-margin; position: relative; background-color: $input-bg; - margin-right: $gf-form-margin; select.gf-form-input { text-indent: .01px; @@ -151,6 +152,10 @@ $gf-form-margin: 0.25rem; flex-grow: 0; } +.gf-form-switch { + margin-right: $gf-form-margin; +} + .natural-language-input { &input[type="number"] { font-size: $font-size-base; @@ -160,3 +165,20 @@ $gf-form-margin: 0.25rem; } } +.gf-form-dropdown-typeahead { + margin-right: $gf-form-margin; + position: relative; + background-color: $input-bg; + padding-right: $input-padding-x; + + &: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; + } +} diff --git a/public/sass/components/_old_stuff.scss b/public/sass/components/_old_stuff.scss index eae5eb558ce..bb53b657862 100644 --- a/public/sass/components/_old_stuff.scss +++ b/public/sass/components/_old_stuff.scss @@ -1,9 +1,9 @@ -div.editor-row { +.editor-row { vertical-align: top; } -div.editor-row div.section { +.section { margin-right: 20px; vertical-align: top; display: inline-block; diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss new file mode 100644 index 00000000000..4216029bab2 --- /dev/null +++ b/public/sass/components/_switch.scss @@ -0,0 +1,91 @@ +$switch-border-radius: 1rem; +$switch-width: 3.5rem; +$switch-height: 1.5rem; + +/* ============================================================ + SWITCH 3 - YES NO +============================================================ */ + +.gf-form-switch { + position: relative; + max-width: 4.5rem; + flex-grow: 1; + min-width: 4.0rem; + + input { + position: absolute; + margin-left: -9999px; + visibility: hidden; + display: none; + } + + input + label { + display: block; + position: relative; + cursor: pointer; + outline: none; + user-select: none; + width: 100%; + height: 2.65rem; + background-color: $page-bg; + } + + input + label:before, input + label:after { + @include buttonBackground($input-bg, $input-bg); + + display: block; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + color: #fff; + font-size: $font-size-sm; + text-align: center; + line-height: 2.8rem; + font-size: 150%; + } + + &:hover { + input + label:before { + @include buttonBackground($input-bg, lighten($input-bg, 5%)); + color: $text-color; + text-shadow: $text-shadow-faint; + } + + input + label:after { + @include buttonBackground($input-bg, lighten($input-bg, 5%)); + color: lighten($orange, 10%); + text-shadow: $text-shadow-strong; + } + + } + + input + label:before { + font-family: 'FontAwesome'; + content: "\f096"; // square-o + color: $text-color-faint; + transition: transform 0.4s; + backface-visibility: hidden; + text-shadow: $text-shadow-faint; + } + + input + label:after { + content: "\f046"; // check-square-o + color: $orange; + text-shadow: $text-shadow-strong; + + font-family: 'FontAwesome'; + transition: transform 0.4s; + transform: rotateY(180deg); + backface-visibility: hidden; + } + + input:checked + label:before { + transform: rotateY(180deg); + } + + input:checked + label:after { + transform: rotateY(0); + } +} diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index c72dd1d4388..6c8a42a8059 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -1,7 +1,7 @@ .tabbed-view { background-color: $page-bg; background-image: $page-gradient; - margin: -($panel-margin*2); + margin: (-$panel-margin*2) (-$panel-margin); margin-bottom: $spacer*2; padding: $spacer*3; diff --git a/public/sass/utils/_widths.scss b/public/sass/utils/_widths.scss index bd6fc78ef90..b4bdbdb6ccc 100644 --- a/public/sass/utils/_widths.scss +++ b/public/sass/utils/_widths.scss @@ -6,13 +6,13 @@ // widths @for $i from 1 through 30 { .width-#{$i} { - width: ($spacer * $i) - $gf-form-margin; + width: ($spacer * $i) - $gf-form-margin !important; } } @for $i from 1 through 30 { .max-width-#{$i} { - max-width: ($spacer * $i) - $gf-form-margin; + max-width: ($spacer * $i) - $gf-form-margin !important; flex-grow: 1; } } diff --git a/public/test/specs/dashboardSrv-specs.js b/public/test/specs/dashboardSrv-specs.js index 86f54f274fb..e02495c1236 100644 --- a/public/test/specs/dashboardSrv-specs.js +++ b/public/test/specs/dashboardSrv-specs.js @@ -122,7 +122,10 @@ define([ { panels: [ { - type: 'graphite', legend: true, aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }, + type: 'graph', legend: true, aliasYAxis: { test: 2 }, + y_formats: ['kbyte', 'ms'], + grid: {min: 1, max: 10, rightMin: 5, rightMax: 15, leftLogBase: 1, rightLogBase: 2}, + leftYAxisLabel: 'left label', targets: [{refId: 'A'}, {}], }, { @@ -172,11 +175,6 @@ define([ expect(graph.legend.show).to.be(true); }); - it('update grid options', function() { - expect(graph.grid.leftMin).to.be(1); - expect(graph.grid.leftMax).to.be(10); - }); - it('move aliasYAxis to series override', function() { expect(graph.seriesOverrides[0].alias).to.be("test"); expect(graph.seriesOverrides[0].yaxis).to.be(2); @@ -193,8 +191,24 @@ define([ expect(table.styles[1].thresholds[1]).to.be("300"); }); + it('graph grid to yaxes options', function() { + expect(graph.yaxes[0].min).to.be(1); + expect(graph.yaxes[0].max).to.be(10); + expect(graph.yaxes[0].format).to.be('kbyte'); + expect(graph.yaxes[0].label).to.be('left label'); + expect(graph.yaxes[0].logBase).to.be(1); + expect(graph.yaxes[1].min).to.be(5); + expect(graph.yaxes[1].max).to.be(15); + expect(graph.yaxes[1].format).to.be('ms'); + expect(graph.yaxes[1].logBase).to.be(2); + + expect(graph.grid.rightMax).to.be(undefined); + expect(graph.grid.rightLogBase).to.be(undefined); + expect(graph.y_formats).to.be(undefined); + }); + it('dashboard schema version should be set to latest', function() { - expect(model.schemaVersion).to.be(11); + expect(model.schemaVersion).to.be(12); }); }); @@ -248,6 +262,8 @@ define([ rows: [{ panels: [{ type: 'graph', + grid: {}, + yaxes: [{}, {}], targets: [{ "alias": "$tag_datacenter $tag_source $col", "column": "value", diff --git a/public/test/specs/value_select_dropdown_specs.js b/public/test/specs/value_select_dropdown_specs.js index 3e6397ab50c..2bca4aaaf5c 100644 --- a/public/test/specs/value_select_dropdown_specs.js +++ b/public/test/specs/value_select_dropdown_specs.js @@ -11,7 +11,7 @@ function () { var rootScope; beforeEach(module('grafana.core')); - beforeEach(inject(function($controller, $rootScope, $q) { + beforeEach(inject(function($controller, $rootScope, $q, $httpBackend) { rootScope = $rootScope; scope = $rootScope.$new(); ctrl = $controller('ValueSelectDropdownCtrl', {$scope: scope}); @@ -19,6 +19,7 @@ function () { return $q.when(tagValuesMap[obj.tagKey]); }; ctrl.onUpdated = sinon.spy(); + $httpBackend.when('GET', /\.html$/).respond(''); })); describe("Given simple variable", function() { diff --git a/public/vendor/angular-mocks/.bower.json b/public/vendor/angular-mocks/.bower.json index 9831478983e..03fc650628a 100644 --- a/public/vendor/angular-mocks/.bower.json +++ b/public/vendor/angular-mocks/.bower.json @@ -1,20 +1,20 @@ { "name": "angular-mocks", - "version": "1.5.1-build.4601+sha.c966876", + "version": "1.5.3", "license": "MIT", "main": "./angular-mocks.js", "ignore": [], "dependencies": { - "angular": "1.5.1-build.4601+sha.c966876" + "angular": "1.5.3" }, "homepage": "https://github.com/angular/bower-angular-mocks", - "_release": "1.5.1-build.4601+sha.c966876", + "_release": "1.5.3", "_resolution": { "type": "version", - "tag": "v1.5.1-build.4601+sha.c966876", - "commit": "ff7c5c2ac686293829786d26d844391e45c37c11" + "tag": "v1.5.3", + "commit": "319557fe710cecc11e12c772cc1abb8098d29ccb" }, "_source": "git://github.com/angular/bower-angular-mocks.git", - "_target": "~1.5.1", + "_target": "~1.5.3", "_originalSource": "angular-mocks" } \ No newline at end of file diff --git a/public/vendor/angular-mocks/angular-mocks.js b/public/vendor/angular-mocks/angular-mocks.js index ff28e1a7892..a1fc2fcf7ae 100644 --- a/public/vendor/angular-mocks/angular-mocks.js +++ b/public/vendor/angular-mocks/angular-mocks.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.5.1-build.4601+sha.c966876 + * @license AngularJS v1.5.3 * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ @@ -134,12 +134,12 @@ angular.mock.$Browser = function() { }; angular.mock.$Browser.prototype = { -/** - * @name $browser#poll - * - * @description - * run all fns in pollFns - */ + /** + * @name $browser#poll + * + * @description + * run all fns in pollFns + */ poll: function poll() { angular.forEach(this.pollFns, function(pollFn) { pollFn(); @@ -552,7 +552,7 @@ angular.mock.$IntervalProvider = function() { * This directive should go inside the anonymous function but a bug in JSHint means that it would * not be enacted early enough to prevent the warning. */ -var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; +var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; function jsonStringToDate(string) { var match; @@ -578,7 +578,7 @@ function toInt(str) { return parseInt(str, 10); } -function padNumber(num, digits, trim) { +function padNumberInMock(num, digits, trim) { var neg = ''; if (num < 0) { neg = '-'; @@ -727,13 +727,13 @@ angular.mock.TzDate = function(offset, timestamp) { // provide this method only on browsers that already have it if (self.toISOString) { self.toISOString = function() { - return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + - padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + - padNumber(self.origDate.getUTCDate(), 2) + 'T' + - padNumber(self.origDate.getUTCHours(), 2) + ':' + - padNumber(self.origDate.getUTCMinutes(), 2) + ':' + - padNumber(self.origDate.getUTCSeconds(), 2) + '.' + - padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; + return padNumberInMock(self.origDate.getUTCFullYear(), 4) + '-' + + padNumberInMock(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumberInMock(self.origDate.getUTCDate(), 2) + 'T' + + padNumberInMock(self.origDate.getUTCHours(), 2) + ':' + + padNumberInMock(self.origDate.getUTCMinutes(), 2) + ':' + + padNumberInMock(self.origDate.getUTCSeconds(), 2) + '.' + + padNumberInMock(self.origDate.getUTCMilliseconds(), 3) + 'Z'; }; } @@ -1328,7 +1328,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { } // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) { + var xhr = new MockXhr(), expectation = expectations[0], wasExpected = false; @@ -1392,7 +1393,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { // if $browser specified, we do auto flush all requests ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); } else if (definition.passThrough) { - $delegate(method, url, data, callback, headers, timeout, withCredentials); + $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType); } else throw new Error('No response defined !'); return; } @@ -2095,10 +2096,12 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) { /** * */ +var originalRootElement; angular.mock.$RootElementProvider = function() { - this.$get = function() { - return angular.element('
'); - }; + this.$get = ['$injector', function($injector) { + originalRootElement = angular.element('
').data('$injector', $injector); + return originalRootElement; + }]; }; /** @@ -2127,7 +2130,7 @@ angular.mock.$RootElementProvider = function() { * * myMod.controller('MyDirectiveController', ['$log', function($log) { * $log.info(this.name); - * })]; + * }]); * * * // In a test ... @@ -2137,7 +2140,7 @@ angular.mock.$RootElementProvider = function() { * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); * expect(ctrl.name).toEqual('Clark Kent'); * expect($log.info.logs).toEqual(['Clark Kent']); - * }); + * })); * }); * * ``` @@ -2565,11 +2568,16 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { }]; -if (window.jasmine || window.mocha) { +!(function(jasmineOrMocha) { + + if (!jasmineOrMocha) { + return; + } var currentSpec = null, + injectorState = new InjectorState(), annotatedFunctions = [], - isSpecRunning = function() { + wasInjectorCreated = function() { return !!currentSpec; }; @@ -2581,48 +2589,6 @@ if (window.jasmine || window.mocha) { return angular.mock.$$annotate.apply(this, arguments); }; - - (window.beforeEach || window.setup)(function() { - annotatedFunctions = []; - currentSpec = this; - }); - - (window.afterEach || window.teardown)(function() { - var injector = currentSpec.$injector; - - annotatedFunctions.forEach(function(fn) { - delete fn.$inject; - }); - - angular.forEach(currentSpec.$modules, function(module) { - if (module && module.$$hashKey) { - module.$$hashKey = undefined; - } - }); - - currentSpec.$injector = null; - currentSpec.$modules = null; - currentSpec.$providerInjector = null; - currentSpec = null; - - if (injector) { - injector.get('$rootElement').off(); - injector.get('$rootScope').$destroy(); - } - - // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function(val, key) { - delete angular.element.fragments[key]; - }); - - MockXhr.$$lastInstance = null; - - angular.forEach(angular.callbacks, function(val, key) { - delete angular.callbacks[key]; - }); - angular.callbacks.counter = 0; - }); - /** * @ngdoc function * @name angular.mock.module @@ -2643,9 +2609,9 @@ if (window.jasmine || window.mocha) { * {@link auto.$provide $provide}.value, the key being the string name (or token) to associate * with the value on the injector. */ - window.module = angular.mock.module = function() { + var module = window.module = angular.mock.module = function() { var moduleFns = Array.prototype.slice.call(arguments, 0); - return isSpecRunning() ? workFn() : workFn; + return wasInjectorCreated() ? workFn() : workFn; ///////////////////// function workFn() { if (currentSpec.$injector) { @@ -2654,11 +2620,11 @@ if (window.jasmine || window.mocha) { var fn, modules = currentSpec.$modules || (currentSpec.$modules = []); angular.forEach(moduleFns, function(module) { if (angular.isObject(module) && !angular.isArray(module)) { - fn = function($provide) { + fn = ['$provide', function($provide) { angular.forEach(module, function(value, key) { $provide.value(key, value); }); - }; + }]; } else { fn = module; } @@ -2672,6 +2638,165 @@ if (window.jasmine || window.mocha) { } }; + module.$$beforeAllHook = (window.before || window.beforeAll); + module.$$afterAllHook = (window.after || window.afterAll); + + // purely for testing ngMock itself + module.$$currentSpec = function(to) { + if (arguments.length === 0) return to; + currentSpec = to; + }; + + /** + * @ngdoc function + * @name angular.mock.module.sharedInjector + * @description + * + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function ensures a single injector will be used for all tests in a given describe context. + * This contrasts with the default behaviour where a new injector is created per test case. + * + * Use sharedInjector when you want to take advantage of Jasmine's `beforeAll()`, or mocha's + * `before()` methods. Call `module.sharedInjector()` before you setup any other hooks that + * will create (i.e call `module()`) or use (i.e call `inject()`) the injector. + * + * You cannot call `sharedInjector()` from within a context already using `sharedInjector()`. + * + * ## Example + * + * Typically beforeAll is used to make many assertions about a single operation. This can + * cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed + * tests each with a single assertion. + * + * ```js + * describe("Deep Thought", function() { + * + * module.sharedInjector(); + * + * beforeAll(module("UltimateQuestion")); + * + * beforeAll(inject(function(DeepThought) { + * expect(DeepThought.answer).toBeUndefined(); + * DeepThought.generateAnswer(); + * })); + * + * it("has calculated the answer correctly", inject(function(DeepThought) { + * // Because of sharedInjector, we have access to the instance of the DeepThought service + * // that was provided to the beforeAll() hook. Therefore we can test the generated answer + * expect(DeepThought.answer).toBe(42); + * })); + * + * it("has calculated the answer within the expected time", inject(function(DeepThought) { + * expect(DeepThought.runTimeMillennia).toBeLessThan(8000); + * })); + * + * it("has double checked the answer", inject(function(DeepThought) { + * expect(DeepThought.absolutelySureItIsTheRightAnswer).toBe(true); + * })); + * + * }); + * + * ``` + */ + module.sharedInjector = function() { + if (!(module.$$beforeAllHook && module.$$afterAllHook)) { + throw Error("sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll"); + } + + var initialized = false; + + module.$$beforeAllHook(function() { + if (injectorState.shared) { + injectorState.sharedError = Error("sharedInjector() cannot be called inside a context that has already called sharedInjector()"); + throw injectorState.sharedError; + } + initialized = true; + currentSpec = this; + injectorState.shared = true; + }); + + module.$$afterAllHook(function() { + if (initialized) { + injectorState = new InjectorState(); + module.$$cleanup(); + } else { + injectorState.sharedError = null; + } + }); + }; + + module.$$beforeEach = function() { + if (injectorState.shared && currentSpec && currentSpec != this) { + var state = currentSpec; + currentSpec = this; + angular.forEach(["$injector","$modules","$providerInjector", "$injectorStrict"], function(k) { + currentSpec[k] = state[k]; + state[k] = null; + }); + } else { + currentSpec = this; + originalRootElement = null; + annotatedFunctions = []; + } + }; + + module.$$afterEach = function() { + if (injectorState.cleanupAfterEach()) { + module.$$cleanup(); + } + }; + + module.$$cleanup = function() { + var injector = currentSpec.$injector; + + annotatedFunctions.forEach(function(fn) { + delete fn.$inject; + }); + + angular.forEach(currentSpec.$modules, function(module) { + if (module && module.$$hashKey) { + module.$$hashKey = undefined; + } + }); + + currentSpec.$injector = null; + currentSpec.$modules = null; + currentSpec.$providerInjector = null; + currentSpec = null; + + if (injector) { + // Ensure `$rootElement` is instantiated, before checking `originalRootElement` + var $rootElement = injector.get('$rootElement'); + var rootNode = $rootElement && $rootElement[0]; + var cleanUpNodes = !originalRootElement ? [] : [originalRootElement[0]]; + if (rootNode && (!originalRootElement || rootNode !== originalRootElement[0])) { + cleanUpNodes.push(rootNode); + } + angular.element.cleanData(cleanUpNodes); + + // Ensure `$destroy()` is available, before calling it + // (a mocked `$rootScope` might not implement it (or not even be an object at all)) + var $rootScope = injector.get('$rootScope'); + if ($rootScope && $rootScope.$destroy) $rootScope.$destroy(); + } + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }; + + (window.beforeEach || window.setup)(module.$$beforeEach); + (window.afterEach || window.teardown)(module.$$afterEach); + /** * @ngdoc function * @name angular.mock.inject @@ -2774,14 +2899,14 @@ if (window.jasmine || window.mocha) { window.inject = angular.mock.inject = function() { var blockFns = Array.prototype.slice.call(arguments, 0); var errorForStack = new Error('Declaration Location'); - return isSpecRunning() ? workFn.call(currentSpec) : workFn; + return wasInjectorCreated() ? workFn.call(currentSpec) : workFn; ///////////////////// function workFn() { var modules = currentSpec.$modules || []; var strictDi = !!currentSpec.$injectorStrict; - modules.unshift(function($injector) { + modules.unshift(['$injector', function($injector) { currentSpec.$providerInjector = $injector; - }); + }]); modules.unshift('ngMock'); modules.unshift('ng'); var injector = currentSpec.$injector; @@ -2822,7 +2947,7 @@ if (window.jasmine || window.mocha) { angular.mock.inject.strictDi = function(value) { value = arguments.length ? !!value : true; - return isSpecRunning() ? workFn() : workFn; + return wasInjectorCreated() ? workFn() : workFn; function workFn() { if (value !== currentSpec.$injectorStrict) { @@ -2834,7 +2959,16 @@ if (window.jasmine || window.mocha) { } } }; -} + + function InjectorState() { + this.shared = false; + this.sharedError = null; + + this.cleanupAfterEach = function() { + return !this.shared || this.sharedError; + }; + } +})(window.jasmine || window.mocha); })(window, window.angular); diff --git a/public/vendor/angular-mocks/bower.json b/public/vendor/angular-mocks/bower.json index ac8b75413db..4ce65bbc809 100644 --- a/public/vendor/angular-mocks/bower.json +++ b/public/vendor/angular-mocks/bower.json @@ -1,10 +1,10 @@ { "name": "angular-mocks", - "version": "1.5.1-build.4601+sha.c966876", + "version": "1.5.3", "license": "MIT", "main": "./angular-mocks.js", "ignore": [], "dependencies": { - "angular": "1.5.1-build.4601+sha.c966876" + "angular": "1.5.3" } } diff --git a/public/vendor/angular-mocks/package.json b/public/vendor/angular-mocks/package.json index eda7287688f..42d78038650 100644 --- a/public/vendor/angular-mocks/package.json +++ b/public/vendor/angular-mocks/package.json @@ -1,6 +1,6 @@ { "name": "angular-mocks", - "version": "1.5.1-build.4601+sha.c966876", + "version": "1.5.3", "description": "AngularJS mocks for testing", "main": "angular-mocks.js", "scripts": { diff --git a/public/vendor/angular-route/.bower.json b/public/vendor/angular-route/.bower.json index 43ecb04633b..0abe0215778 100644 --- a/public/vendor/angular-route/.bower.json +++ b/public/vendor/angular-route/.bower.json @@ -1,20 +1,20 @@ { "name": "angular-route", - "version": "1.5.1-build.4601+sha.c966876", + "version": "1.5.3", "license": "MIT", "main": "./angular-route.js", "ignore": [], "dependencies": { - "angular": "1.5.1-build.4601+sha.c966876" + "angular": "1.5.3" }, "homepage": "https://github.com/angular/bower-angular-route", - "_release": "1.5.1-build.4601+sha.c966876", + "_release": "1.5.3", "_resolution": { "type": "version", - "tag": "v1.5.1-build.4601+sha.c966876", - "commit": "967fdabf084ac9f37c6b984d8893ebfebde5fc02" + "tag": "v1.5.3", + "commit": "750e4833612071d30993c8e4a547a6982eba3b84" }, "_source": "git://github.com/angular/bower-angular-route.git", - "_target": "~1.5.1", + "_target": "~1.5.3", "_originalSource": "angular-route" } \ No newline at end of file diff --git a/public/vendor/angular-route/angular-route.js b/public/vendor/angular-route/angular-route.js index cde0787fecf..3c9a4e8595e 100644 --- a/public/vendor/angular-route/angular-route.js +++ b/public/vendor/angular-route/angular-route.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.5.1-build.4601+sha.c966876 + * @license AngularJS v1.5.3 * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ @@ -22,7 +22,11 @@ */ /* global -ngRouteModule */ var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), + provider('$route', $RouteProvider). + // Ensure `$route` will be instantiated in time to capture the initial + // `$locationChangeSuccess` event. This is necessary in case `ngView` is + // included in an asynchronously loaded template. + run(['$route', angular.noop]), $routeMinErr = angular.$$minErr('ngRoute'); /** @@ -218,9 +222,9 @@ function $RouteProvider() { path = path .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; + .replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g, function(_, slash, key, option) { + var optional = (option === '?' || option === '*?') ? '?' : null; + var star = (option === '*' || option === '*?') ? '*' : null; keys.push({ name: key, optional: !!optional }); slash = slash || ''; return '' diff --git a/public/vendor/angular-route/angular-route.min.js b/public/vendor/angular-route/angular-route.min.js index d4888631eca..5d2e84f9498 100644 --- a/public/vendor/angular-route/angular-route.min.js +++ b/public/vendor/angular-route/angular-route.min.js @@ -1,15 +1,15 @@ /* - AngularJS v1.5.1-build.4601+sha.c966876 + AngularJS v1.5.3 (c) 2010-2016 Google, Inc. http://angularjs.org License: MIT */ (function(r,d,C){'use strict';function x(s,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,y){function k(){n&&(g.cancel(n),n=null);l&&(l.$destroy(),l=null);m&&(n=g.leave(m),n.then(function(){n=null}),m=null)}function z(){var b=s.current&&s.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),f=s.current;m=y(b,function(b){g.enter(b,null,m||c).then(function(){!d.isDefined(u)||u&&!a.$eval(u)||h()});k()});l=f.scope=b;l.$emit("$viewContentLoaded"); l.$eval(v)}else k()}var l,m,n,u=b.autoscroll,v=b.onload||"";a.$on("$routeChangeSuccess",z);z()}}}function A(d,h,g){return{restrict:"ECA",priority:-400,link:function(a,c){var b=g.current,f=b.locals;c.html(f.$template);var y=d(c.contents());if(b.controller){f.$scope=a;var k=h(b.controller,f);b.controllerAs&&(a[b.controllerAs]=k);c.data("$ngControllerController",k);c.children().data("$ngControllerController",k)}a[b.resolveAs||"$resolve"]=f;y(a)}}}r=d.module("ngRoute",["ng"]).provider("$route",function(){function s(a, -c){return d.extend(Object.create(a),c)}function h(a,d){var b=d.caseInsensitiveMatch,f={originalPath:a,regexp:a},g=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;g.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=new RegExp("^"+a+"$",b?"i":"");return f}var g={};this.when=function(a,c){var b=d.copy(c);d.isUndefined(b.reloadOnSearch)&& -(b.reloadOnSearch=!0);d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=d.extend(b,a&&h(a,b));if(a){var f="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[f]=d.extend({redirectTo:a},h(f,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,c,b,f,h,k,r){function l(b){var e= -t.current;(x=(p=n())&&e&&p.$$route===e.$$route&&d.equals(p.pathParams,e.pathParams)&&!p.reloadOnSearch&&!v)||!e&&!p||a.$broadcast("$routeChangeStart",p,e).defaultPrevented&&b&&b.preventDefault()}function m(){var w=t.current,e=p;if(x)w.params=e.params,d.copy(w.params,b),a.$broadcast("$routeUpdate",w);else if(e||w)v=!1,(t.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?c.path(u(e.redirectTo,e.params)).search(e.params).replace():c.url(e.redirectTo(e.pathParams,c.path(),c.search())).replace()),f.when(e).then(function(){if(e){var a= -d.extend({},e.resolve),b,c;d.forEach(a,function(b,e){a[e]=d.isString(b)?h.get(b):h.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(c=e.templateUrl)&&(d.isFunction(c)&&(c=c(e.params)),d.isDefined(c)&&(e.loadedTemplateUrl=r.valueOf(c),b=k(c)));d.isDefined(b)&&(a.$template=b);return f.all(a)}}).then(function(c){e==t.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,w))},function(b){e==t.current&&a.$broadcast("$routeChangeError", -e,w,b)})}function n(){var a,b;d.forEach(g,function(f,g){var q;if(q=!b){var h=c.path();q=f.keys;var l={};if(f.regexp)if(h=f.regexp.exec(h)){for(var k=1,n=h.length;k + * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. + * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). + * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest + * function will be reflected on the scope and in any templates where the object is data-bound. + * To prevent his, transform functions should have no side-effects. + * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. + *
+ * * ### Default Transformations * * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and @@ -10614,26 +10778,35 @@ function $HttpProvider() { * * ## Caching * - * To enable caching, set the request configuration `cache` property to `true` (to use default - * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). - * When the cache is enabled, `$http` stores the response from the server in the specified - * cache. The next time the same request is made, the response is served from the cache without - * sending a request to the server. + * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must + * set the config.cache value or the default cache value to TRUE or to a cache object (created + * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes + * precedence over the default cache value. * - * Note that even if the response is served from cache, delivery of the data is asynchronous in - * the same way that real requests are. + * In order to: + * * cache all responses - set the default cache value to TRUE or to a cache object + * * cache a specific response - set config.cache value to TRUE or to a cache object * - * If there are multiple GET requests for the same URL that should be cached using the same - * cache, but the cache is not populated yet, only one request to the server will be made and - * the remaining requests will be fulfilled using the response from the first request. + * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, + * then the default `$cacheFactory($http)` object is used. * - * You can change the default cache to a new object (built with - * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set - * their `cache` property to `true` will now use this cache object. + * The default cache value can be set by updating the + * {@link ng.$http#defaults `$http.defaults.cache`} property or the + * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. + * + * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using + * the relevant cache object. The next time the same request is made, the response is returned + * from the cache without sending a request to the server. + * + * Take note that: + * + * * Only GET and JSONP requests are cached. + * * The cache key is the request URL including search parameters; headers are not considered. + * * Cached responses are returned asynchronously, in the same way as responses from the server. + * * If multiple identical requests are made using the same cache, which is not yet populated, + * one request will be made to the server and remaining requests will return the same response. + * * A cache-control header on the response does not affect if or how responses are cached. * - * If you set the default cache to `false` then only requests that specify their own custom - * cache object will be cached. * * ## Interceptors * @@ -10803,7 +10976,7 @@ function $HttpProvider() { * transform function or an array of such functions. The transform function takes the http * response body, headers and status and returns its transformed (typically deserialized) version. * See {@link ng.$http#overriding-the-default-transformations-per-request - * Overriding the Default TransformationjqLiks} + * Overriding the Default Transformations} * - **paramSerializer** - `{string|function(Object):string}` - A function used to * prepare the string representation of request parameters (specified as an object). * If specified as string, it is interpreted as function registered with the @@ -10811,10 +10984,9 @@ function $HttpProvider() { * by registering it as a {@link auto.$provide#service service}. * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} - * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. + * - **cache** – `{boolean|Object}` – A boolean value or object created with + * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. + * See {@link $http#caching $http Caching} for more information. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the @@ -13725,8 +13897,10 @@ AST.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); - } else if (this.constants.hasOwnProperty(this.peek().text)) { - primary = copy(this.constants[this.consume().text]); + } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { + primary = copy(this.selfReferential[this.consume().text]); + } else if (this.options.literals.hasOwnProperty(this.peek().text)) { + primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; } else if (this.peek().identifier) { primary = this.identifier(); } else if (this.peek().constant) { @@ -13878,15 +14052,7 @@ AST.prototype = { return false; }, - - /* `undefined` is not a constant, it is an identifier, - * but using it as an identifier is not supported - */ - constants: { - 'true': { type: AST.Literal, value: true }, - 'false': { type: AST.Literal, value: false }, - 'null': { type: AST.Literal, value: null }, - 'undefined': {type: AST.Literal, value: undefined }, + selfReferential: { 'this': {type: AST.ThisExpression }, '$locals': {type: AST.LocalsExpression } } @@ -14576,7 +14742,7 @@ ASTInterpreter.prototype = { forEach(ast.body, function(expression) { expressions.push(self.recurse(expression.expression)); }); - var fn = ast.body.length === 0 ? function() {} : + var fn = ast.body.length === 0 ? noop : ast.body.length === 1 ? expressions[0] : function(scope, locals) { var lastValue; @@ -14717,7 +14883,7 @@ ASTInterpreter.prototype = { return context ? {value: locals} : locals; }; case AST.NGValueParameter: - return function(scope, locals, assign, inputs) { + return function(scope, locals, assign) { return context ? {value: assign} : assign; }; } @@ -14931,7 +15097,7 @@ var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; - this.ast = new AST(this.lexer); + this.ast = new AST(lexer, options); this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : new ASTCompiler(this.ast, $filter); }; @@ -15008,16 +15174,39 @@ function getValueOf(value) { function $ParseProvider() { var cacheDefault = createMap(); var cacheExpensive = createMap(); + var literals = { + 'true': true, + 'false': false, + 'null': null, + 'undefined': undefined + }; + + /** + * @ngdoc method + * @name $parseProvider#addLiteral + * @description + * + * Configure $parse service to add literal values that will be present as literal at expressions. + * + * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. + * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. + * + **/ + this.addLiteral = function(literalName, literalValue) { + literals[literalName] = literalValue; + }; this.$get = ['$filter', function($filter) { var noUnsafeEval = csp().noUnsafeEval; var $parseOptions = { csp: noUnsafeEval, - expensiveChecks: false + expensiveChecks: false, + literals: copy(literals) }, $parseOptionsExpensive = { csp: noUnsafeEval, - expensiveChecks: true + expensiveChecks: true, + literals: copy(literals) }; var runningChecksEnabled = false; @@ -15266,15 +15455,15 @@ function $ParseProvider() { * [Kris Kowal's Q](https://github.com/kriskowal/q). * * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred - * implementations, and the other which resembles ES6 promises to some degree. + * implementations, and the other which resembles ES6 (ES2015) promises to some degree. * * # $q constructor * * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` - * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, + * function as the first argument. This is similar to the native Promise implementation from ES6, * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). * - * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are + * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are * available yet. * * It can be used like so: @@ -16627,7 +16816,7 @@ function $RootScopeProvider() { dirty, ttl = TTL, next, current, target = this, watchLog = [], - logIdx, logMsg, asyncTask; + logIdx, asyncTask; beginPhase('$digest'); // Check for changes to browser url that happened in sync before the call to $digest @@ -18412,6 +18601,10 @@ function $SceProvider() { function $SnifferProvider() { this.$get = ['$window', '$document', function($window, $document) { var eventSupport = {}, + // Chrome Packaged Apps are not allowed to access `history.pushState`. They can be detected by + // the presence of `chrome.app.runtime` (see https://developer.chrome.com/apps/api_index) + isChromePackagedApp = $window.chrome && $window.chrome.app && $window.chrome.app.runtime, + hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, android = toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), boxee = /Boxee/i.test(($window.navigator || {}).userAgent), @@ -18456,7 +18649,7 @@ function $SnifferProvider() { // so let's not use the history API also // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined // jshint -W018 - history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), + history: !!(hasHistoryPushState && !(android < 4) && !boxee), // jshint +W018 hasEvent: function(event) { // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have @@ -18482,7 +18675,7 @@ function $SnifferProvider() { }]; } -var $compileMinErr = minErr('$compile'); +var $templateRequestMinErr = minErr('$compile'); /** * @ngdoc provider @@ -18578,7 +18771,7 @@ function $TemplateRequestProvider() { function handleError(resp) { if (!ignoreRequestError) { - throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', + throw $templateRequestMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', tpl, resp.status, resp.statusText); } return $q.reject(resp); @@ -19500,7 +19693,7 @@ function currencyFilter($locale) { * Formats a number as text. * * If the input is null or undefined, it will just be returned. - * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned. + * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively. * If the input is not a number an empty string is returned. * * @@ -19636,18 +19829,37 @@ function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { var digit = digits[roundAt]; if (roundAt > 0) { - digits.splice(roundAt); + // Drop fractional digits beyond `roundAt` + digits.splice(Math.max(parsedNumber.i, roundAt)); + + // Set non-fractional digits beyond `roundAt` to 0 + for (var j = roundAt; j < digits.length; j++) { + digits[j] = 0; + } } else { // We rounded to zero so reset the parsedNumber + fractionLen = Math.max(0, fractionLen); parsedNumber.i = 1; - digits.length = roundAt = fractionSize + 1; - for (var i=0; i < roundAt; i++) digits[i] = 0; + digits.length = Math.max(1, roundAt = fractionSize + 1); + digits[0] = 0; + for (var i = 1; i < roundAt; i++) digits[i] = 0; } - if (digit >= 5) digits[roundAt - 1]++; + if (digit >= 5) { + if (roundAt - 1 < 0) { + for (var k = 0; k > roundAt; k--) { + digits.unshift(0); + parsedNumber.i++; + } + digits.unshift(1); + parsedNumber.i++; + } else { + digits[roundAt - 1]++; + } + } // Pad out with zeros to get the required fraction length - for (; fractionLen < fractionSize; fractionLen++) digits.push(0); + for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); // Do any carrying, e.g. a digit was rounded up to 10 @@ -19719,7 +19931,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { // format the integer digits with grouping separators var groups = []; - if (digits.length > pattern.lgSize) { + if (digits.length >= pattern.lgSize) { groups.unshift(digits.splice(-pattern.lgSize).join('')); } while (digits.length > pattern.gSize) { @@ -19746,11 +19958,15 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { } } -function padNumber(num, digits, trim) { +function padNumber(num, digits, trim, negWrap) { var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; + if (num < 0 || (negWrap && num <= 0)) { + if (negWrap) { + num = -num + 1; + } else { + num = -num; + neg = '-'; + } } num = '' + num; while (num.length < digits) num = ZERO_CHAR + num; @@ -19761,7 +19977,7 @@ function padNumber(num, digits, trim) { } -function dateGetter(name, size, offset, trim) { +function dateGetter(name, size, offset, trim, negWrap) { offset = offset || 0; return function(date) { var value = date['get' + name](); @@ -19769,14 +19985,15 @@ function dateGetter(name, size, offset, trim) { value += offset; } if (value === 0 && offset == -12) value = 12; - return padNumber(value, size, trim); + return padNumber(value, size, trim, negWrap); }; } -function dateStrGetter(name, shortForm) { +function dateStrGetter(name, shortForm, standAlone) { return function(date, formats) { var value = date['get' + name](); - var get = uppercase(shortForm ? ('SHORT' + name) : name); + var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); + var get = uppercase(propPrefix + name); return formats[get][value]; }; @@ -19831,13 +20048,14 @@ function longEraGetter(date, formats) { } var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - y: dateGetter('FullYear', 1), + yyyy: dateGetter('FullYear', 4, 0, false, true), + yy: dateGetter('FullYear', 2, 0, true, true), + y: dateGetter('FullYear', 1, 0, false, true), MMMM: dateStrGetter('Month'), MMM: dateStrGetter('Month', true), MM: dateGetter('Month', 2, 1), M: dateGetter('Month', 1, 1), + LLLL: dateStrGetter('Month', false, true), dd: dateGetter('Date', 2), d: dateGetter('Date', 1), HH: dateGetter('Hours', 2), @@ -19863,7 +20081,7 @@ var DATE_FORMATS = { GGGG: longEraGetter }; -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, NUMBER_STRING = /^\-?\d+$/; /** @@ -19883,6 +20101,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+| * * `'MMM'`: Month in year (Jan-Dec) * * `'MM'`: Month in year, padded (01-12) * * `'M'`: Month in year (1-12) + * * `'LLLL'`: Stand-alone month in year (January-December) * * `'dd'`: Day in month, padded (01-31) * * `'d'`: Day in month (1-31) * * `'EEEE'`: Day in Week,(Sunday-Saturday) @@ -21564,8 +21783,8 @@ var ngFormDirective = formDirectiveFactory(true); ngModelMinErr: false, */ -// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 -var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; +// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 +var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/; // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) // Note: We are being more lenient, because browsers are too. // 1. Scheme @@ -21581,12 +21800,18 @@ var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0- var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; -var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; -var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; -var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; -var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; +var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; +var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; +var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; +var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; +var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; +var PARTIAL_VALIDATION_TYPES = createMap(); +forEach('date,datetime-local,month,time,week'.split(','), function(type) { + PARTIAL_VALIDATION_TYPES[type] = true; +}); + var inputType = { /** @@ -21942,7 +22167,7 @@ var inputType = { }]);
- +
@@ -22663,7 +22888,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { if (!$sniffer.android) { var composing = false; - element.on('compositionstart', function(data) { + element.on('compositionstart', function() { composing = true; }); @@ -22673,6 +22898,8 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { }); } + var timeout; + var listener = function(ev) { if (timeout) { $browser.defer.cancel(timeout); @@ -22702,8 +22929,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { if ($sniffer.hasEvent('input')) { element.on('input', listener); } else { - var timeout; - var deferListener = function(ev, input, origValue) { if (!timeout) { timeout = $browser.defer(function() { @@ -22735,6 +22960,26 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { // or form autocomplete on newer browser, we need "change" event to catch it element.on('change', listener); + // Some native input types (date-family) have the ability to change validity without + // firing any input/change events. + // For these event types, when native validators are present and the browser supports the type, + // check for validity changes on various DOM events. + if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { + element.on(PARTIAL_VALIDATION_EVENTS, function(ev) { + if (!timeout) { + var validity = this[VALIDITY_STATE_PROPERTY]; + var origBadInput = validity.badInput; + var origTypeMismatch = validity.typeMismatch; + timeout = $browser.defer(function() { + timeout = null; + if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { + listener(ev); + } + }); + } + }); + } + ctrl.$render = function() { // Workaround for Firefox validation #12102. var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; @@ -25061,7 +25306,7 @@ forEach( */ -var ngIfDirective = ['$animate', function($animate) { +var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { return { multiElement: true, transclude: 'element', @@ -25077,7 +25322,7 @@ var ngIfDirective = ['$animate', function($animate) { if (!childScope) { $transclude(function(clone, newScope) { childScope = newScope; - clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); + clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); // Note: We only need the first/last node of the cloned nodes. // However, we need to keep the reference to the jqlite wrapper as it might be changed later // by a directive with templateUrl when its template arrives. @@ -25876,9 +26121,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ }; ngModelSet = function($scope, newValue) { if (isFunction(parsedNgModel($scope))) { - invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); + invokeModelSetter($scope, {$$$p: newValue}); } else { - parsedNgModelAssign($scope, ctrl.$modelValue); + parsedNgModelAssign($scope, newValue); } }; } else if (!parsedNgModel.assign) { @@ -25903,7 +26148,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * the `$viewValue` are different from last time. * * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of - * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` + * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be * invoked if you only change a property on the objects. */ @@ -26255,7 +26500,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ setValidity(name, undefined); validatorPromises.push(promise.then(function() { setValidity(name, true); - }, function(error) { + }, function() { allValid = false; setValidity(name, false); })); @@ -26729,7 +26974,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) { }); } - element.on('blur', function(ev) { + element.on('blur', function() { if (modelCtrl.$touched) return; if ($rootScope.$$phase) { @@ -27412,8 +27657,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; var value = optionValues[key]; - var locals = getLocals(optionValues[key], key); - var selectValue = getTrackByValueFn(optionValues[key], locals); + var locals = getLocals(value, key); + var selectValue = getTrackByValueFn(value, locals); watchedArray.push(selectValue); // Only need to watch the displayFn if there is a specific label expression @@ -27538,14 +27783,20 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { var option = options.getOptionFromViewValue(value); if (option && !option.disabled) { + // Don't update the option when it is already selected. + // For example, the browser will select the first option by default. In that case, + // most properties are set automatically - except the `selected` attribute, which we + // set always + if (selectElement[0].value !== option.selectValue) { removeUnknownOption(); removeEmptyOption(); selectElement[0].value = option.selectValue; option.element.selected = true; - option.element.setAttribute('selected', 'selected'); } + + option.element.setAttribute('selected', 'selected'); } else { if (value === null || providedEmptyOption) { removeUnknownOption(); @@ -28390,7 +28641,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, */ -var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { +var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); @@ -28425,7 +28676,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { $$tlb: true, compile: function ngRepeatCompile($element, $attr) { var expression = $attr.ngRepeat; - var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); + var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); @@ -29142,7 +29393,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { */ -var ngSwitchDirective = ['$animate', function($animate) { +var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { return { require: 'ngSwitch', @@ -29183,7 +29434,7 @@ var ngSwitchDirective = ['$animate', function($animate) { selectedTransclude.transclude(function(caseElement, selectedScope) { selectedScopes.push(selectedScope); var anchor = selectedTransclude.element; - caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); + caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); var block = { clone: caseElement }; selectedElements.push(block); @@ -29477,7 +29728,7 @@ function chromeHack(optionElement) { * added `