diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb01152d58..e97ab145c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 3.0.0 (unrelased master branch) -### New Features ### +### New Features * **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/pull/3655) * **Metadata**: Settings panel now shows dashboard metadata, closes [#3304](https://github.com/grafana/grafana/issues/3304) * **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018) @@ -10,11 +10,14 @@ **InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds. Can easily be installed via improved plugin system, closes #3523 **KairosDB** The data source is no longer included in default builds. Can easily be installed via improved plugin system, closes #3524 -### Enhancements ### +### Enhancements * **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/pull/3458) * **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/pull/3584) * **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/pull/3635) +### Bug fixes +* **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) + # 2.6.1 (unrelased, 2.6.x branch) ### New Features diff --git a/docs/sources/datasources/prometheus.md b/docs/sources/datasources/prometheus.md index 61f6c0d66e8..a7959a41c51 100644 --- a/docs/sources/datasources/prometheus.md +++ b/docs/sources/datasources/prometheus.md @@ -51,6 +51,8 @@ Name | Description For details of `metric names` & `label names`, and `label values`, please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels). +> Note: The part of queries is incompatible with the version before 2.6, if you specify like `foo.*`, please change like `metrics(foo.*)`. + You can create a template variable in Grafana and have that variable filled with values from any Prometheus metric exploration query. You can then use this variable in your Prometheus metric queries. diff --git a/pkg/api/api_plugin.go b/pkg/api/api_plugin.go index b14afec3a62..751c234bfe8 100644 --- a/pkg/api/api_plugin.go +++ b/pkg/api/api_plugin.go @@ -1,13 +1,17 @@ package api import ( + "bytes" "encoding/json" + "fmt" "net/http" "net/http/httputil" "net/url" + "text/template" "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" @@ -34,32 +38,28 @@ func InitApiPluginRoutes(r *macaron.Macaron) { handlers = append(handlers, middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) } } - handlers = append(handlers, ApiPlugin(route.Url)) + handlers = append(handlers, ApiPlugin(route, plugin.IncludedInAppId)) r.Route(url, route.Method, handlers...) log.Info("Plugin: Adding route %s", url) } } } -func ApiPlugin(routeUrl string) macaron.Handler { +func ApiPlugin(route *plugins.ApiPluginRoute, includedInAppId string) macaron.Handler { return func(c *middleware.Context) { path := c.Params("*") - //Create a HTTP header with the context in it. - ctx, err := json.Marshal(c.SignedInUser) - if err != nil { - c.JsonApiErr(500, "failed to marshal context to json.", err) - return - } - targetUrl, _ := url.Parse(routeUrl) - proxy := NewApiPluginProxy(string(ctx), path, targetUrl) + proxy := NewApiPluginProxy(c, path, route, includedInAppId) proxy.Transport = dataProxyTransport proxy.ServeHTTP(c.Resp, c.Req.Request) } } -func NewApiPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { +func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.ApiPluginRoute, includedInAppId string) *httputil.ReverseProxy { + targetUrl, _ := url.Parse(route.Url) + director := func(req *http.Request) { + req.URL.Scheme = targetUrl.Scheme req.URL.Host = targetUrl.Host req.Host = targetUrl.Host @@ -69,7 +69,46 @@ func NewApiPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httput // clear cookie headers req.Header.Del("Cookie") req.Header.Del("Set-Cookie") - req.Header.Add("Grafana-Context", ctx) + + //Create a HTTP header with the context in it. + ctxJson, err := json.Marshal(ctx.SignedInUser) + if err != nil { + ctx.JsonApiErr(500, "failed to marshal context to json.", err) + return + } + + req.Header.Add("Grafana-Context", string(ctxJson)) + // add custom headers defined in the plugin config. + for _, header := range route.Headers { + var contentBuf bytes.Buffer + t, err := template.New("content").Parse(header.Content) + if err != nil { + ctx.JsonApiErr(500, fmt.Sprintf("could not parse header content template for header %s.", header.Name), err) + return + } + + jsonData := make(map[string]interface{}) + + if includedInAppId != "" { + //lookup appSettings + query := m.GetAppSettingByAppIdQuery{OrgId: ctx.OrgId, AppId: includedInAppId} + + if err := bus.Dispatch(&query); err != nil { + ctx.JsonApiErr(500, "failed to get AppSettings of includedAppId.", err) + return + } + + jsonData = query.Result.JsonData + } + + err = t.Execute(&contentBuf, jsonData) + if err != nil { + ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err) + return + } + log.Debug("Adding header to proxy request. %s: %s", header.Name, contentBuf.String()) + req.Header.Add(header.Name, contentBuf.String()) + } } return &httputil.ReverseProxy{Director: director} diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go index b2b33370135..d10883a26d7 100644 --- a/pkg/api/dtos/apps.go +++ b/pkg/api/dtos/apps.go @@ -31,6 +31,7 @@ func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSet dto.Enabled = data.Enabled dto.Pinned = data.Pinned dto.Info = &def.Info + dto.JsonData = data.JsonData } return dto diff --git a/pkg/models/app_settings.go b/pkg/models/app_settings.go index 558946d0277..f3b60502cb0 100644 --- a/pkg/models/app_settings.go +++ b/pkg/models/app_settings.go @@ -1,6 +1,13 @@ package models -import "time" +import ( + "errors" + "time" +) + +var ( + ErrAppSettingNotFound = errors.New("AppSetting not found") +) type AppSettings struct { Id int64 @@ -33,3 +40,9 @@ type GetAppSettingsQuery struct { OrgId int64 Result []*AppSettings } + +type GetAppSettingByAppIdQuery struct { + AppId string + OrgId int64 + Result *AppSettings +} diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go new file mode 100644 index 00000000000..efb16e696d8 --- /dev/null +++ b/pkg/plugins/api_plugin.go @@ -0,0 +1,38 @@ +package plugins + +import ( + "encoding/json" + + "github.com/grafana/grafana/pkg/models" +) + +type ApiPluginRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"reqSignedIn"` + ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` + ReqRole models.RoleType `json:"reqRole"` + Url string `json:"url"` + Headers []ApiPluginHeader `json:"headers"` +} + +type ApiPlugin struct { + PluginBase + Routes []*ApiPluginRoute `json:"routes"` +} + +type ApiPluginHeader struct { + Name string `json:"name"` + Content string `json:"content"` +} + +func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&app); err != nil { + return err + } + + app.PluginDir = pluginDir + + ApiPlugins[app.Id] = app + return nil +} diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go index 898065be8a2..d2291d487ac 100644 --- a/pkg/plugins/app_plugin.go +++ b/pkg/plugins/app_plugin.go @@ -59,6 +59,18 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { } } + // check if we have child apiPlugins + for _, plugin := range ApiPlugins { + if strings.HasPrefix(plugin.PluginDir, app.PluginDir) { + plugin.IncludedInAppId = app.Id + app.Includes = append(app.Includes, AppIncludeInfo{ + Name: plugin.Name, + Id: plugin.Id, + Type: plugin.Type, + }) + } + } + Apps[app.Id] = app return nil } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 375bdf1a52c..b302181e840 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -2,8 +2,6 @@ package plugins import ( "encoding/json" - - "github.com/grafana/grafana/pkg/models" ) type PluginLoader interface { @@ -44,20 +42,6 @@ type PluginStaticRoute struct { PluginId string } -type ApiPluginRoute struct { - Path string `json:"path"` - Method string `json:"method"` - ReqSignedIn bool `json:"reqSignedIn"` - ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` - ReqRole models.RoleType `json:"reqRole"` - Url string `json:"url"` -} - -type ApiPlugin struct { - PluginBase - Routes []*ApiPluginRoute `json:"routes"` -} - type EnabledPlugins struct { Panels []*PanelPlugin DataSources map[string]*DataSourcePlugin diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go index e9bfbeaa73b..7d9482e7b22 100644 --- a/pkg/services/sqlstore/app_settings.go +++ b/pkg/services/sqlstore/app_settings.go @@ -9,6 +9,7 @@ import ( func init() { bus.AddHandler("sql", GetAppSettings) + bus.AddHandler("sql", GetAppSettingByAppId) bus.AddHandler("sql", UpdateAppSettings) } @@ -19,6 +20,18 @@ func GetAppSettings(query *m.GetAppSettingsQuery) error { return sess.Find(&query.Result) } +func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error { + appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId} + has, err := x.Get(&appSetting) + if err != nil { + return err + } else if has == false { + return m.ErrAppSettingNotFound + } + query.Result = &appSetting + return nil +} + func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { return inTransaction2(func(sess *session) error { var app m.AppSettings diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts new file mode 100644 index 00000000000..ad203f58495 --- /dev/null +++ b/public/app/core/utils/file_export.ts @@ -0,0 +1,37 @@ +/// + +import _ from 'lodash'; + +declare var window: any; + +export function exportSeriesListToCsv(seriesList) { + var text = 'Series;Time;Value\n'; + _.each(seriesList, function(series) { + _.each(series.datapoints, function(dp) { + text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + }); + }); + saveSaveBlob(text, 'grafana_data_export.csv'); +}; + +export function exportTableDataToCsv(table) { + var text = ''; + // add header + _.each(table.columns, function(column) { + text += column.text + ';'; + }); + text += '\n'; + // process data + _.each(table.rows, function(row) { + _.each(row, function(value) { + text += value + ';'; + }); + text += '\n'; + }); + saveSaveBlob(text, 'grafana_data_export.csv'); +}; + +export function saveSaveBlob(payload, fname) { + var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, fname); +}; diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index e323a9223e3..959de5fa42a 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -179,17 +179,6 @@ function($, _) { .replace(/ +/g,'-'); }; - kbn.exportSeriesListToCsv = function(seriesList) { - var text = 'Series;Time;Value\n'; - _.each(seriesList, function(series) { - _.each(series.datapoints, function(dp) { - text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; - }); - }); - var blob = new Blob([text], { type: "text/csv;charset=utf-8" }); - window.saveAs(blob, 'grafana_data_export.csv'); - }; - kbn.stringToJsRegex = function(str) { if (str[0] !== '/') { return new RegExp('^' + str + '$'); diff --git a/public/app/features/apps/partials/edit.html b/public/app/features/apps/partials/edit.html index aae34b8b38d..20df40bd247 100644 --- a/public/app/features/apps/partials/edit.html +++ b/public/app/features/apps/partials/edit.html @@ -98,6 +98,8 @@
+
+
diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts index 7e304d88464..75f0778b01f 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -7,6 +7,7 @@ export class SubmenuCtrl { variables: any; dashboard: any; + /** @ngInject */ constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) { this.annotations = this.dashboard.templating.list; this.variables = this.dashboard.templating.list; diff --git a/public/app/features/dashlinks/module.js b/public/app/features/dashlinks/module.js index b7029e6cf2d..f133290ceb2 100644 --- a/public/app/features/dashlinks/module.js +++ b/public/app/features/dashlinks/module.js @@ -159,7 +159,7 @@ function (angular, _) { }; updateDashLinks(); - $rootScope.onAppEvent('dash-links-updated', updateDashLinks, $rootScope); + $rootScope.onAppEvent('dash-links-updated', updateDashLinks, $scope); }); module.controller('DashLinkEditorCtrl', function($scope, $rootScope) { diff --git a/public/app/features/playlist/partials/playlist-remove.html b/public/app/features/playlist/partials/playlist-remove.html deleted file mode 100644 index 8474f97f8de..00000000000 --- a/public/app/features/playlist/partials/playlist-remove.html +++ /dev/null @@ -1,5 +0,0 @@ -

Are you sure want to delete "{{playlist.title}}" playlist?

-

- - -

diff --git a/public/app/features/playlist/playlist_edit_ctrl.js b/public/app/features/playlist/playlist_edit_ctrl.js index 3817ec12634..9b493c47d25 100644 --- a/public/app/features/playlist/playlist_edit_ctrl.js +++ b/public/app/features/playlist/playlist_edit_ctrl.js @@ -132,11 +132,11 @@ function (angular, config, _) { }; $scope.movePlaylistItemUp = function(playlistItem) { - $scope.moveDashboard(playlistItem, -1); + $scope.movePlaylistItem(playlistItem, -1); }; $scope.movePlaylistItemDown = function(playlistItem) { - $scope.moveDashboard(playlistItem, 1); + $scope.movePlaylistItem(playlistItem, 1); }; $scope.init(); diff --git a/public/app/features/playlist/playlist_srv.ts b/public/app/features/playlist/playlist_srv.ts index 0570a3bfc85..8c6be5edaf5 100644 --- a/public/app/features/playlist/playlist_srv.ts +++ b/public/app/features/playlist/playlist_srv.ts @@ -1,6 +1,7 @@ /// import angular from 'angular'; +import config from 'app/core/config'; import coreModule from '../../core/core_module'; import kbn from 'app/core/utils/kbn'; @@ -20,10 +21,9 @@ class PlaylistSrv { var playedAllDashboards = this.index > this.dashboards.length - 1; if (playedAllDashboards) { - this.start(this.playlistId); + window.location.href = `${config.appSubUrl}/playlists/play/${this.playlistId}`; } else { var dash = this.dashboards[this.index]; - this.$location.url('dashboard/' + dash.uri); this.index++; diff --git a/public/app/grafana.ts b/public/app/grafana.ts index 7316a8164bc..d9ad3a449d3 100644 --- a/public/app/grafana.ts +++ b/public/app/grafana.ts @@ -1,6 +1,7 @@ /// import 'bootstrap'; +import 'vendor/filesaver'; import 'lodash-src'; import 'angular-strap'; import 'angular-route'; diff --git a/public/app/plugins/datasource/elasticsearch/directives.js b/public/app/plugins/datasource/elasticsearch/directives.js deleted file mode 100644 index 3bb27885ef2..00000000000 --- a/public/app/plugins/datasource/elasticsearch/directives.js +++ /dev/null @@ -1,56 +0,0 @@ -define([ - 'angular', - './bucket_agg', - './metric_agg', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorElasticsearch', function() { - return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'}; - }); - - module.directive('metricQueryOptionsElasticsearch', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'}; - }); - - module.directive('annotationsQueryEditorElasticsearch', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; - }); - - module.directive('elastic', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/config.html'}; - }); - - module.directive('elasticMetricAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', - controller: 'ElasticMetricAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - esVersion: '=' - } - }; - }); - - module.directive('elasticBucketAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html', - controller: 'ElasticBucketAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - } - }; - }); - -}); diff --git a/public/app/plugins/datasource/mixed/datasource.ts b/public/app/plugins/datasource/mixed/datasource.ts index bd0628fe210..b5cea9cc5a8 100644 --- a/public/app/plugins/datasource/mixed/datasource.ts +++ b/public/app/plugins/datasource/mixed/datasource.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; class MixedDatasource { + /** @ngInject */ constructor(private $q, private datasourceSrv) { } diff --git a/public/app/plugins/panel/graph/module.js b/public/app/plugins/panel/graph/module.js index 47d0e4f83a3..c1039e394a3 100644 --- a/public/app/plugins/panel/graph/module.js +++ b/public/app/plugins/panel/graph/module.js @@ -3,13 +3,14 @@ define([ 'lodash', 'moment', 'app/core/utils/kbn', + 'app/core/utils/file_export', 'app/core/time_series', 'app/features/panel/panel_meta', './seriesOverridesCtrl', './graph', './legend', ], -function (angular, _, moment, kbn, TimeSeries, PanelMeta) { +function (angular, _, moment, kbn, fileExport, TimeSeries, PanelMeta) { 'use strict'; /** @ngInject */ @@ -282,7 +283,7 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { }; $scope.exportCsv = function() { - kbn.exportSeriesListToCsv($scope.seriesList); + fileExport.exportSeriesListToCsv($scope.seriesList); }; panelSrv.init($scope); diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index f05a36c4cdf..376f36fd84d 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -3,6 +3,7 @@ import angular from 'angular'; import _ from 'lodash'; import moment from 'moment'; +import * as FileExport from 'app/core/utils/file_export'; import PanelMeta from 'app/features/panel/panel_meta2'; import {transformDataToTable} from './transformers'; @@ -22,6 +23,7 @@ export class TablePanelCtrl { $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); var panelDefaults = { targets: [{}], @@ -124,6 +126,10 @@ export class TablePanelCtrl { panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); }; + $scope.exportCsv = function() { + FileExport.exportTableDataToCsv($scope.table); + }; + $scope.init(); } } diff --git a/public/less/sidemenu.less b/public/less/sidemenu.less index 3bb1ae393a7..61882f0e4c3 100644 --- a/public/less/sidemenu.less +++ b/public/less/sidemenu.less @@ -13,6 +13,7 @@ min-height: 100%; z-index: 101; transform: translate3d(-100%, 0, 0); + visibility: hidden; a:focus { text-decoration: none; diff --git a/public/views/index.html b/public/views/index.html index 2d05f414658..728cd084c88 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -56,8 +56,10 @@ + + diff --git a/symlink_git_hooks.sh b/symlink_git_hooks.sh index 7c3b58feaca..9e272f82ed6 100755 --- a/symlink_git_hooks.sh +++ b/symlink_git_hooks.sh @@ -1,3 +1,5 @@ #/bin/bash -ln -s .hooks/* .git/hooks/ +#ln -s -f .hooks/* .git/hooks/ +cd .git/hooks/ +cp --symbolic-link -f ../../.hooks/* . diff --git a/tasks/build_task.js b/tasks/build_task.js index d10a83d78dc..d844b661722 100644 --- a/tasks/build_task.js +++ b/tasks/build_task.js @@ -10,7 +10,7 @@ module.exports = function(grunt) { 'clean:release', 'copy:public_to_gen', 'typescript:build', - // 'karma:test', + 'karma:test', 'phantomjs', 'css', 'htmlmin:build', diff --git a/tasks/options/concat.js b/tasks/options/concat.js index f62e1f09982..0c76ce98d23 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -28,8 +28,10 @@ module.exports = function(config) { js: { src: [ + '<%= genDir %>/vendor/npm/es5-shim/es5-shim.js', '<%= genDir %>/vendor/npm/es6-shim/es6-shim.js', - '<%= genDir %>/vendor/npm/es6-promise/es6-promise.js', + '<%= genDir %>/vendor/npm/es6-promise/dist/es6-promise.js', + '<%= genDir %>/vendor/npm/systemjs/dist/system-polyfills.js', '<%= genDir %>/vendor/npm/systemjs/dist/system.js', '<%= genDir %>/app/system.conf.js', '<%= genDir %>/app/boot.js', diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 057d464a74c..95885a17886 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -1,55 +1,63 @@ -var page = require('webpage').create(); -var args = require('system').args; -var params = {}; -var regexp = /^([^=]+)=([^$]+)/; +(function() { + 'use strict'; -args.forEach(function(arg) { - var parts = arg.match(regexp); - if (!parts) { return; } - params[parts[1]] = parts[2]; -}); + var page = require('webpage').create(); + var args = require('system').args; + var params = {}; + var regexp = /^([^=]+)=([^$]+)/; -var usage = "url= png= width= height= cookiename= sessionid= domain="; + args.forEach(function(arg) { + var parts = arg.match(regexp); + if (!parts) { return; } + params[parts[1]] = parts[2]; + }); -if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) { - console.log(usage); - phantom.exit(); -} + var usage = "url= png= width= height= cookiename= sessionid= domain="; -phantom.addCookie({ - 'name': params.cookiename, - 'value': params.sessionid, - 'domain': params.domain -}); - -page.viewportSize = { - width: params.width || '800', - height: params.height || '400' -}; - -var tries = 0; - -page.open(params.url, function (status) { - console.log('Loading a web page: ' + params.url); - - function checkIsReady() { - var canvas = page.evaluate(function() { - var body = angular.element(document.body); // 1 - var rootScope = body.scope().$root; - var panelsToLoad = angular.element('div.panel').length; - return rootScope.performance.panelsRendered >= panelsToLoad; - }); - - if (canvas || tries === 1000) { - page.render(params.png); - phantom.exit(); - } - else { - tries++; - setTimeout(checkIsReady, 10); - } + if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) { + console.log(usage); + phantom.exit(); } - setTimeout(checkIsReady, 200); + phantom.addCookie({ + 'name': params.cookiename, + 'value': params.sessionid, + 'domain': params.domain + }); -}); + page.viewportSize = { + width: params.width || '800', + height: params.height || '400' + }; + + var tries = 0; + + page.open(params.url, function (status) { + console.log('Loading a web page: ' + params.url + ' status: ' + status); + + function checkIsReady() { + var canvas = page.evaluate(function() { + if (!window.angular) { return false; } + var body = window.angular.element(document.body); // 1 + if (!body.scope) { return false; } + + var rootScope = body.scope(); + if (!rootScope) {return false;} + if (!rootScope.performance) { return false; } + var panelsToLoad = window.angular.element('div.panel').length; + return rootScope.performance.panelsRendered >= panelsToLoad; + }); + + if (canvas || tries === 1000) { + page.render(params.png); + phantom.exit(); + } + else { + tries++; + setTimeout(checkIsReady, 10); + } + } + + setTimeout(checkIsReady, 200); + }); +})();