Commit 48f5a77e by Torkel Ödegaard

Merge branch 'master' into kariosdb

parents 607b273b c42d09b2
...@@ -6,45 +6,5 @@ page_keywords: grafana, export, import, documentation ...@@ -6,45 +6,5 @@ page_keywords: grafana, export, import, documentation
# Export and Import # Export and Import
## Export (Json file) You find the import view in the bottom of the search dropdown. From this view you
can import local json files or migrate dashboards stored in Elasticsearch or InfluxDB.
Load the dashboard you wish to export (e.g. via search)
Click on the "Save" icon (floppy disk)
![](/img/v1/export_import_save_menu.png)
Click on "Export schema"
![](/img/v1/export_schema_link.png)
Save file on local disk
![](/img/v1/export_save_file.png)
## Import
Click the "Search" icon (open folder)
![](/img/v1/export_import_search_menu.png)
Click "Import" button
![](/img/v1/export_import_button.png)
Click "Browse" button and browse to previously exported dashboard on local disk (which should be a valid Json file)
![](/img/v1/export_import_browse_button.png)
Select exported dashboard
![](/img/v1/export_import_popup.png)
Click "Save" icon to show save menu
![](/img/v1/export_import_save_menu.png)
Click new save icon (floppy disk) to Save newly imported dashboard
![](/img/v1/export_import_save_dashboard.png)
...@@ -6,28 +6,6 @@ page_keywords: grafana, playlist, documentation ...@@ -6,28 +6,6 @@ page_keywords: grafana, playlist, documentation
# Playlist Guide # Playlist Guide
In Grafana v1.5 a playlist feature was added. You can use this feature by first marking a couple *TODO*
of dashboards as favorites. This is accomplished in the Save menu. Click on the save icon and then _Mark As Favorite_.
Needs update for Grafana 2.0
## Step 1 - Mark as favorire
![](/img/v1/mark_as_favorite.png)
Then open the playlist modal. You will find this button in the open dashboard / search popup (CTRL+F).
## Step 2 - Open playlist view
![](/img/v1/playlist_button.png)
This opens the playlist view.
## Step 3 - Select dashbords and interval
![](/img/v1/playlist_modal.png)
In this view you can select the dashboards you want to include in the playlist, and even remove dashboards from you favorites list. Set the time span between dashboard change, for example 1m, 10m, 1h for 1 minute, 10 minute or 1 hour interval.
To start the dashboard click the Start button.
When a dashboard playlist is running, most menu buttons and dashboard controls are hidden. This is in order to present as clear a view of the dashboard as possible for big tv displays. Click the stop playlist link in the top menu to the right to stop the playlist.
![](playlist_playing_hiden_menu.png)
...@@ -5,7 +5,7 @@ define([ ...@@ -5,7 +5,7 @@ define([
'./saveDashboardAsCtrl', './saveDashboardAsCtrl',
'./playlistCtrl', './playlistCtrl',
'./rowCtrl', './rowCtrl',
'./sharePanelCtrl', './shareModalCtrl',
'./shareSnapshotCtrl', './shareSnapshotCtrl',
'./submenuCtrl', './submenuCtrl',
'./dashboardSrv', './dashboardSrv',
......
...@@ -37,7 +37,7 @@ function (angular, _) { ...@@ -37,7 +37,7 @@ function (angular, _) {
$scope.shareDashboard = function() { $scope.shareDashboard = function() {
$scope.appEvent('show-modal', { $scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/shareDashboard.html', src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new(), scope: $scope.$new(),
}); });
}; };
......
...@@ -78,6 +78,19 @@ function (angular, $, kbn, _, moment) { ...@@ -78,6 +78,19 @@ function (angular, $, kbn, _, moment) {
} }
}; };
p.getPanelById = function(id) {
for (var i = 0; i < this.rows.length; i++) {
var row = this.rows[i];
for (var j = 0; j < row.panels.length; j++) {
var panel = row.panels[j];
if (panel.id === id) {
return panel;
}
}
}
return null;
};
p.rowSpan = function(row) { p.rowSpan = function(row) {
return _.reduce(row.panels, function(p,v) { return _.reduce(row.panels, function(p,v) {
return p + v.span; return p + v.span;
......
<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl"> <div class="modal-body gf-box gf-box-no-margin" ng-controller="ShareModalCtrl" ng-init="init()">
<div class="gf-box-header"> <div class="gf-box-header">
<div class="gf-box-title"> <div class="gf-box-title">
<i class="fa fa-share-square-o"></i> <i class="fa fa-share"></i>
Share Dashboard {{modalTitle}}
</div> </div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;"> <div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in ['Link', 'Snapshot sharing']" data-title="{{tab}}"> <div ng-repeat="tab in tabs" data-title="{{tab.title}}">
</div> </div>
</div> </div>
...@@ -15,8 +15,32 @@ ...@@ -15,8 +15,32 @@
</button> </button>
</div> </div>
<div class="gf-box-body" ng-if="editor.index === 0"> <div class="gf-box-body" ng-repeat="tab in tabs" ng-if="editor.index == $index">
<br> <ng-include src="tab.src"></ng-include>
</div>
</div>
<script type="text/ng-template" id="shareEmbed.html">
<h5>IFrame embedding</h5>
<p>
<em>
The html code below can be pasted and included in another web page. Unless anonymous access
is enabled the user viewing that page need to be signed into grafana for the graph to load.
</em>
</p>
<div class="gf-form">
<div class="gf-form-row">
<span class="gf-fluid-input">
<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea>
</span>
</div>
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div>
</script>
<script type="text/ng-template" id="shareLink.html">
<h5>Link options</h5>
<div class="gf-form"> <div class="gf-form">
<div class="gf-form-row"> <div class="gf-form-row">
<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox> <editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
...@@ -27,7 +51,6 @@ ...@@ -27,7 +51,6 @@
<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox> <editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
</div> </div>
</div> </div>
<br> <br>
<div class="gf-form"> <div class="gf-form">
<div class="gf-form-row"> <div class="gf-form-row">
...@@ -36,17 +59,14 @@ ...@@ -36,17 +59,14 @@
<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input> <input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
</span> </span>
</div> </div>
<div> <div class="editor-row" style="margin-top: 5px;" ng-show="modeSharePanel">
<div class="editor-row" style="margin-top: 5px;" ng-if="options.toPanel">
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a> <a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
</div> </div>
</div> </div>
</div> </script>
</div>
<div class="gf-box-body share-snapshot ng-cloak" ng-cloak ng-if="editor.index === 1" ng-controller="ShareSnapshotCtrl">
<script type="text/ng-template" id="shareSnapshot.html">
<div class="share-snapshot ng-cloak" ng-cloak ng-controller="ShareSnapshotCtrl">
<div style="margin: 10px 0"> <div style="margin: 10px 0">
<i ng-if="loading" class="fa fa-spinner fa-spin"></i> <i ng-if="loading" class="fa fa-spinner fa-spin"></i>
<i ng-if="!loading" class="gf-icon gf-icon-snap-multi"></i> <i ng-if="!loading" class="gf-icon gf-icon-snap-multi"></i>
...@@ -118,5 +138,4 @@ ...@@ -118,5 +138,4 @@
Did you make a mistake? <a href="{{deleteUrl}}" target="_blank">delete snapshot.</a> Did you make a mistake? <a href="{{deleteUrl}}" target="_blank">delete snapshot.</a>
</div> </div>
</div> </div>
</div> </script>
<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-share"></i>
Share Panel
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Link', 'Embed']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="gf-box-body" ng-if="editor.index === 0">
<br>
<div class="gf-form">
<div class="gf-form-row">
<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
</div>
</div>
<div class="gf-form">
<div class="gf-form-row">
<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
</div>
</div>
<br>
<div class="gf-form">
<div class="gf-form-row">
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
<span class="gf-fluid-input">
<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
</span>
</div>
<div>
<div class="editor-row" style="margin-top: 5px;" ng-if="options.toPanel">
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
</div>
</div>
</div>
</div>
<div class="gf-box-body" ng-if="editor.index === 1">
<h5>IFrame embedding</h5>
<p><em>The html code below can be pasted and included in another web page. Unless anonymous access
is enabled the user viewing that page need to be signed into grafana for the graph to load.
</em>
</p>
<div class="gf-form">
<div class="gf-form-row">
<span class="gf-fluid-input">
<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea>
</span>
</div>
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
<br>
<br>
</div>
</div>
</div>
...@@ -19,6 +19,12 @@ ...@@ -19,6 +19,12 @@
</div> </div>
</div> </div>
<ul class="nav pull-left top-nav-dash-actions">
<li>
<a class="pointer" ng-click="shareDashboard()" bs-tooltip="'Share dashboard'" data-placement="bottom"><i class="fa fa-share-square-o"></i></a>
</li>
</ul>
<ul class="nav pull-right"> <ul class="nav pull-right">
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"> <li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
<grafana-simple-panel type="pulldown.type" ng-cloak> <grafana-simple-panel type="pulldown.type" ng-cloak>
......
...@@ -9,15 +9,25 @@ function (angular, _, require, config) { ...@@ -9,15 +9,25 @@ function (angular, _, require, config) {
var module = angular.module('grafana.controllers'); var module = angular.module('grafana.controllers');
module.controller('SharePanelCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, $element, templateSrv) { module.controller('ShareModalCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, $element, templateSrv) {
$scope.init = function() { $scope.init = function() {
$scope.editor = { index: 0 }; $scope.editor = { index: 0 };
$scope.options = { $scope.options = { forCurrent: true, includeTemplateVars: true };
forCurrent: true, $scope.modeSharePanel = $scope.panel ? true : false;
toPanel: $scope.panel ? true : false,
includeTemplateVars: true $scope.tabs = [{title: 'Link', src: 'shareLink.html'}];
};
if ($scope.modeSharePanel) {
$scope.modalTitle = 'Share Panel';
$scope.tabs.push({title: 'Embed', src: 'shareEmbed.html'});
} else {
$scope.modalTitle = 'Share Dashboard';
}
if (!$scope.dashboardMeta.isSnapshot) {
$scope.tabs.push({title: 'Snapshot sharing', src: 'shareSnapshot.html'});
}
$scope.buildUrl(); $scope.buildUrl();
}; };
...@@ -52,7 +62,7 @@ function (angular, _, require, config) { ...@@ -52,7 +62,7 @@ function (angular, _, require, config) {
delete params.to; delete params.to;
} }
if ($scope.options.toPanel) { if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id; params.panelId = $scope.panel.id;
params.fullscreen = true; params.fullscreen = true;
} else { } else {
...@@ -85,8 +95,6 @@ function (angular, _, require, config) { ...@@ -85,8 +95,6 @@ function (angular, _, require, config) {
$scope.imageUrl += '&height=500'; $scope.imageUrl += '&height=500';
}; };
$scope.init();
}); });
module.directive('clipboardButton',function() { module.directive('clipboardButton',function() {
......
...@@ -44,33 +44,12 @@ function (angular, _) { ...@@ -44,33 +44,12 @@ function (angular, _) {
$timeout(function() { $timeout(function() {
$scope.saveSnapshot(external); $scope.saveSnapshot(external);
}, 3000); }, 4000);
}; };
$scope.saveSnapshot = function(external) { $scope.saveSnapshot = function(external) {
var dash = angular.copy($scope.dashboard); var dash = angular.copy($scope.dashboard);
// change title $scope.scrubDashboard(dash);
dash.title = $scope.snapshot.name;
// make relative times absolute
dash.time = timeSrv.timeRange();
// remove panel queries & links
dash.forEachPanel(function(panel) {
panel.targets = [];
panel.links = [];
});
// remove annotations
dash.annotations.list = [];
// remove template queries
_.each(dash.templating.list, function(variable) {
variable.query = "";
variable.refresh = false;
});
// cleanup snapshotData
delete $scope.dashboard.snapshot;
$scope.dashboard.forEachPanel(function(panel) {
delete panel.snapshotData;
});
var cmdData = { var cmdData = {
dashboard: dash, dashboard: dash,
...@@ -98,6 +77,38 @@ function (angular, _) { ...@@ -98,6 +77,38 @@ function (angular, _) {
}); });
}; };
$scope.scrubDashboard = function(dash) {
// change title
dash.title = $scope.snapshot.name;
// make relative times absolute
dash.time = timeSrv.timeRange();
// remove panel queries & links
dash.forEachPanel(function(panel) {
panel.targets = [];
panel.links = [];
});
// remove annotations
dash.annotations.list = [];
// remove template queries
_.each(dash.templating.list, function(variable) {
variable.query = "";
variable.options = [];
variable.refresh = false;
});
// snapshot single panel
if ($scope.modeSharePanel) {
var singlePanel = dash.getPanelById($scope.panel.id);
dash.rows = [{ height: '500px', span: 12, panels: [singlePanel] }];
}
// cleanup snapshotData
delete $scope.dashboard.snapshot;
$scope.dashboard.forEachPanel(function(panel) {
delete panel.snapshotData;
});
};
$scope.saveExternalSnapshotRef = function(cmdData, results) { $scope.saveExternalSnapshotRef = function(cmdData, results) {
// save external in local instance as well // save external in local instance as well
cmdData.external = true; cmdData.external = true;
......
...@@ -17,6 +17,13 @@ function (angular, moment) { ...@@ -17,6 +17,13 @@ function (angular, moment) {
} }
}; };
$scope.shareDashboard = function() {
$scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new(),
});
};
}); });
}); });
...@@ -26,7 +26,7 @@ function (angular, _, config) { ...@@ -26,7 +26,7 @@ function (angular, _, config) {
$scope.sharePanel = function() { $scope.sharePanel = function() {
$scope.appEvent('show-modal', { $scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/sharePanel.html', src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new() scope: $scope.$new()
}); });
}; };
......
...@@ -49,7 +49,7 @@ function (angular, $) { ...@@ -49,7 +49,7 @@ function (angular, $) {
$scope.test = "Hej"; $scope.test = "Hej";
$scope.$index = 0; $scope.$index = 0;
$scope.panel = $scope.getPanelById(panelId); $scope.panel = $scope.dashboard.getPanelById(panelId);
if (!$scope.panel) { if (!$scope.panel) {
$scope.appEvent('alert-error', ['Panel not found', '']); $scope.appEvent('alert-error', ['Panel not found', '']);
...@@ -63,20 +63,6 @@ function (angular, $) { ...@@ -63,20 +63,6 @@ function (angular, $) {
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState); templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
}; };
$scope.getPanelById = function(id) {
var rows = $scope.dashboard.rows;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
for (var j = 0; j < row.panels.length; j++) {
var panel = row.panels[j];
if (panel.id === id) {
return panel;
}
}
}
return null;
};
if (!$scope.skipAutoInit) { if (!$scope.skipAutoInit) {
$scope.init(); $scope.init();
} }
......
...@@ -44,6 +44,7 @@ define([ ...@@ -44,6 +44,7 @@ define([
self.scope.panel = {}; self.scope.panel = {};
self.scope.row = { panels:[] }; self.scope.row = { panels:[] };
self.scope.dashboard = {}; self.scope.dashboard = {};
self.scope.dashboardMeta = {};
self.scope.dashboardViewState = new DashboardViewStateStub(); self.scope.dashboardViewState = new DashboardViewStateStub();
self.scope.appEvent = sinon.spy(); self.scope.appEvent = sinon.spy();
self.scope.onAppEvent = sinon.spy(); self.scope.onAppEvent = sinon.spy();
......
define([ define([
'helpers', 'helpers',
'features/dashboard/sharePanelCtrl' 'features/dashboard/shareModalCtrl'
], function(helpers) { ], function(helpers) {
'use strict'; 'use strict';
describe('SharePanelCtrl', function() { describe('ShareModalCtrl', function() {
var ctx = new helpers.ControllerTestContext(); var ctx = new helpers.ControllerTestContext();
function setTime(range) { function setTime(range) {
...@@ -16,7 +16,7 @@ define([ ...@@ -16,7 +16,7 @@ define([
beforeEach(module('grafana.controllers')); beforeEach(module('grafana.controllers'));
beforeEach(ctx.providePhase()); beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('SharePanelCtrl')); beforeEach(ctx.createControllerPhase('ShareModalCtrl'));
describe('shareUrl with current time range and panel', function() { describe('shareUrl with current time range and panel', function() {
...@@ -26,7 +26,7 @@ define([ ...@@ -26,7 +26,7 @@ define([
setTime({ from: 'now-1h', to: 'now' }); setTime({ from: 'now-1h', to: 'now' });
ctx.scope.buildUrl(); ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen'); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen');
}); });
...@@ -35,24 +35,23 @@ define([ ...@@ -35,24 +35,23 @@ define([
ctx.scope.panel = { id: 22 }; ctx.scope.panel = { id: 22 };
setTime({ from: 1362178800000, to: 1396648800000 }); setTime({ from: 1362178800000, to: 1396648800000 });
ctx.scope.buildUrl(); ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1362178800000&to=1396648800000&panelId=22&fullscreen'); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1362178800000&to=1396648800000&panelId=22&fullscreen');
}); });
it('should remove panel id when toPanel is false', function() { it('should remove panel id when no panel in scope', function() {
ctx.$location.path('/test'); ctx.$location.path('/test');
ctx.scope.panel = { id: 22 }; ctx.scope.options = { forCurrent: true };
ctx.scope.options = { toPanel: false, forCurrent: true }; ctx.scope.panel = null;
setTime({ from: 'now-1h', to: 'now' }); setTime({ from: 'now-1h', to: 'now' });
ctx.scope.buildUrl(); ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now'); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now');
}); });
it('should include template variables in url', function() { it('should include template variables in url', function() {
ctx.$location.path('/test'); ctx.$location.path('/test');
ctx.scope.panel = { id: 22 }; ctx.scope.options = { includeTemplateVars: true, forCurrent: true };
ctx.scope.options = { includeTemplateVars: true, toPanel: false, forCurrent: true };
ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}]; ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}];
setTime({ from: 'now-1h', to: 'now' }); setTime({ from: 'now-1h', to: 'now' });
......
...@@ -132,7 +132,7 @@ require([ ...@@ -132,7 +132,7 @@ require([
'specs/graph-specs', 'specs/graph-specs',
'specs/graph-tooltip-specs', 'specs/graph-tooltip-specs',
'specs/seriesOverridesCtrl-specs', 'specs/seriesOverridesCtrl-specs',
'specs/sharePanelCtrl-specs', 'specs/shareModalCtrl-specs',
'specs/timeSrv-specs', 'specs/timeSrv-specs',
'specs/templateSrv-specs', 'specs/templateSrv-specs',
'specs/templateValuesSrv-specs', 'specs/templateValuesSrv-specs',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment