You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by mi...@apache.org on 2015/06/03 21:22:28 UTC

fauxton commit: updated refs/heads/master to b4e5f53

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 5d1cb4378 -> b4e5f5353


Add loading lines to active tasks
- Add tabs for filtering
- Add source sequence tray
- garrensmith helped me out a whole bunch with this


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

Branch: refs/heads/master
Commit: b4e5f535309d6a807d1628d9cbf5eef91d105190
Parents: 5d1cb43
Author: Michelle Phung <mi...@gmail.com>
Authored: Wed Jun 3 09:50:28 2015 +0200
Committer: michellephung@gmail.com <mi...@gmail.com>
Committed: Wed Jun 3 14:09:56 2015 -0400

----------------------------------------------------------------------
 app/addons/activetasks/actions.js               |  18 +-
 app/addons/activetasks/actiontypes.js           |   3 +-
 .../activetasks/assets/less/activetasks.less    | 171 +++++++----
 app/addons/activetasks/components.react.jsx     | 294 +++++++++----------
 app/addons/activetasks/routes.js                |   4 +-
 app/addons/activetasks/stores.js                |  36 ++-
 .../tests/activetasks.componentsSpec.react.jsx  |  25 +-
 .../activetasks/tests/activetasks.storesSpec.js |  45 +--
 app/addons/fauxton/components.react.jsx         |  35 +++
 9 files changed, 376 insertions(+), 255 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/actions.js b/app/addons/activetasks/actions.js
index 74fae94..55469ab 100644
--- a/app/addons/activetasks/actions.js
+++ b/app/addons/activetasks/actions.js
@@ -16,17 +16,17 @@ define([
 ],
 function (FauxtonAPI, ActionTypes, Resources) {
   return {
-    fetchAndSetActiveTasks: function (options) {
-      var activeTasks = options;
-
+    init: function (activeTasks) {
+      this.fetchAndSetActiveTasks(activeTasks.table, activeTasks);
       FauxtonAPI.when(activeTasks.fetch()).then(function () {
-        this.init(activeTasks.table, activeTasks);
+        this.fetchAndSetActiveTasks(activeTasks.table, activeTasks);
+        this.setActiveTaskIsLoading(false);
       }.bind(this));
     },
 
-    init: function (collection, backboneCollection) {
+    fetchAndSetActiveTasks: function (collection, backboneCollection) {
       FauxtonAPI.dispatch({
-        type: ActionTypes.ACTIVE_TASKS_INIT,
+        type: ActionTypes.ACTIVE_TASKS_FETCH_AND_SET,
         options: {
           collectionTable: collection,
           backboneCollection: backboneCollection
@@ -64,6 +64,12 @@ function (FauxtonAPI, ActionTypes, Resources) {
           columnName: columnName
         }
       });
+    },
+    setActiveTaskIsLoading: function (boolean) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.ACTIVE_TASKS_SET_IS_LOADING,
+        options: boolean
+      });
     }
   };
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/actiontypes.js b/app/addons/activetasks/actiontypes.js
index 8cd2838..cdba87d 100644
--- a/app/addons/activetasks/actiontypes.js
+++ b/app/addons/activetasks/actiontypes.js
@@ -16,6 +16,7 @@ define([], function () {
     ACTIVE_TASKS_SET_COLLECTION: 'ACTIVE_TASKS_SET_COLLECTION',
     ACTIVE_TASKS_SET_SEARCH_TERM: 'ACTIVE_TASKS_SET_SEARCH_TERM',
     ACTIVE_TASKS_SORT_BY_COLUMN_HEADER: 'ACTIVE_TASKS_SORT_BY_COLUMN_HEADER',
-    ACTIVE_TASKS_INIT: 'ACTIVE_TASKS_INIT'
+    ACTIVE_TASKS_FETCH_AND_SET: 'ACTIVE_TASKS_FETCH_AND_SET',
+    ACTIVE_TASKS_SET_IS_LOADING: 'ACTIVE_TASKS_SET_IS_LOADING'
   };
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/assets/less/activetasks.less
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/assets/less/activetasks.less b/app/addons/activetasks/assets/less/activetasks.less
index 1116671..e11c0a8 100644
--- a/app/addons/activetasks/assets/less/activetasks.less
+++ b/app/addons/activetasks/assets/less/activetasks.less
@@ -10,6 +10,10 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+@import "../../../../../assets/less/bootstrap/variables.less";
+@import "../../../../../assets/less/variables.less";
+@import "../../../../../assets/less/bootstrap/mixins.less";
+
 .task-tabs li,
 .active-tasks th {
   cursor: pointer;
@@ -65,7 +69,7 @@
 }
 
 .no-matching-database-on-search {
-  color: #e33f3b;
+  color: #af2d24;
 }
 
 p.multiline-active-tasks-message {
@@ -88,67 +92,63 @@ p.multiline-active-tasks-message {
   }
 }
 
-.dashboard-lower-menu {
-  padding-top: 90px;
-  padding-left: 20px;
-}
+#active-tasks-filter-tabs {
 
-input[type="text"].searchbox {
-  width: 200px;
-  height: 40px;
-  float: right;
-  margin:0px;
-}
+  height: 100px;
+  background-color: #CBCBCB;
+  padding-left: 20px;
+  min-width: 550px;
+  overflow: scroll;
 
-#toggle-filter-tab:hover {
-  color: white;
-  background-color: #e33f3b;
-}
 
-.filter-tray.toggleFilterTray-enter {     // starting css
-  overflow: hidden;
-  padding-top: 0px;
-  max-height: 0px;
-}
-                                          // animate opening 
-.filter-tray.toggleFilterTray-enter.toggleFilterTray-enter-active {     
-  max-height: 300px;
-  height: auto;
-  transition: max-height 0.5s linear;
-}
+  input {
+    display:none;
+  }
 
-.filter-tray {                            // final css 
-  height: auto;
-  max-height: 300px;                      
-  overflow: hidden;
-}
-                                          // animate closing 
-.filter-tray.toggleFilterTray-leave.toggleFilterTray-leave-active {     
-  max-height: 0;
-  padding-top: 0px;
-  transition: max-height 0.5s linear;
-}
+  li {
+    background-color: #eee;
+    margin-top: 65px;
+    margin-left: 3px;
+    &.active-tasks-checked {
+      background-color: #af2d24;
+
+      label {
+        color: #fff;
+      }
+    };
+
+    &:hover {
+      border: 1px solid #af2d24;
+      color: #af2d24;
+    };
+
+    label {
+      height: 35px;
+      width: 100%;
+      line-height: 35px;
+      text-align: center;
+    } 
+  }
 
-.filter-checkboxes {
-  float: left;
-  height: auto;
-  width: auto;
-  margin-top: 10px;
-}
+  #active-tasks-search-box {
+    display: inline;
+    font-size: 14px;
+    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+    height: 36px;
+    background-color: #eee;
+    width: 200px;
+    margin: 0px;
 
-.active-tasks-one-checkbox {
-  input {
-    vertical-align: middle;
-    margin-right: 5px;
-    margin-bottom: 3px;
+    &:focus {
+      background-color: #fff;
+      outline-color: #fff;
+    };
   }
-  margin-right: 10px;
-  display: inline-block;
 }
 
-.active-tasks-checkbox-label {
-  display: inline-block;
-  color: #666;
+.dashboard-lower-menu {
+  padding-top: 90px;
+  padding-left: 20px;
 }
 
 @media (max-width: 940px) {
@@ -159,16 +159,13 @@ input[type="text"].searchbox {
   }
 }
 
-.filter-checkboxes-form {
-  margin:0px;
-}
-
 .polling-interval-widget {
 
   width: 250px;
   margin-right: 20px;
   margin-top: 10px;
   color: #666;
+  float:right;
 
   li {
     list-style-type: none;
@@ -208,4 +205,64 @@ input[type="text"].searchbox {
     padding: 0px;
     margin: 0px;
   }
+
+  .active-tasks-header {
+    background-color: #ccc;
+  }
+
+  .icon-caret-up, .icon-caret-down {
+    color: #af2d24;
+  }
+}
+
+.active-tasks-loading-lines {
+  padding-top: 15px;
+  float: left;
+  margin-left: -40px;
+
+  #line1, #line2, #line3, #line4 {
+    background-color: #bbb;
+  }
+}
+
+.view-source-sequence-btn { // "View" Button
+  background-color: #666;
+  display: inline;
+  border-radius: 3px; 
+  padding: 5px;
+  color: #fff !important; 
+}
+
+.view_source_sequence_tray {
+  padding: 16px 20px 28px;
+
+  position: relative;
+  min-width: 360px;
+  top: 15px;
+  float: right;
+
+  &:before {
+    right: 20px;
+  }
+  input.input-xxlarge {
+    margin-bottom: 0px;
+    width: 250px;
+    .border-radius(5px 0 0 5px);
+  }
+
+  a.btn {
+    color: white;
+    background-color: #af2d24;
+    margin-left: 0;
+    line-height: 1.5em;
+    border: 0px;
+    padding: 10px 10px 9px;
+    font-size: 14px;
+    .border-radius(0 5px 5px 0);
+
+    &:hover, &.copy-button.zeroclipboard-is-hover {
+      background-color: #cbcbcb;
+      color: white;
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/components.react.jsx b/app/addons/activetasks/components.react.jsx
index 0110580..5f146ab 100644
--- a/app/addons/activetasks/components.react.jsx
+++ b/app/addons/activetasks/components.react.jsx
@@ -16,8 +16,10 @@ define([
   'react',
   'addons/activetasks/stores',
   'addons/activetasks/resources',
-  'addons/activetasks/actions'
-], function (app, FauxtonAPI, React, Stores, Resources, Actions) {
+  'addons/activetasks/actions',
+  'addons/components/react-components.react',
+  'addons/fauxton/components.react'
+], function (app, FauxtonAPI, React, Stores, Resources, Actions, Components, ComponentsReact) {
 
   var activeTasksStore = Stores.activeTasksStore;
   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@@ -34,7 +36,7 @@ define([
         headerIsAscending: activeTasksStore.getHeaderIsAscending(),
 
         setPolling: activeTasksStore.setPolling,
-        clearPolling: activeTasksStore.clearPolling,
+        clearPolling: activeTasksStore.clearPolling
       };
     },
 
@@ -60,7 +62,7 @@ define([
       Actions.setSearchTerm(searchTerm);
     },
 
-    switchTab: function (newRadioButton) {  //radio buttons
+    switchTab: function (newRadioButton) {  //tabs buttons
       Actions.switchTab(newRadioButton);
     },
 
@@ -84,14 +86,14 @@ define([
       return (
         <div className="scrollable">
           <div className="inner">
-            <ActiveTasksFilter 
-              searchTerm={searchTerm} 
-              selectedRadio={selectedRadio} 
-              onSearch={setSearchTerm} 
+            <ActiveTasksFilterTabs
+              searchTerm={searchTerm}
+              selectedRadio={selectedRadio}
+              onSearch={setSearchTerm}
               onRadioClick={this.switchTab}/>
-            <ActiveTaskTable 
-              collection={collection} 
-              searchTerm={searchTerm} 
+            <ActiveTaskTable
+              collection={collection}
+              searchTerm={searchTerm}
               selectedRadio={selectedRadio}
               onTableHeaderClick={onTableHeaderClick}
               sortByHeader={sortByHeader}
@@ -102,93 +104,7 @@ define([
     }
   });
 
-  var ActiveTasksFilter = React.createClass({
-    getStoreState: function () {
-      return {
-        isFilterTrayVisible: false
-      };
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    toggleFilterTray: function () {
-      this.setState({
-        isFilterTrayVisible : !this.state.isFilterTrayVisible
-      });
-    },
-
-    render: function () {
-      var filterTray = '';
-
-      if (this.state.isFilterTrayVisible) {
-        filterTray = <ActiveTasksFilterTray 
-                        key="filter-tray" 
-                        selectedRadio={this.props.selectedRadio}
-                        onSearch={this.props.onSearch} 
-                        onRadioClick={this.props.onRadioClick} />;
-      }
-
-      return (
-        <div id="dashboard-upper-content">
-          <div className="dashboard-upper-menu active-tasks">
-            <ActiveTasksFilterTab onClick={this.toggleFilterTray} />
-          </div>
-          <ReactCSSTransitionGroup 
-            className="dashboard-lower-menu" 
-            transitionName="toggleFilterTray" 
-            component="div" >
-            {filterTray}
-          </ReactCSSTransitionGroup>
-        </div>
-      );
-    }
-  });
-
-  var ActiveTasksFilterTab = React.createClass({
-    render: function () {
-      return (
-        <ul className="nav nav-tabs" id="db-views-tabs-nav">
-          <li>
-            <a id="toggle-filter-tab"
-               className="toggle-filter-tab"
-               data-bypass="true" 
-               data-toggle="button"
-               onClick={this.props.onClick}>
-              <i className="fonticon fonticon-plus"></i>
-              Filter
-            </a>
-          </li>
-        </ul>);
-    }
-  });
-
-  var ActiveTasksFilterTray = React.createClass({
-    searchTermChange: function (e) {
-      var searchTerm = e.target.value;
-      this.props.onSearch(searchTerm);
-    },
-
-    render: function () {
-      return (
-        <div className="filter-tray">
-          <ActiveTasksFilterTrayCheckBoxes 
-            onRadioClick={this.props.onRadioClick} 
-            selectedRadio={this.props.selectedRadio} />
-          <input  
-            className="searchbox" 
-            type="text" 
-            name="search" 
-            placeholder="Search for databases..." 
-            value={this.props.searchTerm}
-            onChange={this.searchTermChange} />  
-        </div>
-      );
-    }
-  });
-
-  var ActiveTasksFilterTrayCheckBoxes = React.createClass({
+  var ActiveTasksFilterTabs = React.createClass({
     getDefaultProps: function () {
       return {
         radioNames : [
@@ -210,21 +126,24 @@ define([
       this.props.onRadioClick(radioName);
     },
 
-    createCheckboxes: function () {
+    createFilterTabs: function () {
       return (
         this.props.radioNames.map(function (radioName) {
           var checked = this.checked(radioName);
           var id = radioName.replace(' ', '-');
           var radioClassName = "radio-" + id;
           var radioClick = this.onRadioClick;
+          var checkedClassName = checked ? 'active-tasks-checked' : '';
 
           return (
-            <li className="active-tasks-one-checkbox" key={radioName + "li"}>
+            <li className={"active-tasks-one-checkbox " + checkedClassName} key={radioName + "li"}>
               <input
+                  className="toggle-filter-tab"
+                  data-bypass="true"
                   id={id}
                   type="radio"
-                  key ={radioName} 
-                  name="radio-button-active-task-filter-tray" 
+                  key ={radioName}
+                  name="radio-button-active-task-filter-tray"
                   value={radioName}
                   checked={checked}
                   onChange={radioClick} />
@@ -237,15 +156,27 @@ define([
       );
     },
 
+    searchTermChange: function (e) {
+      var searchTerm = e.target.value;
+      this.props.onSearch(searchTerm);
+    },
+
     render: function () {
-      var filterCheckboxes = this.createCheckboxes();
+      var filterTabs = this.createFilterTabs();
       return (
-        <ul className="filter-checkboxes">
-          <form className="filter-checkboxes-form">
-          {filterCheckboxes}
-          </form>
-        </ul>
-      );
+        <ul className="nav nav-tabs" id="active-tasks-filter-tabs">
+          {filterTabs}
+          <li>
+            <input
+              id="active-tasks-search-box"
+              className="searchbox"
+              type="text"
+              name="search"
+              placeholder="Search for databases..."
+              value={this.props.searchTerm}
+              onChange={this.searchTermChange} />
+          </li>
+        </ul>);
     }
   });
 
@@ -261,13 +192,13 @@ define([
       return (
         <div id="dashboard-lower-content">
           <table className="table table-bordered table-striped active-tasks">
-            <ActiveTasksTableHeader 
+            <ActiveTasksTableHeader
               onTableHeaderClick={onTableHeaderClick}
               sortByHeader={sortByHeader}
               headerIsAscending={headerIsAscending}/>
-            <ActiveTasksTableBody 
-              collection={collection} 
-              selectedRadio={selectedRadio} 
+            <ActiveTasksTableBody
+              collection={collection}
+              selectedRadio={selectedRadio}
               searchTerm={searchTerm}/>
           </table>
         </div>
@@ -293,26 +224,21 @@ define([
       var onTableHeaderClick = this.props.onTableHeaderClick;
       var sortByHeader = this.props.sortByHeader;
       var headerIsAscending = this.props.headerIsAscending;
-      return (
-        this.props.headerNames.map(function (header) {
-          return (
-            <TableHeader 
-              headerName={header[0]}
-              displayName={header[1]}
-              key={header[0]}
-              onTableHeaderClick={onTableHeaderClick}
-              sortByHeader={sortByHeader}
-              headerIsAscending={headerIsAscending} />
-          );
-        })
-      );
+      return this.props.headerNames.map(function (header) {
+        return <TableHeader
+          headerName={header[0]}
+          displayName={header[1]}
+          key={header[0]}
+          onTableHeaderClick={onTableHeaderClick}
+          sortByHeader={sortByHeader}
+          headerIsAscending={headerIsAscending} />;
+      });
     },
 
     render: function () {
-      var tableHeadingFields = this.createTableHeadingFields();
       return (
         <thead>
-          <tr>{tableHeadingFields}</tr>
+          <tr>{this.createTableHeadingFields()}</tr>
         </thead>
       );
     }
@@ -348,8 +274,8 @@ define([
           className="header-field radio"
           onChange={this.onTableHeaderClick}>
           <th className={th_class} value={this.props.headerName}>
-            <label 
-              className="header-field label-text"
+            <label
+              className="header-field label-text active-tasks-header"
               htmlFor={this.props.headerName}>
               {this.props.displayName} {arrow}
             </label>
@@ -384,8 +310,8 @@ define([
         return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
       }
 
-      return _.map(this.state.filteredTable, function (item, iteration) {
-        return <ActiveTaskTableBodyContents key={Math.random()} item={item} />;
+      return _.map(this.state.filteredTable, function (item, key) {
+        return <ActiveTaskTableBodyContents key={key} item={item} />;
       });
     },
 
@@ -406,10 +332,9 @@ define([
     },
 
     render: function () {
-      var tableBody = this.createRows();
       return (
         <tbody className="js-tasks-go-here">
-        {tableBody}
+        {this.createRows()}
         </tbody>
       );
     }
@@ -453,17 +378,66 @@ define([
           <td>{startedOnMsg}</td>
           <td>{updatedOnMsg}</td>
           <td>{rowData.pid}</td>
-          <td>{progressMsg}</td>
+          <td>{progressMsg}<ActiveTasksViewSourceSequence item={this.props.item}/></td>
         </tr>
       );
     }
   });
 
+  var ActiveTasksViewSourceSequence = React.createClass({
+    onTrayToggle: function (e) {
+      e.preventDefault();
+      this.refs.view_source_sequence_btn.toggle(function (shown) {
+        if (shown) {
+          this.refs.view_source_sequence_btn.getDOMNode().focus();
+        }
+      }.bind(this));
+    },
+
+    sequences: function (item) {
+      if (_.isNumber(item) || _.isString(item)) {
+        return <ComponentsReact.ClipboardWithTextField textToCopy={item} uniqueKey={item}/>;
+      }
+
+      if (_.isArray(item)) {
+        return _.map(item, function (seq, i) {
+            return <ComponentsReact.ClipboardWithTextField textToCopy={seq} uniqueKey={i + Math.random(100)} key={i}/>;
+          });
+      }
+
+      return  <ComponentsReact.ClipboardWithTextField textToCopy="???" uniqueKey='unknownRevision'/>;
+    },
+
+    render: function () {
+
+      if (_.has(this.props.item, 'source_seq')) {
+        var sequences = this.sequences(this.props.item.source_seq);
+        return (
+          <div>
+            Current source sequence:
+            <a href="#"
+              className="view-source-sequence-btn"
+              onClick={this.onTrayToggle}
+              data-bypass="true">
+              View
+            </a>
+            <ComponentsReact.Tray ref="view_source_sequence_btn" className="view_source_sequence_tray">
+              <span className="add-on">Source Sequence</span>
+              {sequences}
+            </ComponentsReact.Tray>
+          </div>
+        );
+      }
+      return null;
+    }
+  });
+
   var ActiveTasksPollingWidgetController = React.createClass({
 
     getStoreState: function () {
       return {
-        pollingInterval:  activeTasksStore.getPollingInterval()
+        pollingInterval:  activeTasksStore.getPollingInterval(),
+        isLoading: false//activeTasksStore.isLoading()
       };
     },
 
@@ -497,18 +471,18 @@ define([
       return (
         <ul className="polling-interval-widget">
           <li className="polling-interval-name">Polling interval
-            <label className="polling-interval-time-label" htmlFor="pollingRange"> 
-              <span>{pollingInterval}</span> second{s} 
+            <label className="polling-interval-time-label" htmlFor="pollingRange">
+              <span>{pollingInterval}</span> second{s}
             </label>
           </li>
           <li>
-            <input 
-              id="pollingRange" 
-              type="range" 
-              min="1" 
-              max="30" 
-              step="1" 
-              value={pollingInterval} 
+            <input
+              id="pollingRange"
+              type="range"
+              min="1"
+              max="30"
+              step="1"
+              value={pollingInterval}
               onChange={onChangeHandle}/>
           </li>
         </ul>
@@ -517,8 +491,22 @@ define([
 
     render: function () {
       var pollingWidget = this.createPollingWidget();
+      var loadLines = null;
+
+      if (this.state.isLoading || this.state.pollingInterval === "1") {
+        // show loading lines constantly if the polling interval is
+        // 1 second, so that the lines aren't choppy
+        loadLines = <Components.LoadLines />;
+      }
 
-      return  <div>{pollingWidget}</div>;
+      return (
+        <div>
+          <span className="active-tasks-loading-lines">
+            {loadLines}
+          </span>
+          {pollingWidget}
+        </div>
+      );
     }
   });
 
@@ -568,29 +556,29 @@ define([
         }
       }
 
-      if (_.has(item, 'source_seq')) {
-        progressMessage.push('Current source sequence: ' + item.source_seq + '. ');
-      }
-
       if (_.has(item, 'changes_done')) {
         progressMessage.push(item.changes_done + ' Changes done.');
       }
 
       return progressMessage;
+    },
+
+    getSourceSequence: function (item) {
+      return item.source_seq;
     }
+
   };
 
   return {
     ActiveTasksController: ActiveTasksController,
-    ActiveTasksFilter: ActiveTasksFilter,
-    ActiveTasksFilterTab: ActiveTasksFilterTab,
-    ActiveTasksFilterTray: ActiveTasksFilterTray,
+    ActiveTasksFilterTabs: ActiveTasksFilterTabs,
 
     ActiveTaskTable: ActiveTaskTable,
     ActiveTasksTableHeader: ActiveTasksTableHeader,
     TableHeader: TableHeader,
     ActiveTasksTableBody: ActiveTasksTableBody,
     ActiveTaskTableBodyContents: ActiveTaskTableBodyContents,
+    ActiveTasksViewSourceSequence: ActiveTasksViewSourceSequence,
 
     ActiveTasksPollingWidgetController: ActiveTasksPollingWidgetController
   };

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/routes.js b/app/addons/activetasks/routes.js
index cb9f673..b03d54a 100644
--- a/app/addons/activetasks/routes.js
+++ b/app/addons/activetasks/routes.js
@@ -40,9 +40,7 @@ function (app, FauxtonAPI, ActiveTasksResources, ActiveTasksComponents, Actions)
       this.allTasks = new ActiveTasksResources.AllTasks();
     },
     showActiveTasks: function () {
-      Actions.fetchAndSetActiveTasks(this.allTasks);
-      Actions.changePollingInterval(1);
-
+      Actions.init(this.allTasks);
       this.setComponent('#dashboard-content', ActiveTasksComponents.ActiveTasksController);
       this.setComponent('#right-header', ActiveTasksComponents.ActiveTasksPollingWidgetController);
     }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/stores.js b/app/addons/activetasks/stores.js
index 4c61a52..baf5607 100644
--- a/app/addons/activetasks/stores.js
+++ b/app/addons/activetasks/stores.js
@@ -18,7 +18,7 @@ define([
 
   var ActiveTasksStore = FauxtonAPI.Store.extend({
 
-    init: function (collectionTable, backboneCollection) {
+    initAfterFetching: function (collectionTable, backboneCollection) {
       this._prevSortbyHeader = 'started_on';
       this._headerIsAscending = true;
       this._selectedRadio = 'All Tasks';
@@ -28,6 +28,29 @@ define([
       this._pollingIntervalSeconds = 5;
       this.sortCollectionByColumnHeader(this._sortByHeader);
       this._backboneCollection = backboneCollection;
+      this.setIsLoading(true, new Date());
+    },
+
+    isLoading: function () {
+      return this._isLoading;
+    },
+
+    setIsLoading: function (bool, time) {
+      if (bool) {
+        this._startTimeForLoading = time;
+        this._isLoading = true;
+      } else {
+        var stoptime = time;
+        var responseTime = stoptime - this._startTimeForLoading;
+        if (responseTime < 800) {
+          setTimeout(function () {
+            this._isLoading = false;
+            this.triggerChange();
+          }.bind(this), 800);  //stop after 800ms for smooth animation
+        } else {
+          this._isLoading = false;
+        }
+      }
     },
 
     getSelectedRadio: function () {
@@ -47,7 +70,7 @@ define([
     },
 
     setPolling: function () {
-      clearInterval(this.getIntervalID());
+      this.clearPolling();
       var id = setInterval(function () {
         this._backboneCollection.pollingFetch();
         this.setCollection(this._backboneCollection.table);
@@ -170,8 +193,8 @@ define([
     dispatch: function (action) {
       switch (action.type) {
 
-        case ActionTypes.ACTIVE_TASKS_INIT:
-          this.init(action.options.collectionTable, action.options.backboneCollection);
+        case ActionTypes.ACTIVE_TASKS_FETCH_AND_SET:
+          this.initAfterFetching(action.options.collectionTable, action.options.backboneCollection);
         break;
 
         case ActionTypes.ACTIVE_TASKS_CHANGE_POLLING_INTERVAL:
@@ -202,6 +225,11 @@ define([
           this.triggerChange();
         break;
 
+        case ActionTypes.ACTIVE_TASKS_SET_IS_LOADING:
+          this.setIsLoading(action.options, new Date());
+          this.triggerChange();
+        break;
+
         default:
         return;
       }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx b/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx
index 4c22c3d..a147abd 100644
--- a/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx
+++ b/app/addons/activetasks/tests/activetasks.componentsSpec.react.jsx
@@ -18,8 +18,9 @@ define([
   'react',
   'addons/activetasks/actions',
   'testUtils'
-], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, Actions, testUtils) {
-  var assert = testUtils.assert;
+], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, Actions, utils) {
+  var assert = utils.assert;
+  var restore = utils.restore;
   var TestUtils = React.addons.TestUtils;
   var activeTasksStore = Stores.activeTasksStore;
   var activeTasksCollection = new ActiveTasks.AllTasks({});
@@ -33,12 +34,13 @@ define([
       beforeEach(function () {
         pollingWidgetDiv = document.createElement('div');
         pollingWidget = TestUtils.renderIntoDocument(
-          React.createElement(Components.ActiveTasksPollingWidgetController, null), pollingWidgetDiv
+          <Components.ActiveTasksPollingWidgetController />, pollingWidgetDiv
         );
       });
 
       afterEach(function () {
         React.unmountComponentAtNode(pollingWidgetDiv);
+        restore(Actions.changePollingInterval);
       });
 
       it('should trigger update polling interval', function () {
@@ -56,17 +58,17 @@ define([
 
       beforeEach(function () {
         tableDiv = document.createElement('div');
-        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
-        table = TestUtils.renderIntoDocument(React.createElement(Components.ActiveTasksController, null), tableDiv);
+        activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection);
+        table = TestUtils.renderIntoDocument(<Components.ActiveTasksController />, tableDiv);
 
         // open filter tray
-        filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
-        TestUtils.Simulate.click(filterTab);
+        //filterTab = TestUtils.findRenderedDOMComponentWithClass(table, 'toggle-filter-tab');
+        //TestUtils.Simulate.click(filterTab);
       });
 
       afterEach(function () {
         React.unmountComponentAtNode(tableDiv);
-        window.confirm.restore && window.confirm.restore();
+        restore(window.confirm);
       });
 
       it('it displays a message instead of an empty table, if there are undefined active tasks', function () {
@@ -78,7 +80,8 @@ define([
       describe('Active Tasks Filter tray', function () {
 
         afterEach(function () {
-          spy.restore();
+          restore(Actions.switchTab);
+          restore(Actions.setSearchTerm);
         });
 
         var radioIDs = [
@@ -114,6 +117,10 @@ define([
           'progress'
         ];
 
+        afterEach(function () {
+          restore(Actions.sortByColumnHeader);
+        });
+
         it('should trigger change to which header to sort by', function () {
           _.each(headerNames, function (header) {
             spy = sinon.spy(Actions, 'sortByColumnHeader');

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/activetasks/tests/activetasks.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/tests/activetasks.storesSpec.js b/app/addons/activetasks/tests/activetasks.storesSpec.js
index 462d94d..c059280 100644
--- a/app/addons/activetasks/tests/activetasks.storesSpec.js
+++ b/app/addons/activetasks/tests/activetasks.storesSpec.js
@@ -12,13 +12,13 @@
 define([
   'api',
   'addons/activetasks/resources',
-  'addons/activetasks/components.react',
   'addons/activetasks/stores',
   'addons/activetasks/tests/fakeActiveTaskResponse',
   'react',
   'testUtils'
-], function (FauxtonAPI, ActiveTasks, Components, Stores, fakedResponse, React, testUtils) {
-  var assert = testUtils.assert;
+], function (FauxtonAPI, ActiveTasks, Stores, fakedResponse, React, utils) {
+  var assert = utils.assert;
+  var restore = utils.restore;
   var TestUtils = React.addons.TestUtils;
 
   var activeTasksStore = Stores.activeTasksStore;
@@ -26,29 +26,30 @@ define([
   activeTasksCollection.parse(fakedResponse);
 
   describe('Active Tasks -- Stores', function () {
-    var spy;
+    var spy, clock;
 
     beforeEach(function () {
-      activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
-      this.clock = sinon.useFakeTimers();
+      activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection);
+      clock = sinon.useFakeTimers();
     });
 
     afterEach(function () {
-      testUtils.restore(spy);
-      testUtils.restore(this.clock);
+      restore(spy);
+      restore(clock);
     });
 
     describe('Active Task Stores - Polling', function () {
       var pollingWidgetDiv, pollingWidget;
 
       beforeEach(function () {
-        activeTasksStore.init(activeTasksCollection.table, activeTasksCollection);
-        this.clock = sinon.useFakeTimers();
+        activeTasksStore.initAfterFetching(activeTasksCollection.table, activeTasksCollection);
+        clock = sinon.useFakeTimers();
       });
 
       afterEach(function () {
-        testUtils.restore(spy);
-        testUtils.restore(this.clock);
+        restore(activeTasksStore.getPollingInterval);
+        restore(window.clearInterval);
+        restore(clock);
       });
 
       it('should poll at the min time', function () {
@@ -59,10 +60,10 @@ define([
         assert.ok(spy.calledOnce);
 
         setInterval(spy, minTime * 1000);
-        this.clock.tick(minTime * 1000);
+        clock.tick(minTime * 1000);
         assert.ok(spy.calledTwice);
 
-        this.clock.tick(minTime * 1000);
+        clock.tick(minTime * 1000);
         assert.ok(spy.calledThrice);
       });
 
@@ -75,10 +76,10 @@ define([
         assert.ok(spy.calledOnce);
 
         setInterval(spy, maxTime * 1000);
-        this.clock.tick(maxTime * 1000);
+        clock.tick(maxTime * 1000);
         assert.ok(spy.calledTwice);
 
-        this.clock.tick(maxTime * 1000);
+        clock.tick(maxTime * 1000);
         assert.ok(spy.calledThrice);
       });
 
@@ -91,10 +92,10 @@ define([
         assert.ok(spy.calledOnce);
 
         setInterval(spy, midtime * 1000);
-        this.clock.tick(midtime * 1000);
+        clock.tick(midtime * 1000);
         assert.ok(spy.calledTwice);
 
-        this.clock.tick(midtime * 1000);
+        clock.tick(midtime * 1000);
         assert.ok(spy.calledThrice);
       });
 
@@ -132,7 +133,7 @@ define([
         //parse table and check that it only contains objects with type: Replication
         _.each(storeFilteredtable, function (activeTask) {
           assert.ok(activeTasksStore.passesRadioFilter(activeTask));
-          assert.ok(activeTask.type === activeTasksStore.getSelectedRadio());
+          assert.deepEqual(activeTask.type, activeTasksStore.getSelectedRadio());
         });
       });
 
@@ -158,20 +159,20 @@ define([
       it('should set header as ascending, on default', function () {
         activeTasksStore.setSelectedRadio('all_tasks');
         activeTasksStore._headerIsAscending = true;
-        assert.ok(activeTasksStore.getHeaderIsAscending() === true);
+        assert.ok(activeTasksStore.getHeaderIsAscending());
       });
 
       it('should set header as descending, if same header is selected again', function () {
         activeTasksStore._prevSortbyHeader = 'sameHeader';
         activeTasksStore._sortByHeader = 'sameHeader';
         activeTasksStore.toggleHeaderIsAscending();
-        assert.ok(activeTasksStore.getHeaderIsAscending() === false);
+        assert.notOk(activeTasksStore.getHeaderIsAscending());
       });
 
       it('should set header as ascending, if different header is selected', function () {
         activeTasksStore._sortByHeader = 'differentHeader';
         activeTasksStore.toggleHeaderIsAscending();
-        assert.ok(activeTasksStore.getHeaderIsAscending() === true);
+        assert.ok(activeTasksStore.getHeaderIsAscending());
       });
     });
   });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/b4e5f535/app/addons/fauxton/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.react.jsx b/app/addons/fauxton/components.react.jsx
index 5e7e891..a08f8a6 100644
--- a/app/addons/fauxton/components.react.jsx
+++ b/app/addons/fauxton/components.react.jsx
@@ -46,6 +46,40 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
     }
   });
 
+  // use like this:
+  //  <ComponentsReact.ClipboardWithTextField textToCopy={yourText} uniqueKey={someUniqueValue}>
+  //  </ComponentsReact.ClipboardWithTextField>
+  // pass in the text and a unique key, the key has to be unique or you'll get a warning
+  var ClipboardWithTextField = React.createClass({
+    componentWillMount: function () {
+      ZeroClipboard.config({ moviePath: app.zeroClipboardPath });
+    },
+
+    componentDidUpdate: function () {
+      this.clipboard = new ZeroClipboard(document.getElementById("copy-text-" + this.props.uniqueKey));
+    },
+
+    render: function () {
+      return (
+        <p key={this.props.uniqueKey}>
+          <input
+            type="text"
+            className="input-xxlarge text-field-to-copy"
+            readOnly
+            value={this.props.textToCopy} />
+          <a 
+            id={"copy-text-" + this.props.uniqueKey}
+            className="fonticon-clipboard icon btn copy-button" 
+            data-clipboard-text={this.props.textToCopy} 
+            data-bypass="true" 
+            title="Copy to clipboard" >
+            Copy
+          </a>
+        </p>
+      );
+    }
+  });
+
   // formats a block of code and pretty-prints it in the page. Currently uses the prettyPrint plugin
   var CodeFormat = React.createClass({
     getDefaultProps: function () {
@@ -223,6 +257,7 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
 
   return {
     Clipboard: Clipboard,
+    ClipboardWithTextField: ClipboardWithTextField,
     CodeFormat: CodeFormat,
     Tray: Tray,
     Pagination: Pagination