diff --git a/src/app/home/index.js b/src/app/home/index.js index b084fb35e2d..b5a9abc8c2d 100644 --- a/src/app/home/index.js +++ b/src/app/home/index.js @@ -151,12 +151,6 @@ var HomeView = View.extend({ 'server memory size (gb)': app.instance.host.memory_bits / 1024 / 1024 / 1024 }); - const model = this._getCollection(); - // When the current collection no longer exists - if (NamespaceStore.ns && !model) { - NamespaceStore.ns = null; - } - if (!NamespaceStore.ns) { app.instance.collections.unselectAll(); if (app.instance.collections.length === 0) { diff --git a/src/internal-packages/database/index.js b/src/internal-packages/database/index.js index d981c929591..44c3bd5d3cf 100644 --- a/src/internal-packages/database/index.js +++ b/src/internal-packages/database/index.js @@ -1,11 +1,17 @@ const app = require('ampersand-app'); const CollectionsTable = require('./lib/components'); +const CreateCollectionCheckbox = require('./lib/components/create-collection-checkbox'); +const CreateCollectionInput = require('./lib/components/create-collection-input'); +const CreateCollectionSizeInput = require('./lib/components/create-collection-size-input'); /** * Activate all the components in the Schema package. */ function activate() { app.appRegistry.registerComponent('Database.CollectionsTable', CollectionsTable); + app.appRegistry.registerComponent('Database.CreateCollectionCheckbox', CreateCollectionCheckbox); + app.appRegistry.registerComponent('Database.CreateCollectionInput', CreateCollectionInput); + app.appRegistry.registerComponent('Database.CreateCollectionSizeInput', CreateCollectionSizeInput); } /** @@ -13,6 +19,9 @@ function activate() { */ function deactivate() { app.appRegistry.deregisterComponent('Database.CollectionsTable'); + app.appRegistry.deregisterComponent('Database.CreateCollectionCheckbox'); + app.appRegistry.deregisterComponent('Database.CreateCollectionInput'); + app.appRegistry.deregisterComponent('Database.CreateCollectionSizeInput'); } module.exports.activate = activate; diff --git a/src/internal-packages/database/lib/actions/collections-actions.js b/src/internal-packages/database/lib/actions/collections-actions.js index 8520ee37f8a..db458055dfd 100644 --- a/src/internal-packages/database/lib/actions/collections-actions.js +++ b/src/internal-packages/database/lib/actions/collections-actions.js @@ -1,12 +1,14 @@ const Reflux = require('reflux'); /** - * The actions used by the server stats components. + * The actions used by the database components. */ const Actions = Reflux.createActions([ 'sortCollections', - 'deleteCollection', - 'createCollection' + 'dropCollection', + 'createCollection', + 'openCreateCollectionDialog', + 'openDropCollectionDialog' ]); module.exports = Actions; diff --git a/src/internal-packages/database/lib/components/collections-table.jsx b/src/internal-packages/database/lib/components/collections-table.jsx index 6ac6979d8c3..6140a85816f 100644 --- a/src/internal-packages/database/lib/components/collections-table.jsx +++ b/src/internal-packages/database/lib/components/collections-table.jsx @@ -1,6 +1,8 @@ const React = require('react'); const app = require('ampersand-app'); const CollectionsActions = require('../actions/collections-actions'); +const CreateCollectionDialog = require('./create-collection-dialog'); +const TextButton = require('hadron-app-registry').TextButton; const numeral = require('numeral'); const _ = require('lodash'); @@ -22,6 +24,10 @@ class CollectionsTable extends React.Component { // CollectionsActions.deleteCollection(collName); } + onCreateCollectionButtonClicked() { + CollectionsActions.openCreateCollectionDialog(); + } + render() { // convert some of the values to human-readable units (MB, GB, ...) // we do this here so that sorting is not affected in the store @@ -45,6 +51,12 @@ class CollectionsTable extends React.Component { return (
+
+ +
+
); } diff --git a/src/internal-packages/server-stats/lib/component/create-collection-checkbox.jsx b/src/internal-packages/database/lib/components/create-collection-checkbox.jsx similarity index 94% rename from src/internal-packages/server-stats/lib/component/create-collection-checkbox.jsx rename to src/internal-packages/database/lib/components/create-collection-checkbox.jsx index 1a6243e4523..4451c973b2a 100644 --- a/src/internal-packages/server-stats/lib/component/create-collection-checkbox.jsx +++ b/src/internal-packages/database/lib/components/create-collection-checkbox.jsx @@ -1,5 +1,8 @@ const React = require('react'); +/** + * A checkbox in the create collection dialog. + */ class CreateCollectionCheckbox extends React.Component { /** diff --git a/src/internal-packages/database/lib/components/create-collection-dialog.jsx b/src/internal-packages/database/lib/components/create-collection-dialog.jsx new file mode 100644 index 00000000000..11a1dd0e520 --- /dev/null +++ b/src/internal-packages/database/lib/components/create-collection-dialog.jsx @@ -0,0 +1,204 @@ +const app = require('ampersand-app'); +const shell = require('electron').shell; +const React = require('react'); +const Modal = require('react-bootstrap').Modal; +const TextButton = require('hadron-app-registry').TextButton; +const NamespaceStore = require('hadron-reflux-store').NamespaceStore; +const toNS = require('mongodb-ns'); +const Actions = require('../actions/collections-actions'); +const CreateCollectionStore = require('../stores/create-collection-store'); +const CreateCollectionInput = require('./create-collection-input'); +const CreateCollectionSizeInput = require('./create-collection-size-input'); +const CreateCollectionCheckbox = require('./create-collection-checkbox'); + +/** + * The help icon for capped collections url. + */ +const HELP_URL = 'https://docs.mongodb.com/manual/core/capped-collections/'; + +/** + * The dialog to create a collection. + */ +class CreateCollectionDialog extends React.Component { + + /** + * The component constructor. + * + * @param {Object} props - The properties. + */ + constructor(props) { + super(props); + this.state = { open: false }; + this.ModalStatusMessage = app.appRegistry.getComponent('App.ModalStatusMessage'); + } + + /** + * Subscribe to the open dialog store. + */ + componentWillMount() { + this.unsubscribeOpen = Actions.openCreateCollectionDialog.listen(this.onOpenDialog.bind(this)); + this.unsubscribeCreate = CreateCollectionStore.listen(this.onCollectionCreated.bind(this)); + } + + /** + * Unsubscribe from the store. + */ + componentWillUnmount() { + this.unsubscribeOpen(); + this.unsubscribeCreate(); + } + + /** + * When the open dialog action is fired. + */ + onOpenDialog() { + this.setState({ + open: true, + collectionName: '', + databaseName: toNS(NamespaceStore.ns).database, + capped: false, + maxSize: '', + error: false, + inProgress: false, + errorMessage: '' + }); + } + + /** + * When the cancel button is clicked. + */ + onCancelButtonClicked() { + this.setState({ open: false }); + } + + /** + * Initiate the attempt to create a collection. + */ + onCreateCollectionButtonClicked() { + this.setState({ inProgress: true, error: false, errorMessage: '' }); + Actions.createCollection( + this.state.databaseName, + this.state.collectionName, + this.state.capped, + this.state.maxSize + ); + } + + /** + * Handle finish collection creation. + * + * @param {Error} error - The error, if any. + */ + onCollectionCreated(error) { + if (error) { + this.setState({ inProgress: false, error: true, errorMessage: error.message }); + } else { + this.setState({ inProgress: false, error: false, errorMessage: '', open: false }); + } + } + + /** + * Handle changing the collection name. + * + * @param {Event} evt - The change event. + */ + onCollectionNameChange(evt) { + this.setState({ collectionName: evt.target.value }); + } + + /** + * Handle clicking the capped checkbox. + */ + onCappedClicked() { + this.setState({ capped: !this.state.capped }); + } + + /** + * Handle clicking the help icon. + + * @param {Event} evt - The event. + */ + onHelpClicked(evt) { + evt.preventDefault(); + evt.stopPropagation(); + shell.openExternal(HELP_URL); + } + + /** + * Change the max collection size. + * + * @param {Event} evt - The event. + */ + onMaxSizeChange(evt) { + this.setState({ maxSize: evt.target.value }); + } + + /** + * Render the max size component when capped is selected. + * + * @returns {React.Component} The component. + */ + renderMaxSize() { + if (this.state.capped) { + return ( + + ); + } + } + + /** + * Render the modal dialog. + * + * @returns {React.Component} The react component. + */ + render() { + return ( + + + Create Collection + + + +
+ + + {this.renderMaxSize()} + {this.state.error ? + + : null} + {this.state.inProgress ? + + : null} + +
+ + + + + +
+ ); + } +} + +CreateCollectionDialog.displayName = 'CreateCollectionDialog'; + +module.exports = CreateCollectionDialog; diff --git a/src/internal-packages/server-stats/lib/component/create-collection-input.jsx b/src/internal-packages/database/lib/components/create-collection-input.jsx similarity index 92% rename from src/internal-packages/server-stats/lib/component/create-collection-input.jsx rename to src/internal-packages/database/lib/components/create-collection-input.jsx index 06c2240400d..4d5b0fddc01 100644 --- a/src/internal-packages/server-stats/lib/component/create-collection-input.jsx +++ b/src/internal-packages/database/lib/components/create-collection-input.jsx @@ -1,5 +1,8 @@ const React = require('react'); +/** + * An input field in the create collection checkbox. + */ class CreateCollectionInput extends React.Component { /** diff --git a/src/internal-packages/server-stats/lib/component/create-collection-size-input.jsx b/src/internal-packages/database/lib/components/create-collection-size-input.jsx similarity index 78% rename from src/internal-packages/server-stats/lib/component/create-collection-size-input.jsx rename to src/internal-packages/database/lib/components/create-collection-size-input.jsx index 840767ba7c8..cb0daa1e4c5 100644 --- a/src/internal-packages/server-stats/lib/component/create-collection-size-input.jsx +++ b/src/internal-packages/database/lib/components/create-collection-size-input.jsx @@ -1,5 +1,8 @@ const React = require('react'); +/** + * A size input field in the create collection checkbox. + */ class CreateCollectionSizeInput extends React.Component { /** @@ -12,11 +15,11 @@ class CreateCollectionSizeInput extends React.Component {
-

{this.props.name}

+

{this.props.name}

); } diff --git a/src/internal-packages/database/lib/stores/collections-store.jsx b/src/internal-packages/database/lib/stores/collections-store.js similarity index 66% rename from src/internal-packages/database/lib/stores/collections-store.jsx rename to src/internal-packages/database/lib/stores/collections-store.js index e0ab4e05203..c610ca35068 100644 --- a/src/internal-packages/database/lib/stores/collections-store.jsx +++ b/src/internal-packages/database/lib/stores/collections-store.js @@ -39,9 +39,8 @@ const CollectionsStore = Reflux.createStore({ * Initialize everything that is not part of the store's state. */ init() { - this.InstanceStore = app.appRegistry.getStore('App.InstanceStore'); NamespaceStore.listen(this.onNamespaceChanged.bind(this)); - // this.listenToExternalStore('App.InstanceStore', ); + this.listenToExternalStore('App.InstanceStore', this.onNamespaceChanged.bind(this)); this.indexes = []; }, @@ -62,38 +61,40 @@ const CollectionsStore = Reflux.createStore({ }, onNamespaceChanged() { - const ns = toNS(NamespaceStore.ns); - if (!ns.database) { - this.setState({ - collections: [] - }); - return; - } - - app.dataService.database(ns.database, {}, (err, res) => { - if (err) { + if (NamespaceStore.ns) { + const ns = toNS(NamespaceStore.ns); + if (!ns.database) { this.setState({ - fetchState: 'error', - errorMessage: err + collections: [] }); return; } - debug('collections', res.collections); - const unsorted = _.map(res.collections, (coll) => { - return _.zipObject(COLL_COLUMNS, [ - coll.name, // Collection Name - coll.document_count, // Num. Documents - coll.size / coll.document_count, // Avg. Document Size - coll.size, // Total Document Size - coll.index_count, // Num Indexes - coll.index_size // Total Index Size - ]); - }); - this.setState({ - collections: this._sort(unsorted) + app.dataService.database(ns.database, {}, (err, res) => { + if (err) { + this.setState({ + fetchState: 'error', + errorMessage: err + }); + return; + } + debug('collections', res.collections); + const unsorted = _.map(res.collections, (coll) => { + return _.zipObject(COLL_COLUMNS, [ + coll.name, // Collection Name + coll.document_count, // Num. Documents + coll.size / coll.document_count, // Avg. Document Size + coll.size, // Total Document Size + coll.index_count, // Num Indexes + coll.index_size // Total Index Size + ]); + }); + + this.setState({ + collections: this._sort(unsorted) + }); }); - }); + } }, sortCollections(column, order) { @@ -104,14 +105,6 @@ const CollectionsStore = Reflux.createStore({ }); }, - // deleteCollection(dbName) { - // // TODO remove collection on server - // }, - // - // createCollection(collName) { - // // TODO create collection - // } - /** * log changes to the store as debug messages. * @param {Object} prevState previous state. @@ -119,7 +112,6 @@ const CollectionsStore = Reflux.createStore({ storeDidUpdate(prevState) { debug('collections store changed from', prevState, 'to', this.state); } - }); module.exports = CollectionsStore; diff --git a/src/internal-packages/database/lib/stores/create-collection-store.js b/src/internal-packages/database/lib/stores/create-collection-store.js new file mode 100644 index 00000000000..991e97533c8 --- /dev/null +++ b/src/internal-packages/database/lib/stores/create-collection-store.js @@ -0,0 +1,51 @@ +const Reflux = require('reflux'); +const app = require('ampersand-app'); +const Actions = require('../actions/collections-actions'); + +/** + * The reflux store for creating collections. + */ +const CreateCollectionStore = Reflux.createStore({ + + /** + * Initialize the store. + */ + init: function() { + this.refreshInstance = app.appRegistry.getAction('App.InstanceActions').refreshInstance; + this.listenTo(Actions.createCollection, this.createCollection); + }, + + /** + * Create the database. + * + * @param {String} dbName - The database name. + * @param {String} collection - The collection name. + * @param {Boolean} capped - If the collection is capped. + * @param {Number} size - The max size of the capped collection. + */ + createCollection(dbName, collection, capped, size) { + const options = capped ? { capped: true, size: parseInt(size, 10) } : {}; + try { + app.dataService.createCollection(`${dbName}.${collection}`, options, this.handleResult.bind(this)); + } catch (e) { + this.handleResult(e, null); + } + }, + + /** + * Handle the create database result. + * + * @param {Error} error - The error. + * @param {Object} result - The result. + */ + handleResult(error, result) { + if (error) { + this.trigger(error, result); + } else { + this.refreshInstance(); + this.trigger(error, result); + } + } +}); + +module.exports = CreateCollectionStore; diff --git a/src/internal-packages/database/styles/index.less b/src/internal-packages/database/styles/index.less index 7a7c151ada5..f75d2aa2086 100644 --- a/src/internal-packages/database/styles/index.less +++ b/src/internal-packages/database/styles/index.less @@ -5,4 +5,9 @@ height: 100vh; min-width: 100%; overflow-x: scroll; + + &-create-button { + margin-left: 23px; + margin-top: 15px; + } } diff --git a/src/internal-packages/server-stats/lib/component/create-database-dialog.jsx b/src/internal-packages/server-stats/lib/component/create-database-dialog.jsx index efe6057ad65..2304ab0b9b3 100644 --- a/src/internal-packages/server-stats/lib/component/create-database-dialog.jsx +++ b/src/internal-packages/server-stats/lib/component/create-database-dialog.jsx @@ -5,9 +5,6 @@ const Modal = require('react-bootstrap').Modal; const TextButton = require('hadron-app-registry').TextButton; const Actions = require('../action/databases-actions'); const CreateDatabaseStore = require('../store/create-database-store'); -const CreateCollectionInput = require('./create-collection-input'); -const CreateCollectionSizeInput = require('./create-collection-size-input'); -const CreateCollectionCheckbox = require('./create-collection-checkbox'); /** * The more information url. @@ -33,6 +30,9 @@ class CreateDatabaseDialog extends React.Component { super(props); this.state = { open: false }; this.ModalStatusMessage = app.appRegistry.getComponent('App.ModalStatusMessage'); + this.CreateCollectionInput = app.appRegistry.getComponent('Database.CreateCollectionInput'); + this.CreateCollectionSizeInput = app.appRegistry.getComponent('Database.CreateCollectionSizeInput'); + this.CreateCollectionCheckbox = app.appRegistry.getComponent('Database.CreateCollectionCheckbox'); } /** @@ -164,7 +164,7 @@ class CreateDatabaseDialog extends React.Component { renderMaxSize() { if (this.state.capped) { return ( - + Create Database -
- + - - {this.renderMaxSize()} -
+
Before MongoDB can save your new database, a collection name must also be specified at the time of creation. More Information diff --git a/src/internal-packages/server-stats/styles/create-database-dialog.less b/src/internal-packages/server-stats/styles/create-collection-dialog.less similarity index 93% rename from src/internal-packages/server-stats/styles/create-database-dialog.less rename to src/internal-packages/server-stats/styles/create-collection-dialog.less index 00dfa2e23d4..0bcd05afc99 100644 --- a/src/internal-packages/server-stats/styles/create-database-dialog.less +++ b/src/internal-packages/server-stats/styles/create-collection-dialog.less @@ -1,4 +1,4 @@ -.create-database-dialog, .create-collection-dialog { +.create-collection-dialog { width: 500px; p { diff --git a/src/internal-packages/server-stats/styles/index.less b/src/internal-packages/server-stats/styles/index.less index 95a0cb32ed7..3e97a151b60 100644 --- a/src/internal-packages/server-stats/styles/index.less +++ b/src/internal-packages/server-stats/styles/index.less @@ -4,7 +4,7 @@ @import './rt-graphs.less'; @import './rt-errors.less'; @import './rtss-databases.less'; -@import './create-database-dialog.less'; +@import './create-collection-dialog.less'; .RTSS { font-family: "Akzidenz", "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/src/internal-packages/sidebar/lib/components/sidebar-database.jsx b/src/internal-packages/sidebar/lib/components/sidebar-database.jsx index 2e05597101d..f92ef2a0e5e 100644 --- a/src/internal-packages/sidebar/lib/components/sidebar-database.jsx +++ b/src/internal-packages/sidebar/lib/components/sidebar-database.jsx @@ -46,10 +46,10 @@ class SidebarDatabase extends React.Component { render() { return (
-
- +
+ -
+
{this.props._id}
diff --git a/src/internal-packages/sidebar/styles/index.less b/src/internal-packages/sidebar/styles/index.less index 301056c6a59..eb4c7d77fb6 100644 --- a/src/internal-packages/sidebar/styles/index.less +++ b/src/internal-packages/sidebar/styles/index.less @@ -27,6 +27,7 @@ overflow-x: hidden; text-overflow: ellipsis; font-size: 14px; + margin-right: 20px; &-is-bold { font-weight: bold;