diff options
Diffstat (limited to 'app/assets/javascripts/ide/stores/modules/terminal')
13 files changed, 487 insertions, 0 deletions
diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js new file mode 100644 index 00000000000..43b6650b241 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/checks.js @@ -0,0 +1,98 @@ +import Api from '~/api'; +import httpStatus from '~/lib/utils/http_status'; +import * as types from '../mutation_types'; +import * as messages from '../messages'; +import { CHECK_CONFIG, CHECK_RUNNERS, RETRY_RUNNERS_INTERVAL } from '../constants'; +import * as terminalService from '../../../../services/terminals'; + +export const requestConfigCheck = ({ commit }) => { + commit(types.REQUEST_CHECK, CHECK_CONFIG); +}; + +export const receiveConfigCheckSuccess = ({ commit }) => { + commit(types.SET_VISIBLE, true); + commit(types.RECEIVE_CHECK_SUCCESS, CHECK_CONFIG); +}; + +export const receiveConfigCheckError = ({ commit, state }, e) => { + const { status } = e.response; + const { paths } = state; + + const isVisible = status !== httpStatus.FORBIDDEN && status !== httpStatus.NOT_FOUND; + commit(types.SET_VISIBLE, isVisible); + + const message = messages.configCheckError(status, paths.webTerminalConfigHelpPath); + commit(types.RECEIVE_CHECK_ERROR, { type: CHECK_CONFIG, message }); +}; + +export const fetchConfigCheck = ({ dispatch, rootState, rootGetters }) => { + dispatch('requestConfigCheck'); + + const { currentBranchId } = rootState; + const { currentProject } = rootGetters; + + terminalService + .checkConfig(currentProject.path_with_namespace, currentBranchId) + .then(() => { + dispatch('receiveConfigCheckSuccess'); + }) + .catch(e => { + dispatch('receiveConfigCheckError', e); + }); +}; + +export const requestRunnersCheck = ({ commit }) => { + commit(types.REQUEST_CHECK, CHECK_RUNNERS); +}; + +export const receiveRunnersCheckSuccess = ({ commit, dispatch, state }, data) => { + if (data.length) { + commit(types.RECEIVE_CHECK_SUCCESS, CHECK_RUNNERS); + } else { + const { paths } = state; + + commit(types.RECEIVE_CHECK_ERROR, { + type: CHECK_RUNNERS, + message: messages.runnersCheckEmpty(paths.webTerminalRunnersHelpPath), + }); + + dispatch('retryRunnersCheck'); + } +}; + +export const receiveRunnersCheckError = ({ commit }) => { + commit(types.RECEIVE_CHECK_ERROR, { + type: CHECK_RUNNERS, + message: messages.UNEXPECTED_ERROR_RUNNERS, + }); +}; + +export const retryRunnersCheck = ({ dispatch, state }) => { + // if the overall check has failed, don't worry about retrying + const check = state.checks[CHECK_CONFIG]; + if (!check.isLoading && !check.isValid) { + return; + } + + setTimeout(() => { + dispatch('fetchRunnersCheck', { background: true }); + }, RETRY_RUNNERS_INTERVAL); +}; + +export const fetchRunnersCheck = ({ dispatch, rootGetters }, options = {}) => { + const { background = false } = options; + + if (!background) { + dispatch('requestRunnersCheck'); + } + + const { currentProject } = rootGetters; + + Api.projectRunners(currentProject.id, { params: { scope: 'active' } }) + .then(({ data }) => { + dispatch('receiveRunnersCheckSuccess', data); + }) + .catch(e => { + dispatch('receiveRunnersCheckError', e); + }); +}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js new file mode 100644 index 00000000000..112b3794114 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js @@ -0,0 +1,5 @@ +export * from './setup'; +export * from './checks'; +export * from './session_controls'; +export * from './session_status'; +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js new file mode 100644 index 00000000000..d3dcb9dd125 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js @@ -0,0 +1,118 @@ +import axios from '~/lib/utils/axios_utils'; +import httpStatus from '~/lib/utils/http_status'; +import flash from '~/flash'; +import * as types from '../mutation_types'; +import * as messages from '../messages'; +import * as terminalService from '../../../../services/terminals'; +import { STARTING, STOPPING, STOPPED } from '../constants'; + +export const requestStartSession = ({ commit }) => { + commit(types.SET_SESSION_STATUS, STARTING); +}; + +export const receiveStartSessionSuccess = ({ commit, dispatch }, data) => { + commit(types.SET_SESSION, { + id: data.id, + status: data.status, + showPath: data.show_path, + cancelPath: data.cancel_path, + retryPath: data.retry_path, + terminalPath: data.terminal_path, + proxyWebsocketPath: data.proxy_websocket_path, + services: data.services, + }); + + dispatch('pollSessionStatus'); +}; + +export const receiveStartSessionError = ({ dispatch }) => { + flash(messages.UNEXPECTED_ERROR_STARTING); + dispatch('killSession'); +}; + +export const startSession = ({ state, dispatch, rootGetters, rootState }) => { + if (state.session && state.session.status === STARTING) { + return; + } + + const { currentProject } = rootGetters; + const { currentBranchId } = rootState; + + dispatch('requestStartSession'); + + terminalService + .create(currentProject.path_with_namespace, currentBranchId) + .then(({ data }) => { + dispatch('receiveStartSessionSuccess', data); + }) + .catch(error => { + dispatch('receiveStartSessionError', error); + }); +}; + +export const requestStopSession = ({ commit }) => { + commit(types.SET_SESSION_STATUS, STOPPING); +}; + +export const receiveStopSessionSuccess = ({ dispatch }) => { + dispatch('killSession'); +}; + +export const receiveStopSessionError = ({ dispatch }) => { + flash(messages.UNEXPECTED_ERROR_STOPPING); + dispatch('killSession'); +}; + +export const stopSession = ({ state, dispatch }) => { + const { cancelPath } = state.session; + + dispatch('requestStopSession'); + + axios + .post(cancelPath) + .then(() => { + dispatch('receiveStopSessionSuccess'); + }) + .catch(err => { + dispatch('receiveStopSessionError', err); + }); +}; + +export const killSession = ({ commit, dispatch }) => { + dispatch('stopPollingSessionStatus'); + commit(types.SET_SESSION_STATUS, STOPPED); +}; + +export const restartSession = ({ state, dispatch, rootState }) => { + const { status, retryPath } = state.session; + const { currentBranchId } = rootState; + + if (status !== STOPPED) { + return; + } + + if (!retryPath) { + dispatch('startSession'); + return; + } + + dispatch('requestStartSession'); + + axios + .post(retryPath, { branch: currentBranchId, format: 'json' }) + .then(({ data }) => { + dispatch('receiveStartSessionSuccess', data); + }) + .catch(error => { + const responseStatus = error.response && error.response.status; + // We may have removed the build, in this case we'll just create a new session + if ( + responseStatus === httpStatus.NOT_FOUND || + responseStatus === httpStatus.UNPROCESSABLE_ENTITY + ) { + dispatch('startSession'); + } else { + dispatch('receiveStartSessionError', error); + } + }); +}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js new file mode 100644 index 00000000000..59ba1605c47 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_status.js @@ -0,0 +1,64 @@ +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import * as types from '../mutation_types'; +import * as messages from '../messages'; +import { isEndingStatus } from '../utils'; + +export const pollSessionStatus = ({ state, dispatch, commit }) => { + dispatch('stopPollingSessionStatus'); + dispatch('fetchSessionStatus'); + + const interval = setInterval(() => { + if (!state.session) { + dispatch('stopPollingSessionStatus'); + } else { + dispatch('fetchSessionStatus'); + } + }, 5000); + + commit(types.SET_SESSION_STATUS_INTERVAL, interval); +}; + +export const stopPollingSessionStatus = ({ state, commit }) => { + const { sessionStatusInterval } = state; + + if (!sessionStatusInterval) { + return; + } + + clearInterval(sessionStatusInterval); + + commit(types.SET_SESSION_STATUS_INTERVAL, 0); +}; + +export const receiveSessionStatusSuccess = ({ commit, dispatch }, data) => { + const status = data && data.status; + + commit(types.SET_SESSION_STATUS, status); + + if (isEndingStatus(status)) { + dispatch('killSession'); + } +}; + +export const receiveSessionStatusError = ({ dispatch }) => { + flash(messages.UNEXPECTED_ERROR_STATUS); + dispatch('killSession'); +}; + +export const fetchSessionStatus = ({ dispatch, state }) => { + if (!state.session) { + return; + } + + const { showPath } = state.session; + + axios + .get(showPath) + .then(({ data }) => { + dispatch('receiveSessionStatusSuccess', data); + }) + .catch(error => { + dispatch('receiveSessionStatusError', error); + }); +}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js new file mode 100644 index 00000000000..78ad94f8a91 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/setup.js @@ -0,0 +1,14 @@ +import * as types from '../mutation_types'; + +export const init = ({ dispatch }) => { + dispatch('fetchConfigCheck'); + dispatch('fetchRunnersCheck'); +}; + +export const hideSplash = ({ commit }) => { + commit(types.HIDE_SPLASH); +}; + +export const setPaths = ({ commit }, paths) => { + commit(types.SET_PATHS, paths); +}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/constants.js b/app/assets/javascripts/ide/stores/modules/terminal/constants.js new file mode 100644 index 00000000000..f7ae9d8f4ea --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/constants.js @@ -0,0 +1,9 @@ +export const CHECK_CONFIG = 'config'; +export const CHECK_RUNNERS = 'runners'; +export const RETRY_RUNNERS_INTERVAL = 10000; + +export const STARTING = 'starting'; +export const PENDING = 'pending'; +export const RUNNING = 'running'; +export const STOPPING = 'stopping'; +export const STOPPED = 'stopped'; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/getters.js b/app/assets/javascripts/ide/stores/modules/terminal/getters.js new file mode 100644 index 00000000000..6d64ee4ab6e --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/getters.js @@ -0,0 +1,19 @@ +export const allCheck = state => { + const checks = Object.values(state.checks); + + if (checks.some(check => check.isLoading)) { + return { isLoading: true }; + } + + const invalidCheck = checks.find(check => !check.isValid); + const isValid = !invalidCheck; + const message = !invalidCheck ? '' : invalidCheck.message; + + return { + isLoading: false, + isValid, + message, + }; +}; + +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/index.js b/app/assets/javascripts/ide/stores/modules/terminal/index.js new file mode 100644 index 00000000000..ef1289e1722 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/index.js @@ -0,0 +1,12 @@ +import * as actions from './actions'; +import * as getters from './getters'; +import mutations from './mutations'; +import state from './state'; + +export default () => ({ + namespaced: true, + actions, + getters, + mutations, + state: state(), +}); diff --git a/app/assets/javascripts/ide/stores/modules/terminal/messages.js b/app/assets/javascripts/ide/stores/modules/terminal/messages.js new file mode 100644 index 00000000000..38c5a8a28d8 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/messages.js @@ -0,0 +1,55 @@ +import { escape } from 'lodash'; +import { __, sprintf } from '~/locale'; +import httpStatus from '~/lib/utils/http_status'; + +export const UNEXPECTED_ERROR_CONFIG = __( + 'An unexpected error occurred while checking the project environment.', +); +export const UNEXPECTED_ERROR_RUNNERS = __( + 'An unexpected error occurred while checking the project runners.', +); +export const UNEXPECTED_ERROR_STATUS = __( + 'An unexpected error occurred while communicating with the Web Terminal.', +); +export const UNEXPECTED_ERROR_STARTING = __( + 'An unexpected error occurred while starting the Web Terminal.', +); +export const UNEXPECTED_ERROR_STOPPING = __( + 'An unexpected error occurred while stopping the Web Terminal.', +); +export const EMPTY_RUNNERS = __( + 'Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}', +); +export const ERROR_CONFIG = __( + 'Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}', +); +export const ERROR_PERMISSION = __( + 'You do not have permission to run the Web Terminal. Please contact a project administrator.', +); + +export const configCheckError = (status, helpUrl) => { + if (status === httpStatus.UNPROCESSABLE_ENTITY) { + return sprintf( + ERROR_CONFIG, + { + helpStart: `<a href="${escape(helpUrl)}" target="_blank">`, + helpEnd: '</a>', + }, + false, + ); + } else if (status === httpStatus.FORBIDDEN) { + return ERROR_PERMISSION; + } + + return UNEXPECTED_ERROR_CONFIG; +}; + +export const runnersCheckEmpty = helpUrl => + sprintf( + EMPTY_RUNNERS, + { + helpStart: `<a href="${escape(helpUrl)}" target="_blank">`, + helpEnd: '</a>', + }, + false, + ); diff --git a/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js b/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js new file mode 100644 index 00000000000..b6a6f28abfa --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/mutation_types.js @@ -0,0 +1,11 @@ +export const SET_VISIBLE = 'SET_VISIBLE'; +export const HIDE_SPLASH = 'HIDE_SPLASH'; +export const SET_PATHS = 'SET_PATHS'; + +export const REQUEST_CHECK = 'REQUEST_CHECK'; +export const RECEIVE_CHECK_SUCCESS = 'RECEIVE_CHECK_SUCCESS'; +export const RECEIVE_CHECK_ERROR = 'RECEIVE_CHECK_ERROR'; + +export const SET_SESSION = 'SET_SESSION'; +export const SET_SESSION_STATUS = 'SET_SESSION_STATUS'; +export const SET_SESSION_STATUS_INTERVAL = 'SET_SESSION_STATUS_INTERVAL'; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/mutations.js b/app/assets/javascripts/ide/stores/modules/terminal/mutations.js new file mode 100644 index 00000000000..37f40af9c2e --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/mutations.js @@ -0,0 +1,64 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_VISIBLE](state, isVisible) { + Object.assign(state, { + isVisible, + }); + }, + [types.HIDE_SPLASH](state) { + Object.assign(state, { + isShowSplash: false, + }); + }, + [types.SET_PATHS](state, paths) { + Object.assign(state, { + paths, + }); + }, + [types.REQUEST_CHECK](state, type) { + Object.assign(state.checks, { + [type]: { + isLoading: true, + }, + }); + }, + [types.RECEIVE_CHECK_ERROR](state, { type, message }) { + Object.assign(state.checks, { + [type]: { + isLoading: false, + isValid: false, + message, + }, + }); + }, + [types.RECEIVE_CHECK_SUCCESS](state, type) { + Object.assign(state.checks, { + [type]: { + isLoading: false, + isValid: true, + message: null, + }, + }); + }, + [types.SET_SESSION](state, session) { + Object.assign(state, { + session, + }); + }, + [types.SET_SESSION_STATUS](state, status) { + const session = { + ...(state.session || {}), + status, + }; + + Object.assign(state, { + session, + }); + }, + [types.SET_SESSION_STATUS_INTERVAL](state, sessionStatusInterval) { + Object.assign(state, { + sessionStatusInterval, + }); + }, +}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/state.js b/app/assets/javascripts/ide/stores/modules/terminal/state.js new file mode 100644 index 00000000000..f35a10ed2fe --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/state.js @@ -0,0 +1,13 @@ +import { CHECK_CONFIG, CHECK_RUNNERS } from './constants'; + +export default () => ({ + checks: { + [CHECK_CONFIG]: { isLoading: true }, + [CHECK_RUNNERS]: { isLoading: true }, + }, + isVisible: false, + isShowSplash: true, + paths: {}, + session: null, + sessionStatusInterval: 0, +}); diff --git a/app/assets/javascripts/ide/stores/modules/terminal/utils.js b/app/assets/javascripts/ide/stores/modules/terminal/utils.js new file mode 100644 index 00000000000..c30136b5277 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/terminal/utils.js @@ -0,0 +1,5 @@ +import { STARTING, PENDING, RUNNING } from './constants'; + +export const isStartingStatus = status => status === STARTING || status === PENDING; +export const isRunningStatus = status => status === RUNNING; +export const isEndingStatus = status => !isStartingStatus(status) && !isRunningStatus(status); |