couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From benk...@apache.org
Subject fauxton commit: updated refs/heads/master to eb5124a
Date Wed, 07 Oct 2015 22:23:31 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master c7f09aad3 -> eb5124af7


Notification Center

This PR adds a new notification center panel. A bell icon now
appears at the top right of all pages which when clicked,
opens the notification center. That shows you a log of all
notifications that have occured during your session with the
option to clear single ones, clear all, and view by group.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/eb5124af
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/eb5124af
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/eb5124af

Branch: refs/heads/master
Commit: eb5124af7d19219f74a818720d4235f743fdd4bd
Parents: c7f09aa
Author: Ben Keen <ben.keen@gmail.com>
Authored: Mon Sep 28 13:41:53 2015 -0700
Committer: Ben Keen <ben.keen@gmail.com>
Committed: Wed Oct 7 14:54:03 2015 -0700

----------------------------------------------------------------------
 app/addons/auth/routes.js                       |   3 +-
 .../assets/less/header-togglebutton.less        |   6 +-
 app/addons/databases/assets/less/databases.less |   2 +-
 app/addons/databases/components.react.jsx       |   6 +-
 app/addons/documents/assets/less/changes.less   |   5 -
 .../documents/assets/less/doc-editor.less       |   5 -
 .../documents/assets/less/query-options.less    |   2 +-
 app/addons/documents/header/header.react.jsx    |   2 +-
 .../documents/templates/all_docs_header.html    |  20 +-
 app/addons/documents/templates/jumpdoc.html     |   8 +-
 app/addons/documents/views.js                   |   2 +-
 app/addons/fauxton/base.js                      |  33 ++-
 app/addons/fauxton/components.react.jsx         |  21 +-
 app/addons/fauxton/notifications/actions.js     |  67 +++++
 app/addons/fauxton/notifications/actiontypes.js |  22 ++
 .../notifications/notifications.react.jsx       | 262 +++++++++++++++++++
 app/addons/fauxton/notifications/stores.js      | 134 ++++++++++
 .../tests/componentsSpec.react.jsx              | 162 ++++++++++++
 .../fauxton/notifications/tests/storesSpec.js   | 101 +++++++
 app/addons/fauxton/tests/baseSpec.js            |  26 +-
 .../fauxton/tests/componentsSpec.react.jsx      |  43 ++-
 .../tests/nightwatch/notificationCenter.js      |  38 +++
 app/core/routeObject.js                         |   1 +
 app/core/tests/utilsSpec.js                     |  11 +
 app/core/utils.js                               |   6 +
 app/templates/layouts/doc_editor.html           |   1 +
 app/templates/layouts/one_pane.html             |   1 +
 app/templates/layouts/two_pane.html             |   7 +-
 app/templates/layouts/with_sidebar.html         |   3 +-
 app/templates/layouts/with_tabs_sidebar.html    |   8 +-
 assets/index.underscore                         |   5 +-
 assets/less/fauxton.less                        |  20 +-
 assets/less/layouts.less                        |  52 ++--
 assets/less/mixins.less                         |   8 +
 assets/less/notification-center.less            | 223 ++++++++++++++++
 assets/less/templates.less                      |   3 +-
 assets/less/trays.less                          |   2 +-
 assets/less/variables.less                      |   5 +
 38 files changed, 1214 insertions(+), 112 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/auth/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js
index 5189c92..9fd7189 100644
--- a/app/addons/auth/routes.js
+++ b/app/addons/auth/routes.js
@@ -29,9 +29,10 @@ function (app, FauxtonAPI, Auth, AuthActions, Components, ClusterActions) {
       'login': 'login',
       'logout': 'logout',
       'createAdmin': 'checkNodes',
-      'createAdmin/:node': 'createAdminForNode',
+      'createAdmin/:node': 'createAdminForNode'
     },
     disableLoader: true,
+    hideNotificationCenter: true,
 
     checkNodes: function () {
       ClusterActions.navigateToNodeBasedOnNodeCount('/createAdmin/');

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/components/assets/less/header-togglebutton.less
----------------------------------------------------------------------
diff --git a/app/addons/components/assets/less/header-togglebutton.less b/app/addons/components/assets/less/header-togglebutton.less
index 24b860b..d381b25 100644
--- a/app/addons/components/assets/less/header-togglebutton.less
+++ b/app/addons/components/assets/less/header-togglebutton.less
@@ -21,12 +21,11 @@
   border: none;
   border-left: 1px solid @btnBorder;
   text-decoration: none;
-  float: left;
   .icon {
     font-size: 20px;
     &:before {
       float: left;
-      margin: 10px 6px 0px 0px;
+      margin: 10px 6px 0 0;
     }
   }
 }
@@ -37,7 +36,7 @@
   float: left;
   .icon {
     &:before {
-      margin: 0px;
+      margin: 0;
       float: none;
     }
   }
@@ -61,6 +60,7 @@ button.header-control-box:focus {
 }
 
 button.control-toggle-alternative-header {
+  float: left;
   border-right: 1px solid @btnBorder;
 }
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/databases/assets/less/databases.less
----------------------------------------------------------------------
diff --git a/app/addons/databases/assets/less/databases.less b/app/addons/databases/assets/less/databases.less
index 23765e5..652530b 100644
--- a/app/addons/databases/assets/less/databases.less
+++ b/app/addons/databases/assets/less/databases.less
@@ -33,7 +33,7 @@
 .new-database-tray {
   padding: 16px 20px 28px;
   &:before {
-    right: 153px;
+    right: 243px;
   }
 
   input.input-xxlarge {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/databases/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/databases/components.react.jsx b/app/addons/databases/components.react.jsx
index 374ffe6..3442163 100644
--- a/app/addons/databases/components.react.jsx
+++ b/app/addons/databases/components.react.jsx
@@ -167,9 +167,9 @@ define([
 
     render: function () {
       return (
-        <div className="header-right">
-          <AddDatabaseWidget />
+        <div className="header-right right-db-header flex-layout flex-row">
           <JumpToDatabaseWidget />
+          <AddDatabaseWidget />
         </div>
       );
     }
@@ -211,7 +211,6 @@ define([
 
       return (
         <div>
-
           <ToggleHeaderButton
             selected={this.state.isPromptVisible}
             toggleCallback={this.onTrayToggle}
@@ -219,7 +218,6 @@ define([
             title="Add New Database"
             fonticon="fonticon-new-database"
             text="Add New Database" />
-
           <ComponentsReact.Tray ref="newDbTray" className="new-database-tray">
             <span className="add-on">Add New Database</span>
             <input id="js-new-database-name" type="text" onKeyUp={this.onKeyUpInInput} ref="newDbName" className="input-xxlarge" placeholder="Name of database" />

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/assets/less/changes.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/changes.less b/app/addons/documents/assets/less/changes.less
index 9c883c8..a42a26d 100644
--- a/app/addons/documents/assets/less/changes.less
+++ b/app/addons/documents/assets/less/changes.less
@@ -16,11 +16,6 @@
 .change-box {
   margin: 0 20px 20px 20px;
 }
-.searchbox-container {
-  position: fixed;
-  right: 15px;
-}
-
 
 .change-wrapper {
   margin-top: 20px;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/assets/less/doc-editor.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/doc-editor.less b/app/addons/documents/assets/less/doc-editor.less
index 94921ee..3eac02d 100644
--- a/app/addons/documents/assets/less/doc-editor.less
+++ b/app/addons/documents/assets/less/doc-editor.less
@@ -20,11 +20,6 @@
     padding-bottom: 0;
     top: 0;
   }
-
-  /* temporary wedge: can be removed at end of flexbox refactor */
-  #api-navbar {
-    float: right;
-  }
 }
 
 #editor-container {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/assets/less/query-options.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/query-options.less b/app/addons/documents/assets/less/query-options.less
index 7445036..3ddeecc 100644
--- a/app/addons/documents/assets/less/query-options.less
+++ b/app/addons/documents/assets/less/query-options.less
@@ -57,7 +57,7 @@
 }
 
 #query-options-tray:before {
-  right: 140px;
+  right: 230px;
 }
 
 #query-options-tray {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/header/header.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.react.jsx b/app/addons/documents/header/header.react.jsx
index f343030..b277e31 100644
--- a/app/addons/documents/header/header.react.jsx
+++ b/app/addons/documents/header/header.react.jsx
@@ -171,7 +171,7 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents, IndexResults
 
     componentDidUpdate: function () {
       // todo reactify right header (api bar, query options)
-      var $oldHeader = $('#api-navbar, #right-header');
+      var $oldHeader = $('#api-navbar, #right-header, #notification-center-btn');
       if (this.state.isToggled) {
         $oldHeader.hide();
         return;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/templates/all_docs_header.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/all_docs_header.html b/app/addons/documents/templates/all_docs_header.html
index 9d04848..73e10c6 100644
--- a/app/addons/documents/templates/all_docs_header.html
+++ b/app/addons/documents/templates/all_docs_header.html
@@ -14,17 +14,13 @@ the License.
 */
 %>
 
-  <!-- Query Options-->
-  <div id="header-query-options">
-    <div id="query-options"></div>
-  </div>
-
-  <!-- search (jump to doc)-->
-  <div class="searchbox-wrapper">
+<!-- search (jump to doc)-->
+<div class="searchbox-wrapper">
     <div id="header-search" class="js-search searchbox-container"></div>
-  </div>
-  <!-- Select toggle -->
-  <!--<div id="header-select-all" class="button">
-    <span class="toggle-select-menu icon fonticon-ok-circled">Select</span>
-  </div>-->
+</div>
+
+<!-- Query Options-->
+<div id="header-query-options">
+  <div id="query-options"></div>
+</div>
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/templates/jumpdoc.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/jumpdoc.html b/app/addons/documents/templates/jumpdoc.html
index 65d6a96..5038069 100644
--- a/app/addons/documents/templates/jumpdoc.html
+++ b/app/addons/documents/templates/jumpdoc.html
@@ -1,4 +1,4 @@
-<!--
+<%/*
 Licensed under the Apache License, Version 2.0 (the "License"); you may not
 use this file except in compliance with the License. You may obtain a copy of
 the License at
@@ -10,11 +10,11 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 License for the specific language governing permissions and limitations under
 the License.
--->
+*/%>
 
-<form id="jump-to-doc" class="pull-right">
+<form id="jump-to-doc">
   <div class="input-append">
-    <input type="text" id="jump-to-doc-id" class="input-large" autocomplete="off" placeholder="Document ID"></input>
+    <input type="text" id="jump-to-doc-id" class="input-large" autocomplete="off" placeholder="Document ID" />
     <button class="btn btn-primary" type="submit"><i class="icon icon-search"></i></button>
   </div>
 </form>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 0b4831e..d5375f4 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -38,7 +38,7 @@ function (app, FauxtonAPI, Components, Documents, Databases, QueryOptions, Query
   }
 
   Views.RightAllDocsHeader = FauxtonAPI.View.extend({
-    className: "header-right",
+    className: "header-right right-db-header flex-layout flex-row",
     template: "addons/documents/templates/all_docs_header",
     events: {
       'click .toggle-select-menu': 'selectAllMenu'

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index c1d159a..85cd44e 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -14,14 +14,16 @@ define([
   "app",
   "api",
   "addons/fauxton/components",
+  'addons/fauxton/notifications/notifications.react',
+  'addons/fauxton/notifications/actions',
   "addons/fauxton/navigation/components.react",
   "addons/fauxton/navigation/actions",
   'addons/fauxton/dependencies/ZeroClipboard',
-  'addons/components/react-components.react',
+  'addons/components/react-components.react'
 ],
 
-function (app, FauxtonAPI, Components, NavbarReactComponents,
-          NavigationActions, ZeroClipboard, ReactComponents) {
+function (app, FauxtonAPI, Components, NotificationComponents, Actions, NavbarReactComponents, NavigationActions,
+          ZeroClipboard, ReactComponents) {
 
   var Fauxton = FauxtonAPI.addon();
   FauxtonAPI.addNotification = function (options) {
@@ -32,6 +34,9 @@ function (app, FauxtonAPI, Components, NavbarReactComponents,
       escape: true
     }, options);
 
+    // log all notifications in a store
+    Actions.addNotification(options);
+
     var view = new Fauxton.Notification(options);
     return view.renderNotification();
   };
@@ -56,15 +61,17 @@ function (app, FauxtonAPI, Components, NavbarReactComponents,
     FauxtonAPI.RouteObject.on('beforeFullRender', function (routeObject) {
       NavigationActions.setNavbarActiveLink(_.result(routeObject, 'selectedHeader'));
 
-      if (!routeObject.get('apiUrl')) {
-        return;
+      if (routeObject.get('apiUrl')) {
+        var apiAndDocs = routeObject.get('apiUrl');
+        routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
+          endpoint: apiAndDocs[0],
+          documentation: apiAndDocs[1]
+        });
       }
 
-      var apiAndDocs = routeObject.get('apiUrl');
-      routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
-        endpoint: apiAndDocs[0],
-        documentation: apiAndDocs[1]
-      });
+      if (!routeObject.get('hideNotificationCenter')) {
+        routeObject.setComponent('#notification-center-btn', NotificationComponents.NotificationCenterButton);
+      }
     });
 
     FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
@@ -78,7 +85,6 @@ function (app, FauxtonAPI, Components, NavbarReactComponents,
           crumbs: crumbs
         }), true).render();
       }
-
     });
 
     var primaryNavBarEl = $('#primary-navbar')[0];
@@ -86,6 +92,11 @@ function (app, FauxtonAPI, Components, NavbarReactComponents,
       NavbarReactComponents.renderNavBar(primaryNavBarEl);
     }
 
+    var notificationCenterEl = $('#notification-center')[0];
+    if (notificationCenterEl) {
+      NotificationComponents.renderNotificationCenter(notificationCenterEl);
+    }
+
     var versionInfo = new Fauxton.VersionInfo();
 
     versionInfo.fetch().then(function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.react.jsx b/app/addons/fauxton/components.react.jsx
index d877ce1..e8dc237 100644
--- a/app/addons/fauxton/components.react.jsx
+++ b/app/addons/fauxton/components.react.jsx
@@ -34,7 +34,15 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
   var Clipboard = React.createClass({
     propTypes: function () {
       return {
-        text: React.PropTypes.string.isRequired
+        text: React.PropTypes.string.isRequired,
+        displayType: React.PropTypes.string.oneOf(['icon', 'text'])
+      };
+    },
+
+    getDefaultProps: function () {
+      return {
+        displayType: 'icon',
+        textDisplay: 'Copy'
       };
     },
 
@@ -42,6 +50,13 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
       ZeroClipboard.config({ moviePath: getZeroClipboardSwfPath() });
     },
 
+    getClipboardElement: function () {
+      if (this.props.displayType === 'icon') {
+        return (<i className="fonticon-clipboard"></i>);
+      }
+      return this.props.textDisplay;
+    },
+
     componentDidMount: function () {
       var el = this.getDOMNode();
       this.clipboard = new ZeroClipboard(el);
@@ -49,8 +64,8 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
 
     render: function () {
       return (
-        <a href="#" ref="copy" data-clipboard-text={this.props.text} data-bypass="true" title="Copy to clipboard">
-          <i className="fonticon-clipboard"></i>
+        <a href="#" ref="copy" className="copy" data-clipboard-text={this.props.text} data-bypass="true" title="Copy to clipboard">
+          {this.getClipboardElement()}
         </a>
       );
     }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/actions.js b/app/addons/fauxton/notifications/actions.js
new file mode 100644
index 0000000..16d566d
--- /dev/null
+++ b/app/addons/fauxton/notifications/actions.js
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/fauxton/notifications/actiontypes'
+],
+function (FauxtonAPI, ActionTypes) {
+
+  function addNotification (notificationInfo) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.ADD_NOTIFICATION,
+      options: {
+        info: notificationInfo
+      }
+    });
+  }
+
+  function showNotificationCenter () {
+    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_NOTIFICATION_CENTER });
+  }
+
+  function hideNotificationCenter () {
+    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_NOTIFICATION_CENTER });
+  }
+
+  function clearAllNotifications () {
+    FauxtonAPI.dispatch({ type: ActionTypes.CLEAR_ALL_NOTIFICATIONS });
+  }
+
+  function clearSingleNotification (notificationId) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.CLEAR_SINGLE_NOTIFICATION,
+      options: {
+        notificationId: notificationId
+      }
+    });
+  }
+
+  function selectNotificationFilter (filter) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.SELECT_NOTIFICATION_FILTER,
+      options: {
+        filter: filter
+      }
+    });
+  }
+
+  return {
+    addNotification: addNotification,
+    showNotificationCenter: showNotificationCenter,
+    hideNotificationCenter: hideNotificationCenter,
+    clearAllNotifications: clearAllNotifications,
+    clearSingleNotification: clearSingleNotification,
+    selectNotificationFilter: selectNotificationFilter
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/actiontypes.js b/app/addons/fauxton/notifications/actiontypes.js
new file mode 100644
index 0000000..103ec58
--- /dev/null
+++ b/app/addons/fauxton/notifications/actiontypes.js
@@ -0,0 +1,22 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([],  function () {
+  return {
+    ADD_NOTIFICATION: 'ADD_NOTIFICATION',
+    SHOW_NOTIFICATION_CENTER: 'SHOW_NOTIFICATION_CENTER',
+    HIDE_NOTIFICATION_CENTER: 'HIDE_NOTIFICATION_CENTER',
+    CLEAR_SINGLE_NOTIFICATION: 'CLEAR_SINGLE_NOTIFICATION',
+    CLEAR_ALL_NOTIFICATIONS: 'CLEAR_ALL_NOTIFICATIONS',
+    SELECT_NOTIFICATION_FILTER: 'SELECT_NOTIFICATION_FILTER'
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/notifications.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/notifications.react.jsx b/app/addons/fauxton/notifications/notifications.react.jsx
new file mode 100644
index 0000000..f05841a
--- /dev/null
+++ b/app/addons/fauxton/notifications/notifications.react.jsx
@@ -0,0 +1,262 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'app',
+  'api',
+  'react',
+  'addons/fauxton/notifications/actions',
+  'addons/fauxton/notifications/stores',
+  'addons/fauxton/components.react',
+
+  // needed to run the test individually. Don't remove
+  'velocity.ui'
+],
+
+function (app, FauxtonAPI, React, Actions, Stores, Components) {
+
+  var notificationStore = Stores.notificationStore;
+  var Clipboard = Components.Clipboard;
+
+
+  var NotificationCenterButton = React.createClass({
+    getInitialState: function () {
+      return {
+        visible: true
+      };
+    },
+
+    hide: function () {
+      this.setState({ visible: false });
+    },
+
+    show: function () {
+      this.setState({ visible: true });
+    },
+
+    render: function () {
+      var classes = 'fonticon fonticon-bell' + ((!this.state.visible) ? ' hide' : '');
+      return (
+        <div className={classes} onClick={Actions.showNotificationCenter}></div>
+      );
+    }
+  });
+
+  var NotificationCenterPanel = React.createClass({
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
+      return {
+        isVisible: notificationStore.isNotificationCenterVisible(),
+        filter: notificationStore.getNotificationFilter(),
+        notifications: notificationStore.getNotifications()
+      };
+    },
+
+    componentDidMount: function () {
+      notificationStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      notificationStore.off('change', this.onChange);
+    },
+
+    onChange: function () {
+      if (this.isMounted()) {
+        this.setState(this.getStoreState());
+      }
+    },
+
+    getNotifications: function () {
+      if (!this.state.notifications.length) {
+        return (
+          <li className="no-notifications">No notifications.</li>
+        );
+      }
+
+      return _.map(this.state.notifications, function (notification, i) {
+        return (
+          <NotificationRow
+            isVisible={this.state.isVisible}
+            item={notification}
+            filter={this.state.filter}
+            key={notification.notificationId}
+          />
+        );
+      }, this);
+    },
+
+    render: function () {
+      var panelClasses = 'notification-center-panel flex-layout flex-col';
+      if (this.state.isVisible) {
+        panelClasses += ' visible';
+      }
+
+      var filterClasses = {
+        all: 'flex-body',
+        success: 'flex-body',
+        error: 'flex-body',
+        info: 'flex-body'
+      };
+      filterClasses[this.state.filter] += ' selected';
+
+      var maskClasses = 'notification-page-mask' + ((this.state.isVisible) ? ' visible' : '');
+      return (
+        <div>
+          <div className={panelClasses}>
+
+            <header className="flex-layout flex-row">
+              <span className="fonticon fonticon-bell"></span>
+              <h1 className="flex-body">Notifications</h1>
+              <button type="button" onClick={Actions.hideNotificationCenter}>×</button>
+            </header>
+
+            <ul className="notification-filter flex-layout flex-row">
+              <li className={filterClasses.all} title="All notifications" data-filter="all"
+                onClick={Actions.selectNotificationFilter.bind(this, 'all')}>All</li>
+              <li className={filterClasses.success} title="Success notifications" data-filter="success"
+                onClick={Actions.selectNotificationFilter.bind(this, 'success')}>
+                <span className="fonticon fonticon-ok-circled"></span>
+              </li>
+              <li className={filterClasses.error} title="Error notifications" data-filter="error"
+                onClick={Actions.selectNotificationFilter.bind(this, 'error')}>
+                <span className="fonticon fonticon-attention-circled"></span>
+              </li>
+              <li className={filterClasses.info} title="Info notifications" data-filter="info"
+                onClick={Actions.selectNotificationFilter.bind(this, 'info')}>
+                <span className="fonticon fonticon-info-circled"></span>
+              </li>
+            </ul>
+
+            <div className="flex-body">
+              <ul className="notification-list">
+              {this.getNotifications()}
+              </ul>
+            </div>
+
+            <footer>
+              <input type="button" value="Clear All" className="btn btn-small btn-info" onClick={Actions.clearAllNotifications} />
+            </footer>
+          </div>
+
+          <div className={maskClasses} onClick={Actions.hideNotificationCenter}></div>
+        </div>
+      );
+    }
+  });
+
+  var NotificationRow = React.createClass({
+    propTypes: {
+      item: React.PropTypes.object.isRequired,
+      filter: React.PropTypes.string.isRequired,
+      transitionSpeed: React.PropTypes.number
+    },
+
+    getDefaultProps: function () {
+      return {
+        transitionSpeed: 300
+      };
+    },
+
+    clearNotification: function () {
+      var notificationId = this.props.item.notificationId;
+      this.hide(function () {
+        Actions.clearSingleNotification(notificationId);
+      });
+    },
+
+    componentDidMount: function () {
+      this.setState({
+        elementHeight: this.getHeight()
+      });
+    },
+
+    componentDidUpdate: function (prevProps) {
+      // in order for the nice slide effects to work we need a concrete element height to slide to and from.
+      // $.outerHeight() only works reliably on visible elements, hence this additional setState here
+      if (!prevProps.isVisible && this.props.isVisible) {
+        this.setState({
+          elementHeight: this.getHeight()
+        });
+      }
+
+      var show = true;
+      if (this.props.filter !== 'all') {
+        show = this.props.item.type === this.props.filter;
+      }
+      if (show) {
+        $(this.getDOMNode()).velocity({ opacity: 1, height: this.state.elementHeight }, this.props.transitionSpeed);
+        return;
+      }
+      this.hide();
+    },
+
+    getHeight: function () {
+      return $(this.getDOMNode()).outerHeight(true);
+    },
+
+    hide: function (onHidden) {
+      $(this.getDOMNode()).velocity({ opacity: 0, height: 0 }, this.props.transitionSpeed, function () {
+        if (onHidden) {
+          onHidden();
+        }
+      });
+    },
+
+    render: function () {
+      var iconMap = {
+        success: 'fonticon-ok-circled',
+        error: 'fonticon-attention-circled',
+        info: 'fonticon-info-circled'
+      };
+
+      var timeElapsed = this.props.item.time.fromNow();
+
+      // we can safely do this because the store ensures all notifications are of known types
+      var rowIconClasses = 'fonticon ' + iconMap[this.props.item.type];
+      var hidden = (this.props.filter === 'all' || this.props.filter === this.props.item.type) ? false : true;
+
+      // N.B. wrapper <div> needed to ensure smooth hide/show transitions
+      return (
+        <li aria-hidden={hidden}>
+          <div className="flex-layout flex-row">
+            <span className={rowIconClasses}></span>
+            <div className="flex-body">
+              <p>{this.props.item.cleanMsg}</p>
+              <div className="notification-actions">
+                <span className="time-elapsed">{timeElapsed}</span>
+                <span className="divider">|</span>
+                <Clipboard text={this.props.item.cleanMsg} displayType="text" />
+              </div>
+            </div>
+            <button type="button" onClick={this.clearNotification}>×</button>
+          </div>
+        </li>
+      );
+    }
+  });
+
+
+  return {
+    NotificationCenterButton: NotificationCenterButton,
+    NotificationCenterPanel: NotificationCenterPanel,
+    NotificationRow: NotificationRow,
+
+    renderNotificationCenter: function (el) {
+      return React.render(<NotificationCenterPanel />, el);
+    }
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/stores.js b/app/addons/fauxton/notifications/stores.js
new file mode 100644
index 0000000..957d18d
--- /dev/null
+++ b/app/addons/fauxton/notifications/stores.js
@@ -0,0 +1,134 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'app',
+  'addons/fauxton/notifications/actiontypes',
+  'moment'
+],
+
+function (FauxtonAPI, app, ActionTypes, moment) {
+  var Stores = {};
+
+  // static var used to assign a unique ID to each notification
+  var counter = 0;
+  var validNotificationTypes = ['success', 'error', 'info'];
+
+
+  /**
+   * Notifications are of the form:
+   * {
+   *   notificationId: N,
+   *   message: "string",
+   *   type: "success"|etc. see above list
+   *   clear: true|false,
+   *   escape: true|false
+   * }
+   */
+
+  Stores.NotificationStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._notifications = [];
+      this._notificationCenterVisible = false;
+      this._selectedNotificationFilter = 'all';
+    },
+
+    isNotificationCenterVisible: function () {
+      return this._notificationCenterVisible;
+    },
+
+    addNotification: function (info) {
+      if (_.isEmpty(info.type) || !_.contains(validNotificationTypes, info.type)) {
+        console.warn('Invalid message type: ', info);
+        return;
+      }
+
+      info.notificationId = ++counter;
+      info.cleanMsg = app.utils.stripHTML(info.msg);
+      info.time = moment();
+
+      this._notifications.unshift(info);
+    },
+
+    getNotifications: function () {
+      return this._notifications;
+    },
+
+    clearNotification: function (notificationId) {
+      this._notifications = _.without(this._notifications, _.findWhere(this._notifications, { notificationId: notificationId }));
+    },
+
+    clearNotifications: function () {
+      this._notifications = [];
+    },
+
+    getNotificationFilter: function () {
+      return this._selectedNotificationFilter;
+    },
+
+    setNotificationFilter: function (filter) {
+      if ((_.isEmpty(filter) || !_.contains(validNotificationTypes, filter)) && filter !== 'all') {
+        console.warn('Invalid notification filter: ', filter);
+        return;
+      }
+      this._selectedNotificationFilter = filter;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.ADD_NOTIFICATION:
+          this.addNotification(action.options.info);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.CLEAR_ALL_NOTIFICATIONS:
+          this.clearNotifications();
+          this.triggerChange();
+        break;
+
+        case ActionTypes.CLEAR_SINGLE_NOTIFICATION:
+          this.clearNotification(action.options.notificationId);
+        break;
+
+        case ActionTypes.SHOW_NOTIFICATION_CENTER:
+          this._notificationCenterVisible = true;
+          this.triggerChange();
+        break;
+
+        case ActionTypes.HIDE_NOTIFICATION_CENTER:
+          this._notificationCenterVisible = false;
+          this.triggerChange();
+        break;
+
+        case ActionTypes.SELECT_NOTIFICATION_FILTER:
+          this.setNotificationFilter(action.options.filter);
+          this.triggerChange();
+        break;
+
+        default:
+        return;
+          // do nothing
+      }
+    }
+  });
+
+  Stores.notificationStore = new Stores.NotificationStore();
+  Stores.notificationStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.notificationStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/tests/componentsSpec.react.jsx b/app/addons/fauxton/notifications/tests/componentsSpec.react.jsx
new file mode 100644
index 0000000..b72eac5
--- /dev/null
+++ b/app/addons/fauxton/notifications/tests/componentsSpec.react.jsx
@@ -0,0 +1,162 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+define([
+  'api',
+  'addons/fauxton/notifications/notifications.react',
+  'addons/fauxton/notifications/stores',
+  'testUtils',
+  'react',
+  'moment'
+], function (FauxtonAPI, Views, Stores, utils, React, moment) {
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+  var store = Stores.notificationStore;
+
+
+  describe('NotificationRow', function () {
+    var container;
+
+    var notifications = {
+      success: {
+        notificationId: 1,
+        type: 'success',
+        msg: 'Success!',
+        time: moment()
+      },
+      info: {
+        notificationId: 2,
+        type: 'info',
+        msg: 'Error!',
+        time: moment()
+      },
+      error: {
+        notificationId: 3,
+        type: 'error',
+        msg: 'Error!',
+        time: moment()
+      }
+    };
+
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('shows all notification types when "all" filter applied', function () {
+      var row1 = TestUtils.renderIntoDocument(
+        <Views.NotificationRow filter="all" item={notifications.success} />,
+        container
+      );
+      assert.equal($(row1.getDOMNode()).attr('aria-hidden'), 'false');
+      React.unmountComponentAtNode(container);
+
+      var row2 = TestUtils.renderIntoDocument(
+        <Views.NotificationRow filter="all" item={notifications.error} />,
+        container
+      );
+      assert.equal($(row2.getDOMNode()).attr('aria-hidden'), 'false');
+      React.unmountComponentAtNode(container);
+
+      var row3 = TestUtils.renderIntoDocument(
+        <Views.NotificationRow filter="all" item={notifications.info} />,
+        container
+      );
+      assert.equal($(row3.getDOMNode()).attr('aria-hidden'), 'false');
+      React.unmountComponentAtNode(container);
+    });
+
+    it('hides notification when filter doesn\'t match', function () {
+      var rowEl = TestUtils.renderIntoDocument(
+        <Views.NotificationRow filter="success" item={notifications.info} />,
+        container
+      );
+      assert.equal($(rowEl.getDOMNode()).attr('aria-hidden'), 'true');
+    });
+
+    it('shows notification when filter exact match', function () {
+      var rowEl = TestUtils.renderIntoDocument(
+        <Views.NotificationRow filter="info" item={notifications.info} />,
+        container
+      );
+      assert.equal($(rowEl.getDOMNode()).attr('aria-hidden'), 'false');
+    });
+
+  });
+
+
+  describe('NotificationCenterPanel', function () {
+    var container;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+      store.reset();
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('shows all notifications by default', function () {
+      store.addNotification({ type: 'success', msg: 'Success are okay' });
+      store.addNotification({ type: 'success', msg: 'another success.' });
+      store.addNotification({ type: 'info', msg: 'A single info message' });
+      store.addNotification({ type: 'error', msg: 'Error #1' });
+      store.addNotification({ type: 'error', msg: 'Error #2' });
+      store.addNotification({ type: 'error', msg: 'Error #3' });
+
+      var panelEl = TestUtils.renderIntoDocument(<Views.NotificationCenterPanel />, container);
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 6);
+    });
+
+    it('clicking on a filter icon filters applies appropriate filter', function () {
+      store.addNotification({ type: 'success', msg: 'Success are okay' });
+      store.addNotification({ type: 'success', msg: 'another success.' });
+      store.addNotification({ type: 'info', msg: 'A single info message' });
+      store.addNotification({ type: 'error', msg: 'Error #1' });
+      store.addNotification({ type: 'error', msg: 'Error #2' });
+      store.addNotification({ type: 'error', msg: 'Error #3' });
+
+      var panelEl = TestUtils.renderIntoDocument(<Views.NotificationCenterPanel />, container);
+
+      // there are 2 success messages
+      TestUtils.Simulate.click($(panelEl.getDOMNode()).find('.notification-filter li[data-filter="success"]')[0]);
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 2);
+
+      // 3 errors
+      TestUtils.Simulate.click($(panelEl.getDOMNode()).find('.notification-filter li[data-filter="error"]')[0]);
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 3);
+
+      // 1 info
+      TestUtils.Simulate.click($(panelEl.getDOMNode()).find('.notification-filter li[data-filter="info"]')[0]);
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 1);
+    });
+
+    it('clear all clears all notifications', function () {
+      store.addNotification({ type: 'success', msg: 'Success are okay' });
+      store.addNotification({ type: 'info', msg: 'A single info message' });
+      store.addNotification({ type: 'error', msg: 'Error #2' });
+      store.addNotification({ type: 'error', msg: 'Error #3' });
+
+      var panelEl = TestUtils.renderIntoDocument(<Views.NotificationCenterPanel />, container);
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 4);
+      TestUtils.Simulate.click($(panelEl.getDOMNode()).find('footer input')[0]);
+
+      assert.equal($(panelEl.getDOMNode()).find('.notification-list li[aria-hidden=false]').length, 0);
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/notifications/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/tests/storesSpec.js b/app/addons/fauxton/notifications/tests/storesSpec.js
new file mode 100644
index 0000000..d08e457
--- /dev/null
+++ b/app/addons/fauxton/notifications/tests/storesSpec.js
@@ -0,0 +1,101 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'app',
+  'api',
+  'testUtils',
+  'addons/fauxton/notifications/stores'
+], function (app, FauxtonAPI, utils, Stores) {
+
+  var assert = utils.assert;
+  var store = Stores.notificationStore;
+
+  describe('Notification Store', function () {
+
+    beforeEach(function () {
+      store.reset();
+    });
+
+    it("sets reasonable defaults", function () {
+      assert.equal(store.getNotifications().length, 0);
+      assert.equal(store.isNotificationCenterVisible(), false);
+      assert.equal(store.getNotificationFilter(), 'all');
+    });
+
+    it("confirm only known notification types get added", function () {
+      assert.equal(store.getNotifications().length, 0);
+      store.addNotification({ type: 'success', msg: 'Success are okay' });
+
+      assert.equal(store.getNotifications().length, 1);
+      store.addNotification({ type: 'info', msg: 'Infos are also okay' });
+
+      assert.equal(store.getNotifications().length, 2);
+      store.addNotification({ type: 'error', msg: 'Errors? Bring em on' });
+
+      assert.equal(store.getNotifications().length, 3);
+      store.addNotification({ type: 'rhubarb', msg: 'But rhubarb is NOT a valid notification type' });
+
+      // confirm it wasn't added
+      assert.equal(store.getNotifications().length, 3);
+    });
+
+    it("clearNotification clears a specific notification", function () {
+      store.addNotification({ type: 'success', msg: 'one' });
+      store.addNotification({ type: 'success', msg: 'two' });
+      store.addNotification({ type: 'success', msg: 'three' });
+      store.addNotification({ type: 'success', msg: 'four' });
+
+      var notifications = store.getNotifications();
+      assert.equal(notifications.length, 4);
+
+      // find the notification ID of the "three" message
+      var notification = _.findWhere(notifications, { msg: 'three' });
+      store.clearNotification(notification.notificationId);
+
+      // confirm it was removed
+      var updatedNotifications = store.getNotifications();
+      assert.equal(updatedNotifications.length, 3);
+      assert.equal(_.findWhere(updatedNotifications, { msg: 'three' }), undefined);
+    });
+
+    it("setNotificationFilter only sets for known notification types", function () {
+      store.setNotificationFilter('all');
+      assert.equal(store.getNotificationFilter(), 'all');
+
+      store.setNotificationFilter('success');
+      assert.equal(store.getNotificationFilter(), 'success');
+
+      store.setNotificationFilter('error');
+      assert.equal(store.getNotificationFilter(), 'error');
+
+      store.setNotificationFilter('info');
+      assert.equal(store.getNotificationFilter(), 'info');
+
+      store.setNotificationFilter('broccoli');
+      assert.equal(store.getNotificationFilter(), 'info'); // this check it's still set to the previously set value
+    });
+
+    it("clear all notifications", function () {
+      store.addNotification({ type: 'success', msg: 'one' });
+      store.addNotification({ type: 'success', msg: 'two' });
+      store.addNotification({ type: 'success', msg: 'three' });
+      store.addNotification({ type: 'success', msg: 'four' });
+      assert.equal(store.getNotifications().length, 4);
+
+      store.clearNotifications();
+      assert.equal(store.getNotifications().length, 0);
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/tests/baseSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/tests/baseSpec.js b/app/addons/fauxton/tests/baseSpec.js
index da49273..83e8b91 100644
--- a/app/addons/fauxton/tests/baseSpec.js
+++ b/app/addons/fauxton/tests/baseSpec.js
@@ -19,25 +19,23 @@ define([
 
 
   describe('Fauxton RouteObject:beforeEstablish', function () {
-    var TestRouteObject, testRouteObject, mockLayout, _layout;
+    var TestRouteObject, testRouteObject, mockLayout, _layout, setViewCalled;
 
     before(function () {
       Base.initialize();
       _layout = FauxtonAPI.masterLayout;
+      setViewCalled = false;
     });
 
     beforeEach(function () {
       TestRouteObject = FauxtonAPI.RouteObject.extend({
-        crumbs: ['mycrumbs']
+        crumbs: ['mycrumbs'],
+        hideNotificationCenter: true
       });
 
       testRouteObject = new TestRouteObject();
       var apiBar = {};
       apiBar.hide = sinon.spy();
-      var setViewSpy = sinon.stub();
-      setViewSpy.returns({
-        render: function () {}
-      });
 
       // Need to find a better way of doing this
       mockLayout = {
@@ -47,8 +45,18 @@ define([
           return promise;
         },
         clearBreadcrumbs: sinon.spy(),
-        setView: setViewSpy,
-        renderView: sinon.spy(),
+        setView: function () {
+          return {
+            render: function () {
+              setViewCalled = true;
+            }
+          };
+        },
+        renderView: function () {
+          var d = $.Deferred();
+          d.resolve();
+          return d.promise();
+        },
         removeView: sinon.spy(),
         hooks: [],
         setBreadcrumbs: sinon.spy(),
@@ -74,7 +82,7 @@ define([
     it('Should set breadcrumbs when breadcrumbs exist', function () {
       FauxtonAPI.masterLayout = mockLayout;
       testRouteObject.renderWith('the-route', mockLayout, 'args');
-      assert.ok(mockLayout.setView.calledOnce, 'Set Breadcrumbs was called');
+      assert.ok(setViewCalled, 'Set Breadcrumbs was called');
     });
 
   });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/tests/componentsSpec.react.jsx b/app/addons/fauxton/tests/componentsSpec.react.jsx
index f9b1ba3..9c05f36 100644
--- a/app/addons/fauxton/tests/componentsSpec.react.jsx
+++ b/app/addons/fauxton/tests/componentsSpec.react.jsx
@@ -28,7 +28,7 @@ define([
       container = document.createElement('div');
 
       // when we want to control the diff, we have to render directly
-      trayEl = React.render(<Views.Tray className="traytest" />, container);
+      trayEl = TestUtils.renderIntoDocument(<Views.Tray className="traytest" />, container);
 
       oldToggleSpeed = FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED;
 
@@ -109,7 +109,7 @@ define([
         }
       });
 
-      var reactEl = React.render(React.createElement(wrapper), container);
+      var reactEl = TestUtils.renderIntoDocument(React.createElement(wrapper), container);
       reactEl.runTest();
 
       React.unmountComponentAtNode(container);
@@ -135,7 +135,7 @@ define([
     });
 
     it('renders 20-wise pages per default', function () {
-      var pageEl = React.render(
+      var pageEl = TestUtils.renderIntoDocument(
         <Views.Pagination page={3} total={55} urlPrefix="?prefix=" urlSuffix="&suffix=88" />,
         container
       );
@@ -152,7 +152,7 @@ define([
     });
 
     it("can overwrite collection size", function () {
-      var pageEl = React.render(
+      var pageEl = TestUtils.renderIntoDocument(
         <Views.Pagination perPage={10} page={3} total={55} urlPrefix="?prefix=" urlSuffix="&suffix=88" />,
         container
       );
@@ -162,7 +162,7 @@ define([
     });
 
     it("handles large collections properly - beginning", function () {
-      var pageEl = React.render(
+      var pageEl = TestUtils.renderIntoDocument(
         <Views.Pagination page={3} total={600} />,
         container
       );
@@ -175,7 +175,7 @@ define([
     });
 
     it("handles large collections properly - middle", function () {
-      var pageEl = React.render(
+      var pageEl = TestUtils.renderIntoDocument(
         <Views.Pagination page={10} total={600} />,
         container
       );
@@ -189,7 +189,7 @@ define([
     });
 
     it("handles large collections properly - end", function () {
-      var pageEl = React.render(
+      var pageEl = TestUtils.renderIntoDocument(
         <Views.Pagination page={29} total={600} />,
         container
       );
@@ -204,5 +204,32 @@ define([
 
   });
 
-});
 
+  describe('Clipboard', function () {
+    var container;
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('shows a clipboard icon by default', function () {
+      var clipboard = TestUtils.renderIntoDocument(<Views.Clipboard text="copy me" />, container);
+      assert.equal($(clipboard.getDOMNode()).find('.fonticon-clipboard').length, 1);
+    });
+
+    it('shows text if specified', function () {
+      var clipboard = TestUtils.renderIntoDocument(<Views.Clipboard displayType="text" text="copy me" />, container);
+      assert.equal($(clipboard.getDOMNode()).find('.fonticon-clipboard').length, 0);
+    });
+
+    it('shows custom text if specified ', function () {
+      var clipboard = TestUtils.renderIntoDocument(<Views.Clipboard displayType="text" textDisplay='booyah!' text="copy me" />, container);
+      assert.ok(/booyah!/.test($(clipboard.getDOMNode())[0].outerHTML));
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/addons/fauxton/tests/nightwatch/notificationCenter.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/tests/nightwatch/notificationCenter.js b/app/addons/fauxton/tests/nightwatch/notificationCenter.js
new file mode 100644
index 0000000..ef3ffb8
--- /dev/null
+++ b/app/addons/fauxton/tests/nightwatch/notificationCenter.js
@@ -0,0 +1,38 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+module.exports = {
+  'Notification Center' : function (client) {
+    var waitTime = client.globals.maxWaitTime,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .url(baseUrl + '/#login')
+
+      // confirm the btn doesn't appear on the login screen
+      .waitForElementPresent('a[href="#login"]', waitTime, false)
+      .waitForElementNotPresent('#notification-center-btn div.fonticon-bell', waitTime, false)
+
+      .loginToGUI()
+      .waitForElementPresent('#notification-center-btn', waitTime, false)
+      .assert.cssClassNotPresent('.notification-center-panel', 'visible')
+      .clickWhenVisible('#notification-center-btn .fonticon-bell', waitTime, false)
+      .waitForElementPresent('.notification-center-panel.visible', waitTime, false)
+
+      .getText('.notification-center-panel', function (result) {
+        var content = result.value;
+        this.verify.ok(/You\shave\sbeen\slogged\sin\./.test(content),
+          'Confirming login message appears');
+      })
+    .end();
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/core/routeObject.js
----------------------------------------------------------------------
diff --git a/app/core/routeObject.js b/app/core/routeObject.js
index 94f6c60..33ff37f 100644
--- a/app/core/routeObject.js
+++ b/app/core/routeObject.js
@@ -76,6 +76,7 @@ function (FauxtonAPI, React, Backbone) {
     crumbs: [],
     layout: "with_sidebar",
     apiUrl: null,
+    hideNotificationPanel: null,
     disableLoader: false,
     loaderClassname: 'loader',
     renderedState: false,

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/core/tests/utilsSpec.js
----------------------------------------------------------------------
diff --git a/app/core/tests/utilsSpec.js b/app/core/tests/utilsSpec.js
index 17d09ef..c6e09af 100644
--- a/app/core/tests/utilsSpec.js
+++ b/app/core/tests/utilsSpec.js
@@ -71,6 +71,17 @@ define([
         assert.deepEqual(utils.localStorageGet(key), obj);
       });
 
+      it ('stripHTML removes HTML', function () {
+        [
+          { html: '<span>okay</span>', text: 'okay' },
+          { html: 'test <span>before</span> and after', text: 'test before and after' },
+          { html: 'testing <a href="#whatever">attributes</span>', text: 'testing attributes' },
+          { html: '<span>testing</span> multiple <p>elements</p>', text: 'testing multiple elements' }
+        ].forEach(function (item) {
+          assert.equal(utils.stripHTML(item.html), item.text);
+        });
+      });
+
     });
   });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/core/utils.js
----------------------------------------------------------------------
diff --git a/app/core/utils.js b/app/core/utils.js
index 43fabbc..6728a1b 100644
--- a/app/core/utils.js
+++ b/app/core/utils.js
@@ -145,6 +145,12 @@ function ($, _) {
         }
       }
       return data;
+    },
+
+    stripHTML: function (str) {
+      var tmpElement = document.createElement("div");
+      tmpElement.innerHTML = str;
+      return tmpElement.textContent || tmpElement.innerText;
     }
   };
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/templates/layouts/doc_editor.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/doc_editor.html b/app/templates/layouts/doc_editor.html
index c76a460..4fb85df 100644
--- a/app/templates/layouts/doc_editor.html
+++ b/app/templates/layouts/doc_editor.html
@@ -16,6 +16,7 @@ the License.
   <header class="flex-layout flex-row">
     <div id="breadcrumbs" class="flex-body"></div>
     <div id="api-navbar"></div>
+    <div id="notification-center-btn"></div>
   </header>
 
   <div id="dashboard-content"></div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/templates/layouts/one_pane.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/one_pane.html b/app/templates/layouts/one_pane.html
index bb518d1..dc31545 100644
--- a/app/templates/layouts/one_pane.html
+++ b/app/templates/layouts/one_pane.html
@@ -16,6 +16,7 @@ the License.
     <div id="breadcrumbs" class="flex-body"></div>
     <div id="right-header"></div>
     <div id="api-navbar"></div>
+    <div id="notification-center-btn"></div>
   </header>
 
   <div class="content-area container-fluid">

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/templates/layouts/two_pane.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/two_pane.html b/app/templates/layouts/two_pane.html
index ee6e62b..2b8d154 100644
--- a/app/templates/layouts/two_pane.html
+++ b/app/templates/layouts/two_pane.html
@@ -15,10 +15,11 @@ the License.
 
   <div class="header-wrapper flex-layout flex-row">
     <div id="breadcrumbs"></div>
-    <div class="right-header-wrapper">
-      <div id="react-headerbar"></div>
+    <div class="right-header-wrapper flex-layout flex-row flex-body">
+      <div id="react-headerbar" class="flex-body"></div>
+      <div id="right-header" class="flex-body"></div>
       <div id="api-navbar"></div>
-      <div id="right-header"></div>
+      <div id="notification-center-btn"></div>
     </div>
   </div>
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/templates/layouts/with_sidebar.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/with_sidebar.html b/app/templates/layouts/with_sidebar.html
index 287be0e..a4bf1ea 100644
--- a/app/templates/layouts/with_sidebar.html
+++ b/app/templates/layouts/with_sidebar.html
@@ -13,8 +13,9 @@ the License.
 */%>
 <div id="dashboard">
   <header class="flex-layout flex-row">
-    <div id="breadcrumbs"></div>
+    <div id="breadcrumbs" class="flex-body"></div>
     <div id="api-navbar"></div>
+    <div id="notification-center-btn"></div>
   </header>
 
   <div class="with-sidebar content-area">

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/app/templates/layouts/with_tabs_sidebar.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/with_tabs_sidebar.html b/app/templates/layouts/with_tabs_sidebar.html
index 2c6a969..34d673a 100644
--- a/app/templates/layouts/with_tabs_sidebar.html
+++ b/app/templates/layouts/with_tabs_sidebar.html
@@ -13,12 +13,14 @@ the License.
 */%>
 
 <div id="dashboard" class="with-sidebar">
+
   <header class="flex-layout flex-row two-panel-header">
     <div id="breadcrumbs" class="sidebar"></div>
-    <div class="right-header-wrapper flex-body">
-      <div id="react-headerbar"></div>
+    <div class="header-right-panel flex-layout flex-row flex-body">
+      <div id="react-headerbar" class="flex-body"></div>
+      <div id="right-header" class="flex-body"></div>
       <div id="api-navbar"></div>
-      <div id="right-header"></div>
+      <div id="notification-center-btn"></div>
     </div>
   </header>
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/index.underscore
----------------------------------------------------------------------
diff --git a/assets/index.underscore b/assets/index.underscore
index 095bf10..17078fa 100644
--- a/assets/index.underscore
+++ b/assets/index.underscore
@@ -29,11 +29,12 @@
 </head>
 
 <body id="home">
-  <!-- Main container. -->
+
   <div id="global-notifications" class="container errors-container"></div>
+  <div id="notification-center"></div>
 
+  <!-- Main container. -->
   <div role="main" id="main">
-
     <div id="app-container">
       <div class="wrapper">
         <div id="primary-navbar"></div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/fauxton.less
----------------------------------------------------------------------
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 7799f16..14f5387 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -24,6 +24,7 @@
 @import "mixins.less";
 @import "animations.less";
 @import "react-animations.less";
+@import "notification-center.less";
 
 /**
  * HTML-wide overrides
@@ -326,6 +327,7 @@ div.spinner {
 }
 
 #header-search {
+  position: relative;
   height: @collapsedNavWidth;
 }
 
@@ -365,7 +367,7 @@ div.spinner {
 }
 
 .api-bar-tray:before {
-  right: 25px;
+  right: 95px;
 }
 
 .js-filter-form {
@@ -519,18 +521,13 @@ body #dashboard .two-panel-header {
     padding: 0;
     margin: 0;
   }
-  .searchbox-wrapper {
-    width: 295px;
-  }
   .searchbox-container {
     margin: 12px;
-    position: absolute;
-    right: inherit;
     input[type="text"] {
       .border-radius(5px);
       font-size: 13px;
       padding: 8px 35px 8px 10px;
-      width: 275px;
+      width: 200px;
     }
     .btn-primary {
       background: none repeat scroll 0% 0% transparent;
@@ -544,17 +541,9 @@ body #dashboard .two-panel-header {
       .icon-search {
         position: absolute;
         top: 11px;
-        right: 5px;
       }
     }
   }
-  > div {
-    float: right;
-    margin: 0;
-  }
-  > div:last-child {
-    border-left: none;
-  }
 }
 
 //----footer--///
@@ -612,6 +601,7 @@ footer.pagination-footer {
   z-index: 6;
 }
 
+
 // left navigationbar is opened
 @media (max-width: 780px) {
   .closeMenu {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/layouts.less
----------------------------------------------------------------------
diff --git a/assets/less/layouts.less b/assets/less/layouts.less
index 30b6057..cae896c 100644
--- a/assets/less/layouts.less
+++ b/assets/less/layouts.less
@@ -2,8 +2,10 @@
 @import "mixins.less";
 
 
-/* new flex layout for templates. "body #dashboard" needed for specificity: will remove at end */
-body #dashboard .flex-layout, body #dashboard.flex-layout {
+/* new flex layout for templates. Specificity needed for now. Will remove at end */
+body #dashboard .flex-layout,
+body #dashboard.flex-layout,
+body #notification-center .flex-layout {
   .display-flex();
 
   &.flex-col {
@@ -43,9 +45,14 @@ body #dashboard .flex-layout, body #dashboard.flex-layout {
 
 /* can be added to any element in a display:flex element that you want to act as the main body. It expands to the
    available space and shows a scrollbar */
-body #dashboard .flex-body {
+body #dashboard .flex-body, body #notification-center .flex-body {
   .flex(1);
   overflow: auto;
+
+  /* yet more stuff that can be removed at end */
+  &.right-header-wrapper {
+    overflow: hidden;
+  }
 }
 
 /* used on the databases page. To be removed once moved to flexbox */
@@ -59,16 +66,8 @@ body #dashboard .flex-body {
 /* this drops .fixed-header, which was a position:absolute'd element, and switches it and all children to use flexbox. */
 .one-pane > header {
   width: 100%;
-
-  /* TODO move all style stuff out of this file. layouts.less should be entirely layout CSS, nothing else */
   .bottom-shadow-border();
 
-  #breadcrumbs {
-    overflow: hidden;
-
-    /* overrides - can be removed later */
-    float: none;
-  }
   #right-header {
     -ms-flex-preferred-size: auto;
     -webkit-flex: 0 0 auto;
@@ -76,26 +75,50 @@ body #dashboard .flex-body {
     flex-shrink: 0;
     flex-basis: inherit;
   }
-  #api-navbar {
-    .flex(0 0 117px);
+  #breadcrumbs {
+    overflow: hidden;
   }
 }
 
+body #dashboard .flex-body#right-header {
+  .flex(0 1 auto);
+  overflow: hidden;
+}
+
+body #dashboard .flex-layout #api-navbar {
+  .flex(0 0 auto);
+}
+
 /* temporary wedge. Can be replaced at end */
 .with-sidebar {
+
+  /* temporary wedge. Can be replaced at end */
   #breadcrumbs {
     float: left;
   }
   #api-navbar {
     float: right;
   }
+  /* end tmp wedge */
+}
+
+body #dashboard .header-right-panel {
+  overflow: hidden;
 }
+
 .right-header-wrapper {
   #api-navbar {
     float: right;
   }
 }
 
+body #dashboard .right-db-header {
+  &>div {
+    .flex(1 0 auto);
+    /*overflow: hidden; */
+  }
+}
+
 /** FLEXBOX REFACTOR: "body #dashboard" can be removed at end. Specificity needed right now */
 body #dashboard.two-pane {
   height: 100%;
@@ -105,9 +128,6 @@ body #dashboard.two-pane {
     .display-flex();
     .flex-direction(row);
   }
-  .right-header-wrapper {
-    .flex(1);
-  }
   #left-content {
     .flex(0 0 440px);
   }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/mixins.less
----------------------------------------------------------------------
diff --git a/assets/less/mixins.less b/assets/less/mixins.less
index 0448475..19c6d35 100644
--- a/assets/less/mixins.less
+++ b/assets/less/mixins.less
@@ -20,6 +20,14 @@
         transition-timing-function: cubic-bezier(@a, @b, @c, @d); /* custom */
 }
 
+.transition(...) {
+  -webkit-transition: @arguments;
+  -moz-transition: @arguments;
+  -ms-transition: @arguments;
+  -o-transition: @arguments;
+  transition: @arguments;
+}
+
 .bottom-shadow-border(@height: 6px) {
   border-bottom: 1px solid #999;
   .box-shadow(0px @height 0 0 rgba(0, 0, 0, 0.1));

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/notification-center.less
----------------------------------------------------------------------
diff --git a/assets/less/notification-center.less b/assets/less/notification-center.less
new file mode 100644
index 0000000..6f873fd
--- /dev/null
+++ b/assets/less/notification-center.less
@@ -0,0 +1,223 @@
+//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
+//  use this file except in compliance with the License. You may obtain a copy of
+//  the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+//  License for the specific language governing permissions and limitations under
+//  the License.
+
+@import "variables.less";
+@import "mixins.less";
+
+body #dashboard #notification-center-btn {
+  border: 0;
+  background-color: @background;
+  border-left: 1px solid #cccccc;
+  font-size: 24px;
+  color: #666;
+  padding: 0;
+  cursor: pointer;
+  .flex(0 0 auto);
+
+  &>div {
+    padding: 17px;
+  }
+  &:hover {
+    color: @darkRed;
+  }
+}
+
+#notification-center {
+  .notification-center-panel {
+    z-index: 12;
+    .translate(306px, 0px);/* the 6px extra is for the left shadow */
+    position: fixed;
+    box-shadow: 0 6px 5px 4px rgba(0, 0, 0, 0.1);
+    top: 0;
+    right: 0;
+    width: 300px;
+    height: 100%;
+    .transition(all .3s ease-out);
+    background-color: #333333;
+    color: #dddddd;
+
+    &.visible {
+      .translate(0px, 0px);
+    }
+
+    header {
+      .flex(0 0 auto);
+      padding: 16px;
+
+      h1 {
+        font-size: 16px;
+        line-height: 20px;
+        margin: 0;
+        color: white;
+        font-weight: normal;
+      }
+      .fonticon-bell {
+        .flex(0 0 30px);
+      }
+      button {
+        .flex(0 0 46px);
+        background-color: transparent;
+        border: 0;
+        margin: -18px -16px -14px; /* enlarges the hit area */
+        font-size: 21px;
+        color: #dddddd;
+      }
+    }
+
+    .notification-filter {
+      .flex(0 0 29px);
+
+      list-style: none;
+      margin: 0;
+      font-size: 13px;
+
+      li {
+        text-align: center;
+        margin-right: 1px;
+        background-color: #444444;
+        padding: 4px 0 3px;
+        cursor: pointer;
+
+        &.selected {
+          background-color: #555555;
+        }
+      }
+      .fonticon {
+        font-size: 16px;
+      }
+      .fonticon-ok-circled {
+        font-size: 14px;
+      }
+    }
+
+    .notification-list {
+      margin: 15px 0 0;
+      list-style-type: none;
+
+      li {
+        margin: 0 16px;
+        line-height: 16px;
+        font-size: 12px;
+        overflow: hidden;
+        border-bottom: 1px solid #222222;
+
+        &.no-notifications {
+          color: #999999;
+          margin-top: 25px;
+          border-bottom: 0;
+        }
+        &>div {
+          padding: 10px 0 15px;
+        }
+        span {
+          .flex(0 0 30px);
+        }
+        button {
+          .flex(0 0 22px);
+          color: #dddddd;
+          background: transparent;
+          border: 0;
+          height: 10px;
+          margin-top: -4px;
+          margin-left: 6px;
+        }
+        p {
+          margin-bottom: 0;
+        }
+      }
+      .fonticon {
+        font-size: 15px;
+      }
+      .fonticon-ok-circled {
+        font-size: 13px;
+      }
+      a {
+        color: white;
+        text-decoration: underline;
+      }
+      .notification-actions {
+        font-size: 11px;
+        margin-top: 2px;
+        span.divider {
+          color: #444444;
+          padding: 0 5px;
+        }
+      }
+      .time-elapsed {
+        color: #565656;
+      }
+      .copy {
+        color: @blue;
+        text-decoration: none;
+        &:hover {
+          color: @red;
+          text-decoration: underline;
+        }
+      }
+    }
+
+    footer {
+      border-top: 1px solid #555555;
+      .flex(0 0 auto);
+      text-align: center;
+      padding: 4px;
+    }
+  }
+
+  .fonticon-ok-circled {
+    color: @successAlertColor;
+  }
+  .fonticon-attention-circled {
+    color: @errorAlertColor;
+  }
+  .fonticon-info-circled {
+    color: @infoAlertColor;
+  }
+
+  .notification-page-mask {
+    z-index: -1;
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    opacity: 0;
+    .transition(all .3s ease-out);
+
+    &.visible {
+      z-index: 10;
+      opacity: 0.3;
+      background-color: #000000;
+    }
+  }
+}
+
+@-webkit-keyframes in {
+  0% { max-height: 0; }
+  100% { max-height: 1000px; }
+}
+
+@-webkit-keyframes out {
+  0% { max-height: 1000px; }
+  100% { max-height:0; }
+}
+
+@keyframes in {
+  0% { max-height: 0; }
+  100% { max-height: 1000px; }
+}
+
+@keyframes out {
+  0% { max-height: 1000px; }
+  100% { max-height:0; }
+}
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/templates.less
----------------------------------------------------------------------
diff --git a/assets/less/templates.less b/assets/less/templates.less
index 26bdfbf..ddba0d0 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -374,7 +374,7 @@ with_tabs_sidebar.html
   position: absolute;
   left: 0;
   right: 0;
-  z-index: 50;
+  /*z-index: 50; */
 }
 
 #app-container > div {
@@ -395,7 +395,6 @@ with_tabs_sidebar.html
   right: 0;
   left: @sidebarWidth+@navWidth;
   position: fixed;
-  z-index: 11;
   display: block;
 
   .topmenu-defaults;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/trays.less
----------------------------------------------------------------------
diff --git a/assets/less/trays.less b/assets/less/trays.less
index c3007e9..d9da2be 100644
--- a/assets/less/trays.less
+++ b/assets/less/trays.less
@@ -152,7 +152,7 @@
   .dropdown-menu {
     position: relative;
     border: 0px;
-    background-color: #3333333;
+    background-color: #333333;
 
     /* unfortunate, but the typeahead plugin adds these values inline */
     left: 0px !important;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/eb5124af/assets/less/variables.less
----------------------------------------------------------------------
diff --git a/assets/less/variables.less b/assets/less/variables.less
index 05feb10..02793eb 100644
--- a/assets/less/variables.less
+++ b/assets/less/variables.less
@@ -109,3 +109,8 @@
 
 /* padding and margins */
 @panelPadding: 15px;
+
+/* alerts */
+@infoAlertColor: #329898;
+@successAlertColor: #448c11;
+@errorAlertColor: #c45b55;


Mime
View raw message