Commit d9d46096 by Torkel Ödegaard

feat(import): lots of work on dashboard import

parent ca8df679
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa fa-upload"></i>
<span class="p-l-1">Import Dashboard</span>
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</a>
</div>
<div class="modal-content" ng-cloak>
<div ng-if="model.step === 0">
<form class="gf-form-group">
<dash-upload on-upload="model.onUpload(dash)"></dash-upload>
</form>
<h5 class="section-heading">Or paste JSON:</h5>
<div class="gf-form-group">
<div class="gf-form">
<textarea rows="7" data-share-panel-url="" class="gf-form-input" ng-model="model.jsonText"></textarea>
</div>
<button type="button" class="btn btn-secondary" ng-click="model.loadJsonText()">
<i class="fa fa-paste"></i>
Load
</button>
<span ng-if="model.parseError" class="text-error p-l-1">
<i class="fa fa-warning"></i>
{{model.parseError}}
</span>
</div>
</div>
<div ng-if="model.step === 2">
<div class="gf-form-group">
<h3 class="section-heading p-b-1" ng-if="model.nameExists">
<i class="fa fa-warning"></i> Dashboard with same title already exists
</h3>
<h3 class="section-heading p-b-1" ng-if="!model.nameExists">
<i class="fa fa-check"></i> Dashboard title available
</h3>
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<label class="gf-form-label">New title</label>
<input type="text" class="gf-form-input" ng-model="model.dash.title" give-focus="true" ng-change="model.titleChanged()" ng-class="{'validation-error': model.nameExists}">
<button type="button" class="btn btn-success gf-form-btn width-10" ng-click="model.saveDashboard()">
<i class="fa fa-save"></i>
<span ng-show="model.nameExists">Overwrite &amp; Open</span>
<span ng-show="!model.nameExists">Save &amp; Open</span>
</button>
</div>
</div>
</div>
</div>
<!-- <table class="filter&#45;table"> -->
<!-- <tbody> -->
<!-- <tr ng&#45;repeat="step in model.steps"> -->
<!-- <td>{{step.name}}</td> -->
<!-- <td>{{step.status}}</td> -->
<!-- <td width="1%"> -->
<!-- <i class="fa fa&#45;check" style="color: #39A039"></i> -->
<!-- </td> -->
<!-- </tr> -->
<!-- </tbody> -->
<!-- </table> -->
<div class="gf-form-button-row text-right">
<a class="btn-text" ng-click="dismiss();">Cancel</a>
</div>
</div>
</div>
...@@ -62,15 +62,15 @@ ...@@ -62,15 +62,15 @@
</div> </div>
<div class="search-button-row"> <div class="search-button-row">
<button class="btn btn-inverse pull-left" ng-click="ctrl.newDashboard()" ng-show="ctrl.contextSrv.isEditor"> <a class="btn btn-inverse pull-left" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Create New Create New
</button> </a>
<button class="btn btn-inverse pull-left" ng-click="ctrl.import()" ng-show="ctrl.contextSrv.isEditor"> <a class="btn btn-inverse pull-left" ng-click="ctrl.import()" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-upload"></i> <i class="fa fa-upload"></i>
Import Import
</button> </a>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
...@@ -5,7 +5,7 @@ import config from 'app/core/config'; ...@@ -5,7 +5,7 @@ import config from 'app/core/config';
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import coreModule from '../../core_module'; import coreModule from '../../core_module';
import {DashImporter} from '../dash_importer/dash_importer'; import appEvents from 'app/core/app_events';
export class SearchCtrl { export class SearchCtrl {
isOpen: boolean; isOpen: boolean;
...@@ -149,12 +149,10 @@ export class SearchCtrl { ...@@ -149,12 +149,10 @@ export class SearchCtrl {
this.searchDashboards(); this.searchDashboards();
}; };
newDashboard() {
this.$location.url('dashboard/new');
};
import() { import() {
new DashImporter(this.backendSrv, this.$location).run(); appEvents.emit('show-modal', {
templateHtml: '<dash-import></dash-import>',
});
} }
} }
......
...@@ -6,27 +6,12 @@ function ($, coreModule) { ...@@ -6,27 +6,12 @@ function ($, coreModule) {
'use strict'; 'use strict';
var editViewMap = { var editViewMap = {
'settings': { src: 'public/app/features/dashboard/partials/settings.html', title: "Settings" }, 'settings': { src: 'public/app/features/dashboard/partials/settings.html'},
'annotations': { src: 'public/app/features/annotations/partials/editor.html', title: "Annotations" }, 'annotations': { src: 'public/app/features/annotations/partials/editor.html'},
'templating': { src: 'public/app/features/templating/partials/editor.html', title: "Templating" } 'templating': { src: 'public/app/features/templating/partials/editor.html'},
'import': { src: '<dash-import></dash-import>' }
}; };
coreModule.default.directive('dashEditorLink', function($timeout) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var partial = attrs.dashEditorLink;
elem.bind('click',function() {
$timeout(function() {
var editorScope = attrs.editorScope === 'isolated' ? null : scope;
scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
});
});
}
};
});
coreModule.default.directive('dashEditorView', function($compile, $location) { coreModule.default.directive('dashEditorView', function($compile, $location) {
return { return {
restrict: 'A', restrict: 'A',
...@@ -72,8 +57,10 @@ function ($, coreModule) { ...@@ -72,8 +57,10 @@ function ($, coreModule) {
} }
}; };
var src = "'" + payload.src + "'"; var view = payload.src;
var view = $('<div class="tabbed-view" ng-include="' + src + '"></div>'); if (view.indexOf('.html') > 0) {
view = $('<div class="tabbed-view" ng-include="' + "'" + view + "'" + '"></div>');
}
elem.append(view); elem.append(view);
$compile(elem.contents())(editorScope); $compile(elem.contents())(editorScope);
......
...@@ -26,6 +26,7 @@ export class UtilSrv { ...@@ -26,6 +26,7 @@ export class UtilSrv {
var modal = this.$modal({ var modal = this.$modal({
modalClass: options.modalClass, modalClass: options.modalClass,
template: options.src, template: options.src,
templateHtml: options.templateHtml,
persist: false, persist: false,
show: false, show: false,
scope: options.scope, scope: options.scope,
...@@ -34,7 +35,6 @@ export class UtilSrv { ...@@ -34,7 +35,6 @@ export class UtilSrv {
Promise.resolve(modal).then(function(modalEl) { Promise.resolve(modal).then(function(modalEl) {
modalEl.modal('show'); modalEl.modal('show');
options.scope.model.dismiss = options.scope.dismiss;
}); });
} }
} }
......
...@@ -17,4 +17,5 @@ define([ ...@@ -17,4 +17,5 @@ define([
'./importCtrl', './importCtrl',
'./impression_store', './impression_store',
'./upload', './upload',
'./import/import',
], function () {}); ], function () {});
...@@ -29,6 +29,7 @@ export class DashboardExporter { ...@@ -29,6 +29,7 @@ export class DashboardExporter {
name: refName, name: refName,
type: 'datasource', type: 'datasource',
pluginId: ds.meta.id, pluginId: ds.meta.id,
pluginName: ds.meta.name,
}; };
panel.datasource = '${' + refName +'}'; panel.datasource = '${' + refName +'}';
......
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa fa-upload"></i>
<span class="p-l-1">Import Dashboard</span>
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</a>
</div>
<div class="modal-content" ng-cloak>
<div ng-if="ctrl.step === 1">
<form class="gf-form-group">
<dash-upload on-upload="ctrl.onUpload(dash)"></dash-upload>
</form>
<h5 class="section-heading">Or paste JSON:</h5>
<div class="gf-form-group">
<div class="gf-form">
<textarea rows="7" data-share-panel-url="" class="gf-form-input" ng-ctrl="ctrl.jsonText"></textarea>
</div>
<button type="button" class="btn btn-secondary" ng-click="ctrl.loadJsonText()">
<i class="fa fa-paste"></i>
Load
</button>
<span ng-if="ctrl.parseError" class="text-error p-l-1">
<i class="fa fa-warning"></i>
{{ctrl.parseError}}
</span>
</div>
</div>
<div ng-if="ctrl.step === 2">
<h3 class="section-heading">
Options
</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<label class="gf-form-label width-15">Title</label>
<input type="text" class="gf-form-input" ng-model="ctrl.dash.title" give-focus="true" ng-change="ctrl.titleChanged()" ng-class="{'validation-error': ctrl.nameExists}">
<label class="gf-form-label text-success" ng-if="!ctrl.nameExists">
<i class="fa fa-check"></i>
</label>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.nameExists">
<div class="gf-form offset-width-15 gf-form--grow">
<label class="gf-form-label text-warning gf-form-label--grow">
<i class="fa fa-warning"></i>
A Dashboard with the same name already exists
</label>
</div>
</div>
<div ng-repeat="input in ctrl.inputs">
<div class="gf-form">
<label class="gf-form-label width-15">{{input.name}}</label>
<div class="gf-form-select-wrapper" style="width: 100%">
<select class="gf-form-input" ng-model="input.value" ng-options="v.value as v.text for v in input.options" ng-change="ctrl.inputValueChanged()"></select>
</div>
<label class="gf-form-label text-success" ng-show="input.value">
<i class="fa fa-check"></i>
</label>
</div>
<div class="gf-form offset-width-15 gf-form--grow">
<label class="gf-form-label gf-form-label--grow" ng-show="input.info">
<i class="fa fa-info-circle"></i>
{{input.info}}
</label>
<label class="gf-form-label gf-form-label--grow" ng-show="input.error">
<i class="fa fa-info-circle"></i>
{{input.info}}
</label>
</div>
</div>
</div>
<div class="gf-form-button-row">
<button type="button" class="btn gf-form-btn width-10" ng-click="ctrl.saveDashboard()" ng-class="{'btn-danger': ctrl.nameExists, 'btn-success': !ctrl.nameExists}" ng-disable="!ctrl.inputsOk">
<i class="fa fa-save"></i> Save &amp; Open
</button>
<a class="btn btn-link" ng-click="dismiss()">Cancel</a>
</div>
</div>
</div>
</div>
...@@ -2,37 +2,75 @@ ...@@ -2,37 +2,75 @@
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import {WizardFlow} from 'app/core/core'; import config from 'app/core/config';
import _ from 'lodash';
var wnd: any = window;
export class DashImporter { export class DashImportCtrl {
step: number; step: number;
jsonText: string; jsonText: string;
parseError: string; parseError: string;
nameExists: boolean; nameExists: boolean;
dash: any; dash: any;
dismiss: any; dismiss: any;
inputs: any[];
inputsValid: boolean;
constructor(private backendSrv, private $location) { /** @ngInject */
constructor(private backendSrv, private $location, private $scope) {
this.step = 1;
this.nameExists = false;
} }
onUpload(dash) { onUpload(dash) {
this.dash = dash; this.dash = dash;
this.dash.id = null; this.dash.id = null;
this.step = 2;
this.inputs = [];
this.backendSrv.saveDashboard(this.dash, {overwrite: false}).then(res => { if (this.dash.__inputs) {
for (let input of this.dash.__inputs) {
var inputModel = {
name: input.name,
type: input.type,
options: []
};
}).catch(err => { if (input.type === 'datasource') {
if (err.data.status === 'name-exists') { this.setDatasourceOptions(input, inputModel);
err.isHandled = true;
this.step = 2;
this.nameExists = true;
} }
console.log(err);
this.inputs.push(inputModel);
}
}
this.inputsValid = this.inputs.length === 0;
this.titleChanged();
}
setDatasourceOptions(input, inputModel) {
var sources = _.filter(config.datasources, val => {
return val.type === input.pluginId;
}); });
if (sources.length === 0) {
inputModel.error = "No data sources of type " + input.pluginName + " found";
} else {
inputModel.info = "Select a " + input.pluginName + " data source";
}
inputModel.options = sources.map(val => {
return {text: val.name, value: val.name};
});
}
inputOptionChanged() {
this.inputsValid = true;
for (let input of this.inputs) {
if (!input.value) {
this.inputsValid = false;
}
}
} }
titleChanged() { titleChanged() {
...@@ -66,12 +104,16 @@ export class DashImporter { ...@@ -66,12 +104,16 @@ export class DashImporter {
} }
} }
run() { }
this.step = 0;
appEvents.emit('show-modal', { export function dashImportDirective() {
src: 'public/app/core/components/dash_importer/dash_importer.html', return {
model: this restrict: 'E',
}); templateUrl: 'public/app/features/dashboard/import/import.html',
} controller: DashImportCtrl,
bindToController: true,
controllerAs: 'ctrl',
};
} }
coreModule.directive('dashImport', dashImportDirective);
...@@ -33,7 +33,9 @@ function uploadDashboardDirective(timer, alertSrv, $location) { ...@@ -33,7 +33,9 @@ function uploadDashboardDirective(timer, alertSrv, $location) {
return; return;
} }
scope.$apply(function() {
scope.onUpload({dash: dash}); scope.onUpload({dash: dash});
});
}; };
}; };
......
...@@ -232,13 +232,13 @@ $paginationActiveBackground: $blue; ...@@ -232,13 +232,13 @@ $paginationActiveBackground: $blue;
// Form states and alerts // Form states and alerts
// ------------------------- // -------------------------
$state-warning-text: darken(#c09853, 10%); $state-warning-text: $warn;
$state-warning-bg: $brand-warning; $state-warning-bg: $brand-warning;
$errorText: #E84D4D; $errorText: #E84D4D;
$errorBackground: $btn-danger-bg; $errorBackground: $btn-danger-bg;
$successText: #468847; $successText: #12D95A;
$successBackground: $btn-success-bg; $successBackground: $btn-success-bg;
$infoText: $blue-dark; $infoText: $blue-dark;
......
...@@ -25,11 +25,16 @@ angular.module('$strap.directives').factory('$modal', [ ...@@ -25,11 +25,16 @@ angular.module('$strap.directives').factory('$modal', [
function ($rootScope, $compile, $http, $timeout, $q, $templateCache, $strapConfig) { function ($rootScope, $compile, $http, $timeout, $q, $templateCache, $strapConfig) {
var ModalFactory = function ModalFactory(config) { var ModalFactory = function ModalFactory(config) {
function Modal(config) { function Modal(config) {
var options = angular.extend({ show: true }, $strapConfig.modal, config), scope = options.scope ? options.scope : $rootScope.$new(), templateUrl = options.template; var options = angular.extend({ show: true }, $strapConfig.modal, config);
return $q.when($templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) { var scope = options.scope ? options.scope : $rootScope.$new()
var templateUrl = options.template;
return $q.when(options.templateHtml || $templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) {
return res.data; return res.data;
})).then(function onSuccess(template) { })).then(function onSuccess(template) {
var id = templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; var id = scope.$id;
if (templateUrl) {
id += templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-');
}
// grafana change, removed fade // grafana change, removed fade
var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).html(template); var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).html(template);
if (options.modalClass) if (options.modalClass)
......
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