| import FauxtonAPI from "../../core/api"; |
| |
| // 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 classnames from 'classnames'; |
| import PropTypes from 'prop-types'; |
| import React from "react"; |
| import Components from "../components/react-components"; |
| import ComponentsStore from "../components/stores"; |
| import ComponentsActions from "../components/actions"; |
| import PerPageSelector from '../documents/index-results/components/pagination/PerPageSelector'; |
| import FauxtonComponentsReact from "..//fauxton/components"; |
| import Stores from "./stores"; |
| import Actions from "./actions"; |
| |
| import { Tooltip, OverlayTrigger } from 'react-bootstrap'; |
| |
| const databasesStore = Stores.databasesStore; |
| const deleteDbModalStore = ComponentsStore.deleteDbModalStore; |
| |
| const { Accordion, AccordionItem, DeleteDatabaseModal, ToggleHeaderButton, TrayContents } = Components; |
| |
| |
| class DatabasesController extends React.Component { |
| |
| constructor(props) { |
| super(props); |
| this.reloadAfterDatabaseDeleted = this.reloadAfterDatabaseDeleted.bind(this); |
| } |
| |
| getStoreState = () => { |
| return { |
| dbList: databasesStore.getDbList(), |
| loading: databasesStore.isLoading(), |
| showDeleteDatabaseModal: deleteDbModalStore.getShowDeleteDatabaseModal(), |
| showPartitionedColumn: databasesStore.isPartitionedDatabasesAvailable(), |
| page: databasesStore.getPage(), |
| limit: databasesStore.getLimit() |
| }; |
| }; |
| |
| componentDidMount() { |
| databasesStore.on('change', this.onChange, this); |
| deleteDbModalStore.on('change', this.onChange, this); |
| this.loadData(); |
| } |
| |
| componentDidUpdate(_, prevState) { |
| if (prevState.page !== this.state.page || prevState.limit !== this.state.limit) { |
| this.loadData(); |
| } |
| } |
| |
| loadData() { |
| Actions.init({ |
| page: this.state.page, |
| limit: this.state.limit |
| }); |
| } |
| |
| componentWillUnmount() { |
| databasesStore.off('change', this.onChange, this); |
| deleteDbModalStore.off('change', this.onChange, this); |
| } |
| |
| onChange = () => { |
| this.setState(this.getStoreState()); |
| }; |
| |
| reloadAfterDatabaseDeleted() { |
| let page = this.state.page; |
| if (page > 1 && this.state.dbList.length === 1) { |
| //show previous page if the db deleted was the only one in the page |
| page = page - 1; |
| } |
| Actions.init({ |
| page, |
| limit: this.state.limit |
| }); |
| } |
| |
| state = this.getStoreState(); |
| |
| render() { |
| const { loading, dbList } = this.state; |
| |
| return ( |
| <DatabaseTable |
| showDeleteDatabaseModal={this.state.showDeleteDatabaseModal} |
| dbList={dbList} |
| loading={loading} |
| showPartitionedColumn={this.state.showPartitionedColumn} |
| onDatabaseDeleted={this.reloadAfterDatabaseDeleted} /> |
| ); |
| } |
| } |
| |
| class DatabaseTable extends React.Component { |
| static propTypes = { |
| dbList: PropTypes.array.isRequired, |
| showDeleteDatabaseModal: PropTypes.object.isRequired, |
| loading: PropTypes.bool.isRequired, |
| showPartitionedColumn: PropTypes.bool.isRequired, |
| onDatabaseDeleted: PropTypes.func |
| }; |
| |
| createRows = (dbList) => { |
| return dbList.map((item, k) => { |
| return ( |
| <DatabaseRow item={item} key={k} showPartitionedColumn={this.props.showPartitionedColumn} /> |
| ); |
| }); |
| }; |
| |
| getExtensionColumns = () => { |
| var cols = FauxtonAPI.getExtensions('DatabaseTable:head'); |
| return _.map(cols, (Item, index) => { |
| return <Item key={index} />; |
| }); |
| }; |
| |
| showDeleteDatabaseModal = () => { |
| ComponentsActions.showDeleteDatabaseModal({ |
| showDeleteModal: !this.props.showDeleteDatabaseModal.showDeleteModal |
| }); |
| }; |
| |
| render() { |
| if (this.props.loading) { |
| return ( |
| <div className="view"> |
| <Components.LoadLines /> |
| </div> |
| ); |
| } |
| |
| const rows = this.createRows(this.props.dbList); |
| return ( |
| <div className="view"> |
| <DeleteDatabaseModal |
| showHide={this.showDeleteDatabaseModal} |
| modalProps={this.props.showDeleteDatabaseModal} |
| onSuccess={this.props.onDatabaseDeleted} /> |
| <table className="table table-striped fauxton-table-list databases"> |
| <thead> |
| <tr> |
| <th>Name</th> |
| <th>Size</th> |
| <th># of Docs</th> |
| {this.props.showPartitionedColumn ? (<th>Partitioned</th>) : null} |
| {this.getExtensionColumns()} |
| <th>Actions</th> |
| </tr> |
| </thead> |
| <tbody> |
| {rows} |
| </tbody> |
| </table> |
| </div> |
| ); |
| } |
| } |
| |
| class DatabaseRow extends React.Component { |
| static propTypes = { |
| item: PropTypes.shape({ |
| id: PropTypes.string.isRequired, |
| encodedId: PropTypes.string.isRequired, |
| url: PropTypes.string.isRequired, |
| failed: PropTypes.bool.isRequired, |
| dataSize: PropTypes.string, |
| docCount: PropTypes.number, |
| docDelCount: PropTypes.number, |
| isPartitioned: PropTypes.bool, |
| showTombstoneWarning: PropTypes.bool |
| }).isRequired, |
| showPartitionedColumn: PropTypes.bool.isRequired |
| }; |
| |
| getExtensionColumns = (row) => { |
| var cols = FauxtonAPI.getExtensions('DatabaseTable:databaseRow'); |
| return _.map(cols, (Item, index) => { |
| return <Item row={row} key={index} />; |
| }); |
| }; |
| |
| showDeleteDatabaseModal = (name) => { |
| ComponentsActions.showDeleteDatabaseModal({ showDeleteModal: true, dbId: name }); |
| }; |
| |
| render() { |
| const { |
| item |
| } = this.props; |
| |
| const { encodedId, id, url, dataSize, docCount, docDelCount, showTombstoneWarning, failed, isPartitioned } = item; |
| const tombStoneWarning = showTombstoneWarning ? |
| (<GraveyardInfo docCount={docCount} docDelCount={docDelCount} />) : null; |
| |
| // if the row status failed to load, inform the user |
| if (failed) { |
| return ( |
| <tr> |
| <td data-name="database-load-fail-name">{id}</td> |
| <td colSpan="4" className="database-load-fail">This database failed to load.</td> |
| </tr> |
| ); |
| } |
| const partitionedCol = this.props.showPartitionedColumn ? |
| (<td>{isPartitioned ? 'Yes' : 'No'}</td>) : |
| null; |
| return ( |
| <tr> |
| <td> |
| <a href={url}>{id}</a> |
| </td> |
| <td>{dataSize}</td> |
| <td>{docCount} {tombStoneWarning}</td> |
| {partitionedCol} |
| {this.getExtensionColumns(item)} |
| |
| <td className="database-actions"> |
| <a className="db-actions btn fonticon-replicate set-replication-start" |
| aria-label={`Replicate ${id}`} |
| title={"Replicate " + name} |
| href={"#/replication/_create/" + encodedId} /> |
| <a |
| aria-label={`Set permissions for ${id}`} |
| className="db-actions btn icon-lock set-permissions" |
| title={"Set permissions for " + name} href={"#/database/" + encodedId + "/permissions"} /> |
| <a |
| aria-label={`Delete ${id}`} |
| className="db-actions btn icon-trash" |
| onClick={this.showDeleteDatabaseModal.bind(this, id, encodedId)} |
| title={'Delete ' + id} data-bypass="true" /> |
| </td> |
| </tr> |
| ); |
| } |
| } |
| |
| const GraveyardInfo = ({ docCount, docDelCount }) => { |
| const graveyardTitle = `This database has just ${docCount} docs and ${docDelCount} deleted docs`; |
| const tooltip = <Tooltip id="graveyard-tooltip">{graveyardTitle}</Tooltip>; |
| |
| return ( |
| <OverlayTrigger placement="top" overlay={tooltip}> |
| <i className="js-db-graveyard icon icon-exclamation-sign" title={graveyardTitle}></i> |
| </OverlayTrigger> |
| ); |
| }; |
| |
| class RightDatabasesHeader extends React.Component { |
| |
| constructor(props) { |
| super(props); |
| this.state = this.getStoreState(); |
| } |
| |
| getStoreState() { |
| return { |
| showPartitionedOption: databasesStore.isPartitionedDatabasesAvailable() |
| }; |
| } |
| |
| componentDidMount() { |
| databasesStore.on('change', this.onChange, this); |
| } |
| |
| componentWillUnmount() { |
| databasesStore.off('change', this.onChange, this); |
| } |
| |
| onChange() { |
| this.setState(this.getStoreState()); |
| } |
| |
| render() { |
| return ( |
| <div className="header-right right-db-header flex-layout flex-row"> |
| <JumpToDatabaseWidget loadOptions={Actions.fetchAllDbsWithKey} /> |
| <AddDatabaseWidget |
| showPartitionedOption={this.state.showPartitionedOption} |
| partitionedDbHelpText={this.props.partitionedDbHelpText} /> |
| </div> |
| ); |
| } |
| } |
| |
| class AddDatabaseWidget extends React.Component { |
| static defaultProps = { |
| showPartitionedOption: false |
| }; |
| |
| static propTypes = { |
| showPartitionedOption: PropTypes.bool.isRequired, |
| partitionedDbHelpText: PropTypes.string |
| }; |
| |
| constructor(props) { |
| super(props); |
| this.state = { |
| isPromptVisible: false, |
| databaseName: '', |
| partitionedSelected: undefined |
| }; |
| |
| this.onTrayToggle = this.onTrayToggle.bind(this); |
| this.closeTray = this.closeTray.bind(this); |
| this.focusInput = this.focusInput.bind(this); |
| this.onKeyUpInInput = this.onKeyUpInInput.bind(this); |
| this.onChange = this.onChange.bind(this); |
| this.onAddDatabase = this.onAddDatabase.bind(this); |
| this.onSetPartitioned = this.onSetPartitioned.bind(this); |
| this.onSetNotPartitioned = this.onSetNotPartitioned.bind(this); |
| } |
| |
| getI18nText(key) { |
| if (this.props.i18n) { |
| return this.props.i18n[key]; |
| } |
| return ''; |
| } |
| |
| onTrayToggle() { |
| this.setState({ isPromptVisible: !this.state.isPromptVisible }); |
| } |
| |
| closeTray() { |
| this.setState({ isPromptVisible: false }); |
| } |
| |
| focusInput() { |
| this.newDbName.focus(); |
| } |
| |
| onKeyUpInInput(e) { |
| if (e.which === 13) { |
| this.onAddDatabase(); |
| } |
| } |
| |
| onChange(e) { |
| this.setState({ databaseName: e.target.value }); |
| } |
| |
| onAddDatabase() { |
| if (this.props.showPartitionedOption && this.state.partitionedSelected === undefined) { |
| FauxtonAPI.addNotification({ |
| type: 'error', |
| msg: 'Please select either Partitioned or Non-partitioned', |
| clear: true |
| }); |
| return; |
| } |
| const partitioned = this.props.showPartitionedOption ? |
| this.state.partitionedSelected : |
| undefined; |
| |
| Actions.createNewDatabase( |
| this.state.databaseName, |
| partitioned |
| ); |
| } |
| |
| onSetNotPartitioned() { |
| this.setState({ partitionedSelected: false }); |
| } |
| |
| onSetPartitioned() { |
| this.setState({ partitionedSelected: true }); |
| } |
| |
| partitionedOption() { |
| if (!this.props.showPartitionedOption) { |
| return null; |
| } |
| const partitionedDbHelp = this.props.partitionedDbHelpText ? ( |
| <Accordion className='partitioned-db-help'> |
| <AccordionItem title='What is a Partitioned Database?'> |
| <p dangerouslySetInnerHTML={{__html: this.props.partitionedDbHelpText}} /> |
| </AccordionItem> |
| </Accordion> |
| ) : null; |
| return ( |
| <div className='partitioned-db-section' > |
| <label htmlFor="partitioned-db" className='partitioned-db-label'> |
| Partitioning |
| </label> |
| <div className='partitioned-db-options'> |
| <input |
| id="partitioned-db" |
| type="radio" |
| checked={this.state.partitionedSelected === true} |
| onChange={this.onSetPartitioned} /> |
| <label htmlFor="partitioned-db">Partitioned</label> |
| <input |
| id="non-partitioned-db" |
| type="radio" |
| checked={this.state.partitionedSelected === false} |
| onChange={this.onSetNotPartitioned} /> |
| <label htmlFor="non-partitioned-db">Non-partitioned</label> |
| </div> |
| {partitionedDbHelp} |
| </div> |
| ); |
| } |
| |
| render() { |
| const classNames = classnames('new-database-tray', { |
| 'new-database-tray--expanded': this.props.showPartitionedOption |
| }); |
| |
| return ( |
| <div> |
| <ToggleHeaderButton |
| selected={this.state.isPromptVisible} |
| toggleCallback={this.onTrayToggle} |
| containerClasses='header-control-box add-new-database-btn' |
| title="Create Database" |
| fonticon="fonticon-new-database" |
| text="Create Database" /> |
| <TrayContents |
| className={classNames} |
| contentVisible={this.state.isPromptVisible} |
| closeTray={this.closeTray} |
| onEnter={this.focusInput} > |
| <div className='tray-contents'> |
| <div className='tray-header'> |
| <h3>Create Database</h3> |
| </div> |
| <div className='tray-body'> |
| <label htmlFor="js-new-database-name" className='db-name-label'> |
| Database name |
| </label> |
| <input |
| id="js-new-database-name" |
| ref={node => this.newDbName = node} |
| type="text" |
| value={this.state.databaseName} |
| onChange={this.onChange} onKeyUp={this.onKeyUpInInput} |
| className="input-xxlarge" |
| placeholder="database-name" |
| /> |
| {this.partitionedOption()} |
| </div> |
| <div className='tray-footer'> |
| <a className="btn btn-cancel" id="js-cancel-create-database" onClick={this.closeTray}> |
| Cancel |
| </a> |
| <a className="btn btn-primary" id="js-create-database" onClick={this.onAddDatabase}> |
| Create |
| </a> |
| </div> |
| </div> |
| </TrayContents> |
| </div> |
| ); |
| } |
| } |
| |
| |
| const JumpToDatabaseWidget = ({ loadOptions }) => { |
| return ( |
| <div data-name="jump-to-db" className="faux-header__searchboxwrapper"> |
| <div className="faux-header__searchboxcontainer"> |
| |
| <Components.ThrottledReactSelectAsync |
| placeholder="Database name" |
| loadOptions={loadOptions} |
| clearable={false} |
| onChange={({ value: databaseName }) => { |
| Actions.jumpToDatabase(databaseName); |
| }} /> |
| |
| </div> |
| </div> |
| ); |
| }; |
| JumpToDatabaseWidget.propTypes = { |
| loadOptions: PropTypes.func.isRequired |
| }; |
| |
| class DatabasePagination extends React.Component { |
| static defaultProps = { |
| linkPath: '_all_dbs', |
| store: databasesStore |
| }; |
| |
| getStoreState = (props) => { |
| const { store } = props; |
| |
| return { |
| totalAmountOfDatabases: store.getTotalAmountOfDatabases(), |
| page: store.getPage(), |
| limit: store.getLimit() |
| }; |
| }; |
| |
| componentDidMount() { |
| const { store } = this.props; |
| |
| store.on('change', this.onChange, this); |
| } |
| |
| UNSAFE_componentWillReceiveProps(nextProps) { |
| this.setState(this.getStoreState(nextProps)); |
| if (this.props.store) { |
| this.props.store.off('change', this.onChange, this); |
| } |
| const { store } = nextProps; |
| store.on('change', this.onChange, this); |
| } |
| |
| componentWillUnmount() { |
| const { store } = this.props; |
| store.off('change', this.onChange, this); |
| } |
| |
| onChange = () => { |
| this.setState(this.getStoreState(this.props)); |
| }; |
| |
| state = this.getStoreState(this.props); |
| |
| onPageLinkClick(pageNumber) { |
| const url = `#/${this.props.linkPath}?page=${pageNumber}&limit=${this.state.limit}`; |
| FauxtonAPI.navigate(url); |
| } |
| |
| onPerPageSelected(perPage) { |
| const url = `#/${this.props.linkPath}?page=${this.state.page}&limit=${perPage}`; |
| FauxtonAPI.navigate(url); |
| } |
| |
| render() { |
| const { limit, page, totalAmountOfDatabases } = this.state; |
| const start = 1 + (page - 1) * limit; |
| const end = Math.min(totalAmountOfDatabases, page * limit); |
| |
| return ( |
| <footer className="all-db-footer pagination-footer"> |
| <div id="database-pagination"> |
| <FauxtonComponentsReact.Pagination |
| page={page} |
| total={totalAmountOfDatabases} |
| perPage={limit} |
| onClick={this.onPageLinkClick.bind(this)} |
| /> |
| </div> |
| <PerPageSelector label="Databases per page" perPage={limit} perPageChange={this.onPerPageSelected.bind(this)} /> |
| <div className="current-databases"> |
| Showing <span className="all-db-footer__range">{start}–{end}</span> |
| of <span className="all-db-footer__total-db-count">{totalAmountOfDatabases}</span> |
| databases. |
| </div> |
| </footer> |
| ); |
| } |
| } |
| |
| export default { |
| DatabasesController: DatabasesController, |
| DatabaseTable: DatabaseTable, |
| DatabaseRow: DatabaseRow, |
| RightDatabasesHeader: RightDatabasesHeader, |
| GraveyardInfo: GraveyardInfo, |
| AddDatabaseWidget: AddDatabaseWidget, |
| JumpToDatabaseWidget: JumpToDatabaseWidget, |
| DatabasePagination: DatabasePagination |
| }; |