Navigation

SSH Tunnel Status Plugin Tutorial

Estimated time to complete: ~20 minutes

Introduction

This tutorial walks through a MongoDB Compass plugin which displays the hostname and status of the SSH tunnel through which the user is connected to Compass. This plugin only displays when the user connects to Compass through an SSH tunnel.

The SSH tunnel status is displayed in a header item as highlighted in the following image:

../../_images/ssh-tunnel-status-example.png

Note

This plugin is already packaged with MongoDB Compass, so following this tutorial and creating this plugin will not change Compass’ visual contents. The source code for this plugin is maintained by the MongoDB Compass development team in the following repository: https://github.com/mongodb-js/compass-ssh-tunnel-status.

This tutorial illustrates how to create and display a header item in MongoDB Compass so you can employ a similar approach when adding your own header items.

Prerequisites

The following are required to begin building MongoDB Compass plugins:

  • MongoDB Compass (version 1.11 or greater)
  • Node Version Manager (NVM)
  • NodeJS
  • Khaos

The following procedure outlines how to install these dependencies:

  1. Install the latest version of MongoDB Compass for your operating system from the downloads page.

  2. Install the Node Version Manager (NVM):

    For MacOS and Linux operating systems:

    Follow the installation instructions at https://github.com/creationix/nvm#install-script.

    For Windows operating systems:
    1. Download the nvm-setup.zip file from https://github.com/coreybutler/nvm-windows/releases.
    2. Decompress the downloaded .zip file and run nvm-setup.exe.
  3. Install NodeJS via NVM:

    nvm install stable
    
  4. Install the Khaos templating engine:

    npm install -g khaos
    
  5. Create the MongoDB Compass plugins directory. Compass looks for plugins in this directory:

    MongoDB Compass
    mkdir -p ~/.mongodb/compass/plugins
    
    MongoDB Compass Community Edition
    mkdir -p ~/.mongodb/compass-community/plugins
    

Creating the Plugin

Run the following commands to create an empty plugin called ssh-tunnel-status.

MongoDB Compass
cd ~/.mongodb/compass/plugins
khaos create mongodb-js/compass-plugin ./ssh-tunnel-status
MongoDB Compass Community Edition
cd ~/.mongodb/compass-community/plugins
khaos create mongodb-js/compass-plugin ./ssh-tunnel-status

When prompted, enter the following values:

Field Description
Name
ssh-tunnel-status
Description
Displays the current status and port of the SSH tunnel
through which the user is connected to Compass.
Role
Header.Item

Finally, run the following command to install the plugin’s dependencies:

cd ssh-tunnel-status && npm install

Creating the Store

A store is responsible for storing and maintaining the state of the React/Flux application architecture used by MongoDB Compass plugins. The store responds to events and actions, resulting in state changes which are then reflected by the component’s view.

This data flow is shown in the following diagram:

../../_images/react-diagram.png

Stores listen to actions. Components subscribe to stores.

Note

For more information on stores, refer to the Redux documentation.

The SSH tunnel status plugin’s store is called SshTunnelStatus, and it has the following state variables:

sshTunnel A boolean which is true if the user is connected to MongoDB Compass via an SSH tunnel, and false otherwise.
sshTunnelHostname A string indicating the hostname of the SSH tunnel through which the user is connected.
sshTunnelPort A string indicating the port number of the SSH tunnel through which the user is connected.
sshTunnelHostPortString A string representing the concatenation of sshTunnelHostname and sshTunnelPort in the following form: {sshTunnelHostname}:{sshTunnelPort}

Implementing the Store

Update src/stores/store.js to match the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import Reflux from 'reflux';
import StateMixin from 'reflux-state-mixin';

/**
 * Host string max length.
 */
const HOST_STRING_LENGTH = 25;

/**
 * Ssh Tunnel Status store.
 */
const SshTunnelStatusStore = 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
   *
   * If you call `this.setState({...})` this will cause the store to trigger
   * and push down its state as props to connected components.
   */
  mixins: [StateMixin.store],

  /**
   * On activatetd listen to the connection.
   *
   * @param {AppRegistry} appRegistry - The app registry.
   */
  onActivated(appRegistry) {
    appRegistry.on('data-service-connected', this.onConnected.bind(this));
  },

  /**
   * when connected to a deployment, checks if the connection is via an ssh
   * tunnel, and if so, extracts hostname and port from the connection model
   * and sets the new state.
   */
  onConnected(err, ds) {
    if (err) return;
    const sshTunnel = ds.client.model.ssh_tunnel !== 'NONE';
    const sshTunnelHostname = sshTunnel ? ds.client.model.ssh_tunnel_hostname : '';
    const sshTunnelPort = sshTunnel ? ds.client.model.ssh_tunnel_options.dstPort : '';
    const sshTunnelHostPortString = sshTunnel ? this._combineHostPort(
      sshTunnelHostname, sshTunnelPort, true) : '';

    this.setState({
      sshTunnel,
      sshTunnelHostname,
      sshTunnelPort,
      sshTunnelHostPortString
    });
  },

  /**
   * returns the combined host:port string, possibly truncated in the middle
   * of the host.
   * @param  {String} host       The hostname string
   * @param  {String} port       The port string
   * @param  {Boolean} truncate  Whether the string needs to be truncated
   *
   * @return {String}            The resulting host:port string
   */
  _combineHostPort(host, port, truncate) {
    if (host.length >= HOST_STRING_LENGTH && truncate) {
      return host.slice(0, 9) + '...' + host.slice(-9) + ':' + port;
    }
    return host + ':' + port;
  },

  /**
   * Initialize the Server Version store state.
   *
   * @return {Object} initial store state.
   */
  getInitialState() {
    return {
      sshTunnel: false,
      sshTunnelHostname: '',
      sshTunnelPort: '',
      sshTunnelHostPortString: ''
    };
  }
});

export default SshTunnelStatusStore;
export { SshTunnelStatusStore };

Creating the Component

The SSH tunnel status plugin contains a component, which is a view which renders based on the SshTunnelStatus store.

Components are written in JSX, which allows HTML to be inserted and rendered elegantly with ReactJS. HTML is rendered by the render method.

Note

For more information on components, refer to the ReactJS documentation.

Update src/components/ssh-tunnel-status/ssh-tunnel-status.jsx to match the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import styles from './ssh-tunnel-status.less';

class SshTunnelStatus extends Component {
  static displayName = 'SshTunnelStatusComponent';

  static propTypes = {
    sshTunnel: PropTypes.bool,
    sshTunnelHostPortString: PropTypes.string
  };

  static defaultProps = {
    sshTunnel: false,
    sshTunnelHostPortString: ''
  };

  /**
   * Render SshTunnelStatus component.
   *
   * @returns {React.Component} The rendered component.
   */
  render() {
    if (!this.props.sshTunnel) {
      return null;
    }

    return (
      <div
        data-test-id="ssh-tunnel-status"
        className={classnames(styles['ssh-tunnel-status'])}>
        <i className="fa fa-lock" aria-hidden />
        <div className={classnames(styles['ssh-tunnel-status-label'])}>
          <div className={classnames(styles['ssh-tunnel-status-label-is-static'])}>
            SSH connection via:
          </div>
          <div className={classnames(styles['ssh-tunnel-status-string'])}>
            {this.props.sshTunnelHostPortString}
          </div>
        </div>
      </div>
    );
  }
}

export default SshTunnelStatus;
export { SshTunnelStatus };

Styling the Component

The ssh-tunnel-status.jsx component imports its styles from the ssh-tunnel-status.less file.

Update src/components/ssh-tunnel-status/ssh-tunnel-status.less to match the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@import "~less/compass/_theme.less";

.ssh-tunnel-status {
  margin-left: 10px;
  display: flex;
  flex-direction: row;
  align-items: center;

  i {
    &-lock { font-size: 12px; }
    color: @green2;
    flex-basis: auto;
    flex-grow: 0;
    flex-shrink: 0;
    text-align: right;
  }

  &-label {
    color: @gray0;
    font-size: 12px;
    display: flex;
    flex-direction: row;
    align-items: center;

    &-is-static {
      font-size: 11px;
      font-weight: bold;
      color: @green2;
      user-select: none;
      -webkit-user-select: none;
      text-transform: uppercase;
      padding-left: 5px;
      padding-right: 5px;
    }
  }

  &-string {
    text-decoration: none;
  }
}

MongoDB Compass plugins implement CSS Modules and Less syntax to style components.

For more information on styling your MongoDB Compass plugins, see Styling.

Running the Plugin

Run the following command from your plugin’s root directory to build the plugin:

npm run compile

Launch Compass to view your plugin.