+
{this.renderActiveView()}
@@ -55,6 +55,7 @@ class NavBarComponent extends React.Component {
}
NavBarComponent.propTypes = {
+ theme: React.PropTypes.oneOf(['dark', 'light']),
activeTabIndex: React.PropTypes.number,
tabs: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
views: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
@@ -62,6 +63,7 @@ NavBarComponent.propTypes = {
};
NavBarComponent.defaultProps = {
+ theme: 'light',
activeTabIndex: 0
};
diff --git a/src/internal-packages/app/styles/index.less b/src/internal-packages/app/styles/index.less
index ebf806f4048..273b79c20c3 100644
--- a/src/internal-packages/app/styles/index.less
+++ b/src/internal-packages/app/styles/index.less
@@ -1,2 +1,3 @@
@import './sortable-table.less';
@import './modal-status-message.less';
+@import './tab-nav-bar.less';
diff --git a/src/internal-packages/app/styles/sortable-table.less b/src/internal-packages/app/styles/sortable-table.less
index 7568b2bb0bf..2a421f50b43 100644
--- a/src/internal-packages/app/styles/sortable-table.less
+++ b/src/internal-packages/app/styles/sortable-table.less
@@ -14,7 +14,7 @@
user-select: none;
-webkit-user-select: none;
padding-left: 24px;
- background-color: #f5f6f7;
+ background-color: @gray8;
&-is-active {
.sortable-table-sort-icon {
diff --git a/src/internal-packages/app/styles/tab-nav-bar.less b/src/internal-packages/app/styles/tab-nav-bar.less
new file mode 100644
index 00000000000..ddb9fae8973
--- /dev/null
+++ b/src/internal-packages/app/styles/tab-nav-bar.less
@@ -0,0 +1,109 @@
+.tab-nav-bar {
+
+ &-header {
+ height: 45px;
+ background-color: @pw;
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-end;
+ position: relative;
+ }
+
+ &-tabs {
+ display: flex;
+ justify-content: flex-end;
+ align-self: flex-end;
+ margin-bottom: 0;
+ position: absolute;
+ top: 16px;
+ margin-left: 10px;
+ }
+
+ &-tab {
+ height: 30px;
+ width: 130px;
+ margin-right: 5px;
+ font-size: 13px;
+ text-transform: uppercase;
+ border-radius: 3px 3px 0 0;
+ display: flex;
+ justify-content: center;
+ cursor: pointer;
+ margin-bottom: 0;
+ color: #43B1E5;
+ }
+
+ &-tab:hover {
+ background-color: @pw;
+ color: @gray0;
+ border: 1px solid @gray7;
+
+ .tab-nav-bar-link {
+ color: @gray0;
+ }
+ }
+
+ &-link {
+ margin: auto;
+ text-transform: uppercase;
+ font-size: 11px;
+ font-weight: bold;
+ color: #43B1E5;
+ }
+
+ &-link:hover, &-link:active, &-link:focus {
+ text-decoration: none;
+ color: @gray0;
+ }
+
+ &-is-selected {
+ .tab-nav-bar-link {
+ color: @gray0;
+ }
+
+ &.tab-nav-bar-tab:hover {
+ background-color: @gray8;
+ border-bottom: none;
+ }
+ background-color: @gray8;
+ cursor: default;
+ border: #ebebed 1px solid;
+ border-bottom: none;
+ }
+
+ &-is-dark-theme {
+ .tab-nav-bar-header {
+ background-color: #2B3033;
+ }
+
+ .tab-nav-bar-tab {
+ color: #8B8D8F;
+ }
+
+ .tab-nav-bar-link {
+ color: @pw;
+ }
+
+ .tab-nav-bar-is-selected {
+ color: @pw;
+ background-color: #3D4247;
+ border-color: #545454;
+
+ &.tab-nav-bar-tab:hover {
+ background-color: #3D4247;
+ }
+ }
+
+ .tab-nav-bar-tab:hover {
+ background-color: #2B3033;
+ border-color: #545454;
+ border-bottom: none;
+
+ color: @pw;
+
+ .tab-nav-bar-link {
+ color: @pw;
+ }
+ }
+ }
+}
diff --git a/src/internal-packages/database/index.js b/src/internal-packages/database/index.js
new file mode 100644
index 00000000000..d981c929591
--- /dev/null
+++ b/src/internal-packages/database/index.js
@@ -0,0 +1,19 @@
+const app = require('ampersand-app');
+const CollectionsTable = require('./lib/components');
+
+/**
+ * Activate all the components in the Schema package.
+ */
+function activate() {
+ app.appRegistry.registerComponent('Database.CollectionsTable', CollectionsTable);
+}
+
+/**
+ * Deactivate all the components in the Schema package.
+ */
+function deactivate() {
+ app.appRegistry.deregisterComponent('Database.CollectionsTable');
+}
+
+module.exports.activate = activate;
+module.exports.deactivate = deactivate;
diff --git a/src/internal-packages/database/lib/actions/collections-actions.js b/src/internal-packages/database/lib/actions/collections-actions.js
new file mode 100644
index 00000000000..8520ee37f8a
--- /dev/null
+++ b/src/internal-packages/database/lib/actions/collections-actions.js
@@ -0,0 +1,12 @@
+const Reflux = require('reflux');
+
+/**
+ * The actions used by the server stats components.
+ */
+const Actions = Reflux.createActions([
+ 'sortCollections',
+ 'deleteCollection',
+ 'createCollection'
+]);
+
+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
new file mode 100644
index 00000000000..6ac6979d8c3
--- /dev/null
+++ b/src/internal-packages/database/lib/components/collections-table.jsx
@@ -0,0 +1,73 @@
+const React = require('react');
+const app = require('ampersand-app');
+const CollectionsActions = require('../actions/collections-actions');
+const numeral = require('numeral');
+
+const _ = require('lodash');
+
+// const debug = require('debug')('mongodb-compass:server-stats:databases');
+
+class CollectionsTable extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.SortableTable = app.appRegistry.getComponent('App.SortableTable');
+ }
+
+ onColumnHeaderClicked(column, order) {
+ CollectionsActions.sortCollections(column, order);
+ }
+
+ onRowDeleteButtonClicked(/* collName */) {
+ // CollectionsActions.deleteCollection(collName);
+ }
+
+ 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
+ //
+ // 'Collection Name',
+ // 'Num. Documents',
+ // 'Avg. Document Size',
+ // 'Total Document Size',
+ // 'Num. Indexes',
+ // 'Total Index Size'
+
+ const rows = _.map(this.props.collections, (coll) => {
+ return _.assign({}, coll, {
+ 'Documents': numeral(coll.Documents).format('0,0'),
+ 'Avg. Document Size': _.isNaN(coll['Avg. Document Size']) ?
+ '-' : numeral(coll['Avg. Document Size']).format('0.0 b'),
+ 'Total Document Size': numeral(coll['Total Document Size']).format('0.0 b'),
+ 'Total Index Size': numeral(coll['Total Index Size']).format('0.0 b')
+ });
+ });
+
+ return (
+
+
+
+ );
+ }
+}
+
+CollectionsTable.propTypes = {
+ columns: React.PropTypes.arrayOf(React.PropTypes.string),
+ collections: React.PropTypes.arrayOf(React.PropTypes.object),
+ sortOrder: React.PropTypes.oneOf(['asc', 'desc']),
+ sortColumn: React.PropTypes.string
+};
+
+CollectionsTable.displayName = 'CollectionsTable';
+
+module.exports = CollectionsTable;
diff --git a/src/internal-packages/database/lib/components/connected-collections.jsx b/src/internal-packages/database/lib/components/connected-collections.jsx
new file mode 100644
index 00000000000..4251ec126cd
--- /dev/null
+++ b/src/internal-packages/database/lib/components/connected-collections.jsx
@@ -0,0 +1,28 @@
+const React = require('react');
+
+const app = require('ampersand-app');
+const StoreConnector = app.appRegistry.getComponent('App.StoreConnector');
+const CollectionsStore = require('../stores/collections-store');
+const CollectionsTable = require('./collections-table');
+
+// const debug = require('debug')('mongodb-compass:compass-explain:index');
+
+class ConnectedCollectionsTable extends React.Component {
+
+ /**
+ * Connect CompassExplainComponent to store and render.
+ *
+ * @returns {React.Component} The rendered component.
+ */
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+ConnectedCollectionsTable.displayName = 'ConnectedCollectionsTable';
+
+module.exports = ConnectedCollectionsTable;
diff --git a/src/internal-packages/database/lib/components/index.jsx b/src/internal-packages/database/lib/components/index.jsx
new file mode 100644
index 00000000000..7891293646e
--- /dev/null
+++ b/src/internal-packages/database/lib/components/index.jsx
@@ -0,0 +1,34 @@
+const React = require('react');
+const app = require('ampersand-app');
+const CollectionsTableView = require('./connected-collections');
+
+class DatabaseView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.TabNavBar = app.appRegistry.getComponent('App.TabNavBar');
+ }
+
+ /**
+ * Renders the component.
+ *
+ * @returns {React.Component} The component.
+ */
+ render() {
+ const collectionsTableView =
;
+ return (
+
+
+
+ );
+ }
+}
+
+DatabaseView.displayName = 'DatabaseView';
+
+module.exports = DatabaseView;
diff --git a/src/internal-packages/database/lib/stores/collections-store.jsx b/src/internal-packages/database/lib/stores/collections-store.jsx
new file mode 100644
index 00000000000..e0ab4e05203
--- /dev/null
+++ b/src/internal-packages/database/lib/stores/collections-store.jsx
@@ -0,0 +1,125 @@
+const Reflux = require('reflux');
+const StateMixin = require('reflux-state-mixin');
+const CollectionsActions = require('../actions/collections-actions');
+const NamespaceStore = require('hadron-reflux-store').NamespaceStore;
+const app = require('ampersand-app');
+const toNS = require('mongodb-ns');
+const _ = require('lodash');
+
+const debug = require('debug')('mongodb-compass:stores:collections');
+
+const COLL_COLUMNS = [
+ 'Collection Name',
+ 'Documents',
+ 'Avg. Document Size',
+ 'Total Document Size',
+ 'Num. Indexes',
+ 'Total Index Size'
+];
+
+/**
+ * Databases store, used to present a table of databases with some basic
+ * stats, and allow DDL (create / delete databases).
+ *
+ * This store refreshes every time the App.InstanceStore changes.
+ */
+const CollectionsStore = Reflux.createStore({
+ /**
+ * adds a state to the store, similar to React.Component's state
+ * @see https://github.com/yonatanmn/Super-Simple-Flux#reflux-state-mixin
+ */
+ mixins: [StateMixin.store],
+
+ /**
+ * listen to all database related actions
+ */
+ listenables: CollectionsActions,
+
+ /**
+ * 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.indexes = [];
+ },
+
+ getInitialState() {
+ return {
+ columns: COLL_COLUMNS,
+ collections: [],
+ sortOrder: 'asc',
+ sortColumn: 'Collection Name',
+ fetchState: 'initial',
+ errorMessage: ''
+ };
+ },
+
+ _sort(collections, column, order) {
+ return _.sortByOrder(collections,
+ column || this.state.sortColumn, order || this.state.sortOrder);
+ },
+
+ onNamespaceChanged() {
+ const ns = toNS(NamespaceStore.ns);
+ if (!ns.database) {
+ this.setState({
+ collections: []
+ });
+ return;
+ }
+
+ 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) {
+ this.setState({
+ collections: this._sort(this.state.collections, column, order),
+ sortColumn: column,
+ sortOrder: order
+ });
+ },
+
+ // 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.
+ */
+ storeDidUpdate(prevState) {
+ debug('collections store changed from', prevState, 'to', this.state);
+ }
+
+});
+
+module.exports = CollectionsStore;
diff --git a/src/internal-packages/database/package.json b/src/internal-packages/database/package.json
new file mode 100644
index 00000000000..c0b72a45b0f
--- /dev/null
+++ b/src/internal-packages/database/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "database",
+ "productName": "Compass Database View",
+ "description": "Responsible for the view at the database level.",
+ "version": "0.0.1",
+ "authors": "MongoDB Inc.",
+ "private": true,
+ "main": "./index.js"
+}
diff --git a/src/internal-packages/database/styles/index.less b/src/internal-packages/database/styles/index.less
new file mode 100644
index 00000000000..7a7c151ada5
--- /dev/null
+++ b/src/internal-packages/database/styles/index.less
@@ -0,0 +1,8 @@
+.collections-table {
+ font-family: "Akzidenz", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 500;
+ background-color: @gray8;
+ height: 100vh;
+ min-width: 100%;
+ overflow-x: scroll;
+}
diff --git a/src/internal-packages/explain/lib/stores/index.js b/src/internal-packages/explain/lib/stores/index.js
index b89bbcfe763..c2b804ef5b5 100644
--- a/src/internal-packages/explain/lib/stores/index.js
+++ b/src/internal-packages/explain/lib/stores/index.js
@@ -3,6 +3,7 @@ const ExplainActions = require('../actions');
const StateMixin = require('reflux-state-mixin');
const app = require('ampersand-app');
const NamespaceStore = require('hadron-reflux-store').NamespaceStore;
+const toNS = require('mongodb-ns');
const ExplainPlanModel = require('mongodb-explain-plan-model');
const _ = require('lodash');
@@ -117,8 +118,11 @@ const CompassExplainStore = Reflux.createStore({
const QueryStore = app.appRegistry.getStore('Query.Store');
const filter = QueryStore.state.query;
const options = {};
-
- app.dataService.explain(NamespaceStore.ns, filter, options, (err, explain) => {
+ const ns = toNS(NamespaceStore.ns);
+ if (!ns.database || !ns.collection) {
+ return;
+ }
+ app.dataService.explain(ns.ns, filter, options, (err, explain) => {
if (err) {
return debug('error fetching explain plan:', err);
}
diff --git a/src/internal-packages/schema/lib/store/index.jsx b/src/internal-packages/schema/lib/store/index.jsx
index bfac8321569..93255508172 100644
--- a/src/internal-packages/schema/lib/store/index.jsx
+++ b/src/internal-packages/schema/lib/store/index.jsx
@@ -2,6 +2,8 @@ const app = require('ampersand-app');
const Reflux = require('reflux');
const StateMixin = require('reflux-state-mixin');
const schemaStream = require('mongodb-schema').stream;
+const toNS = require('mongodb-ns');
+
const _ = require('lodash');
const ReadPreference = require('mongodb').ReadPreference;
@@ -34,9 +36,11 @@ const SchemaStore = Reflux.createStore({
* Initialize the document list store.
*/
init: function() {
- NamespaceStore.listen(() => {
- this._reset();
- SchemaAction.startSampling();
+ NamespaceStore.listen((ns) => {
+ if (ns && toNS(ns).collection) {
+ this._reset();
+ SchemaAction.startSampling();
+ }
});
this.samplingStream = null;
diff --git a/src/internal-packages/server-stats/lib/component/index.jsx b/src/internal-packages/server-stats/lib/component/index.jsx
index 60816d7817e..409e91de17e 100644
--- a/src/internal-packages/server-stats/lib/component/index.jsx
+++ b/src/internal-packages/server-stats/lib/component/index.jsx
@@ -2,7 +2,7 @@ const React = require('react');
const Actions = require('../action');
const Performance = require('./performance-component');
const Databases = require('./connected-databases');
-const NavBarComponent = require('./navbar-component');
+const app = require('ampersand-app');
// const debug = require('debug')('mongodb-compass:server-stats-RTSSComponent');
/**
@@ -17,6 +17,7 @@ class RTSSComponent extends React.Component {
*/
constructor(props) {
super(props);
+ this.TabNavBar = app.appRegistry.getComponent('App.TabNavBar');
}
componentDidMount() {
@@ -27,19 +28,18 @@ class RTSSComponent extends React.Component {
* Renders the component.
*
* @returns {React.Component} The component.
-
-
-
*/
render() {
const performanceView =
;
const databasesView =
;
return (
-
);
diff --git a/src/internal-packages/server-stats/styles/rt-nav.less b/src/internal-packages/server-stats/styles/rt-nav.less
index d921242ab20..bc7d066c23b 100644
--- a/src/internal-packages/server-stats/styles/rt-nav.less
+++ b/src/internal-packages/server-stats/styles/rt-nav.less
@@ -1,11 +1,3 @@
-.rt-nav {
- height: 45px;
- background-color: #2B3033;
- display: flex;
- justify-content: flex-start;
- align-items: flex-end;
- position: relative;
-}
/* time indicator */
.time {
border-radius: 3px 3px 3px 3px;
@@ -68,45 +60,3 @@
margin-right: 7px;
color: hsla(0, 0%, 100%, .7);
}
-
-/* tabs */
-.rt-nav__tabs {
- display: flex;
- justify-content: flex-end;
- align-self: flex-end;
- margin-bottom: 0;
- position: absolute;
- top: 16px;
- margin-left: 10px;
-}
-
-.rt-nav__tab {
- height: 30px;
- width: 130px;
- font-size: 13px;
- text-transform: uppercase;
- border-radius: 3px 3px 0 0;
- display: flex;
- justify-content: center;
- cursor: pointer;
- margin-bottom: 0;
- color: #8B8D8F;
-}
-.rt-nav__link {
- margin: auto;
- text-transform: uppercase;
- font-size: 11px;
- font-weight: bold;
- color: white;
-}
-.rt-nav__link:hover, .rt-nav__link:active, .rt-nav__link:focus {
- text-decoration: none;
- color: white;
-}
-.rt-nav--selected {
- color: #FFFFFF;
- background-color: #3D4247;
- cursor: default;
- border: #545454 1px solid;
- border-bottom: none;
-}
diff --git a/src/internal-packages/sidebar/lib/components/sidebar-collection.jsx b/src/internal-packages/sidebar/lib/components/sidebar-collection.jsx
index 5db20ba3d41..00aabe7b01a 100644
--- a/src/internal-packages/sidebar/lib/components/sidebar-collection.jsx
+++ b/src/internal-packages/sidebar/lib/components/sidebar-collection.jsx
@@ -16,7 +16,9 @@ class SidebarCollection extends React.Component {
}
handleClick() {
- NamespaceStore.ns = this.props._id;
+ if (NamespaceStore.ns !== this.props._id) {
+ NamespaceStore.ns = this.props._id;
+ }
}
render() {
diff --git a/src/internal-packages/sidebar/lib/components/sidebar-database.jsx b/src/internal-packages/sidebar/lib/components/sidebar-database.jsx
index d5553c75ae4..2e05597101d 100644
--- a/src/internal-packages/sidebar/lib/components/sidebar-database.jsx
+++ b/src/internal-packages/sidebar/lib/components/sidebar-database.jsx
@@ -1,6 +1,7 @@
const React = require('react');
const SidebarCollection = require('./sidebar-collection');
-const debug = require('debug')('mongodb-compass:sidebar');
+const { NamespaceStore } = require('hadron-reflux-store');
+// const debug = require('debug')('mongodb-compass:sidebar');
class SidebarDatabase extends React.Component {
constructor() {
@@ -33,7 +34,9 @@ class SidebarDatabase extends React.Component {
}
handleDBClick(db) {
- debug('db clicked', db);
+ if (NamespaceStore.ns !== db) {
+ NamespaceStore.ns = db;
+ }
}
handleArrowClick() {
diff --git a/src/internal-packages/sidebar/lib/components/sidebar-instance-properties.jsx b/src/internal-packages/sidebar/lib/components/sidebar-instance-properties.jsx
index beb1c814931..6546453023e 100644
--- a/src/internal-packages/sidebar/lib/components/sidebar-instance-properties.jsx
+++ b/src/internal-packages/sidebar/lib/components/sidebar-instance-properties.jsx
@@ -1,5 +1,6 @@
const React = require('react');
const app = require('ampersand-app');
+const { NamespaceStore } = require('hadron-reflux-store');
const InstanceActions = app.appRegistry.getAction('App.InstanceActions');
@@ -34,11 +35,7 @@ class SidebarInstanceProperties extends React.Component {
}
handleClickHostname() {
- app.navigate('/', {
- params: {
- connectionId: app.connection.getId()
- }
- });
+ NamespaceStore.ns = '';
}
render() {
diff --git a/src/internal-packages/validation/lib/stores/index.js b/src/internal-packages/validation/lib/stores/index.js
index 27fad972b00..92cfafa3508 100644
--- a/src/internal-packages/validation/lib/stores/index.js
+++ b/src/internal-packages/validation/lib/stores/index.js
@@ -35,9 +35,10 @@ const ValidationStore = Reflux.createStore({
init() {
this.lastFetchedValidatorDoc = {};
- NamespaceStore.listen(() => {
- debug('new namespace');
- ValidationActions.fetchValidationRules();
+ NamespaceStore.listen((ns) => {
+ if (ns && toNS(ns).collection) {
+ ValidationActions.fetchValidationRules();
+ }
});
},