diff options
Diffstat (limited to 'app/assets')
6 files changed, 122 insertions, 4 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 01aec4f36af..6bf9dca1112 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -31,6 +31,7 @@ export default class Clusters { installHelmPath, installIngressPath, installRunnerPath, + installJupyterPath, installPrometheusPath, managePrometheusPath, clusterStatus, @@ -51,6 +52,7 @@ export default class Clusters { installIngressEndpoint: installIngressPath, installRunnerEndpoint: installRunnerPath, installPrometheusEndpoint: installPrometheusPath, + installJupyterEndpoint: installJupyterPath, }); this.installApplication = this.installApplication.bind(this); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 9c12b89240c..e03db7b8974 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -37,6 +37,11 @@ export default { default: '', }, }, + data() { + return { + jupyterSuggestHostnameValue: '', + }; + }, computed: { generalApplicationDescription() { return sprintf( @@ -121,6 +126,20 @@ export default { false, ); }, + jupyterInstalled() { + return this.applications.jupyter.status === APPLICATION_INSTALLED; + }, + jupyterHostname() { + return this.applications.jupyter.hostname; + }, + jupyterSuggestHostname() { + return `jupyter.${this.applications.ingress.externalIp}.xip.io`; + }, + }, + watch: { + jupyterSuggestHostname() { + this.jupyterSuggestHostnameValue = this.jupyterSuggestHostname; + }, }, }; </script> @@ -278,11 +297,89 @@ export default { applications to production.`) }} </div> </application-row> + <application-row + id="jupyter" + :title="applications.jupyter.title" + title-link="https://jupyterhub.readthedocs.io/en/stable/" + :status="applications.jupyter.status" + :status-reason="applications.jupyter.statusReason" + :request-status="applications.jupyter.requestStatus" + :request-reason="applications.jupyter.requestReason" + > + <div slot="description"> + <p> + {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, + manages, and proxies multiple instances of the single-user + Jupyter notebook server. JupyterHub can be used to serve + notebooks to a class of students, a corporate data science group, + or a scientific research group.`) }} + </p> + <template v-if="jupyterInstalled"> + <div class="form-group"> + <label for="jupyter-hostname"> + {{ s__('ClusterIntegration|Jupyter Hostname') }} + </label> + <div + v-if="jupyterHostname" + class="input-group" + > + <input + type="text" + id="jupyter-hostname" + class="form-control js-hostname" + :value="jupyterHostname" + readonly + /> + <span class="input-group-btn"> + <clipboard-button + :text="jupyterHostname" + :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" + class="js-clipboard-btn" + /> + </span> + </div> + </div> + </template> + <template v-else-if="ingressInstalled"> + <div class="form-group"> + <label for="jupyter-hostname"> + {{ s__('ClusterIntegration|Jupyter Hostname') }} + </label> + <div class="input-group"> + <input + type="text" + id="jupyter-hostname" + class="form-control js-hostname" + v-model="jupyterSuggestHostnameValue" + /> + <span class="input-group-btn"> + <clipboard-button + :text="jupyterHostname" + :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" + class="js-clipboard-btn" + /> + </span> + </div> + </div> + <p> + {{ s__(`ClusterIntegration|Replace this with your own hostname if you want. + If you do so, point hostname to Ingress IP Address from above.`) }} + <a + :href="ingressDnsHelpPath" + target="_blank" + rel="noopener noreferrer" + > + {{ __('More information') }} + </a> + </p> + </template> + </div> + </application-row> <!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests --> - <!-- Add GitLab Runner row, all other plumbing is complete --> + <!-- Add Jupyter row, all other plumbing is complete --> </div> </div> </section> diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index b7179f52bb3..371f71fde44 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -11,3 +11,4 @@ export const REQUEST_LOADING = 'request-loading'; export const REQUEST_SUCCESS = 'request-success'; export const REQUEST_FAILURE = 'request-failure'; export const INGRESS = 'ingress'; +export const JUPYTER = 'jupyter'; diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index 13468578f4f..e49db9c2f4f 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -1,4 +1,5 @@ import axios from '../../lib/utils/axios_utils'; +import { JUPYTER } from '../constants'; export default class ClusterService { constructor(options = {}) { @@ -8,6 +9,7 @@ export default class ClusterService { ingress: this.options.installIngressEndpoint, runner: this.options.installRunnerEndpoint, prometheus: this.options.installPrometheusEndpoint, + jupyter: this.options.installJupyterEndpoint, }; } @@ -16,7 +18,13 @@ export default class ClusterService { } installApplication(appId) { - return axios.post(this.appInstallEndpointMap[appId]); + const data = {}; + + if (appId === JUPYTER) { + data.hostname = document.getElementById('jupyter-hostname').value; + } + + return axios.post(this.appInstallEndpointMap[appId], data); } static updateCluster(endpoint, data) { diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 348bbec3b25..f609b425190 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -1,5 +1,5 @@ import { s__ } from '../../locale'; -import { INGRESS } from '../constants'; +import { INGRESS, JUPYTER } from '../constants'; export default class ClusterStore { constructor() { @@ -38,6 +38,14 @@ export default class ClusterStore { requestStatus: null, requestReason: null, }, + jupyter: { + title: s__('ClusterIntegration|JupyterHub'), + status: null, + statusReason: null, + requestStatus: null, + requestReason: null, + hostname: null, + }, }, }; } @@ -83,6 +91,8 @@ export default class ClusterStore { if (appId === INGRESS) { this.state.applications.ingress.externalIp = serverAppEntry.external_ip; + } else if (appId === JUPYTER) { + this.state.applications.jupyter.hostname = serverAppEntry.hostname; } }); } diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 3fd13078131..cfcce91f514 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -6,7 +6,7 @@ .cluster-applications-table { // Wait for the Vue to kick-in and render the applications block - min-height: 400px; + min-height: 500px; } .clusters-dropdown-menu { |