mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into 10630_folder_api
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import appEvents from 'app/core/app_events';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
export class CreateFolderCtrl {
|
||||
title = '';
|
||||
@@ -19,7 +20,7 @@ export class CreateFolderCtrl {
|
||||
|
||||
return this.backendSrv.createFolder({ title: this.title }).then(result => {
|
||||
appEvents.emit('alert-success', ['Folder Created', 'OK']);
|
||||
this.$location.url(result.url);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ export class CreateFolderCtrl {
|
||||
this.titleTouched = true;
|
||||
|
||||
this.validationSrv
|
||||
.validateNewDashboardOrFolderName(this.title)
|
||||
.validateNewFolderName(this.title)
|
||||
.then(() => {
|
||||
this.hasValidationError = false;
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ export class DashboardImportCtrl {
|
||||
nameValidationError: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, private $scope, $routeParams) {
|
||||
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
|
||||
this.navModel = navModelSrv.getNav('create', 'import');
|
||||
|
||||
this.step = 1;
|
||||
@@ -93,7 +93,7 @@ export class DashboardImportCtrl {
|
||||
this.nameExists = false;
|
||||
|
||||
this.validationSrv
|
||||
.validateNewDashboardOrFolderName(this.dash.title)
|
||||
.validateNewDashboardName(0, this.dash.title)
|
||||
.then(() => {
|
||||
this.hasNameValidationError = false;
|
||||
})
|
||||
@@ -124,8 +124,7 @@ export class DashboardImportCtrl {
|
||||
inputs: inputs,
|
||||
})
|
||||
.then(res => {
|
||||
this.$location.url('dashboard/' + res.importedUri);
|
||||
this.$scope.dismiss();
|
||||
this.$location.url(res.importedUrl);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { DashboardModel } from './dashboard_model';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
export class DashboardSrv {
|
||||
dash: any;
|
||||
@@ -19,7 +20,10 @@ export class DashboardSrv {
|
||||
return this.dash;
|
||||
}
|
||||
|
||||
handleSaveDashboardError(clone, err) {
|
||||
handleSaveDashboardError(clone, options, err) {
|
||||
options = options || {};
|
||||
options.overwrite = true;
|
||||
|
||||
if (err.data && err.data.status === 'version-mismatch') {
|
||||
err.isHandled = true;
|
||||
|
||||
@@ -30,7 +34,7 @@ export class DashboardSrv {
|
||||
yesText: 'Save & Overwrite',
|
||||
icon: 'fa-warning',
|
||||
onConfirm: () => {
|
||||
this.save(clone, { overwrite: true });
|
||||
this.save(clone, options);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -40,12 +44,12 @@ export class DashboardSrv {
|
||||
|
||||
this.$rootScope.appEvent('confirm-modal', {
|
||||
title: 'Conflict',
|
||||
text: 'Dashboard with the same name exists.',
|
||||
text: 'A dashboard with the same name in selected folder already exists.',
|
||||
text2: 'Would you still like to save this dashboard?',
|
||||
yesText: 'Save & Overwrite',
|
||||
icon: 'fa-warning',
|
||||
onConfirm: () => {
|
||||
this.save(clone, { overwrite: true });
|
||||
this.save(clone, options);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -74,7 +78,7 @@ export class DashboardSrv {
|
||||
this.dash.version = data.version;
|
||||
|
||||
if (data.url !== this.$location.path()) {
|
||||
this.$location.url(data.url);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(data.url)).replace();
|
||||
}
|
||||
|
||||
this.$rootScope.appEvent('dashboard-saved', this.dash);
|
||||
@@ -90,7 +94,7 @@ export class DashboardSrv {
|
||||
return this.backendSrv
|
||||
.saveDashboard(clone, options)
|
||||
.then(this.postSave.bind(this, clone))
|
||||
.catch(this.handleSaveDashboardError.bind(this, clone));
|
||||
.catch(this.handleSaveDashboardError.bind(this, clone, options));
|
||||
}
|
||||
|
||||
saveDashboard(options, clone) {
|
||||
|
||||
@@ -93,7 +93,6 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
|
||||
}
|
||||
|
||||
renderPanelItem(panel, index) {
|
||||
console.log('render panel', index);
|
||||
return (
|
||||
<div key={index} className="add-panel__item" onClick={() => this.onAddPanel(panel)} title={panel.name}>
|
||||
<img className="add-panel__item-img" src={panel.info.logos.small} />
|
||||
|
||||
@@ -67,7 +67,7 @@ export class FolderPickerCtrl {
|
||||
this.newFolderNameTouched = true;
|
||||
|
||||
this.validationSrv
|
||||
.validateNewDashboardOrFolderName(this.newFolderName)
|
||||
.validateNewFolderName(this.newFolderName)
|
||||
.then(() => {
|
||||
this.hasValidationError = false;
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './history_srv';
|
||||
|
||||
@@ -185,7 +186,7 @@ export class HistoryListCtrl {
|
||||
return this.historySrv
|
||||
.restoreDashboard(this.dashboard, version)
|
||||
.then(response => {
|
||||
this.$location.path('dashboard/db/' + response.slug);
|
||||
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
|
||||
this.$route.reload();
|
||||
this.$rootScope.appEvent('alert-success', ['Dashboard restored', 'Restored from version ' + version]);
|
||||
})
|
||||
|
||||
@@ -96,13 +96,14 @@
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'" >
|
||||
<dashboard-permissions ng-if="ctrl.dashboard"
|
||||
<dashboard-permissions ng-if="ctrl.dashboard && !ctrl.hasUnsavedFolderChange"
|
||||
dashboardId="ctrl.dashboard.id"
|
||||
backendSrv="ctrl.backendSrv"
|
||||
folderTitle="ctrl.dashboard.meta.folderTitle"
|
||||
folderSlug="ctrl.dashboard.meta.folderSlug"
|
||||
folderId="ctrl.dashboard.meta.folderId"
|
||||
folder="ctrl.getFolder()"
|
||||
/>
|
||||
<div ng-if="ctrl.hasUnsavedFolderChange">
|
||||
<h5>You have changed folder, please save to view permissions.</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'">
|
||||
|
||||
@@ -14,6 +14,7 @@ export class SettingsCtrl {
|
||||
canSave: boolean;
|
||||
canDelete: boolean;
|
||||
sections: any[];
|
||||
hasUnsavedFolderChange: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope, private $location, private $rootScope, private backendSrv, private dashboardSrv) {
|
||||
@@ -38,6 +39,7 @@ export class SettingsCtrl {
|
||||
|
||||
this.$rootScope.onAppEvent('$routeUpdate', this.onRouteUpdated.bind(this), $scope);
|
||||
this.$rootScope.appEvent('dash-scroll', { animate: false, pos: 0 });
|
||||
this.$rootScope.onAppEvent('dashboard-saved', this.onPostSave.bind(this), $scope);
|
||||
}
|
||||
|
||||
buildSectionList() {
|
||||
@@ -135,6 +137,10 @@ export class SettingsCtrl {
|
||||
this.dashboardSrv.saveDashboard();
|
||||
}
|
||||
|
||||
onPostSave() {
|
||||
this.hasUnsavedFolderChange = false;
|
||||
}
|
||||
|
||||
hideSettings() {
|
||||
var urlParams = this.$location.search();
|
||||
delete urlParams.editview;
|
||||
@@ -195,7 +201,15 @@ export class SettingsCtrl {
|
||||
onFolderChange(folder) {
|
||||
this.dashboard.meta.folderId = folder.id;
|
||||
this.dashboard.meta.folderTitle = folder.title;
|
||||
this.dashboard.meta.folderSlug = folder.slug;
|
||||
this.hasUnsavedFolderChange = true;
|
||||
}
|
||||
|
||||
getFolder() {
|
||||
return {
|
||||
id: this.dashboard.meta.folderId,
|
||||
title: this.dashboard.meta.folderTitle,
|
||||
url: this.dashboard.meta.folderUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ describe('DashboardImportCtrl', function() {
|
||||
};
|
||||
|
||||
validationSrv = {
|
||||
validateNewDashboardOrFolderName: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
};
|
||||
|
||||
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {}, {});
|
||||
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {});
|
||||
});
|
||||
|
||||
describe('when uploading json', function() {
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
const hitTypes = {
|
||||
FOLDER: 'dash-folder',
|
||||
DASHBOARD: 'dash-db',
|
||||
};
|
||||
|
||||
export class ValidationSrv {
|
||||
rootName = 'general';
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $q, private backendSrv) {}
|
||||
|
||||
validateNewDashboardOrFolderName(name) {
|
||||
validateNewDashboardName(folderId, name) {
|
||||
return this.validate(folderId, name, 'A dashboard in this folder with the same name already exists');
|
||||
}
|
||||
|
||||
validateNewFolderName(name) {
|
||||
return this.validate(0, name, 'A folder or dashboard in the general folder with the same name already exists');
|
||||
}
|
||||
|
||||
private validate(folderId, name, existingErrorMessage) {
|
||||
name = (name || '').trim();
|
||||
const nameLowerCased = name.toLowerCase();
|
||||
|
||||
if (name.length === 0) {
|
||||
return this.$q.reject({
|
||||
@@ -16,7 +30,7 @@ export class ValidationSrv {
|
||||
});
|
||||
}
|
||||
|
||||
if (name.toLowerCase() === this.rootName) {
|
||||
if (folderId === 0 && nameLowerCased === this.rootName) {
|
||||
return this.$q.reject({
|
||||
type: 'EXISTING',
|
||||
message: 'This is a reserved name and cannot be used for a folder.',
|
||||
@@ -25,12 +39,26 @@ export class ValidationSrv {
|
||||
|
||||
let deferred = this.$q.defer();
|
||||
|
||||
this.backendSrv.search({ query: name }).then(res => {
|
||||
for (let hit of res) {
|
||||
if (name.toLowerCase() === hit.title.toLowerCase()) {
|
||||
const promises = [];
|
||||
promises.push(this.backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
|
||||
promises.push(this.backendSrv.search({ type: hitTypes.DASHBOARD, folderIds: [folderId], query: name }));
|
||||
|
||||
this.$q.all(promises).then(res => {
|
||||
let hits = [];
|
||||
|
||||
if (res.length > 0 && res[0].length > 0) {
|
||||
hits = res[0];
|
||||
}
|
||||
|
||||
if (res.length > 1 && res[1].length > 0) {
|
||||
hits = hits.concat(res[1]);
|
||||
}
|
||||
|
||||
for (let hit of hits) {
|
||||
if (nameLowerCased === hit.title.toLowerCase()) {
|
||||
deferred.reject({
|
||||
type: 'EXISTING',
|
||||
message: 'A folder or dashboard with the same name already exists',
|
||||
message: existingErrorMessage,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
Old picker
|
||||
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
|
||||
-->
|
||||
<select-user-picker handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
|
||||
<select-user-picker class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
|
||||
// update scrollbar after mounting
|
||||
ctrl.events.on('component-did-mount', () => {
|
||||
if (ctrl.__proto__.constructor.scrollable) {
|
||||
panelScrollbar = new PerfectScrollbar(panelContent[0]);
|
||||
panelScrollbar = new PerfectScrollbar(panelContent[0], {
|
||||
wheelPropagation: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export class SoloPanelCtrl {
|
||||
|
||||
$scope.init = function() {
|
||||
contextSrv.sidemenu = false;
|
||||
appEvents.emit('toggle-sidemenu');
|
||||
appEvents.emit('toggle-sidemenu-hidden');
|
||||
|
||||
var params = $location.search();
|
||||
panelId = parseInt(params.panelId);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<i class="icon-gf icon-gf-dashboard"></i>
|
||||
</td>
|
||||
<td>
|
||||
<a href="dashboard/{{dash.importedUri}}" ng-show="dash.imported">
|
||||
<a href="{{dash.importedUrl}}" ng-show="dash.imported">
|
||||
{{dash.title}}
|
||||
</a>
|
||||
<span ng-show="!dash.imported">
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<empty-list-cta model="{
|
||||
title: 'There are no data sources defined yet',
|
||||
buttonIcon: 'gicon gicon-add-datasources',
|
||||
buttonLink: '/datasources/new',
|
||||
buttonLink: 'datasources/new',
|
||||
buttonTitle: 'Add data source',
|
||||
proTip: 'You can also define data sources through configuration files.',
|
||||
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
|
||||
|
||||
Reference in New Issue
Block a user