blob: 7e653999b7108b5f54d7556b36edd07747d438ae [file] [log] [blame]
// 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/activetasks/stores',
'addons/activetasks/resources',
'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;
var ActiveTasksController = React.createClass({
getStoreState: function () {
return {
collection: activeTasksStore.getCollection(),
searchTerm: activeTasksStore.getSearchTerm(),
selectedRadio: activeTasksStore.getSelectedRadio(),
sortByHeader: activeTasksStore.getSortByHeader(),
headerIsAscending: activeTasksStore.getHeaderIsAscending(),
setPolling: activeTasksStore.setPolling,
clearPolling: activeTasksStore.clearPolling
};
},
getInitialState: function () {
return this.getStoreState();
},
componentDidMount: function () {
this.state.setPolling();
activeTasksStore.on('change', this.onChange, this);
},
componentWillUnmount: function () {
this.state.clearPolling();
activeTasksStore.off('change', this.onChange, this);
},
onChange: function () {
this.setState(this.getStoreState());
},
setNewSearchTerm: function (searchTerm) {
Actions.setSearchTerm(searchTerm);
},
switchTab: function (newRadioButton) { //tabs buttons
Actions.switchTab(newRadioButton);
},
tableHeaderOnClick: function (headerClicked) {
Actions.sortByColumnHeader(headerClicked);
},
render: function () {
var collection = this.state.collection;
var searchTerm = this.state.searchTerm;
var selectedRadio = this.state.selectedRadio;
var sortByHeader = this.state.sortByHeader;
var headerIsAscending = this.state.headerIsAscending;
var setSearchTerm = this.setNewSearchTerm;
var onTableHeaderClick = this.tableHeaderOnClick;
return (
<div id="active-tasks-page" className="scrollable">
<div className="inner">
<ActiveTasksFilterTabs
searchTerm={searchTerm}
selectedRadio={selectedRadio}
onSearch={setSearchTerm}
onRadioClick={this.switchTab}/>
<ActiveTaskTable
collection={collection}
searchTerm={searchTerm}
selectedRadio={selectedRadio}
onTableHeaderClick={onTableHeaderClick}
sortByHeader={sortByHeader}
headerIsAscending={headerIsAscending} />
</div>
</div>
);
}
});
var ActiveTasksFilterTabs = React.createClass({
getDefaultProps: function () {
return {
radioNames : [
'All Tasks',
'Replication',
'Database Compaction',
'Indexer',
'View Compaction'
]
};
},
checked: function (radioName) {
return this.props.selectedRadio === radioName;
},
onRadioClick: function (e) {
var radioName = e.target.value;
this.props.onRadioClick(radioName);
},
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 " + 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"
value={radioName}
checked={checked}
onChange={radioClick} />
<label htmlFor={id} className="active-tasks-checkbox-label">
{radioName}
</label>
</li>
);
}.bind(this))
);
},
searchTermChange: function (e) {
var searchTerm = e.target.value;
this.props.onSearch(searchTerm);
},
render: function () {
var filterTabs = this.createFilterTabs();
return (
<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>);
}
});
var ActiveTaskTable = React.createClass({
render: function () {
var collection = this.props.collection;
var selectedRadio = this.props.selectedRadio;
var searchTerm = this.props.searchTerm;
var sortByHeader = this.props.sortByHeader;
var onTableHeaderClick = this.props.onTableHeaderClick;
var headerIsAscending = this.props.headerIsAscending;
return (
<div id="dashboard-lower-content">
<table id="active-tasks-table" className="table table-bordered table-striped active-tasks">
<ActiveTasksTableHeader
onTableHeaderClick={onTableHeaderClick}
sortByHeader={sortByHeader}
headerIsAscending={headerIsAscending}/>
<ActiveTasksTableBody
collection={collection}
selectedRadio={selectedRadio}
searchTerm={searchTerm}/>
</table>
</div>
);
}
});
var ActiveTasksTableHeader = React.createClass({
getDefaultProps: function () {
return {
headerNames : [
['type', 'Type'],
['database', 'Database'],
['started-on', 'Started on'],
['updated-on', 'Updated on'],
['pid', 'PID'],
['progress', 'Status']
]
};
},
createTableHeadingFields: function () {
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} />;
});
},
render: function () {
return (
<thead>
<tr>{this.createTableHeadingFields()}</tr>
</thead>
);
}
});
var TableHeader = React.createClass({
arrow: function () {
var sortBy = this.props.sortByHeader;
var currentName = this.props.headerName;
var headerIsAscending = this.props.headerIsAscending;
var arrow = headerIsAscending ? 'icon icon-caret-up' : 'icon icon-caret-down';
if (sortBy === currentName) {
return <i className={arrow}></i>;
}
},
onTableHeaderClick: function (e) {
var headerSelected = e.target.value;
this.props.onTableHeaderClick(headerSelected);
},
render: function () {
var arrow = this.arrow();
var th_class = 'header-field ' + this.props.headerName;
return (
<input
type="radio"
name="header-field"
id={this.props.headerName}
value={this.props.headerName}
className="header-field radio"
onChange={this.onTableHeaderClick}>
<td className={th_class + " tableheader"} value={this.props.headerName}>
<label
className="header-field label-text active-tasks-header noselect"
htmlFor={this.props.headerName}>
{this.props.displayName} {arrow}
</label>
</td>
</input>
);
}
});
var ActiveTasksTableBody = React.createClass({
getStoreState: function () {
return {
filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
};
},
getInitialState: function () {
return this.getStoreState();
},
componentWillReceiveProps: function (nextProps) {
this.setState({
filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
});
},
createRows: function () {
var isThereASearchTerm = this.props.searchTerm.trim() === "";
if (this.state.filteredTable.length === 0) {
return isThereASearchTerm ? this.noActiveTasks() : this.noActiveTasksMatchFilter();
}
return _.map(this.state.filteredTable, function (item, key) {
return <ActiveTaskTableBodyContents key={key} item={item} />;
});
},
noActiveTasks: function () {
var type = this.props.selectedRadio;
if (type === "All Tasks") {
type = "";
}
return (
<tr className="no-matching-database-on-search">
<td colSpan="6">No active {type} tasks.</td>
</tr>
);
},
noActiveTasksMatchFilter: function () {
var type = this.props.selectedRadio;
if (type === "All Tasks") {
type = "";
}
return (
<tr className="no-matching-database-on-search">
<td colSpan="6">No active {type} tasks match with filter: "{this.props.searchTerm}"</td>
</tr>
);
},
render: function () {
return (
<tbody className="js-tasks-go-here">
{this.createRows()}
</tbody>
);
}
});
var ActiveTaskTableBodyContents = React.createClass({
getInfo: function (item) {
return {
type : item.type,
objectField: activeTasksHelpers.getDatabaseFieldMessage(item),
started_on: activeTasksHelpers.getTimeInfo(item.started_on),
updated_on: activeTasksHelpers.getTimeInfo(item.updated_on),
pid: item.pid.replace(/[<>]/g, ''),
progress: activeTasksHelpers.getProgressMessage(item),
};
},
multilineMessage: function (messageArray, optionalClassName) {
if (!optionalClassName) {
optionalClassName = '';
}
var cssClasses = 'multiline-active-tasks-message ' + optionalClassName;
return messageArray.map(function (msgLine, iterator) {
return <p key={iterator} className={cssClasses}>{msgLine}</p>;
});
},
render: function () {
var rowData = this.getInfo(this.props.item);
var objectFieldMsg = this.multilineMessage(rowData.objectField, 'to-from-database');
var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
var updatedOnMsg = this.multilineMessage(rowData.updated_on, 'time');
var progressMsg = this.multilineMessage(rowData.progress);
return (
<tr>
<td>{rowData.type}</td>
<td>{objectFieldMsg}</td>
<td>{startedOnMsg}</td>
<td>{updatedOnMsg}</td>
<td>{rowData.pid}</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) {
React.findDOMNode(this.refs.view_source_sequence_btn).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(),
isLoading: activeTasksStore.isLoading()
};
},
getInitialState: function () {
return this.getStoreState();
},
componentDidMount: function () {
activeTasksStore.on('change', this.onChange, this);
},
onChange: function () {
if (this.isMounted()) {
this.setState(this.getStoreState());
}
},
pollingIntervalChange: function (event) {
Actions.changePollingInterval(event.target.value);
},
getPluralForLabel: function () {
return this.state.pollingInterval === "1" ? '' : 's';
},
createPollingWidget: function () {
var pollingInterval = this.state.pollingInterval;
var s = this.getPluralForLabel();
var onChangeHandle = this.pollingIntervalChange;
return (
<ul className="polling-interval-widget">
<li className="polling-interval-name">Polling interval
<label className="polling-interval-time-label" htmlFor="polling-range">
<span>{pollingInterval}</span> second{s}
</label>
</li>
<li>
<input
id="polling-range"
type="range"
min="1"
max="30"
step="1"
value={pollingInterval}
onChange={onChangeHandle}/>
</li>
</ul>
);
},
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 className="active-tasks-loading-lines-container">
<span className="active-tasks-loading-lines">
{loadLines}
</span>
{pollingWidget}
</div>
);
}
});
var activeTasksHelpers = {
getTimeInfo: function (timeStamp) {
var timeMessage = [
app.helpers.formatDate(timeStamp),
app.helpers.getDateFromNow(timeStamp)
];
return timeMessage;
},
getDatabaseFieldMessage: function (item) {
var type = item.type;
var databaseFieldMessage = [];
if (type === 'replication') {
databaseFieldMessage.push('From: ' + item.source);
databaseFieldMessage.push('To: ' + item.target);
} else if (type === 'indexer') {
databaseFieldMessage.push(item.database);
databaseFieldMessage.push('(View: ' + item.design_document + ')');
} else {
databaseFieldMessage.push(item.database);
}
return databaseFieldMessage;
},
getProgressMessage: function (item) {
var progressMessage = [];
var type = item.type;
if (_.has(item, 'progress')) {
progressMessage.push('Progress: ' + item.progress + '%');
}
if (type === 'indexer') {
progressMessage.push(
'Processed ' + item.changes_done + ' of ' + item.total_changes + ' changes.'
);
} else if (type === 'replication') {
progressMessage.push(item.docs_written + ' docs written.');
if (_.has(item, 'changes_pending')) {
progressMessage.push(item.changes_pending + ' pending changes.');
}
}
if (_.has(item, 'changes_done')) {
progressMessage.push(item.changes_done + ' Changes done.');
}
return progressMessage;
},
getSourceSequence: function (item) {
return item.source_seq;
}
};
return {
ActiveTasksController: ActiveTasksController,
ActiveTasksFilterTabs: ActiveTasksFilterTabs,
ActiveTaskTable: ActiveTaskTable,
ActiveTasksTableHeader: ActiveTasksTableHeader,
TableHeader: TableHeader,
ActiveTasksTableBody: ActiveTasksTableBody,
ActiveTaskTableBodyContents: ActiveTaskTableBodyContents,
ActiveTasksViewSourceSequence: ActiveTasksViewSourceSequence,
ActiveTasksPollingWidgetController: ActiveTasksPollingWidgetController
};
});