mirror of
https://github.com/grafana/grafana.git
synced 2025-01-17 04:02:50 -06:00
dashfolders: New Dashboard Folder page
Fixes #10083. Permissions page is just a placeholder for now.
This commit is contained in:
parent
10b0fc79f3
commit
2ea663df78
@ -118,6 +118,21 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
Children: dashboardChildNavs,
|
||||
})
|
||||
|
||||
dashboardFolderChildNavs := []*dtos.NavLink{
|
||||
{Text: "Dashboards", Id: "manage-folder-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "fa fa-fw fa-th-large"},
|
||||
{Text: "Permissions", Id: "manage-folder-permissions", Url: setting.AppSubUrl + "/dashboards?1", Icon: "fa fa-fw fa-lock"},
|
||||
}
|
||||
|
||||
data.NavTree = append(data.NavTree, &dtos.NavLink{
|
||||
Text: "Dashboards",
|
||||
Id: "manage-folder",
|
||||
SubTitle: "Manage folder dashboards & permissions",
|
||||
Icon: "fa fa-folder-open",
|
||||
Url: setting.AppSubUrl + "/",
|
||||
HideFromMenu: true,
|
||||
Children: dashboardFolderChildNavs,
|
||||
})
|
||||
|
||||
if c.IsSignedIn {
|
||||
profileNode := &dtos.NavLink{
|
||||
Text: c.SignedInUser.Name,
|
||||
|
@ -72,6 +72,19 @@ export default class PageHeader extends React.Component<IProps, any> {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderBreadcrumb(breadcrumbs) {
|
||||
const breadcrumbsResult = [];
|
||||
for (let i = 0; i < breadcrumbs.length; i++) {
|
||||
const bc = breadcrumbs[i];
|
||||
if (bc.uri) {
|
||||
breadcrumbsResult.push(<a className="text-link" key={i} href={bc.uri}>{bc.title}</a>);
|
||||
} else {
|
||||
breadcrumbsResult.push(<span key={i}> / {bc.title}</span>);
|
||||
}
|
||||
}
|
||||
return breadcrumbsResult;
|
||||
}
|
||||
|
||||
renderHeaderTitle(main) {
|
||||
return (
|
||||
<div className="page-header__inner">
|
||||
@ -81,7 +94,12 @@ export default class PageHeader extends React.Component<IProps, any> {
|
||||
</span>
|
||||
|
||||
<div className="page-header__info-block">
|
||||
<h1 className="page-header__title">{main.text}</h1>
|
||||
{main.text && <h1 className="page-header__title">{main.text}</h1>}
|
||||
{main.breadcrumbs && main.breadcrumbs.length > 0 && (
|
||||
<h1 className="page-header__title">
|
||||
{this.renderBreadcrumb(main.breadcrumbs)}
|
||||
</h1>)
|
||||
}
|
||||
{main.subTitle && <div className="page-header__sub-title">{main.subTitle}</div>}
|
||||
{main.subType && (
|
||||
<div className="page-header__stamps">
|
||||
|
@ -0,0 +1,98 @@
|
||||
<div class="page-action-bar">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label">Search</label>
|
||||
<input type="text" class="gf-form-input max-width-30" placeholder="Find Dashboard by name" tabindex="1" give-focus="true" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.onQueryChange()" />
|
||||
</div>
|
||||
<div class="page-action-bar__spacer"></div>
|
||||
<a class="btn btn-success" href="/dashboard/new">
|
||||
<i class="fa fa-plus"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
<a class="btn btn-success" href="/dashboard/new/?editview=new-folder" ng-if="!ctrl.folderId">
|
||||
<i class="fa fa-plus"></i>
|
||||
Folder
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.query.tag.length">
|
||||
Filters:
|
||||
<span ng-repeat="tagName in ctrl.query.tag">
|
||||
<a ng-click="ctrl.removeTag(tagName, $event)" tag-color-from-name="tagName" class="label label-tag">
|
||||
<i class="fa fa-remove"></i>
|
||||
{{tagName}}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<div class="gf-form-button-row" ng-show="ctrl.hasFilters">
|
||||
<button
|
||||
type="button"
|
||||
class="btn gf-form-button btn-inverse btn-small"
|
||||
ng-click="ctrl.clearFilters()">
|
||||
<i class="fa fa-close"></i> Clear current search query and filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-list" ng-show="ctrl.sections.length > 0">
|
||||
<div class="search-results-filter-row">
|
||||
<gf-form-switch
|
||||
on-change="ctrl.onSelectAllChanged()"
|
||||
checked="ctrl.selectAllChecked"
|
||||
switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox"
|
||||
/>
|
||||
<div class="search-results-filter-row__filters">
|
||||
<select
|
||||
class="search-results-filter-row__filters-item gf-form-input"
|
||||
ng-model="ctrl.selectedStarredFilter"
|
||||
ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions"
|
||||
ng-change="ctrl.onStarredFilterChange()"
|
||||
ng-show="!(ctrl.canMove || ctrl.canDelete)"
|
||||
/>
|
||||
<select
|
||||
class="search-results-filter-row__filters-item gf-form-input"
|
||||
ng-model="ctrl.selectedTagFilter"
|
||||
ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions"
|
||||
ng-change="ctrl.onTagFilterChange()"
|
||||
ng-show="!(ctrl.canMove || ctrl.canDelete)"
|
||||
/>
|
||||
<div class="gf-form-button-row" ng-show="ctrl.canMove || ctrl.canDelete">
|
||||
<button type="button"
|
||||
class="btn gf-form-button btn-inverse"
|
||||
ng-disabled="!ctrl.canMove"
|
||||
ng-click="ctrl.moveTo()"
|
||||
bs-tooltip="ctrl.canMove ? '' : 'Select a dashboard to move (cannot move folders)'"
|
||||
data-placement="bottom">
|
||||
<i class="fa fa-exchange"></i> Move
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn gf-form-button btn-danger"
|
||||
ng-click="ctrl.delete()"
|
||||
ng-disabled="!ctrl.canDelete">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-results-container">
|
||||
<dashboard-search-results
|
||||
results="ctrl.sections"
|
||||
editable="true"
|
||||
on-selection-changed="ctrl.selectionChanged()"
|
||||
on-tag-selected="ctrl.filterByTag($tag)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.folderId && !ctrl.hasFilters && ctrl.sections.length === 0">
|
||||
<empty-list-cta model="{
|
||||
title: 'This folder doesn\'t have any dashboards yet',
|
||||
buttonIcon: 'gicon gicon-dashboard-new',
|
||||
buttonLink: '/dashboard/new',
|
||||
buttonTitle: 'Create Dashboard',
|
||||
proTip: 'You can bulk move dashboards into this folder from the main dashboard list.',
|
||||
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
|
||||
proTipLinkTitle: 'Learn more',
|
||||
proTipTarget: '_blank'
|
||||
}" />
|
||||
</div>
|
@ -0,0 +1,221 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
|
||||
export class ManageDashboardsCtrl {
|
||||
public sections: any[];
|
||||
tagFilterOptions: any[];
|
||||
selectedTagFilter: any;
|
||||
query: any;
|
||||
navModel: any;
|
||||
canDelete = false;
|
||||
canMove = false;
|
||||
hasFilters = false;
|
||||
selectAllChecked = false;
|
||||
starredFilterOptions = [{ text: 'Filter by Starred', disabled: true }, { text: 'Yes' }, { text: 'No' }];
|
||||
selectedStarredFilter: any;
|
||||
folderId?: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, navModelSrv, private $q, private searchSrv: SearchSrv) {
|
||||
this.query = { query: '', mode: 'tree', tag: [], starred: false, skipRecent: true, skipStarred: true };
|
||||
|
||||
if (this.folderId) {
|
||||
this.query.folderIds = [this.folderId];
|
||||
}
|
||||
|
||||
this.selectedStarredFilter = this.starredFilterOptions[0];
|
||||
|
||||
this.getDashboards().then(() => {
|
||||
this.getTags();
|
||||
});
|
||||
}
|
||||
|
||||
getDashboards() {
|
||||
return this.searchSrv.search(this.query).then((result) => {
|
||||
return this.initDashboardList(result);
|
||||
});
|
||||
}
|
||||
|
||||
initDashboardList(result: any) {
|
||||
this.canMove = false;
|
||||
this.canDelete = false;
|
||||
this.selectAllChecked = false;
|
||||
this.hasFilters = this.query.query.length > 0 || this.query.tag.length > 0 || this.query.starred;
|
||||
|
||||
if (!result) {
|
||||
this.sections = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.sections = result;
|
||||
|
||||
for (let section of this.sections) {
|
||||
section.checked = false;
|
||||
|
||||
for (let dashboard of section.items) {
|
||||
dashboard.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectionChanged() {
|
||||
|
||||
let selectedDashboards = 0;
|
||||
|
||||
for (let section of this.sections) {
|
||||
selectedDashboards += _.filter(section.items, { checked: true }).length;
|
||||
}
|
||||
|
||||
const selectedFolders = _.filter(this.sections, { checked: true }).length;
|
||||
this.canMove = selectedDashboards > 0 && selectedFolders === 0;
|
||||
this.canDelete = selectedDashboards > 0 || selectedFolders > 0;
|
||||
}
|
||||
|
||||
getDashboardsToDelete() {
|
||||
let selectedDashboards = [];
|
||||
|
||||
for (const section of this.sections) {
|
||||
if (section.checked) {
|
||||
selectedDashboards.push(section.uri);
|
||||
} else {
|
||||
const selected = _.filter(section.items, { checked: true });
|
||||
selectedDashboards.push(..._.map(selected, 'uri'));
|
||||
}
|
||||
}
|
||||
|
||||
return selectedDashboards;
|
||||
}
|
||||
|
||||
getFolderIds(sections) {
|
||||
const ids = [];
|
||||
for (let s of sections) {
|
||||
if (s.checked) {
|
||||
ids.push(s.id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
delete() {
|
||||
const selectedDashboards = this.getDashboardsToDelete();
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: `Do you want to delete the ${selectedDashboards.length} selected dashboards?`,
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
const promises = [];
|
||||
for (let dash of selectedDashboards) {
|
||||
promises.push(this.backendSrv.delete(`/api/dashboards/${dash}`));
|
||||
}
|
||||
|
||||
this.$q.all(promises).then(() => {
|
||||
this.getDashboards();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDashboardsToMove() {
|
||||
let selectedDashboards = [];
|
||||
|
||||
for (const section of this.sections) {
|
||||
const selected = _.filter(section.items, { checked: true });
|
||||
selectedDashboards.push(..._.map(selected, 'uri'));
|
||||
}
|
||||
|
||||
return selectedDashboards;
|
||||
}
|
||||
|
||||
moveTo() {
|
||||
const selectedDashboards = this.getDashboardsToMove();
|
||||
|
||||
const template = '<move-to-folder-modal dismiss="dismiss()" ' +
|
||||
'dashboards="model.dashboards" after-save="model.afterSave()">' +
|
||||
'</move-to-folder-modal>`';
|
||||
appEvents.emit('show-modal', {
|
||||
templateHtml: template,
|
||||
modalClass: 'modal--narrow',
|
||||
model: { dashboards: selectedDashboards, afterSave: this.getDashboards.bind(this) }
|
||||
});
|
||||
}
|
||||
|
||||
getTags() {
|
||||
return this.searchSrv.getDashboardTags().then((results) => {
|
||||
this.tagFilterOptions = [{ term: 'Filter By Tag', disabled: true }].concat(results);
|
||||
this.selectedTagFilter = this.tagFilterOptions[0];
|
||||
});
|
||||
}
|
||||
|
||||
filterByTag(tag) {
|
||||
if (_.indexOf(this.query.tag, tag) === -1) {
|
||||
this.query.tag.push(tag);
|
||||
}
|
||||
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onQueryChange() {
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onTagFilterChange() {
|
||||
var res = this.filterByTag(this.selectedTagFilter.term);
|
||||
this.selectedTagFilter = this.tagFilterOptions[0];
|
||||
return res;
|
||||
}
|
||||
|
||||
removeTag(tag, evt) {
|
||||
this.query.tag = _.without(this.query.tag, tag);
|
||||
this.getDashboards();
|
||||
if (evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onStarredFilterChange() {
|
||||
this.query.starred = this.selectedStarredFilter.text === 'Yes';
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onSelectAllChanged() {
|
||||
for (let section of this.sections) {
|
||||
if (!section.hideHeader) {
|
||||
section.checked = this.selectAllChecked;
|
||||
}
|
||||
|
||||
section.items = _.map(section.items, (item) => {
|
||||
item.checked = this.selectAllChecked;
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
clearFilters() {
|
||||
this.query.query = '';
|
||||
this.query.tag = [];
|
||||
this.query.starred = false;
|
||||
this.getDashboards();
|
||||
}
|
||||
}
|
||||
|
||||
export function manageDashboardsDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'public/app/core/components/manage_dashboards/manage_dashboards.html',
|
||||
controller: ManageDashboardsCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: {
|
||||
folderId: '='
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('manageDashboards', manageDashboardsDirective);
|
@ -21,12 +21,12 @@
|
||||
<div class="search-dropdown">
|
||||
<div class="search-dropdown__col_1">
|
||||
<div class="search-results-container" grafana-scrollbar>
|
||||
<h6 ng-show="!ctrl.isLoading && ctrl.results.length === 0">No dashboards matching your query were found.</h6>
|
||||
<dashboard-search-results
|
||||
results="ctrl.results"
|
||||
on-tag-selected="ctrl.filterByTag($tag)"
|
||||
on-folder-expanding="ctrl.folderExpanding()"
|
||||
on-folder-expanded="ctrl.folderExpanded($folder)" />
|
||||
<h6 ng-show="!ctrl.isLoading && ctrl.results.length === 0">No dashboards matching your query were found.</h6>
|
||||
<dashboard-search-results
|
||||
results="ctrl.results"
|
||||
on-tag-selected="ctrl.filterByTag($tag)"
|
||||
on-folder-expanding="ctrl.folderExpanding()"
|
||||
on-folder-expanded="ctrl.folderExpanded($folder)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<i class="search-section__header__icon" ng-class="section.icon"></i>
|
||||
<span class="search-section__header__text">{{::section.title}}</span>
|
||||
<div ng-show="ctrl.editable && section.id > 0 && section.expanded" ng-click="ctrl.navigateToFolder(section, $event)">
|
||||
<div ng-show="ctrl.editable && section.id > 0" ng-click="ctrl.navigateToFolder(section, $event)">
|
||||
<i class="fa fa-cog search-section__header__toggle"></i>
|
||||
</div>
|
||||
<i class="fa fa-minus search-section__header__toggle" ng-show="section.expanded"></i>
|
||||
|
@ -53,6 +53,7 @@ import {orgSwitcher} from './components/org_switcher';
|
||||
import {profiler} from './profiler';
|
||||
import {registerAngularDirectives} from './angular_wrappers';
|
||||
import {searchResultsDirective} from './components/search/search_results';
|
||||
import {manageDashboardsDirective} from './components/manage_dashboards/manage_dashboards';
|
||||
|
||||
export {
|
||||
profiler,
|
||||
@ -85,5 +86,6 @@ export {
|
||||
geminiScrollbar,
|
||||
gfPageDirective,
|
||||
orgSwitcher,
|
||||
searchResultsDirective
|
||||
searchResultsDirective,
|
||||
manageDashboardsDirective
|
||||
};
|
||||
|
@ -69,13 +69,18 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/dashboards', {
|
||||
templateUrl: 'public/app/features/dashboard/partials/dashboardList.html',
|
||||
templateUrl: 'public/app/features/dashboard/partials/dashboard_list.html',
|
||||
controller : 'DashboardListCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/dashboards/folder/:folderId/:type/:slug/permissions', {
|
||||
templateUrl: 'public/app/features/dashboard/partials/folder_permissions.html',
|
||||
controller : 'FolderPermissionsCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/dashboards/folder/:folderId/:type/:slug', {
|
||||
templateUrl: 'public/app/features/dashboard/partials/dashboardList.html',
|
||||
controller : 'DashboardListCtrl',
|
||||
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
|
||||
controller : 'FolderDashboardsCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/org', {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DashboardListCtrl } from '../dashboard_list_ctrl';
|
||||
import { ManageDashboardsCtrl } from 'app/core/components/manage_dashboards/manage_dashboards';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
import q from 'q';
|
||||
|
||||
describe('DashboardListCtrl', () => {
|
||||
describe('ManageDashboards', () => {
|
||||
let ctrl;
|
||||
|
||||
describe('when browsing dashboards', () => {
|
||||
@ -542,5 +542,5 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
|
||||
}
|
||||
};
|
||||
|
||||
return new DashboardListCtrl({}, { getNav: () => { } }, q, <SearchSrv>searchSrvStub, {});
|
||||
return new ManageDashboardsCtrl({}, { getNav: () => { } }, q, <SearchSrv>searchSrvStub);
|
||||
}
|
@ -29,7 +29,11 @@ import './move_to_folder_modal/move_to_folder';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
import {DashboardListCtrl} from './dashboard_list_ctrl';
|
||||
import {FolderDashboardsCtrl} from './folder_dashboards_ctrl';
|
||||
import {FolderPermissionsCtrl} from './folder_permissions_ctrl';
|
||||
import {DashboardImportCtrl} from './dashboard_import_ctrl';
|
||||
|
||||
coreModule.controller('DashboardListCtrl', DashboardListCtrl);
|
||||
coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl);
|
||||
coreModule.controller('FolderPermissionsCtrl', FolderPermissionsCtrl);
|
||||
coreModule.controller('DashboardImportCtrl', DashboardImportCtrl);
|
||||
|
@ -1,213 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { SearchSrv } from 'app/core/services/search_srv';
|
||||
|
||||
export class DashboardListCtrl {
|
||||
public sections: any [];
|
||||
tagFilterOptions: any [];
|
||||
selectedTagFilter: any;
|
||||
query: any;
|
||||
navModel: any;
|
||||
canDelete = false;
|
||||
canMove = false;
|
||||
hasFilters = false;
|
||||
selectAllChecked = false;
|
||||
starredFilterOptions = [{text: 'Filter by Starred', disabled: true}, {text: 'Yes'}, {text: 'No'}];
|
||||
selectedStarredFilter: any;
|
||||
folderTitle = null;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, navModelSrv, private $q, private searchSrv: SearchSrv, private $routeParams) {
|
||||
constructor(navModelSrv) {
|
||||
this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0);
|
||||
this.query = {query: '', mode: 'tree', tag: [], starred: false, skipRecent: true, skipStarred: true};
|
||||
|
||||
this.selectedStarredFilter = this.starredFilterOptions[0];
|
||||
|
||||
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) {
|
||||
backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
|
||||
this.folderTitle = result.dashboard.title;
|
||||
this.query.folderIds = [result.dashboard.id];
|
||||
|
||||
this.getDashboards().then(() => {
|
||||
this.getTags();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.getDashboards().then(() => {
|
||||
this.getTags();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getDashboards() {
|
||||
return this.searchSrv.search(this.query).then((result) => {
|
||||
return this.initDashboardList(result);
|
||||
});
|
||||
}
|
||||
|
||||
initDashboardList(result: any) {
|
||||
this.canMove = false;
|
||||
this.canDelete = false;
|
||||
this.selectAllChecked = false;
|
||||
this.hasFilters = this.query.query.length > 0 || this.query.tag.length > 0 || this.query.starred;
|
||||
|
||||
if (!result) {
|
||||
this.sections = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.sections = result;
|
||||
|
||||
for (let section of this.sections) {
|
||||
section.checked = false;
|
||||
|
||||
for (let dashboard of section.items) {
|
||||
dashboard.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectionChanged() {
|
||||
|
||||
let selectedDashboards = 0;
|
||||
|
||||
for (let section of this.sections) {
|
||||
selectedDashboards += _.filter(section.items, {checked: true}).length;
|
||||
}
|
||||
|
||||
const selectedFolders = _.filter(this.sections, {checked: true}).length;
|
||||
this.canMove = selectedDashboards > 0 && selectedFolders === 0;
|
||||
this.canDelete = selectedDashboards > 0 || selectedFolders > 0;
|
||||
}
|
||||
|
||||
getDashboardsToDelete() {
|
||||
let selectedDashboards = [];
|
||||
|
||||
for (const section of this.sections) {
|
||||
if (section.checked) {
|
||||
selectedDashboards.push(section.uri);
|
||||
} else {
|
||||
const selected = _.filter(section.items, {checked: true});
|
||||
selectedDashboards.push(... _.map(selected, 'uri'));
|
||||
}
|
||||
}
|
||||
|
||||
return selectedDashboards;
|
||||
}
|
||||
|
||||
getFolderIds(sections) {
|
||||
const ids = [];
|
||||
for (let s of sections) {
|
||||
if (s.checked) {
|
||||
ids.push(s.id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
delete() {
|
||||
const selectedDashboards = this.getDashboardsToDelete();
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: `Do you want to delete the ${selectedDashboards.length} selected dashboards?`,
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
const promises = [];
|
||||
for (let dash of selectedDashboards) {
|
||||
promises.push(this.backendSrv.delete(`/api/dashboards/${dash}`));
|
||||
}
|
||||
|
||||
this.$q.all(promises).then(() => {
|
||||
this.getDashboards();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDashboardsToMove() {
|
||||
let selectedDashboards = [];
|
||||
|
||||
for (const section of this.sections) {
|
||||
const selected = _.filter(section.items, {checked: true});
|
||||
selectedDashboards.push(... _.map(selected, 'uri'));
|
||||
}
|
||||
|
||||
return selectedDashboards;
|
||||
}
|
||||
|
||||
moveTo() {
|
||||
const selectedDashboards = this.getDashboardsToMove();
|
||||
|
||||
const template = '<move-to-folder-modal dismiss="dismiss()" ' +
|
||||
'dashboards="model.dashboards" after-save="model.afterSave()">' +
|
||||
'</move-to-folder-modal>`';
|
||||
appEvents.emit('show-modal', {
|
||||
templateHtml: template,
|
||||
modalClass: 'modal--narrow',
|
||||
model: {dashboards: selectedDashboards, afterSave: this.getDashboards.bind(this)}
|
||||
});
|
||||
}
|
||||
|
||||
getTags() {
|
||||
return this.searchSrv.getDashboardTags().then((results) => {
|
||||
this.tagFilterOptions = [{ term: 'Filter By Tag', disabled: true }].concat(results);
|
||||
this.selectedTagFilter = this.tagFilterOptions[0];
|
||||
});
|
||||
}
|
||||
|
||||
filterByTag(tag) {
|
||||
if (_.indexOf(this.query.tag, tag) === -1) {
|
||||
this.query.tag.push(tag);
|
||||
}
|
||||
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onQueryChange() {
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onTagFilterChange() {
|
||||
var res = this.filterByTag(this.selectedTagFilter.term);
|
||||
this.selectedTagFilter = this.tagFilterOptions[0];
|
||||
return res;
|
||||
}
|
||||
|
||||
removeTag(tag, evt) {
|
||||
this.query.tag = _.without(this.query.tag, tag);
|
||||
this.getDashboards();
|
||||
if (evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onStarredFilterChange() {
|
||||
this.query.starred = this.selectedStarredFilter.text === 'Yes';
|
||||
return this.getDashboards();
|
||||
}
|
||||
|
||||
onSelectAllChanged() {
|
||||
for (let section of this.sections) {
|
||||
if (!section.hideHeader) {
|
||||
section.checked = this.selectAllChecked;
|
||||
}
|
||||
|
||||
section.items = _.map(section.items, (item) => {
|
||||
item.checked = this.selectAllChecked;
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
clearFilters() {
|
||||
this.query.query = '';
|
||||
this.query.tag = [];
|
||||
this.query.starred = false;
|
||||
this.getDashboards();
|
||||
}
|
||||
}
|
||||
|
16
public/app/features/dashboard/folder_dashboards_ctrl.ts
Normal file
16
public/app/features/dashboard/folder_dashboards_ctrl.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {FolderPageLoader} from './folder_page_loader';
|
||||
|
||||
export class FolderDashboardsCtrl {
|
||||
navModel: any;
|
||||
folderId: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, navModelSrv, private $routeParams) {
|
||||
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) {
|
||||
this.folderId = $routeParams.folderId;
|
||||
this.navModel = navModelSrv.getNav('manage-folder', 'manage-folder-dashboards', 0);
|
||||
|
||||
new FolderPageLoader(this.backendSrv, this.$routeParams).load(this.navModel, this.folderId);
|
||||
}
|
||||
}
|
||||
}
|
21
public/app/features/dashboard/folder_page_loader.ts
Normal file
21
public/app/features/dashboard/folder_page_loader.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import _ from "lodash";
|
||||
|
||||
export class FolderPageLoader {
|
||||
constructor(private backendSrv, private $routeParams) { }
|
||||
|
||||
load(navModel, folderId) {
|
||||
this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
|
||||
const folderTitle = result.dashboard.title;
|
||||
navModel.main.text = '';
|
||||
navModel.main.breadcrumbs = [
|
||||
{ title: 'Dashboards', uri: '/dashboards' },
|
||||
{ title: folderTitle }
|
||||
];
|
||||
const folderUrl = `/dashboards/folder/${folderId}/${result.meta.type}/${result.meta.slug}`;
|
||||
const dashTab = _.find(navModel.main.children, { id: 'manage-folder-dashboards' });
|
||||
dashTab.url = folderUrl;
|
||||
const permTab = _.find(navModel.main.children, { id: 'manage-folder-permissions' });
|
||||
permTab.url = folderUrl + '/permissions';
|
||||
});
|
||||
}
|
||||
}
|
16
public/app/features/dashboard/folder_permissions_ctrl.ts
Normal file
16
public/app/features/dashboard/folder_permissions_ctrl.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {FolderPageLoader} from './folder_page_loader';
|
||||
|
||||
export class FolderPermissionsCtrl {
|
||||
navModel: any;
|
||||
folderId: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, navModelSrv, private $routeParams) {
|
||||
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) {
|
||||
this.folderId = $routeParams.folderId;
|
||||
this.navModel = navModelSrv.getNav('manage-folder', 'manage-folder-permissions', 0);
|
||||
|
||||
new FolderPageLoader(this.backendSrv, this.$routeParams).load(this.navModel, this.folderId);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
<page-header model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<div class="page-action-bar" ng-show="ctrl.folderTitle">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<h3 class="page-sub-heading">
|
||||
<i class="fa fa-folder-open"></i> {{ctrl.folderTitle}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="page-action-bar__spacer"></div>
|
||||
<button class="btn btn-inverse" disabled>Permissions</button>
|
||||
<a class="btn btn-success" href="/dashboard/new">
|
||||
<i class="fa fa-plus"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
<a class="btn btn-success" href="/dashboard/new/?editview=new-folder">
|
||||
<i class="fa fa-plus"></i>
|
||||
Folder
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="page-action-bar">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label">Search</label>
|
||||
<input type="text" class="gf-form-input max-width-30" placeholder="Find Dashboard by name" tabindex="1" give-focus="true" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.onQueryChange()" />
|
||||
</div>
|
||||
<div class="page-action-bar__spacer"></div>
|
||||
<a class="btn btn-success" href="/dashboard/new" ng-hide="ctrl.folderTitle">
|
||||
<i class="fa fa-plus"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
<a class="btn btn-success" href="/dashboard/new/?editview=new-folder" ng-hide="ctrl.folderTitle">
|
||||
<i class="fa fa-plus"></i>
|
||||
Folder
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.query.tag.length">
|
||||
Filters:
|
||||
<span ng-repeat="tagName in ctrl.query.tag">
|
||||
<a ng-click="ctrl.removeTag(tagName, $event)" tag-color-from-name="tagName" class="label label-tag">
|
||||
<i class="fa fa-remove"></i>
|
||||
{{tagName}}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<div class="gf-form-button-row"
|
||||
ng-show="ctrl.hasFilters">
|
||||
<button
|
||||
type="button"
|
||||
class="btn gf-form-button btn-inverse btn-small"
|
||||
ng-click="ctrl.clearFilters()">
|
||||
<i class="fa fa-close"></i> Clear current search query and filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!ctrl.hasFilters && ctrl.sections.length === 0">
|
||||
<empty-list-cta model="{
|
||||
title: 'This folder doesn\'t have any dashboards yet',
|
||||
buttonIcon: 'gicon gicon-dashboard-new',
|
||||
buttonLink: '/dashboard/new',
|
||||
buttonTitle: 'Create Dashboard',
|
||||
proTip: 'You can bulk move dashboards into this folder from the main dashboard list.',
|
||||
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
|
||||
proTipLinkTitle: 'Learn more',
|
||||
proTipTarget: '_blank'
|
||||
}" />
|
||||
</div>
|
||||
|
||||
<div class="dashboard-list" ng-show="ctrl.sections.length > 0">
|
||||
<div class="search-results-filter-row">
|
||||
<gf-form-switch
|
||||
on-change="ctrl.onSelectAllChanged()"
|
||||
checked="ctrl.selectAllChecked"
|
||||
switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox"
|
||||
/>
|
||||
<div class="search-results-filter-row__filters">
|
||||
<select
|
||||
class="search-results-filter-row__filters-item gf-form-input"
|
||||
ng-model="ctrl.selectedStarredFilter"
|
||||
ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions"
|
||||
ng-change="ctrl.onStarredFilterChange()"
|
||||
ng-show="!(ctrl.canMove || ctrl.canDelete)"
|
||||
/>
|
||||
<select
|
||||
class="search-results-filter-row__filters-item gf-form-input"
|
||||
ng-model="ctrl.selectedTagFilter"
|
||||
ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions"
|
||||
ng-change="ctrl.onTagFilterChange()"
|
||||
ng-show="!(ctrl.canMove || ctrl.canDelete)"
|
||||
/>
|
||||
<div class="gf-form-button-row" ng-show="ctrl.canMove || ctrl.canDelete">
|
||||
<button type="button"
|
||||
class="btn gf-form-button btn-inverse"
|
||||
ng-disabled="!ctrl.canMove"
|
||||
ng-click="ctrl.moveTo()"
|
||||
bs-tooltip="ctrl.canMove ? '' : 'Select a dashboard to move (cannot move folders)'"
|
||||
data-placement="bottom">
|
||||
<i class="fa fa-exchange"></i> Move
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn gf-form-button btn-danger"
|
||||
ng-click="ctrl.delete()"
|
||||
ng-disabled="!ctrl.canDelete">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-results-container">
|
||||
<dashboard-search-results
|
||||
results="ctrl.sections"
|
||||
editable="true"
|
||||
on-selection-changed="ctrl.selectionChanged()"
|
||||
on-tag-selected="ctrl.filterByTag($tag)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
<page-header model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<manage-dashboards />
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
<page-header ng-if="ctrl.navModel" model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<manage-dashboards ng-if="ctrl.folderId" folder-id="ctrl.folderId" />
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
<page-header model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<h1>Coming soon! Permissions will be added in Grafana 5.0 beta.</h1>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user