Przeglądaj źródła

feat(import): lots of work on dashboard import

Torkel Ödegaard 9 lat temu
rodzic
commit
d9d46096dd

+ 0 - 76
public/app/core/components/dash_importer/dash_importer.html

@@ -1,76 +0,0 @@
-<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>
-

+ 0 - 77
public/app/core/components/dash_importer/dash_importer.ts

@@ -1,77 +0,0 @@
-///<reference path="../../../headers/common.d.ts" />
-
-import kbn from 'app/core/utils/kbn';
-import coreModule from 'app/core/core_module';
-
-import appEvents from 'app/core/app_events';
-import {WizardFlow} from 'app/core/core';
-
-var wnd: any = window;
-
-export class DashImporter {
-  step: number;
-  jsonText: string;
-  parseError: string;
-  nameExists: boolean;
-  dash: any;
-  dismiss: any;
-
-  constructor(private backendSrv, private $location) {
-  }
-
-  onUpload(dash) {
-    this.dash = dash;
-    this.dash.id = null;
-
-    this.backendSrv.saveDashboard(this.dash, {overwrite: false}).then(res => {
-
-    }).catch(err => {
-      if (err.data.status === 'name-exists') {
-        err.isHandled = true;
-        this.step = 2;
-        this.nameExists = true;
-      }
-      console.log(err);
-    });
-  }
-
-  titleChanged() {
-    this.backendSrv.search({query: this.dash.title}).then(res => {
-      this.nameExists = false;
-      for (let hit of res) {
-        if (this.dash.title === hit.title) {
-          this.nameExists = true;
-          break;
-        }
-      }
-    });
-  }
-
-  saveDashboard() {
-    return this.backendSrv.saveDashboard(this.dash, {overwrite: true}).then(res => {
-      this.$location.url('dashboard/db/' + res.slug);
-      this.dismiss();
-    });
-  }
-
-  loadJsonText() {
-    try {
-      this.parseError = '';
-      var dash = JSON.parse(this.jsonText);
-      this.onUpload(dash);
-    } catch (err) {
-      console.log(err);
-      this.parseError = err.message;
-      return;
-    }
-  }
-
-  run() {
-    this.step = 0;
-
-    appEvents.emit('show-modal', {
-      src: 'public/app/core/components/dash_importer/dash_importer.html',
-      model: this
-    });
-  }
-}

+ 4 - 4
public/app/core/components/search/search.html

@@ -62,15 +62,15 @@
 	</div>
 
 	<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>
 			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>
 			Import
-		</button>
+		</a>
 
  		<div class="clearfix"></div>
 	</div>

+ 4 - 6
public/app/core/components/search/search.ts

@@ -5,7 +5,7 @@ import config from 'app/core/config';
 import _ from 'lodash';
 import $ from 'jquery';
 import coreModule from '../../core_module';
-import {DashImporter} from '../dash_importer/dash_importer';
+import appEvents from 'app/core/app_events';
 
 export class SearchCtrl {
   isOpen: boolean;
@@ -149,12 +149,10 @@ export class SearchCtrl {
     this.searchDashboards();
   };
 
-  newDashboard() {
-    this.$location.url('dashboard/new');
-  };
-
   import() {
-    new DashImporter(this.backendSrv, this.$location).run();
+    appEvents.emit('show-modal', {
+      templateHtml: '<dash-import></dash-import>',
+    });
   }
 }
 

+ 8 - 21
public/app/core/directives/dash_edit_link.js

@@ -6,27 +6,12 @@ function ($, coreModule) {
   'use strict';
 
   var editViewMap = {
-    'settings':    { src: 'public/app/features/dashboard/partials/settings.html', title: "Settings" },
-    'annotations': { src: 'public/app/features/annotations/partials/editor.html', title: "Annotations" },
-    'templating':  { src: 'public/app/features/templating/partials/editor.html', title: "Templating" }
+    'settings':    { src: 'public/app/features/dashboard/partials/settings.html'},
+    'annotations': { src: 'public/app/features/annotations/partials/editor.html'},
+    '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) {
     return {
       restrict: 'A',
@@ -72,8 +57,10 @@ function ($, coreModule) {
             }
           };
 
-          var src = "'" + payload.src + "'";
-          var view = $('<div class="tabbed-view" ng-include="' + src + '"></div>');
+          var view = payload.src;
+          if (view.indexOf('.html') > 0)  {
+            view = $('<div class="tabbed-view" ng-include="' + "'" + view + "'" + '"></div>');
+          }
 
           elem.append(view);
           $compile(elem.contents())(editorScope);

+ 1 - 1
public/app/core/services/util_srv.ts

@@ -26,6 +26,7 @@ export class UtilSrv {
     var modal = this.$modal({
       modalClass: options.modalClass,
       template: options.src,
+      templateHtml: options.templateHtml,
       persist: false,
       show: false,
       scope: options.scope,
@@ -34,7 +35,6 @@ export class UtilSrv {
 
     Promise.resolve(modal).then(function(modalEl) {
       modalEl.modal('show');
-      options.scope.model.dismiss = options.scope.dismiss;
     });
   }
 }

+ 1 - 0
public/app/features/dashboard/all.js

@@ -17,4 +17,5 @@ define([
   './importCtrl',
   './impression_store',
   './upload',
+  './import/import',
 ], function () {});

+ 1 - 0
public/app/features/dashboard/exporter.ts

@@ -29,6 +29,7 @@ export class DashboardExporter {
               name: refName,
               type: 'datasource',
               pluginId: ds.meta.id,
+              pluginName: ds.meta.name,
             };
             panel.datasource = '${' + refName  +'}';
 

+ 96 - 0
public/app/features/dashboard/import/import.html

@@ -0,0 +1,96 @@
+<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>
+

+ 119 - 0
public/app/features/dashboard/import/import.ts

@@ -0,0 +1,119 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import kbn from 'app/core/utils/kbn';
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
+import config from 'app/core/config';
+import _ from 'lodash';
+
+export class DashImportCtrl {
+  step: number;
+  jsonText: string;
+  parseError: string;
+  nameExists: boolean;
+  dash: any;
+  dismiss: any;
+  inputs: any[];
+  inputsValid: boolean;
+
+  /** @ngInject */
+  constructor(private backendSrv, private $location, private $scope) {
+    this.step = 1;
+    this.nameExists = false;
+  }
+
+  onUpload(dash) {
+    this.dash = dash;
+    this.dash.id = null;
+    this.step = 2;
+    this.inputs = [];
+
+    if (this.dash.__inputs) {
+      for (let input of this.dash.__inputs) {
+        var inputModel = {
+          name: input.name,
+          type: input.type,
+          options: []
+        };
+
+        if (input.type === 'datasource') {
+          this.setDatasourceOptions(input, inputModel);
+        }
+
+        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() {
+    this.backendSrv.search({query: this.dash.title}).then(res => {
+      this.nameExists = false;
+      for (let hit of res) {
+        if (this.dash.title === hit.title) {
+          this.nameExists = true;
+          break;
+        }
+      }
+    });
+  }
+
+  saveDashboard() {
+    return this.backendSrv.saveDashboard(this.dash, {overwrite: true}).then(res => {
+      this.$location.url('dashboard/db/' + res.slug);
+      this.dismiss();
+    });
+  }
+
+  loadJsonText() {
+    try {
+      this.parseError = '';
+      var dash = JSON.parse(this.jsonText);
+      this.onUpload(dash);
+    } catch (err) {
+      console.log(err);
+      this.parseError = err.message;
+      return;
+    }
+  }
+
+}
+
+export function dashImportDirective() {
+  return {
+    restrict: 'E',
+    templateUrl: 'public/app/features/dashboard/import/import.html',
+    controller: DashImportCtrl,
+    bindToController: true,
+    controllerAs: 'ctrl',
+  };
+}
+
+coreModule.directive('dashImport', dashImportDirective);

+ 3 - 1
public/app/features/dashboard/upload.ts

@@ -33,7 +33,9 @@ function uploadDashboardDirective(timer, alertSrv, $location) {
               return;
             }
 
-            scope.onUpload({dash: dash});
+            scope.$apply(function() {
+              scope.onUpload({dash: dash});
+            });
           };
         };
 

+ 2 - 2
public/sass/_variables.dark.scss

@@ -232,13 +232,13 @@ $paginationActiveBackground:          $blue;
 
 // Form states and alerts
 // -------------------------
-$state-warning-text:      darken(#c09853, 10%);
+$state-warning-text:      $warn;
 $state-warning-bg:        $brand-warning;
 
 $errorText:               #E84D4D;
 $errorBackground:         $btn-danger-bg;
 
-$successText:             #468847;
+$successText:             #12D95A;
 $successBackground:       $btn-success-bg;
 
 $infoText:                $blue-dark;

+ 8 - 3
public/vendor/angular-other/angular-strap.js

@@ -25,11 +25,16 @@ angular.module('$strap.directives').factory('$modal', [
   function ($rootScope, $compile, $http, $timeout, $q, $templateCache, $strapConfig) {
     var ModalFactory = function ModalFactory(config) {
       function Modal(config) {
-        var options = angular.extend({ show: true }, $strapConfig.modal, config), scope = options.scope ? options.scope : $rootScope.$new(), templateUrl = options.template;
-        return $q.when($templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) {
+        var options = angular.extend({ show: true }, $strapConfig.modal, config);
+        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;
         })).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
           var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).html(template);
           if (options.modalClass)