Commit 48f5a77e by Torkel Ödegaard

Merge branch 'master' into kariosdb

parents 607b273b c42d09b2
......@@ -6,45 +6,5 @@ page_keywords: grafana, export, import, documentation
# Export and Import
## Export (Json file)
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)
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.
......@@ -6,28 +6,6 @@ page_keywords: grafana, playlist, documentation
# Playlist Guide
In Grafana v1.5 a playlist feature was added. You can use this feature by first marking a couple
of dashboards as favorites. This is accomplished in the Save menu. Click on the save icon and then _Mark As Favorite_.
*TODO*
## 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)
Needs update for Grafana 2.0
......@@ -5,7 +5,7 @@ define([
'./saveDashboardAsCtrl',
'./playlistCtrl',
'./rowCtrl',
'./sharePanelCtrl',
'./shareModalCtrl',
'./shareSnapshotCtrl',
'./submenuCtrl',
'./dashboardSrv',
......
......@@ -37,7 +37,7 @@ function (angular, _) {
$scope.shareDashboard = function() {
$scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/shareDashboard.html',
src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new(),
});
};
......
......@@ -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) {
return _.reduce(row.panels, function(p,v) {
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-title">
<i class="fa fa-share-square-o"></i>
Share Dashboard
<i class="fa fa-share"></i>
{{modalTitle}}
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Link', 'Snapshot sharing']" data-title="{{tab}}">
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in tabs" data-title="{{tab.title}}">
</div>
</div>
......@@ -15,38 +15,58 @@
</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>
<div class="gf-box-body" ng-repeat="tab in tabs" ng-if="editor.index == $index">
<ng-include src="tab.src"></ng-include>
</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>
<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>
<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>
<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="shareLink.html">
<h5>Link options</h5>
<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 class="editor-row" style="margin-top: 5px;" ng-show="modeSharePanel">
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
</div>
</div>
</script>
<script type="text/ng-template" id="shareSnapshot.html">
<div class="share-snapshot ng-cloak" ng-cloak ng-controller="ShareSnapshotCtrl">
<div style="margin: 10px 0">
<i ng-if="loading" class="fa fa-spinner fa-spin"></i>
<i ng-if="!loading" class="gf-icon gf-icon-snap-multi"></i>
......@@ -118,5 +138,4 @@
Did you make a mistake? <a href="{{deleteUrl}}" target="_blank">delete snapshot.</a>
</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 @@
</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">
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
<grafana-simple-panel type="pulldown.type" ng-cloak>
......
......@@ -9,15 +9,25 @@ function (angular, _, require, config) {
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.editor = { index: 0 };
$scope.options = {
forCurrent: true,
toPanel: $scope.panel ? true : false,
includeTemplateVars: true
};
$scope.options = { forCurrent: true, includeTemplateVars: true };
$scope.modeSharePanel = $scope.panel ? true : false;
$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();
};
......@@ -52,7 +62,7 @@ function (angular, _, require, config) {
delete params.to;
}
if ($scope.options.toPanel) {
if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
......@@ -85,8 +95,6 @@ function (angular, _, require, config) {
$scope.imageUrl += '&height=500';
};
$scope.init();
});
module.directive('clipboardButton',function() {
......
......@@ -44,33 +44,12 @@ function (angular, _) {
$timeout(function() {
$scope.saveSnapshot(external);
}, 3000);
}, 4000);
};
$scope.saveSnapshot = function(external) {
var dash = angular.copy($scope.dashboard);
// 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.refresh = false;
});
// cleanup snapshotData
delete $scope.dashboard.snapshot;
$scope.dashboard.forEachPanel(function(panel) {
delete panel.snapshotData;
});
$scope.scrubDashboard(dash);
var cmdData = {
dashboard: dash,
......@@ -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) {
// save external in local instance as well
cmdData.external = true;
......
......@@ -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) {
$scope.sharePanel = function() {
$scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/sharePanel.html',
src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new()
});
};
......
......@@ -49,7 +49,7 @@ function (angular, $) {
$scope.test = "Hej";
$scope.$index = 0;
$scope.panel = $scope.getPanelById(panelId);
$scope.panel = $scope.dashboard.getPanelById(panelId);
if (!$scope.panel) {
$scope.appEvent('alert-error', ['Panel not found', '']);
......@@ -63,20 +63,6 @@ function (angular, $) {
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) {
$scope.init();
}
......
......@@ -44,6 +44,7 @@ define([
self.scope.panel = {};
self.scope.row = { panels:[] };
self.scope.dashboard = {};
self.scope.dashboardMeta = {};
self.scope.dashboardViewState = new DashboardViewStateStub();
self.scope.appEvent = sinon.spy();
self.scope.onAppEvent = sinon.spy();
......
define([
'helpers',
'features/dashboard/sharePanelCtrl'
'features/dashboard/shareModalCtrl'
], function(helpers) {
'use strict';
describe('SharePanelCtrl', function() {
describe('ShareModalCtrl', function() {
var ctx = new helpers.ControllerTestContext();
function setTime(range) {
......@@ -16,7 +16,7 @@ define([
beforeEach(module('grafana.controllers'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('SharePanelCtrl'));
beforeEach(ctx.createControllerPhase('ShareModalCtrl'));
describe('shareUrl with current time range and panel', function() {
......@@ -26,7 +26,7 @@ define([
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');
});
......@@ -35,24 +35,23 @@ define([
ctx.scope.panel = { id: 22 };
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');
});
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.scope.panel = { id: 22 };
ctx.scope.options = { toPanel: false, forCurrent: true };
ctx.scope.options = { forCurrent: true };
ctx.scope.panel = null;
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');
});
it('should include template variables in url', function() {
ctx.$location.path('/test');
ctx.scope.panel = { id: 22 };
ctx.scope.options = { includeTemplateVars: true, toPanel: false, forCurrent: true };
ctx.scope.options = { includeTemplateVars: true, forCurrent: true };
ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}];
setTime({ from: 'now-1h', to: 'now' });
......
......@@ -132,7 +132,7 @@ require([
'specs/graph-specs',
'specs/graph-tooltip-specs',
'specs/seriesOverridesCtrl-specs',
'specs/sharePanelCtrl-specs',
'specs/shareModalCtrl-specs',
'specs/timeSrv-specs',
'specs/templateSrv-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