mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into panelbase
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
38
pkg/plugins/api_plugin.go
Normal file
38
pkg/plugins/api_plugin.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
37
public/app/core/utils/file_export.ts
Normal file
37
public/app/core/utils/file_export.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -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 + '$');
|
||||
|
||||
@@ -98,6 +98,8 @@
|
||||
<div class="simple-box-body">
|
||||
<div ng-if="ctrl.appModel.appId">
|
||||
<app-config-view app-model="ctrl.appModel"></app-config-view>
|
||||
<div class="clearfix"></div>
|
||||
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<p class="text-center">Are you sure want to delete "{{playlist.title}}" playlist?</p>
|
||||
<p class="text-center">
|
||||
<button type="button" class="btn btn-danger" ng-click="removePlaylist()">Yes</button>
|
||||
<button type="button" class="btn btn-default" ng-click="dismiss()">No</button>
|
||||
</p>
|
||||
@@ -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();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
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++;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
///<reference path="headers/common.d.ts" />
|
||||
|
||||
import 'bootstrap';
|
||||
import 'vendor/filesaver';
|
||||
import 'lodash-src';
|
||||
import 'angular-strap';
|
||||
import 'angular-route';
|
||||
|
||||
@@ -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: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import _ from 'lodash';
|
||||
|
||||
class MixedDatasource {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $q, private datasourceSrv) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
min-height: 100%;
|
||||
z-index: 101;
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
visibility: hidden;
|
||||
|
||||
a:focus {
|
||||
text-decoration: none;
|
||||
|
||||
@@ -56,8 +56,10 @@
|
||||
</script>
|
||||
|
||||
<!-- build:js [[.AppSubUrl]]/public/app/boot.js -->
|
||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es5-shim/es5-shim.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-shim/es6-shim.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-promise/dist/es6-promise.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/vendor/npm/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/vendor/npm/systemjs/dist/system.src.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/app/system.conf.js"></script>
|
||||
<script src="[[.AppSubUrl]]/public/app/boot.js"></script>
|
||||
|
||||
@@ -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/* .
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = function(grunt) {
|
||||
'clean:release',
|
||||
'copy:public_to_gen',
|
||||
'typescript:build',
|
||||
// 'karma:test',
|
||||
'karma:test',
|
||||
'phantomjs',
|
||||
'css',
|
||||
'htmlmin:build',
|
||||
|
||||
@@ -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',
|
||||
|
||||
104
vendor/phantomjs/render.js
vendored
104
vendor/phantomjs/render.js
vendored
@@ -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=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<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=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<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);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user