فهرست منبع

Merge branch 'develop' into 9879-login

Johannes Schill 8 سال پیش
والد
کامیت
c433597f99
26فایلهای تغییر یافته به همراه313 افزوده شده و 182 حذف شده
  1. 1 1
      pkg/api/index.go
  2. 8 8
      pkg/services/dashboards/dashboards.go
  3. 0 3
      pkg/services/provisioning/dashboards/types.go
  4. 2 2
      public/app/core/components/PageHeader/PageHeader.tsx
  5. 0 1
      public/app/core/components/search/search_results.ts
  6. 5 0
      public/app/core/routes/routes.ts
  7. 2 0
      public/app/features/dashboard/all.ts
  8. 1 4
      public/app/features/dashboard/dashnav/dashnav.ts
  9. 19 3
      public/app/features/dashboard/folder_page_loader.ts
  10. 83 0
      public/app/features/dashboard/folder_settings_ctrl.ts
  11. 24 0
      public/app/features/dashboard/partials/folder_settings.html
  12. 1 1
      public/app/features/dashboard/repeat_option/repeat_option.ts
  13. 1 3
      public/app/features/org/org_users_ctrl.ts
  14. 9 13
      public/app/features/plugins/ds_list_ctrl.ts
  15. 8 2
      public/app/features/plugins/partials/ds_list.html
  16. 58 63
      public/app/features/plugins/partials/plugin_edit.html
  17. 0 37
      public/app/features/plugins/partials/plugin_list.html
  18. 8 6
      public/app/features/plugins/partials/plugin_page.html
  19. 61 19
      public/app/features/plugins/plugin_edit_ctrl.ts
  20. 14 4
      public/app/features/plugins/plugin_page_ctrl.ts
  21. 2 2
      public/app/features/templating/partials/editor.html
  22. 1 1
      public/app/plugins/datasource/grafana/partials/annotations.editor.html
  23. 1 1
      public/app/plugins/panel/graph/tab_display.html
  24. 1 0
      public/sass/components/_dashboard_list.scss
  25. 3 3
      public/sass/components/_switch.scss
  26. 0 5
      public/sass/components/_view_states.scss

+ 1 - 1
pkg/api/index.go

@@ -221,7 +221,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 					Url:         setting.AppSubUrl + "/datasources",
 				},
 				{
-					Text:        "Members",
+					Text:        "Users",
 					Id:          "users",
 					Description: "Manage org members",
 					Icon:        "icon-gf icon-gf-fw icon-gf-users",

+ 8 - 8
pkg/services/dashboards/dashboards.go

@@ -23,14 +23,12 @@ func SetRepository(rep Repository) {
 }
 
 type SaveDashboardItem struct {
-	TitleLower string
-	OrgId      int64
-	Folder     string
-	UpdatedAt  time.Time
-	UserId     int64
-	Message    string
-	Overwrite  bool
-	Dashboard  *models.Dashboard
+	OrgId     int64
+	UpdatedAt time.Time
+	UserId    int64
+	Message   string
+	Overwrite bool
+	Dashboard *models.Dashboard
 }
 
 type DashboardRepository struct{}
@@ -57,6 +55,8 @@ func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.D
 		OrgId:     json.OrgId,
 		Overwrite: json.Overwrite,
 		UserId:    json.UserId,
+		FolderId:  dashboard.FolderId,
+		IsFolder:  dashboard.IsFolder,
 	}
 
 	if !json.UpdatedAt.IsZero() {

+ 0 - 3
pkg/services/provisioning/dashboards/types.go

@@ -1,7 +1,6 @@
 package dashboards
 
 import (
-	"strings"
 	"time"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -23,11 +22,9 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das
 
 	dash := &dashboards.SaveDashboardItem{}
 	dash.Dashboard = models.NewDashboardFromJson(data)
-	dash.TitleLower = strings.ToLower(dash.Dashboard.Title)
 	dash.UpdatedAt = lastModified
 	dash.Overwrite = true
 	dash.OrgId = cfg.OrgId
-	dash.Folder = cfg.Folder
 	dash.Dashboard.Data.Set("editable", cfg.Editable)
 
 	if dash.Dashboard.Title == "" {

+ 2 - 2
public/app/core/components/PageHeader/PageHeader.tsx

@@ -76,8 +76,8 @@ export default class PageHeader extends React.Component<IProps, any> {
     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>);
+      if (bc.url) {
+        breadcrumbsResult.push(<a className="text-link" key={i} href={bc.url}>{bc.title}</a>);
       } else {
         breadcrumbsResult.push(<span key={i}> / {bc.title}</span>);
       }

+ 0 - 1
public/app/core/components/search/search_results.ts

@@ -9,7 +9,6 @@ export class SearchResultsCtrl {
 
   /** @ngInject */
   constructor(private $location) {
-
   }
 
   toggleFolderExpand(section) {

+ 5 - 0
public/app/core/routes/routes.ts

@@ -83,6 +83,11 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
     controller : 'FolderPermissionsCtrl',
     controllerAs: 'ctrl',
   })
+  .when('/dashboards/folder/:folderId/:type/:slug/settings', {
+    templateUrl: 'public/app/features/dashboard/partials/folder_settings.html',
+    controller : 'FolderSettingsCtrl',
+    controllerAs: 'ctrl',
+  })
   .when('/dashboards/folder/:folderId/:type/:slug', {
     templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
     controller : 'FolderDashboardsCtrl',

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

@@ -32,11 +32,13 @@ 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 {FolderSettingsCtrl} from './folder_settings_ctrl';
 import {DashboardImportCtrl} from './dashboard_import_ctrl';
 import {CreateFolderCtrl} from './create_folder_ctrl';
 
 coreModule.controller('DashboardListCtrl', DashboardListCtrl);
 coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl);
 coreModule.controller('FolderPermissionsCtrl', FolderPermissionsCtrl);
+coreModule.controller('FolderSettingsCtrl', FolderSettingsCtrl);
 coreModule.controller('DashboardImportCtrl', DashboardImportCtrl);
 coreModule.controller('CreateFolderCtrl', CreateFolderCtrl);

+ 1 - 4
public/app/features/dashboard/dashnav/dashnav.ts

@@ -14,10 +14,7 @@ export class DashNavCtrl {
     private $rootScope,
     private dashboardSrv,
     private $location,
-    public playlistSrv,
-    navModelSrv) {
-      this.navModel = navModelSrv.getDashboardNav(this.dashboard, this);
-
+    public playlistSrv) {
       appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
 
       if (this.dashboard.meta.isSnapshot) {

+ 19 - 3
public/app/features/dashboard/folder_page_loader.ts

@@ -12,7 +12,7 @@ export class FolderPageLoader {
         url: '/fsdfds',
         text: '',
         breadcrumbs: [
-          { title: 'Dashboards', uri: '/dashboards' },
+          { title: 'Dashboards', url: '/dashboards' },
           { title: ' ' },
         ],
         children: [
@@ -29,12 +29,19 @@ export class FolderPageLoader {
             id: 'manage-folder-permissions',
             text: 'Permissions',
             url: '/dashboards/permissions'
+          },
+          {
+            active: activeChildId === 'manage-folder-settings',
+            icon: 'fa fa-fw fa-cog',
+            id: 'manage-folder-settings',
+            text: 'Settings',
+            url: '/dashboards/settings'
           }
         ]
       }
     };
 
-    this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
+    return this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
       const folderTitle = result.dashboard.title;
       ctrl.navModel.main.text = '';
       ctrl.navModel.main.breadcrumbs = [
@@ -42,13 +49,22 @@ export class FolderPageLoader {
         { title: folderTitle }
       ];
 
-      const folderUrl = `/dashboards/folder/${folderId}/${result.meta.type}/${result.meta.slug}`;
+      const folderUrl = this.createFolderUrl(folderId, result.meta.type, result.meta.slug);
 
       const dashTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-dashboards' });
       dashTab.url = folderUrl;
 
       const permTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-permissions' });
       permTab.url = folderUrl + '/permissions';
+
+      const settingsTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-settings' });
+      settingsTab.url = folderUrl + '/settings';
+
+      return result;
     });
   }
+
+  createFolderUrl(folderId: number, type: string, slug: string) {
+    return `/dashboards/folder/${folderId}/${type}/${slug}`;
+  }
 }

+ 83 - 0
public/app/features/dashboard/folder_settings_ctrl.ts

@@ -0,0 +1,83 @@
+import {FolderPageLoader} from './folder_page_loader';
+import appEvents from 'app/core/app_events';
+
+export class FolderSettingsCtrl {
+  folderPageLoader: FolderPageLoader;
+  navModel: any;
+  folderId: number;
+  canSave = false;
+  dashboard: any;
+  meta: any;
+
+  /** @ngInject */
+  constructor(private backendSrv, navModelSrv, private $routeParams, private $location) {
+    if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) {
+      this.folderId = $routeParams.folderId;
+
+      this.folderPageLoader = new FolderPageLoader(this.backendSrv, this.$routeParams);
+      this.folderPageLoader.load(this, this.folderId, 'manage-folder-settings')
+      .then(result => {
+        this.dashboard = result.dashboard;
+        this.meta = result.meta;
+        this.canSave = result.meta.canSave;
+      });
+    }
+  }
+
+  save() {
+    return this.backendSrv.saveDashboard(this.dashboard, {overwrite: false})
+      .then(result => {
+        var folderUrl = this.folderPageLoader.createFolderUrl(this.folderId, this.meta.type, result.slug);
+        if (folderUrl !== this.$location.path()) {
+          this.$location.url(folderUrl + '/settings');
+        }
+
+        appEvents.emit('dashboard-saved');
+        appEvents.emit('alert-success', ['Folder saved']);
+      })
+      .catch(this.handleSaveFolderError);
+  }
+
+  delete(evt) {
+    if (evt) {
+      evt.stopPropagation();
+      evt.preventDefault();
+    }
+
+    appEvents.emit('confirm-modal', {
+      title: 'Delete',
+      text: `Do you want to delete this folder and all its dashboards?`,
+      icon: 'fa-trash',
+      yesText: 'Delete',
+      onConfirm: () => {
+        return this.backendSrv.delete(`/api/dashboards/${this.meta.type}/${this.meta.slug}`).then(() => {
+          appEvents.emit('alert-success', ['Folder deleted']);
+          this.$location.url('/dashboards');
+        });
+      }
+    });
+  }
+
+  handleSaveFolderError(err) {
+    if (err.data && err.data.status === 'version-mismatch') {
+      err.isHandled = true;
+
+      appEvents.emit('confirm-modal', {
+        title: 'Conflict',
+        text: 'Someone else has updated this folder.',
+        text2: 'Would you still like to save this folder?',
+        yesText: 'Save & Overwrite',
+        icon: 'fa-warning',
+        onConfirm: () => {
+          this.backendSrv.saveDashboard(this.dashboard, {overwrite: true});
+        }
+      });
+    }
+
+    if (err.data && err.data.status === 'name-exists') {
+      err.isHandled = true;
+
+      appEvents.emit('alert-error', ['A folder or dashboard with this name exists already.']);
+    }
+  }
+}

+ 24 - 0
public/app/features/dashboard/partials/folder_settings.html

@@ -0,0 +1,24 @@
+<page-header model="ctrl.navModel"></page-header>
+
+<div class="page-container page-body">
+
+    <div class="section gf-form-group">
+      <h1 class="section-heading">Folder Settings</h1>
+      <form name="folderSettingsForm" ng-submit="ctrl.save()">
+        <div class="gf-form">
+          <label class="gf-form-label width-7">Name</label>
+          <input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input>
+        </div>
+        <div class="gf-form-button-row">
+          <button type="submit" class="btn btn-success" ng-disabled="!ctrl.canSave">
+            <i class="fa fa-trash"></i>
+            Save
+          </button>
+          <button class="btn btn-danger" ng-click="ctrl.delete($event)" ng-disabled="!ctrl.canSave">
+            <i class="fa fa-trash"></i>
+            Delete
+          </button>
+        </div>
+      </form>
+    </div>
+</div>

+ 1 - 1
public/app/features/dashboard/repeat_option/repeat_option.ts

@@ -1,7 +1,7 @@
 import {coreModule} from 'app/core/core';
 
 var template = `
-<div class="gf-form-select-wrapper max-width-13">
+<div class="gf-form-select-wrapper max-width-18">
   <select class="gf-form-input" ng-model="panel.repeat" ng-options="f.value as f.text for f in variables" ng-change="optionChanged()">
   <option value=""></option>
 </div>

+ 1 - 3
public/app/features/org/org_users_ctrl.ts

@@ -1,5 +1,3 @@
-///<reference path="../../headers/common.d.ts" />
-
 import config from 'app/core/config';
 import coreModule from 'app/core/core_module';
 import Remarkable from 'remarkable';
@@ -44,7 +42,7 @@ export class OrgUsersCtrl {
     if (this.externalUserMngLinkName) {
       return this.externalUserMngLinkName;
     }
-    return "Add Member";
+    return "Invite User";
   }
 
   get() {

+ 9 - 13
public/app/features/plugins/ds_list_ctrl.ts

@@ -1,35 +1,31 @@
-///<reference path="../../headers/common.d.ts" />
-
 import coreModule from '../../core/core_module';
-import {appEvents} from 'app/core/core';
+import _ from 'lodash';
 
 export class DataSourcesCtrl {
   datasources: any;
+  unfiltered: any;
   navModel: any;
+  searchQuery: string;
 
   /** @ngInject */
   constructor(
     private $scope,
     private backendSrv,
     private datasourceSrv,
-    private $location,
     private navModelSrv) {
 
     this.navModel = this.navModelSrv.getNav('cfg', 'datasources', 0);
-    this.navigateToUrl = this.navigateToUrl.bind(this);
     backendSrv.get('/api/datasources').then(result => {
       this.datasources = result;
-    });
-
-    appEvents.on('location-change', payload => {
-      this.navigateToUrl(payload.href);
+      this.unfiltered = result;
     });
   }
 
-  navigateToUrl(url) {
-    // debugger;
-    this.$location.path(url);
-    this.$location.replace();
+  onQueryUpdated() {
+    let regex = new RegExp(this.searchQuery, 'ig');
+    this.datasources = _.filter(this.unfiltered, item => {
+      return regex.test(item.name) || regex.test(item.type);
+    });
   }
 
   removeDataSourceConfirmed(ds) {

+ 8 - 2
public/app/features/plugins/partials/ds_list.html

@@ -1,9 +1,15 @@
 <page-header model="ctrl.navModel"></page-header>
 
 <div class="page-container page-body">
-	<div ng-if="ctrl.datasources.length">
+	<div ng-if="ctrl.unfiltered.length">
 		<div class="page-action-bar">
+      <div class="gf-form">
+        <label class="gf-form-label">Search</label>
+        <input type="text" class="gf-form-input width-20" ng-model="ctrl.searchQuery" ng-change="ctrl.onQueryUpdated()" give-focus="true" placeholder="Filter by name or type" />
+      </div>
+
 			<div class="page-action-bar__spacer"></div>
+
 			<a class="page-header__cta btn btn-success" href="datasources/new">
 				<i class="fa fa-plus"></i>
 				Add data source
@@ -42,7 +48,7 @@
 		</section>
 	</div>
 
-	<div ng-if="ctrl.datasources.length === 0">
+	<div ng-if="ctrl.unfiltered.length === 0">
 		<empty-list-cta model="{
 			title: 'There are no data sources defined yet',
 			buttonIcon: 'gicon gicon-dashboard-new',

+ 58 - 63
public/app/features/plugins/partials/plugin_edit.html

@@ -1,73 +1,68 @@
-<page-header model="ctrl.navModel"></page-header>
+<div ng-if="ctrl.navModel">
+  <page-header model="ctrl.navModel"></page-header>
 
-<div class="page-container page-body" ng-init="ctrl.init()">
-  <div class="page-action-bar">
-    <button class="btn" ng-repeat="tab in ctrl.tabs" ng-class="{'btn-secondary': ctrl.tabIndex === $index, 'btn-inverse': ctrl.tabIndex !== $index}" ng-click="ctrl.tabIndex = $index">
-      {{tab}}
-    </button>
-  </div>
-
-  <div class="sidebar-container">
-
-    <div class="tab-content sidebar-content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Readme'">
-      <div ng-bind-html="ctrl.readmeHtml" class="markdown-html">
+  <div class="page-container page-body">
+    <div class="sidebar-container">
+      <div class="tab-content sidebar-content" ng-if="ctrl.tab === 'readme'">
+        <div ng-bind-html="ctrl.readmeHtml" class="markdown-html">
+        </div>
       </div>
-    </div>
 
-    <div class="tab-content sidebar-content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Config'">
-      <div ng-if="ctrl.model.id">
-        <plugin-component type="app-config-ctrl"></plugin-component>
+      <div class="tab-content sidebar-content" ng-if="ctrl.tab === 'config'">
+        <div ng-if="ctrl.model.id">
+          <plugin-component type="app-config-ctrl"></plugin-component>
 
-        <div class="gf-form-button-row">
-          <button type="submit" class="btn btn-success" ng-click="ctrl.enable()" ng-show="!ctrl.model.enabled">Enable</button>
-          <button type="submit" class="btn btn-success" ng-click="ctrl.update()" ng-show="ctrl.model.enabled">Update</button>
-          <button type="submit" class="btn btn-danger" ng-click="ctrl.disable()" ng-show="ctrl.model.enabled">Disable</button>
+          <div class="gf-form-button-row">
+            <button type="submit" class="btn btn-success" ng-click="ctrl.enable()" ng-show="!ctrl.model.enabled">Enable</button>
+            <button type="submit" class="btn btn-success" ng-click="ctrl.update()" ng-show="ctrl.model.enabled">Update</button>
+            <button type="submit" class="btn btn-danger" ng-click="ctrl.disable()" ng-show="ctrl.model.enabled">Disable</button>
+          </div>
         </div>
       </div>
-    </div>
 
-    <div class="tab-content sidebar.content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Dashboards'">
-      <dashboard-import-list plugin="ctrl.model"></dashboard-import-list>
-    </div>
+      <div class="tab-content sidebar-content" ng-if="ctrl.tab === 'dashboards'">
+        <dashboard-import-list plugin="ctrl.model"></dashboard-import-list>
+      </div>
 
-    <aside class="page-sidebar">
-      <section class="page-sidebar-section">
-        <h4>Version</h4>
-        <span>{{ctrl.model.info.version}}</span>
-        <div ng-show="ctrl.model.hasUpdate">
-          <a ng-click="ctrl.updateAvailable()" bs-tooltip="ctrl.model.latestVersion">Update Available!</a>
-        </div>
-      </section>
-      <section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
-        <h5>Includes</h4>
-        <ul class="ui-list plugin-info-list">
-          <li ng-repeat="plug in ctrl.includes" class="plugin-info-list-item">
-            <i class="{{plug.icon}}"></i>
-            {{plug.name}}
-          </li>
-        </ul>
-      </section>
-      <section class="page-sidebar-section">
-        <h5>Dependencies</h4>
-        <ul class="ui-list plugin-info-list">
-          <li class="plugin-info-list-item">
-            <img src="public/img/grafana_icon.svg"></img>
-            Grafana {{ctrl.model.dependencies.grafanaVersion}}
-          </li>
-          <li ng-repeat="plugDep in ctrl.model.dependencies.plugins" class="plugin-info-list-item">
-            <i class="{{plugDep.icon}}"></i>
-            {{plugDep.name}} {{plugDep.version}}
-          </li>
-        </ul>
-      </section>
-      <section class="page-sidebar-section">
-        <h5>Links</h4>
-        <ul class="ui-list">
-          <li ng-repeat="link in ctrl.model.info.links">
-            <a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
-          </li>
-        </ul>
-      </section>
-    </aside>
+      <aside class="page-sidebar">
+        <section class="page-sidebar-section">
+          <h4>Version</h4>
+          <span>{{ctrl.model.info.version}}</span>
+          <div ng-show="ctrl.model.hasUpdate">
+            <a ng-click="ctrl.updateAvailable()" bs-tooltip="ctrl.model.latestVersion">Update Available!</a>
+          </div>
+        </section>
+        <section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
+          <h5>Includes</h4>
+          <ul class="ui-list plugin-info-list">
+            <li ng-repeat="plug in ctrl.includes" class="plugin-info-list-item">
+              <i class="{{plug.icon}}"></i>
+              {{plug.name}}
+            </li>
+          </ul>
+        </section>
+        <section class="page-sidebar-section">
+          <h5>Dependencies</h4>
+          <ul class="ui-list plugin-info-list">
+            <li class="plugin-info-list-item">
+              <img src="public/img/grafana_icon.svg"></img>
+              Grafana {{ctrl.model.dependencies.grafanaVersion}}
+            </li>
+            <li ng-repeat="plugDep in ctrl.model.dependencies.plugins" class="plugin-info-list-item">
+              <i class="{{plugDep.icon}}"></i>
+              {{plugDep.name}} {{plugDep.version}}
+            </li>
+          </ul>
+        </section>
+        <section class="page-sidebar-section">
+          <h5>Links</h4>
+          <ul class="ui-list">
+            <li ng-repeat="link in ctrl.model.info.links">
+              <a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
+            </li>
+          </ul>
+        </section>
+      </aside>
+    </div>
   </div>
 </div>

+ 0 - 37
public/app/features/plugins/partials/plugin_list.html

@@ -1,42 +1,5 @@
 <page-header model="ctrl.navModel"></page-header>
 
-<!-- <div class="page&#45;header&#45;canvas"> -->
-<!--   <div class="page&#45;container"> -->
-<!--     <navbar model="ctrl.navModel"></navbar> -->
-<!--  -->
-<!--     <div class="page&#45;header"> -->
-<!-- 		  <page&#45;h1 model="ctrl.navModel"></page&#45;h1> -->
-<!--  -->
-<!--       <div class="page&#45;header&#45;tabs"> -->
-<!--         <ul class="gf&#45;tabs"> -->
-<!--           <li class="gf&#45;tabs&#45;item"> -->
-<!--             <a class="gf&#45;tabs&#45;link" href="plugins?type=panel" ng&#45;class="{active: ctrl.tabIndex === 0}"> -->
-<!--               <i class="icon&#45;gf icon&#45;gf&#45;panel"></i> -->
-<!--               Panels -->
-<!--             </a> -->
-<!--           </li> -->
-<!--           <li class="gf&#45;tabs&#45;item"> -->
-<!--             <a class="gf&#45;tabs&#45;link" href="plugins?type=datasource" ng&#45;class="{active: ctrl.tabIndex === 1}"> -->
-<!--               <i class="gicon gicon&#45;datasources"></i> -->
-<!--               Data sources -->
-<!--             </a> -->
-<!--           </li> -->
-<!--           <li class="gf&#45;tabs&#45;item"> -->
-<!--             <a class="gf&#45;tabs&#45;link" href="plugins?type=app" ng&#45;class="{active: ctrl.tabIndex === 2}"> -->
-<!--               <i class="icon&#45;gf icon&#45;gf&#45;apps"></i> -->
-<!--               Apps -->
-<!--             </a> -->
-<!--           </li> -->
-<!--         </ul> -->
-<!--  -->
-<!--         <a class="get&#45;more&#45;plugins&#45;link pull&#45;right" href="https://grafana.com/plugins?utm_source=grafana_plugin_list" target="_blank"> -->
-<!--           Find more <img src="public/img/icn&#45;plugins&#45;tiny.svg" />plugins on Grafana.com -->
-<!--         </a> -->
-<!--       </div> -->
-<!--     </div> -->
-<!--   </div> -->
-<!-- </div> -->
-
 <div class="page-container page-body">
   <div class="page-action-bar">
     <div class="gf-form">

+ 8 - 6
public/app/features/plugins/partials/plugin_page.html

@@ -1,8 +1,10 @@
-<navbar model="ctrl.navModel" ng-if="ctrl.navModel"></navbar>
+<div ng-if="ctrl.navModel">
+  <page-header model="ctrl.navModel"></page-header>
 
-<div class="page-container" >
-	<div ng-if="ctrl.page">
-		<plugin-component type="app-page">
-		</plugin-component>
-	</div>
+  <div class="page-container">
+    <div ng-if="ctrl.page">
+      <plugin-component type="app-page">
+      </plugin-component>
+    </div>
+  </div>
 </div>

+ 61 - 19
public/app/features/plugins/plugin_edit_ctrl.ts

@@ -11,8 +11,7 @@ export class PluginEditCtrl {
   includes: any;
   readmeHtml: any;
   includedDatasources: any;
-  tabIndex: number;
-  tabs: any;
+  tab: string;
   navModel: any;
   hasDashboards: any;
   preUpdateHook: () => any;
@@ -24,24 +23,76 @@ export class PluginEditCtrl {
     private $rootScope,
     private backendSrv,
     private $sce,
-    $routeParams,
+    private $routeParams,
     navModelSrv,
   ) {
-    this.navModel = navModelSrv.getNav('cfg', 'plugins', 0);
-    this.model = {};
-    this.pluginId = $routeParams.pluginId;
-    this.tabIndex = 0;
-    this.tabs = ['Readme'];
 
+    this.pluginId = $routeParams.pluginId;
     this.preUpdateHook = () => Promise.resolve();
     this.postUpdateHook = () => Promise.resolve();
+
+    this.init();
+  }
+
+  setNavModel(model) {
+    let defaultTab = 'readme';
+
+    this.navModel = {
+      main: {
+        img: model.info.logos.large,
+        subTitle: model.info.description,
+        url: '',
+        text: '',
+        breadcrumbs: [
+          { title: 'Plugins', url: '/plugins' },
+          { title: model.name },
+        ],
+        children: [
+          {
+            icon: 'fa fa-fw fa-file-text-o',
+            id: 'readme',
+            text: 'Readme',
+            url: `plugins/${this.model.id}/edit?tab=readme`
+          }
+        ]
+      }
+    };
+
+    if (model.type === 'app') {
+        this.navModel.main.children.push({
+            icon: 'fa fa-fw fa-th-large',
+            id: 'config',
+            text: 'Config',
+            url: `plugins/${this.model.id}/edit?tab=config`
+        });
+
+        let hasDashboards = _.find(model.includes, {type: 'dashboard'});
+
+        if (hasDashboards) {
+          this.navModel.main.children.push({
+            icon: 'gicon gicon-dashboard',
+            id: 'dashboards',
+            text: 'Dashboards',
+            url: `plugins/${this.model.id}/edit?tab=dashboards`
+          });
+        }
+
+        defaultTab = 'config';
+    }
+
+    this.tab = this.$routeParams.tab || defaultTab;
+
+    for (let tab of this.navModel.main.children) {
+      if (tab.id === this.tab) {
+        tab.active = true;
+      }
+    }
   }
 
   init() {
     return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {
       this.model = result;
       this.pluginIcon = this.getPluginIcon(this.model.type);
-      this.navModel.breadcrumbs.push({text: this.model.name});
 
       this.model.dependencies.plugins.forEach(plug => {
         plug.icon = this.getPluginIcon(plug.type);
@@ -52,16 +103,7 @@ export class PluginEditCtrl {
         return plug;
       });
 
-      if (this.model.type === 'app') {
-        this.hasDashboards = _.find(result.includes, {type: 'dashboard'});
-        if (this.hasDashboards) {
-          this.tabs.unshift('Dashboards');
-        }
-
-        this.tabs.unshift('Config');
-        this.tabIndex = 0;
-      }
-
+      this.setNavModel(this.model);
       return this.initReadme();
     });
   }

+ 14 - 4
public/app/features/plugins/plugin_page_ctrl.ts

@@ -1,5 +1,3 @@
-///<reference path="../../headers/common.d.ts" />
-
 import angular from 'angular';
 import _ from 'lodash';
 
@@ -35,8 +33,20 @@ export class AppPageCtrl {
       return;
     }
 
-    this.navModel = this.navModelSrv.getNav('plugin-page-' + app.id);
-    this.navModel.breadcrumbs.push({text: this.page.name});
+    let pluginNav = this.navModelSrv.getNav('plugin-page-' + app.id);
+
+    this.navModel = {
+      main: {
+        img: app.info.logos.large,
+        subTitle: app.name,
+        url: '',
+        text: '',
+        breadcrumbs: [
+          { title: app.name, url: pluginNav.main.url },
+          { title: this.page.name },
+        ],
+      }
+    };
   }
 
   loadPluginInfo() {

+ 2 - 2
public/app/features/templating/partials/editor.html

@@ -167,14 +167,14 @@
 						</select>
 					</div>
 				</div>
-				<div class="gf-form max-width-21">
+				<div class="gf-form max-width-22">
 					<span class="gf-form-label width-7">
 						Refresh
 						<info-popover mode="right-normal">
 							When to update the values of this variable.
 						</info-popover>
 					</span>
-					<div class="gf-form-select-wrapper max-width-14">
+					<div class="gf-form-select-wrapper width-15">
 						<select class="gf-form-input" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
 					</div>
 				</div>

+ 1 - 1
public/app/plugins/datasource/grafana/partials/annotations.editor.html

@@ -11,7 +11,7 @@
 					</ul>
 				</info-popover>
 			</span>
-			<div class="gf-form-select-wrapper width-8">
+			<div class="gf-form-select-wrapper width-9">
 				<select class="gf-form-input" ng-model="ctrl.annotation.type" ng-options="f.value as f.text for f in ctrl.types">
 				</select>
 			</div>

+ 1 - 1
public/app/plugins/panel/graph/tab_display.html

@@ -78,7 +78,7 @@
 			<div class="gf-form">
 				<label class="gf-form-label width-7">Null value</label>
 				<div class="gf-form-select-wrapper">
-					<select class="gf-form-input max-width-8" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
+					<select class="gf-form-input max-width-9" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
 				</div>
 			</div>
 		</div>

+ 1 - 0
public/sass/components/_dashboard_list.scss

@@ -24,4 +24,5 @@
 
 .search-results-filter-row__filters-item {
     width: 150px;
+    margin-right: 0;
 }

+ 3 - 3
public/sass/components/_switch.scss

@@ -124,7 +124,7 @@
 }
 
 .gf-form-switch--search-result__section {
-  min-width: 3.3rem;
+  min-width: 3.05rem;
   margin-right: -0.3rem;
 
   input + label {
@@ -133,7 +133,7 @@
 }
 
 .gf-form-switch--search-result__item {
-  min-width: 2.6rem;
+  min-width: 2.7rem;
 
   input + label {
     height: 2.7rem;
@@ -141,7 +141,7 @@
 }
 
 .gf-form-switch--search-result-filter-row__checkbox {
-  min-width: 4.7rem;
+  min-width: 3.75rem;
 
   input + label {
     height: 2.5rem;

+ 0 - 5
public/sass/components/_view_states.scss

@@ -17,29 +17,24 @@
   .panel-info-corner--info,
   .panel-info-corner--links {
     opacity: 0;
-    transition: all 1.5s ease-in-out 1s;
   }
 
   .navbar {
     box-shadow: none;
     background: transparent;
-    padding-left: $side-menu-width + 20px;
   }
 
   .navbar-page-btn {
     border-color: transparent;
     background: transparent;
     transform: translate3d(-40px, 0, 0);
-    transition: all 1.5s ease-in-out 1s;
     i {
       opacity: 0;
-      transition: all 1.5s ease-in-out 1s;
     }
   }
 
   .gf-timepicker-nav-btn {
     transform: translate3d(40px, 0, 0);
-    transition: transform 1.5s ease-in-out 1s;
   }
 }