mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(import): lots of work on dashboard import
This commit is contained in:
parent
ca8df67947
commit
d9d46096dd
@ -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 & Open</span>
|
||||
<span ng-show="!model.nameExists">Save & Open</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <table class="filter-table"> -->
|
||||
<!-- <tbody> -->
|
||||
<!-- <tr ng-repeat="step in model.steps"> -->
|
||||
<!-- <td>{{step.name}}</td> -->
|
||||
<!-- <td>{{step.status}}</td> -->
|
||||
<!-- <td width="1%"> -->
|
||||
<!-- <i class="fa fa-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>
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,5 @@ define([
|
||||
'./importCtrl',
|
||||
'./impression_store',
|
||||
'./upload',
|
||||
'./import/import',
|
||||
], function () {});
|
||||
|
@ -29,6 +29,7 @@ export class DashboardExporter {
|
||||
name: refName,
|
||||
type: 'datasource',
|
||||
pluginId: ds.meta.id,
|
||||
pluginName: ds.meta.name,
|
||||
};
|
||||
panel.datasource = '${' + refName +'}';
|
||||
|
||||
|
96
public/app/features/dashboard/import/import.html
Normal file
96
public/app/features/dashboard/import/import.html
Normal file
@ -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 & Open
|
||||
</button>
|
||||
<a class="btn btn-link" ng-click="dismiss()">Cancel</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
119
public/app/features/dashboard/import/import.ts
Normal file
119
public/app/features/dashboard/import/import.ts
Normal file
@ -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);
|
@ -33,7 +33,9 @@ function uploadDashboardDirective(timer, alertSrv, $location) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.onUpload({dash: dash});
|
||||
scope.$apply(function() {
|
||||
scope.onUpload({dash: dash});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
11
public/vendor/angular-other/angular-strap.js
vendored
11
public/vendor/angular-other/angular-strap.js
vendored
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user