Explorar el Código

dashfolders: New Dashboard Folder page

Fixes #10083. Permissions page is just a placeholder
for now.
Marcus Efraimsson hace 8 años
padre
commit
2ea663df78

+ 15 - 0
pkg/api/index.go

@@ -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,

+ 19 - 1
public/app/core/components/PageHeader/PageHeader.tsx

@@ -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">

+ 98 - 0
public/app/core/components/manage_dashboards/manage_dashboards.html

@@ -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>&nbsp;&nbsp;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>&nbsp;&nbsp;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>

+ 221 - 0
public/app/core/components/manage_dashboards/manage_dashboards.ts

@@ -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);

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

@@ -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>
 

+ 1 - 1
public/app/core/components/search/search_results.html

@@ -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>&nbsp;
     </div>
     <i class="fa fa-minus search-section__header__toggle" ng-show="section.expanded"></i>

+ 3 - 1
public/app/core/core.ts

@@ -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
 };

+ 8 - 3
public/app/core/routes/routes.ts

@@ -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', {

+ 3 - 3
public/app/features/dashboard/specs/dashboard_list_ctrl.jest.ts → public/app/core/specs/manage_dashboards.jest.ts

@@ -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);
 }

+ 4 - 0
public/app/features/dashboard/all.ts

@@ -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 - 206
public/app/features/dashboard/dashboard_list_ctrl.ts

@@ -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 - 0
public/app/features/dashboard/folder_dashboards_ctrl.ts

@@ -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 - 0
public/app/features/dashboard/folder_page_loader.ts

@@ -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 - 0
public/app/features/dashboard/folder_permissions_ctrl.ts

@@ -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);
+    }
+  }
+}

+ 0 - 121
public/app/features/dashboard/partials/dashboardList.html

@@ -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>&nbsp;{{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>&nbsp;&nbsp;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>&nbsp;&nbsp;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>

+ 5 - 0
public/app/features/dashboard/partials/dashboard_list.html

@@ -0,0 +1,5 @@
+<page-header model="ctrl.navModel"></page-header>
+
+<div class="page-container page-body">
+  <manage-dashboards />
+</div>

+ 5 - 0
public/app/features/dashboard/partials/folder_dashboards.html

@@ -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>

+ 5 - 0
public/app/features/dashboard/partials/folder_permissions.html

@@ -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>