From ee50205fbeaf1dfe8a34e4db77b69fd2e8d63b3f Mon Sep 17 00:00:00 2001 From: Josh Pinkney Date: Tue, 23 Feb 2021 11:57:04 -0500 Subject: [PATCH 01/13] Initial support for devworkspaces Includes: - Creating, Listing, Starting, Stopping, Deleting Devworkspaces Signed-off-by: Josh Pinkney --- package.json | 3 +- .../__tests__/WebSocketBanner.spec.tsx | 2 +- .../BannerAlert/WebSocketBanner/index.tsx | 2 +- src/containers/FactoryLoader.tsx | 6 +- src/containers/IdeLoader.tsx | 12 +- .../WorkspaceActions/__tests__/index.spec.tsx | 3 +- src/containers/WorkspaceActions/index.tsx | 8 +- .../__tests__/FactoryLoader.spec.tsx | 10 +- src/containers/__tests__/IdeLoader.spec.tsx | 4 +- src/inversify.config.ts | 4 +- src/pages/GetStarted/index.tsx | 2 +- .../Header/Actions/__tests__/Actions.spec.tsx | 4 +- src/services/bootstrap/PreloadData.ts | 31 +- src/services/cheWorkspaceClient/index.ts | 2 +- src/services/helpers/devworkspace.ts | 80 ++++ .../workspace-client/cheWorkspaceClient.ts | 115 +++++ .../workspace-client/devWorkspaceClient.ts | 157 ++++++ src/services/workspace-client/index.ts | 106 +++++ src/store/Branding.ts | 2 +- src/store/FactoryResolver.ts | 2 +- src/store/InfrastructureNamespace.ts | 2 +- src/store/User.ts | 2 +- src/store/UserPreferences/index.ts | 2 +- src/store/UserProfile.ts | 2 +- src/store/Workspaces/index.ts | 183 ++++--- src/typings/che.d.ts | 4 +- yarn.lock | 446 +++++++++++++++++- 27 files changed, 1088 insertions(+), 108 deletions(-) create mode 100644 src/services/helpers/devworkspace.ts create mode 100644 src/services/workspace-client/cheWorkspaceClient.ts create mode 100644 src/services/workspace-client/devWorkspaceClient.ts create mode 100644 src/services/workspace-client/index.ts diff --git a/package.json b/package.json index 5e0a15fec..0e4d672d8 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "licenseCheck:run": "license-tool/run.sh" }, "dependencies": { - "@eclipse-che/workspace-client": "^0.0.1-1613117389", + "@eclipse-che/devworkspace-client": "^0.0.1-1614091834", + "@eclipse-che/workspace-client": "^0.0.1-1613484098", "@patternfly/react-core": "~4.84.4", "@patternfly/react-icons": "^4.3.5", "@patternfly/react-table": "^4.5.7", diff --git a/src/components/BannerAlert/WebSocketBanner/__tests__/WebSocketBanner.spec.tsx b/src/components/BannerAlert/WebSocketBanner/__tests__/WebSocketBanner.spec.tsx index 92bd7ac35..76671ddc4 100644 --- a/src/components/BannerAlert/WebSocketBanner/__tests__/WebSocketBanner.spec.tsx +++ b/src/components/BannerAlert/WebSocketBanner/__tests__/WebSocketBanner.spec.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { container } from '../../../../inversify.config'; import WebSocketBanner from '..'; -import { CheWorkspaceClient } from '../../../../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../../../../services/workspace-client/cheWorkspaceClient'; import { Provider } from 'react-redux'; import { FakeStoreBuilder } from '../../../../store/__mocks__/storeBuilder'; import { BrandingData } from '../../../../services/bootstrap/branding.constant'; diff --git a/src/components/BannerAlert/WebSocketBanner/index.tsx b/src/components/BannerAlert/WebSocketBanner/index.tsx index 79afcf537..093a4074c 100644 --- a/src/components/BannerAlert/WebSocketBanner/index.tsx +++ b/src/components/BannerAlert/WebSocketBanner/index.tsx @@ -14,7 +14,7 @@ import { Banner } from '@patternfly/react-core'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { container } from '../../../inversify.config'; -import { CheWorkspaceClient } from '../../../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../../../services/workspace-client/cheWorkspaceClient'; import { AppState } from '../../../store'; type Props = MappedProps & {}; diff --git a/src/containers/FactoryLoader.tsx b/src/containers/FactoryLoader.tsx index f3f257658..f56c37af1 100644 --- a/src/containers/FactoryLoader.tsx +++ b/src/containers/FactoryLoader.tsx @@ -149,7 +149,7 @@ export class FactoryLoaderContainer extends React.PureComponent { } this.setState({ currentStep: LoadFactorySteps.OPEN_IDE }); try { - await this.props.requestWorkspace(workspace.id); + await this.props.requestWorkspace(workspace); } catch (e) { this.showAlert(`Getting workspace detail data failed. ${e}`); } @@ -281,9 +281,9 @@ export class FactoryLoaderContainer extends React.PureComponent { if (this.state.currentStep !== LoadFactorySteps.START_WORKSPACE && this.state.currentStep !== LoadFactorySteps.OPEN_IDE) { try { - await this.props.requestWorkspace(workspace.id); + await this.props.requestWorkspace(workspace); if (WorkspaceStatus[workspace.status] === WorkspaceStatus.STOPPED) { - await this.props.startWorkspace(workspace.id); + await this.props.startWorkspace(workspace); this.setState({ currentStep: LoadFactorySteps.START_WORKSPACE }); } else if (WorkspaceStatus[workspace.status] === WorkspaceStatus.RUNNING) { this.setState({ currentStep: LoadFactorySteps.START_WORKSPACE }); diff --git a/src/containers/IdeLoader.tsx b/src/containers/IdeLoader.tsx index 04eb06588..8832857e2 100644 --- a/src/containers/IdeLoader.tsx +++ b/src/containers/IdeLoader.tsx @@ -224,7 +224,7 @@ class IdeLoaderContainer extends React.PureComponent { private async verboseModeHandler(workspace: che.Workspace) { try { - await this.props.startWorkspace(workspace.id, { 'debug-workspace-start': true }); + await this.props.startWorkspace(workspace, { 'debug-workspace-start': true }); this.props.deleteWorkspaceLogs(workspace.id); this.setState({ currentStep: LoadIdeSteps.INITIALIZING, @@ -280,16 +280,16 @@ class IdeLoaderContainer extends React.PureComponent { this.setState({ currentStep: LoadIdeSteps.OPEN_IDE, ideUrl }); } - private async openIDE(workspaceId: string): Promise { + private async openIDE(cheWorkspace: che.Workspace): Promise { this.setState({ currentStep: LoadIdeSteps.OPEN_IDE }); try { - await this.props.requestWorkspace(workspaceId); + await this.props.requestWorkspace(cheWorkspace); } catch (e) { this.showAlert(`Getting workspace detail data failed. ${e}`); return; } const workspace = this.props.allWorkspaces.find(workspace => - workspace.id === workspaceId); + workspace.id === cheWorkspace.id); if (workspace && workspace.runtime) { await this.updateIdeUrl(workspace.runtime); } @@ -317,7 +317,7 @@ class IdeLoaderContainer extends React.PureComponent { this.setState({ workspaceId: workspace.id }); if ((workspace.runtime || this.state.currentStep === LoadIdeSteps.START_WORKSPACE) && workspace.status === WorkspaceStatus[WorkspaceStatus.RUNNING]) { - return this.openIDE(workspace.id); + return this.openIDE(workspace); } } else { this.showAlert('Failed to find the target workspace.'); @@ -327,7 +327,7 @@ class IdeLoaderContainer extends React.PureComponent { this.setState({ currentStep: LoadIdeSteps.START_WORKSPACE }); if (workspace.status === WorkspaceStatus[WorkspaceStatus.STOPPED] && (this.state.hasError !== true)) { try { - await this.props.startWorkspace(`${workspace.id}`); + await this.props.startWorkspace(workspace); } catch (e) { this.showAlert(`Workspace ${this.state.workspaceName} failed to start. ${e}`); return; diff --git a/src/containers/WorkspaceActions/__tests__/index.spec.tsx b/src/containers/WorkspaceActions/__tests__/index.spec.tsx index cbcbe8789..b672d99db 100644 --- a/src/containers/WorkspaceActions/__tests__/index.spec.tsx +++ b/src/containers/WorkspaceActions/__tests__/index.spec.tsx @@ -26,7 +26,8 @@ import { AppThunk } from '../../../store'; jest.mock('../../../store/Workspaces/index', () => { return { actionCreators: { - deleteWorkspace: (id: string): AppThunk> => async (): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + deleteWorkspace: (workspace: che.Workspace): AppThunk> => async (): Promise => { return Promise.resolve(); }, } as ActionCreators, diff --git a/src/containers/WorkspaceActions/index.tsx b/src/containers/WorkspaceActions/index.tsx index 362412516..e2c4de704 100644 --- a/src/containers/WorkspaceActions/index.tsx +++ b/src/containers/WorkspaceActions/index.tsx @@ -92,19 +92,19 @@ export class WorkspaceActionsProvider extends React.Component { } case WorkspaceAction.START_DEBUG_AND_OPEN_LOGS: { - await this.props.startWorkspace(workspace.id, { + await this.props.startWorkspace(workspace, { 'debug-workspace-start': true }); return buildIdeLoaderPath(workspace, IdeLoaderTab.Logs); } case WorkspaceAction.START_IN_BACKGROUND: { - await this.props.startWorkspace(workspace.id); + await this.props.startWorkspace(workspace); } break; case WorkspaceAction.STOP_WORKSPACE: { - await this.props.stopWorkspace(workspace.id); + await this.props.stopWorkspace(workspace); } break; case WorkspaceAction.ADD_PROJECT: @@ -122,7 +122,7 @@ export class WorkspaceActionsProvider extends React.Component { }); try { - await this.props.deleteWorkspace(workspace.id); + await this.props.deleteWorkspace(workspace); this.deleting.delete(id); this.setState({ isDeleted: Array.from(this.deleting), diff --git a/src/containers/__tests__/FactoryLoader.spec.tsx b/src/containers/__tests__/FactoryLoader.spec.tsx index ef2decfa3..3fc1f8d7a 100644 --- a/src/containers/__tests__/FactoryLoader.spec.tsx +++ b/src/containers/__tests__/FactoryLoader.spec.tsx @@ -122,8 +122,8 @@ describe('Factory Loader container', () => { jest.runOnlyPendingTimers(); expect(showAlertMock).not.toHaveBeenCalled(); - await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace.id)); - await waitFor(() => expect(startWorkspaceMock).toHaveBeenCalledWith(workspace.id)); + await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace)); + await waitFor(() => expect(startWorkspaceMock).toHaveBeenCalledWith(workspace)); expect(LoadFactorySteps[elementCurrentStep.innerHTML]).toEqual(LoadFactorySteps[LoadFactorySteps.START_WORKSPACE]); }); @@ -153,11 +153,11 @@ describe('Factory Loader container', () => { 'when the workspace is stopped unless they are pushed to a remote code repository.' ); expect(setWorkspaceIdMock).toHaveBeenCalledWith(workspace.id); - await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace.id)); + await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace)); await waitFor(() => expect(startWorkspaceMock).not.toHaveBeenCalled()); jest.runOnlyPendingTimers(); - await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace.id)); + await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace)); expect(LoadFactorySteps[elementCurrentStep.innerHTML]).toEqual(LoadFactorySteps[LoadFactorySteps.OPEN_IDE]); }); @@ -204,7 +204,7 @@ describe('Factory Loader container', () => { expect(createWorkspaceFromDevfileMock).not.toHaveBeenCalled(); jest.runOnlyPendingTimers(); - await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace.id)); + await waitFor(() => expect(requestWorkspaceMock).toHaveBeenCalledWith(workspace)); expect(LoadFactorySteps[elementCurrentStep.innerHTML]).toEqual(LoadFactorySteps[LoadFactorySteps.OPEN_IDE]); }); diff --git a/src/containers/__tests__/IdeLoader.spec.tsx b/src/containers/__tests__/IdeLoader.spec.tsx index d115e195e..7cb5a0694 100644 --- a/src/containers/__tests__/IdeLoader.spec.tsx +++ b/src/containers/__tests__/IdeLoader.spec.tsx @@ -33,9 +33,9 @@ jest.mock('../../store/Workspaces/index', () => { return { actionCreators: { // eslint-disable-next-line @typescript-eslint/no-unused-vars - requestWorkspace: (id: string): AppThunk> => async (): Promise => { requestWorkspaceMock(); }, + requestWorkspace: (workspace: che.Workspace): AppThunk> => async (): Promise => { requestWorkspaceMock(); }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - startWorkspace: (id: string): AppThunk> => async (): Promise => { startWorkspaceMock(); }, + startWorkspace: (workspace: che.Workspace): AppThunk> => async (): Promise => { startWorkspaceMock(); }, requestWorkspaces: (): AppThunk> => async (): Promise => { return Promise.resolve(); }, diff --git a/src/inversify.config.ts b/src/inversify.config.ts index fb8ec918c..36ddfca36 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -16,9 +16,10 @@ import getDecorators from 'inversify-inject-decorators'; import { KeycloakSetupService } from './services/keycloak/setup'; import { KeycloakAuthService } from './services/keycloak/auth'; import { Debounce } from './services/helpers/debounce'; -import { CheWorkspaceClient } from './services/cheWorkspaceClient'; +import { CheWorkspaceClient } from './services/workspace-client/cheWorkspaceClient'; import { AppAlerts } from './services/alerts/appAlerts'; import { IssuesReporterService } from './services/bootstrap/issuesReporter'; +import { DevWorkspaceClient } from './services/workspace-client/devWorkspaceClient'; const container = new Container(); const { lazyInject } = getDecorators(container); @@ -28,6 +29,7 @@ container.bind(KeycloakSetupService).toSelf().inSingletonScope(); container.bind(KeycloakAuthService).toSelf().inSingletonScope(); container.bind(Debounce).toSelf(); container.bind(CheWorkspaceClient).toSelf().inSingletonScope(); +container.bind(DevWorkspaceClient).toSelf().inSingletonScope(); container.bind(AppAlerts).toSelf().inSingletonScope(); export { container, lazyInject }; diff --git a/src/pages/GetStarted/index.tsx b/src/pages/GetStarted/index.tsx index 761435349..628431f39 100644 --- a/src/pages/GetStarted/index.tsx +++ b/src/pages/GetStarted/index.tsx @@ -113,7 +113,7 @@ export class GetStarted extends React.PureComponent { // force start for the new workspace try { - await this.props.startWorkspace(`${workspace.id}`); + await this.props.startWorkspace(workspace); this.props.history.push(`/ide/${workspace.namespace}/${workspaceName}`); } catch (error) { const errorMessage = `Workspace ${workspaceName} failed to start`; diff --git a/src/pages/WorkspaceDetails/Header/Actions/__tests__/Actions.spec.tsx b/src/pages/WorkspaceDetails/Header/Actions/__tests__/Actions.spec.tsx index 7180b5038..a758c8a49 100644 --- a/src/pages/WorkspaceDetails/Header/Actions/__tests__/Actions.spec.tsx +++ b/src/pages/WorkspaceDetails/Header/Actions/__tests__/Actions.spec.tsx @@ -23,10 +23,10 @@ import { FakeStoreBuilder } from '../../../../../store/__mocks__/storeBuilder'; jest.mock('../../../../../store/Workspaces/index', () => { return { actionCreators: { - startWorkspace: (workspaceId: string, params?: ResourceQueryParams): AppThunk> => async (): Promise => { + startWorkspace: (workspace: che.Workspace, params?: ResourceQueryParams): AppThunk> => async (): Promise => { return Promise.resolve(); }, - stopWorkspace: (workspaceId: string): AppThunk> => async (): Promise => { + stopWorkspace: (workspace: che.Workspace): AppThunk> => async (): Promise => { return Promise.resolve(); } } as ActionCreators, diff --git a/src/services/bootstrap/PreloadData.ts b/src/services/bootstrap/PreloadData.ts index 5089eeb0e..32a20a727 100644 --- a/src/services/bootstrap/PreloadData.ts +++ b/src/services/bootstrap/PreloadData.ts @@ -22,9 +22,10 @@ import * as Plugins from '../../store/Plugins'; import * as UserProfileStore from '../../store/UserProfile'; import * as UserStore from '../../store/User'; import * as WorkspacesStore from '../../store/Workspaces'; -import { CheWorkspaceClient } from '../cheWorkspaceClient'; import { ResourceFetcherService } from '../resource-fetcher'; import { IssuesReporterService } from './issuesReporter'; +import { CheWorkspaceClient } from '../workspace-client/cheWorkspaceClient'; +import { DevWorkspaceClient } from '../workspace-client/devWorkspaceClient'; /** * This class prepares all init data. @@ -41,6 +42,9 @@ export class PreloadData { @lazyInject(CheWorkspaceClient) private readonly cheWorkspaceClient: CheWorkspaceClient; + @lazyInject(DevWorkspaceClient) + private readonly devWorkspaceClient: DevWorkspaceClient; + private store: Store; constructor(store: Store) { @@ -56,6 +60,15 @@ export class PreloadData { this.updateWorkspaces(); new ResourceFetcherService().prefetchResources(this.store.getState()); + const isDevWorkspaceEnabled = await this.devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled) { + const defaultNamespace = await this.cheWorkspaceClient.getDefaultNamespace(); + const namespaceInitialized = await this.initializeNamespace(defaultNamespace); + if (namespaceInitialized) { + this.watchNamespaces(defaultNamespace); + } + } + const settings = await this.updateWorkspaceSettings(); await Promise.all([ this.updateBranding(), @@ -64,6 +77,7 @@ export class PreloadData { this.updatePlugins(settings), this.updateRegistriesMetadata(settings), this.updateDevfileSchema(), + this.updateDefaultComponents() ]); } @@ -85,6 +99,15 @@ export class PreloadData { return this.cheWorkspaceClient.updateJsonRpcMasterApi(); } + private async initializeNamespace(namespace: string): Promise { + return this.devWorkspaceClient.initializeNamespace(namespace); + } + + private async watchNamespaces(namespace: string): Promise { + const { updateDevWorkspaceStatus } = WorkspacesStore.actionCreators; + return this.devWorkspaceClient.subscribeToNamespace(namespace, updateDevWorkspaceStatus, this.store.dispatch); + } + private async updateUser(): Promise { const { requestUser, setUser } = UserStore.actionCreators; const user = this.keycloakSetup.getUser(); @@ -122,6 +145,12 @@ export class PreloadData { await requestRegistriesMetadata(settings.cheWorkspaceDevfileRegistryUrl || '')(this.store.dispatch, this.store.getState, undefined); } + private updateDefaultComponents(): void { + // These are just temporary until we the devworkspace registry is available and we can potentially reference by id + this.devWorkspaceClient.defaultEditor = 'theia-next'; // settings['che.factory.default_editor'] || 'theia-next'; + this.devWorkspaceClient.defaultPlugins = ['machine-exec']; // settings['che.factory.default_plugins'] || 'machine-exec'; + } + private async updateDevfileSchema(): Promise { const { requestJsonSchema } = DevfileRegistriesStore.actionCreators; return requestJsonSchema()(this.store.dispatch, this.store.getState, undefined); diff --git a/src/services/cheWorkspaceClient/index.ts b/src/services/cheWorkspaceClient/index.ts index 4f21ed5e1..66f1d16ad 100644 --- a/src/services/cheWorkspaceClient/index.ts +++ b/src/services/cheWorkspaceClient/index.ts @@ -170,7 +170,7 @@ export class CheWorkspaceClient { config.headers.common[header] = `Bearer ${keycloak.token}`; } } - resolve(keycloak.token); + resolve(keycloak.token as string); }).error(error => { reject(error); }); diff --git a/src/services/helpers/devworkspace.ts b/src/services/helpers/devworkspace.ts new file mode 100644 index 000000000..30deaa472 --- /dev/null +++ b/src/services/helpers/devworkspace.ts @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-2020 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { devWorkspaceToDevfile, IDevWorkspace, IDevWorkspaceDevfile } from '@eclipse-che/devworkspace-client'; + +/** + * Convert a devworkspace to something that the natively dashboard understands + * @param devworkspace The devworkspace that you want to convert + */ +export function convertDevWorkspaceV2ToV1(devworkspace: IDevWorkspace): che.Workspace { + const convertedWorkspace = {} as che.Workspace; + const namespace = devworkspace.metadata.namespace; + convertedWorkspace.namespace = namespace; + convertedWorkspace.devfile = devWorkspaceToDevfile(devworkspace); + const epochCreatedTimestamp = new Date(devworkspace.metadata.creationTimestamp as string).valueOf(); + convertedWorkspace.attributes = { + infrastructureNamespace: namespace, + created: epochCreatedTimestamp.toString(), + }; + if (devworkspace.status.workspaceId) { + convertedWorkspace.id = devworkspace.status?.workspaceId; + } + if (devworkspace.status?.phase && devworkspace.status?.ideUrl) { + const status = devworkspace.status.phase.toUpperCase(); + convertedWorkspace.runtime = { + status, + activeEnv: '', + machines: { + theia: { + servers: { + theia: { + attributes: { + type: 'ide' + }, + url: devworkspace.status.ideUrl, + status + }, + }, + attributes: {}, + status + } + } + }; + convertedWorkspace.status = status; + } + return convertedWorkspace; +} + +/** + * Check to see if the workspace or devfile is a DevWorkspace + * @param devworkspaceCustomResourceOrDevfile The devworkspace or devfile you want to check + */ +export function isDevWorkspace(devworkspaceCustomResourceOrDevfile: che.Workspace | api.che.workspace.devfile.Devfile | IDevWorkspaceDevfile): boolean { + return (devworkspaceCustomResourceOrDevfile as any).kind === 'DevWorkspace' || (devworkspaceCustomResourceOrDevfile as any).devfile?.schemaVersion !== undefined || (devworkspaceCustomResourceOrDevfile as any).schemaVersion !== undefined; +} + +/** + * Check to see if the workspace is currently being deleted + * @param workspace The workspace you want to check + */ +export function isDeleting(workspace: IDevWorkspace): boolean { + return workspace.metadata.deletionTimestamp !== undefined; +} + +/** + * Check to see if the workspace is a web terminal + * @param workspaceOrDevfile The workspace or devfile you want to check + */ +export function isWebTerminal(workspaceOrDevfile: che.Workspace | api.che.workspace.devfile.Devfile): boolean { + return !!(workspaceOrDevfile as any).metadata.labels['console.openshift.io/terminal']; +} diff --git a/src/services/workspace-client/cheWorkspaceClient.ts b/src/services/workspace-client/cheWorkspaceClient.ts new file mode 100644 index 000000000..8bbcc7146 --- /dev/null +++ b/src/services/workspace-client/cheWorkspaceClient.ts @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018-2020 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { inject, injectable } from 'inversify'; +import { default as WorkspaceClientLib, IWorkspaceMasterApi, IRemoteAPI } from '@eclipse-che/workspace-client'; +import { EventEmitter } from 'events'; +import { WorkspaceClient } from '.'; +import { KeycloakSetupService } from '../keycloak/setup'; + +export type WebSocketsFailedCallback = () => void; + +const VALIDITY_TIME = 5; + +/** + * This class manages the api connection. + */ +@injectable() +export class CheWorkspaceClient extends WorkspaceClient { + private originLocation: string; + private baseUrl: string; + private _restApiClient: IRemoteAPI; + private _jsonRpcMasterApi: IWorkspaceMasterApi; + private _failingWebSockets: string[]; + private webSocketEventEmitter: EventEmitter; + private webSocketEventName = 'websocketChanged'; + private defaultNamespace: string; + + /** + * Default constructor that is using resource. + */ + constructor(@inject(KeycloakSetupService) keycloakSetupService: KeycloakSetupService) { + super(keycloakSetupService); + this.baseUrl = '/api'; + this._failingWebSockets = []; + this.webSocketEventEmitter = new EventEmitter(); + + this.originLocation = new URL(window.location.href).origin; + } + + get restApiClient(): IRemoteAPI { + // Lazy initialization of restApiClient + if (!this._restApiClient) { + this.updateRestApiClient(); + } + return this._restApiClient; + } + + get jsonRpcMasterApi(): IWorkspaceMasterApi { + return this._jsonRpcMasterApi; + } + + getBaseUrl(): string { + return this.baseUrl; + } + + setBaseUrl(baseUrl: string): void { + this.baseUrl = baseUrl; + } + + updateRestApiClient(): void { + const baseUrl = this.baseUrl; + const headers = this.token ? { Authorization: `Bearer ${this.token}` } : {}; + this._restApiClient = WorkspaceClientLib.getRestApi({ baseUrl, headers }); + } + + async updateJsonRpcMasterApi(): Promise { + const jsonRpcApiLocation = this.originLocation.replace('http', 'ws'); + const tokenRefresher = () => this.refreshToken(VALIDITY_TIME); + this._jsonRpcMasterApi = WorkspaceClientLib.getJsonRpcApi(jsonRpcApiLocation, tokenRefresher); + this._jsonRpcMasterApi.onDidWebSocketStatusChange((websockets: string[]) => { + this._failingWebSockets = []; + for (const websocket of websockets) { + const trimmedWebSocketId = websocket.substring(0, websocket.indexOf('?')); + this._failingWebSockets.push(trimmedWebSocketId); + } + this.webSocketEventEmitter.emit(this.webSocketEventName); + }); + await this._jsonRpcMasterApi.connect(); + const clientId = this._jsonRpcMasterApi.getClientId(); + console.log('WebSocket connection clientId', clientId); + } + + onWebSocketFailed(callback: WebSocketsFailedCallback) { + this.webSocketEventEmitter.on(this.webSocketEventName, callback); + } + + removeWebSocketFailedListener() { + this.webSocketEventEmitter.removeAllListeners(this.webSocketEventName); + } + + get failingWebSockets(): string[] { + return Array.from(this._failingWebSockets); + } + + async getDefaultNamespace(): Promise { + if (this.defaultNamespace) { + return this.defaultNamespace; + } + const defaultNamespace = (await this.restApiClient.getKubernetesNamespace()).filter(kubernetesNamespace => kubernetesNamespace.attributes.default === 'true'); + if (defaultNamespace.length === 0) { + throw new Error('Default namespace is not found'); + } + this.defaultNamespace = defaultNamespace[0].name; + return this.defaultNamespace; + } +} diff --git a/src/services/workspace-client/devWorkspaceClient.ts b/src/services/workspace-client/devWorkspaceClient.ts new file mode 100644 index 000000000..63eaa0bf3 --- /dev/null +++ b/src/services/workspace-client/devWorkspaceClient.ts @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018-2020 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { inject, injectable } from 'inversify'; +import { convertDevWorkspaceV2ToV1, isDeleting, isWebTerminal } from '../helpers/devworkspace'; +import { WorkspaceClient } from './'; +import { DevWorkspaceClient as DevWorkspaceClientLibrary, IDevWorkspaceApi, IDevWorkspaceDevfile } from '@eclipse-che/devworkspace-client'; +import { WorkspaceStatus } from '../helpers/types'; +import { KeycloakSetupService } from '../keycloak/setup'; + +export interface IStatusUpdate { + error?: string; + status?: string; + prevStatus?: string; + workspaceId: string; +} + +/** + * This class manages the connection between the frontend and the devworkspace typescript library + */ +@injectable() +export class DevWorkspaceClient extends WorkspaceClient { + + private devworkspaceClient: IDevWorkspaceApi; + private previousItems: Map>; + private _defaultEditor?: string; + private _defaultPlugins?: string[]; + + constructor(@inject(KeycloakSetupService) keycloakSetupService: KeycloakSetupService) { + super(keycloakSetupService); + this.axios.defaults.baseURL = '/api/unsupported/k8s'; + this.devworkspaceClient = DevWorkspaceClientLibrary.getRestApi(this.axios).workspaceApi; + this.previousItems = new Map(); + } + + isEnabled(): Promise { + return this.devworkspaceClient.isApiEnabled(); + } + + async getAllWorkspaces(defaultNamespace: string): Promise { + const workspaces = await this.devworkspaceClient.getAllWorkspaces(defaultNamespace); + const availableWorkspaces: che.Workspace[] = []; + for (const workspace of workspaces) { + if (!isDeleting(workspace) && !isWebTerminal(workspace)) { + availableWorkspaces.push(convertDevWorkspaceV2ToV1(workspace)); + } + } + return availableWorkspaces; + } + + async getWorkspaceByName(namespace: string, workspaceName: string): Promise { + const workspace = await this.devworkspaceClient.getWorkspaceByName(namespace, workspaceName); + return convertDevWorkspaceV2ToV1(workspace); + } + + async create(devfile: IDevWorkspaceDevfile): Promise { + const createdWorkspace = await this.devworkspaceClient.create(devfile, this._defaultEditor, this._defaultPlugins); + return convertDevWorkspaceV2ToV1(createdWorkspace); + } + + delete(namespace: string, name: string): void { + this.devworkspaceClient.delete(namespace, name); + } + + async changeWorkspaceStatus(namespace: string, name: string, started: boolean): Promise { + const changedWorkspace = await this.devworkspaceClient.changeWorkspaceStatus(namespace, name, started); + return convertDevWorkspaceV2ToV1(changedWorkspace); + } + + /** + * Initialize the given namespace + * @param namespace The namespace you want to initialize + * @returns If the namespace has been initialized + */ + async initializeNamespace(namespace: string): Promise { + try { + await this.devworkspaceClient.initializeNamespace(namespace); + } catch (e) { + console.error(e); + return false; + } + return true; + } + + subscribeToNamespace( + defaultNamespace: string, + callback: any, + dispatch: any + ): void { + setInterval(async () => { + // This is a temporary solution until websockets work. Ideally we should just have a websocket connection here. + const devworkspaces = await this.getAllWorkspaces(defaultNamespace); + devworkspaces.forEach((devworkspace: che.Workspace) => { + const statusUpdate = this.createStatusUpdate(devworkspace); + callback( + { + id: devworkspace.id, + } as che.Workspace, + statusUpdate + )(dispatch); + }); + }, 1000); + } + + /** + * Create a status update between the previously recieving DevWorkspace with a certain workspace id + * and the new DevWorkspace + * @param devworkspace The incoming DevWorkspace + */ + private createStatusUpdate(devworkspace: che.Workspace): IStatusUpdate { + const namespace = devworkspace.namespace as string; + const workspaceId = devworkspace.id; + // Starting devworkspaces don't have status defined + const status = devworkspace.status && typeof devworkspace.status === 'string' ? devworkspace.status.toUpperCase() : WorkspaceStatus[WorkspaceStatus.STARTING]; + + const prevWorkspace = this.previousItems.get(namespace); + if (prevWorkspace) { + const prevStatus = prevWorkspace.get(workspaceId); + const newUpdate: IStatusUpdate = { + workspaceId: workspaceId, + status: status, + prevStatus: prevStatus?.status, + }; + prevWorkspace.set(workspaceId, newUpdate); + return newUpdate; + } else { + // there is not a previous update + const newStatus: IStatusUpdate = { + workspaceId, + status: status, + prevStatus: status, + }; + + const newStatusMap = new Map(); + newStatusMap.set(workspaceId, newStatus); + this.previousItems.set(namespace, newStatusMap); + return newStatus; + } + } + + set defaultEditor(editor: string) { + this._defaultEditor = editor; + } + + set defaultPlugins(plugins: string[]) { + this._defaultPlugins = plugins; + } +} diff --git a/src/services/workspace-client/index.ts b/src/services/workspace-client/index.ts new file mode 100644 index 000000000..4d45426bd --- /dev/null +++ b/src/services/workspace-client/index.ts @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018-2020 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { AxiosInstance, AxiosRequestConfig } from 'axios'; +import { injectable } from 'inversify'; +import { KeycloakSetupService } from '../keycloak/setup'; +import { KeycloakAuthService } from '../keycloak/auth'; +import { default as WorkspaceClientLib } from '@eclipse-che/workspace-client'; + +const VALIDITY_TIME = 5; +export type WebSocketsFailedCallback = () => void; + +/** + * This class manages the common functions between the che workspace client and the devworkspace client + */ +@injectable() +export abstract class WorkspaceClient { + protected readonly axios: AxiosInstance; + + constructor(private keycloakSetupService: KeycloakSetupService) { + // todo change this temporary solution after adding the proper method to workspace-client /~https://github.com/eclipse/che/issues/18311 + this.axios = (WorkspaceClientLib as any).createAxiosInstance({ loggingEnabled: false }); + if (this.axios.defaults.headers === undefined) { + this.axios.defaults.headers = {}; + } + if (this.axios.defaults.headers.common === undefined) { + this.axios.defaults.headers.common = {}; + } + + this.keycloakSetupService.ready.then(() => { + if (!KeycloakAuthService.sso) { + return; + } + + this.axios.interceptors.request.use(async config => { + await this.handleRefreshToken(VALIDITY_TIME, config); + return config; + }); + + window.addEventListener('message', (event: MessageEvent) => { + if (typeof event.data === 'string' && event.data.startsWith('update-token:')) { + const receivedValue = parseInt(event.data.split(':')[1], 10); + const validityTime = Number.isNaN(receivedValue) ? VALIDITY_TIME : Math.ceil(receivedValue / 1000); + this.handleRefreshToken(validityTime); + } + }, false); + }); + } + + private async handleRefreshToken(minValidity: number, config?: AxiosRequestConfig): Promise { + try { + await this.refreshToken(minValidity, config); + } catch (e) { + console.error('Failed to refresh token.', e); + this.redirectedToKeycloakLogin(); + } + } + + private redirectedToKeycloakLogin(): void { + const { sessionStorage, location: { href } } = window; + const { keycloak } = KeycloakAuthService; + + sessionStorage.setItem('oidcDashboardRedirectUrl', href); + if (keycloak && keycloak.login) { + keycloak.login(); + } + } + + protected get token(): string | undefined { + const { keycloak } = KeycloakAuthService; + return keycloak ? keycloak.token : undefined; + } + + protected refreshToken(minValidity: number, config?: AxiosRequestConfig): Promise { + const { keycloak } = KeycloakAuthService; + if (keycloak) { + return new Promise((resolve, reject) => { + keycloak.updateToken(minValidity).success((refreshed: boolean) => { + if (refreshed && keycloak.token) { + const header = 'Authorization'; + this.axios.defaults.headers.common[header] = `Bearer ${keycloak.token}`; + if (config) { + config.headers.common[header] = `Bearer ${keycloak.token}`; + } + } + resolve(keycloak.token as string); + }).error((error: any) => { + reject(new Error(error)); + }); + }); + } + if (!this.token) { + return Promise.reject(new Error('Unable to resolve token')); + } + return Promise.resolve(this.token); + } +} diff --git a/src/store/Branding.ts b/src/store/Branding.ts index 6ea7d33eb..b7091cdef 100644 --- a/src/store/Branding.ts +++ b/src/store/Branding.ts @@ -16,7 +16,7 @@ import { AppThunk } from '.'; import { merge } from 'lodash'; import { BRANDING_DEFAULT, BrandingData } from '../services/bootstrap/branding.constant'; import { container } from '../inversify.config'; -import { CheWorkspaceClient } from '../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../services/workspace-client/cheWorkspaceClient'; const ASSET_PREFIX = './assets/branding/'; diff --git a/src/store/FactoryResolver.ts b/src/store/FactoryResolver.ts index 4be5e15ad..6c083dcd6 100644 --- a/src/store/FactoryResolver.ts +++ b/src/store/FactoryResolver.ts @@ -14,7 +14,7 @@ import { Action, Reducer } from 'redux'; import { FactoryResolver } from '../services/helpers/types'; import { AppThunk } from './'; import { container } from '../inversify.config'; -import { CheWorkspaceClient } from '../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../services/workspace-client/cheWorkspaceClient'; const WorkspaceClient = container.get(CheWorkspaceClient); diff --git a/src/store/InfrastructureNamespace.ts b/src/store/InfrastructureNamespace.ts index 256bacc7e..6adc9f23b 100644 --- a/src/store/InfrastructureNamespace.ts +++ b/src/store/InfrastructureNamespace.ts @@ -12,7 +12,7 @@ import { Action, Reducer } from 'redux'; import { container } from '../inversify.config'; -import { CheWorkspaceClient } from '../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../services/workspace-client/cheWorkspaceClient'; import { AppThunk } from './'; const WorkspaceClient = container.get(CheWorkspaceClient); diff --git a/src/store/User.ts b/src/store/User.ts index ef25ccbb0..b58372633 100644 --- a/src/store/User.ts +++ b/src/store/User.ts @@ -16,7 +16,7 @@ import { Action, Reducer } from 'redux'; import { createState } from './helpers'; import { AppThunk } from './index'; import { container } from '../inversify.config'; -import { CheWorkspaceClient } from '../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../services/workspace-client/cheWorkspaceClient'; const WorkspaceClient = container.get(CheWorkspaceClient); diff --git a/src/store/UserPreferences/index.ts b/src/store/UserPreferences/index.ts index d61f93e81..2391a8fa0 100644 --- a/src/store/UserPreferences/index.ts +++ b/src/store/UserPreferences/index.ts @@ -16,7 +16,7 @@ import { createState } from '../helpers'; import { Action, Reducer } from 'redux'; import { AppThunk } from '../'; import { container } from '../../inversify.config'; -import { CheWorkspaceClient } from '../../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../../services/workspace-client/cheWorkspaceClient'; import { ContainerCredentials, RegistryRow } from './types'; const WorkspaceClient = container.get(CheWorkspaceClient); diff --git a/src/store/UserProfile.ts b/src/store/UserProfile.ts index ea9d0e710..5d53c0a7b 100644 --- a/src/store/UserProfile.ts +++ b/src/store/UserProfile.ts @@ -16,7 +16,7 @@ import { Action, Reducer } from 'redux'; import { createState } from './helpers'; import { AppThunk } from './index'; import { container } from '../inversify.config'; -import { CheWorkspaceClient } from '../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../services/workspace-client/cheWorkspaceClient'; const WorkspaceClient = container.get(CheWorkspaceClient); diff --git a/src/store/Workspaces/index.ts b/src/store/Workspaces/index.ts index 9adbee195..4c41d2b62 100644 --- a/src/store/Workspaces/index.ts +++ b/src/store/Workspaces/index.ts @@ -15,11 +15,15 @@ import * as api from '@eclipse-che/api'; import { ThunkDispatch } from 'redux-thunk'; import { AppThunk } from '../'; import { container } from '../../inversify.config'; -import { CheWorkspaceClient } from '../../services/cheWorkspaceClient'; +import { CheWorkspaceClient } from '../../services/workspace-client/cheWorkspaceClient'; import { WorkspaceStatus } from '../../services/helpers/types'; import { createState } from '../helpers'; +import { isDevWorkspace } from '../../services/helpers/devworkspace'; +import { DevWorkspaceClient, IStatusUpdate } from '../../services/workspace-client/devWorkspaceClient'; +import { IDevWorkspaceDevfile } from '@eclipse-che/devworkspace-client'; -const WorkspaceClient = container.get(CheWorkspaceClient); +const cheWorkspaceClient = container.get(CheWorkspaceClient); +const devWorkspaceClient = container.get(DevWorkspaceClient); // This state defines the type of data maintained in the Redux store. export interface State { @@ -125,11 +129,12 @@ export type ResourceQueryParams = { [propName: string]: string | boolean | undefined; } export type ActionCreators = { + updateDevWorkspaceStatus: (workspace: che.Workspace, message: IStatusUpdate) => AppThunk>; requestWorkspaces: () => AppThunk>; - requestWorkspace: (workspaceId: string) => AppThunk>; - startWorkspace: (workspaceId: string, params?: ResourceQueryParams) => AppThunk>; - stopWorkspace: (workspaceId: string) => AppThunk>; - deleteWorkspace: (workspaceId: string) => AppThunk>; + requestWorkspace: (workspace: che.Workspace) => AppThunk>; + startWorkspace: (workspace: che.Workspace, params?: ResourceQueryParams) => AppThunk>; + stopWorkspace: (workspace: che.Workspace) => AppThunk>; + deleteWorkspace: (workspace: che.Workspace) => AppThunk>; updateWorkspace: (workspace: che.Workspace) => AppThunk>; createWorkspaceFromDevfile: ( devfile: api.che.workspace.devfile.Devfile, @@ -151,35 +156,41 @@ type EnvironmentOutputMessageHandler = (message: api.che.workspace.event.Runtime const subscribedWorkspaceStatusCallbacks = new Map(); const subscribedEnvironmentOutputCallbacks = new Map(); +function onStatusUpdateReceived( + workspace: che.Workspace, + dispatch: ThunkDispatch, + message: any) { + let status: string; + if (message.error) { + const workspacesLogs = new Map(); + workspacesLogs.set(workspace.id, [`Error: Failed to run the workspace: "${message.error}"`]); + dispatch({ + type: 'UPDATE_WORKSPACES_LOGS', + workspacesLogs, + }); + status = WorkspaceStatus[WorkspaceStatus.ERROR]; + } else { + status = message.status; + } + if (WorkspaceStatus[status]) { + dispatch({ + type: 'UPDATE_WORKSPACE_STATUS', + workspaceId: workspace.id, + status, + }); + } + if (!isDevWorkspace(workspace) && WorkspaceStatus[WorkspaceStatus.STARTING] !== status) { + unSubscribeToEnvironmentOutput(workspace.id); + } +} + function subscribeToStatusChange( - workspaceId: string, + workspace: che.Workspace, dispatch: ThunkDispatch): void { - const callback = message => { - let status: string; - if (message.error) { - const workspacesLogs = new Map(); - workspacesLogs.set(workspaceId, [`Error: Failed to run the workspace: "${message.error}"`]); - dispatch({ - type: 'UPDATE_WORKSPACES_LOGS', - workspacesLogs, - }); - status = WorkspaceStatus[WorkspaceStatus.ERROR]; - } else { - status = message.status; - } - if (WorkspaceStatus[status]) { - dispatch({ - type: 'UPDATE_WORKSPACE_STATUS', - workspaceId, - status, - }); - } - if (WorkspaceStatus[WorkspaceStatus.STARTING] !== status) { - unSubscribeToEnvironmentOutput(workspaceId); - } - }; - WorkspaceClient.jsonRpcMasterApi.subscribeWorkspaceStatus(workspaceId, callback); - subscribedWorkspaceStatusCallbacks.set(workspaceId, callback); + + const callback = (message: any) => onStatusUpdateReceived(workspace, dispatch, message); + cheWorkspaceClient.jsonRpcMasterApi.subscribeWorkspaceStatus(workspace.id, callback); + subscribedWorkspaceStatusCallbacks.set(workspace.id, callback); } function unSubscribeToStatusChange(workspaceId: string): void { @@ -187,7 +198,7 @@ function unSubscribeToStatusChange(workspaceId: string): void { if (!callback) { return; } - WorkspaceClient.jsonRpcMasterApi.unSubscribeWorkspaceStatus(workspaceId, callback); + cheWorkspaceClient.jsonRpcMasterApi.unSubscribeWorkspaceStatus(workspaceId, callback); subscribedWorkspaceStatusCallbacks.delete(workspaceId); } @@ -206,7 +217,7 @@ function subscribeToEnvironmentOutput(workspaceId: string, dispatch: ThunkDispat type: 'DELETE_WORKSPACE_LOGS', workspaceId, }); - WorkspaceClient.jsonRpcMasterApi.subscribeEnvironmentOutput(workspaceId, callback); + cheWorkspaceClient.jsonRpcMasterApi.subscribeEnvironmentOutput(workspaceId, callback); subscribedEnvironmentOutputCallbacks.set(workspaceId, callback); } @@ -215,7 +226,7 @@ function unSubscribeToEnvironmentOutput(workspaceId: string): void { if (!callback) { return; } - WorkspaceClient.jsonRpcMasterApi.unSubscribeEnvironmentOutput(workspaceId, callback); + cheWorkspaceClient.jsonRpcMasterApi.unSubscribeEnvironmentOutput(workspaceId, callback); subscribedEnvironmentOutputCallbacks.delete(workspaceId); } @@ -223,23 +234,34 @@ function unSubscribeToEnvironmentOutput(workspaceId: string): void { // They don't directly mutate state, but they can have external side-effects (such as loading data). export const actionCreators: ActionCreators = { + updateDevWorkspaceStatus: (workspace: che.Workspace, message: IStatusUpdate): AppThunk> => async (dispatch): Promise => { + onStatusUpdateReceived(workspace, dispatch, message); + }, + requestWorkspaces: (): AppThunk> => async (dispatch): Promise => { dispatch({ type: 'REQUEST_WORKSPACES' }); try { - const workspaces = await WorkspaceClient.restApiClient.getAll(); + const workspaces = await cheWorkspaceClient.restApiClient.getAll(); + const defaultNamespace = await cheWorkspaceClient.getDefaultNamespace(); + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + let allWorkspaces = workspaces; + if (isDevWorkspaceEnabled) { + const devworkspaces = await devWorkspaceClient.getAllWorkspaces(defaultNamespace); + allWorkspaces = allWorkspaces.concat(devworkspaces); + } // Unsubscribe subscribedWorkspaceStatusCallbacks.forEach((workspaceStatusCallback: WorkspaceStatusMessageHandler, workspaceId: string) => { unSubscribeToStatusChange(workspaceId); }); - // Subscribe - workspaces.forEach(workspace => { - subscribeToStatusChange(workspace.id, dispatch); + // Only subscribe to v1 workspaces + workspaces.forEach((workspace: any) => { + subscribeToStatusChange(workspace, dispatch); }); - dispatch({ type: 'RECEIVE_WORKSPACES', workspaces }); + dispatch({ type: 'RECEIVE_WORKSPACES', workspaces: allWorkspaces }); } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); throw new Error('Failed to request workspaces: \n' + e); @@ -247,11 +269,19 @@ export const actionCreators: ActionCreators = { }, - requestWorkspace: (workspaceId: string): AppThunk> => async (dispatch): Promise => { + requestWorkspace: (cheWorkspace: che.Workspace): AppThunk> => async (dispatch): Promise => { dispatch({ type: 'REQUEST_WORKSPACES' }); try { - const workspace = await WorkspaceClient.restApiClient.getById(workspaceId); + let workspace: any; + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled && isDevWorkspace(cheWorkspace)) { + const namespace = cheWorkspace.namespace as string; + const name = cheWorkspace.devfile.metadata.name; + workspace = await devWorkspaceClient.getWorkspaceByName(namespace, name); + } else { + workspace = await cheWorkspaceClient.restApiClient.getById(cheWorkspace.id); + } dispatch({ type: 'UPDATE_WORKSPACE', workspace }); } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); @@ -264,7 +294,7 @@ export const actionCreators: ActionCreators = { dispatch({ type: 'REQUEST_WORKSPACES' }); try { - const settings = await WorkspaceClient.restApiClient.getSettings(); + const settings = await cheWorkspaceClient.restApiClient.getSettings(); dispatch({ type: 'RECEIVE_SETTINGS', settings }); } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); @@ -272,10 +302,16 @@ export const actionCreators: ActionCreators = { } }, - startWorkspace: (workspaceId: string, params?: ResourceQueryParams): AppThunk> => async (dispatch): Promise => { + startWorkspace: (cheWorkspace: che.Workspace, params?: ResourceQueryParams): AppThunk> => async (dispatch): Promise => { try { - const workspace = await WorkspaceClient.restApiClient.start(workspaceId, params); - subscribeToEnvironmentOutput(workspaceId, dispatch); + let workspace: che.Workspace; + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled && isDevWorkspace(cheWorkspace)) { + workspace = await devWorkspaceClient.changeWorkspaceStatus(cheWorkspace.namespace as string, cheWorkspace.devfile.metadata.name as string, true); + } else { + workspace = await cheWorkspaceClient.restApiClient.start(cheWorkspace.id, params); + subscribeToEnvironmentOutput(cheWorkspace.id, dispatch); + } dispatch({ type: 'UPDATE_WORKSPACE', workspace }); } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); @@ -283,20 +319,33 @@ export const actionCreators: ActionCreators = { } }, - stopWorkspace: (workspaceId: string): AppThunk> => async (dispatch): Promise => { + stopWorkspace: (workspace: che.Workspace): AppThunk> => async (dispatch): Promise => { try { - await WorkspaceClient.restApiClient.stop(workspaceId); + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled && isDevWorkspace(workspace)) { + devWorkspaceClient.changeWorkspaceStatus(workspace.namespace as string, workspace.devfile.metadata.name as string, false); + } else { + cheWorkspaceClient.restApiClient.stop(workspace.id); + } } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); - throw new Error(`Failed to stop the workspace, ID: ${workspaceId}, ` + e.message); + throw new Error(`Failed to stop the workspace, ID: ${workspace.id}, ` + e.message); } }, - deleteWorkspace: (workspaceId: string): AppThunk> => async (dispatch): Promise => { + deleteWorkspace: (workspace: che.Workspace): AppThunk> => async (dispatch): Promise => { try { - await WorkspaceClient.restApiClient.delete(workspaceId); - dispatch({ type: 'DELETE_WORKSPACE_LOGS', workspaceId }); - dispatch({ type: 'DELETE_WORKSPACE', workspaceId }); + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled && isDevWorkspace(workspace)) { + const namespace = workspace.namespace as string; + const name = workspace.devfile.metadata.name; + await devWorkspaceClient.delete(namespace, name); + dispatch({ type: 'DELETE_WORKSPACE', workspaceId: workspace.id }); + } else { + await cheWorkspaceClient.restApiClient.delete(workspace.id); + dispatch({ type: 'DELETE_WORKSPACE_LOGS', workspaceId: workspace.id }); + dispatch({ type: 'DELETE_WORKSPACE', workspaceId: workspace.id }); + } } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); @@ -316,7 +365,7 @@ export const actionCreators: ActionCreators = { message = 'Unknown error.'; } - throw new Error(`Failed to delete the workspace, ID: ${workspaceId}. ` + message); + throw new Error(`Failed to delete the workspace, ID: ${workspace.id}. ` + message); } }, @@ -324,7 +373,7 @@ export const actionCreators: ActionCreators = { dispatch({ type: 'REQUEST_WORKSPACES' }); try { - const updatedWorkspace = await WorkspaceClient.restApiClient.update(workspace.id, workspace as api.che.workspace.Workspace); + const updatedWorkspace = await cheWorkspaceClient.restApiClient.update(workspace.id, workspace as api.che.workspace.Workspace); dispatch({ type: 'UPDATE_WORKSPACE', workspace: updatedWorkspace }); } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); @@ -334,7 +383,7 @@ export const actionCreators: ActionCreators = { }, createWorkspaceFromDevfile: ( - devfile: api.che.workspace.devfile.Devfile, + devfile: api.che.workspace.devfile.Devfile | IDevWorkspaceDevfile, namespace: string | undefined, infrastructureNamespace: string | undefined, attributes: { [key: string]: string } = {}, @@ -342,11 +391,23 @@ export const actionCreators: ActionCreators = { dispatch({ type: 'REQUEST_WORKSPACES' }); try { const param = { attributes, namespace, infrastructureNamespace }; - const workspace = await WorkspaceClient.restApiClient.create(devfile, param); - dispatch({ type: 'ADD_WORKSPACE', workspace }); - // Subscribe - subscribeToStatusChange(workspace.id, dispatch); + let workspace; + const isDevWorkspaceEnabled = await devWorkspaceClient.isEnabled(); + if (isDevWorkspaceEnabled && isDevWorkspace(devfile)) { + // If the devworkspace doesn't have a namespace then we assign it to the default kubernetesNamespace + const devWorkspaceDevfile = devfile as IDevWorkspaceDevfile; + if (!devWorkspaceDevfile.metadata.namespace) { + const defaultNamespace = await cheWorkspaceClient.getDefaultNamespace(); + devWorkspaceDevfile.metadata.namespace = defaultNamespace; + } + workspace = await devWorkspaceClient.create(devWorkspaceDevfile); + } else { + workspace = await cheWorkspaceClient.restApiClient.create(devfile, param); + // Subscribe + subscribeToStatusChange(workspace, dispatch); + } + dispatch({ type: 'ADD_WORKSPACE', workspace }); return workspace; } catch (e) { dispatch({ type: 'RECEIVE_ERROR' }); diff --git a/src/typings/che.d.ts b/src/typings/che.d.ts index 17b9bd948..d2f793875 100755 --- a/src/typings/che.d.ts +++ b/src/typings/che.d.ts @@ -23,7 +23,7 @@ declare namespace che { status: string; namespace?: string; attributes?: WorkspaceAttributes; - devfile: WorkspaceDevfile; + devfile: WorkspaceDevfile | IDevWorkspaceDevfile; runtime?: WorkspaceRuntime; isLocked?: boolean; usedResources?: string; @@ -37,6 +37,8 @@ declare namespace che { 'che.workspace.storage.available_types': string; 'che.workspace.storage.preferred_type': WorkspaceStorageType; supportedRecipeTypes: string; + 'che.factory.default_plugins': string; + 'che.factory.default_editor': string; } export interface Plugin { diff --git a/yarn.lock b/yarn.lock index ae1e22d90..bc74f27d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -466,13 +466,21 @@ resolved "https://registry.yarnpkg.com/@eclipse-che/api/-/api-7.18.1.tgz#1beae9ebe694e4b58d0edfefcde07995ce6cd0b2" integrity sha512-KnnnDpnxxK0TBgR0Ux3oOYCvmCv6d4cRA+1j9wiLkaJighCqgCUaxnHWXEfolYbRq1+o/sfsxN+BxRDuF+H8wQ== -"@eclipse-che/workspace-client@^0.0.1-1613117389": - version "0.0.1-1613117389" - resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1613117389.tgz#96f3df43a38af6575f865b94f98de59e2abec171" - integrity sha512-CHVxc+hb3E9kMiXssyip7LBRWZD9zxxeTk1nABGnmFgweA1RTebiOKLkrYksTEp+ijV2sjGHKVy7j25xs1BgCQ== +"@eclipse-che/devworkspace-client@^0.0.1-1614091834": + version "0.0.1-1614091834" + resolved "https://registry.yarnpkg.com/@eclipse-che/devworkspace-client/-/devworkspace-client-0.0.1-1614091834.tgz#7b787317838504265b4411a175c7bf0c2d71dc83" + integrity sha512-zQIbQdnuCJi8y3RGR5i1pYqBnjj+p4M4m8JJ0ZMMXJ3InhFGyyvJ9cF7wf42MajZfv4Ki/jKoL50BgEGJrzQqA== + dependencies: + "@kubernetes/client-node" "^0.14.0" + axios "^0.21.1" + +"@eclipse-che/workspace-client@^0.0.1-1613484098": + version "0.0.1-1613484098" + resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1613484098.tgz#b2bf602bd8a1f81e26e8e9af3af7f85c3952720f" + integrity sha512-lzCVdwoXjMfkdAIBLiJ2LDxIgU7w/f7LWD0OdWDwVaN7XkJhEYngwnVFUyvDKMs5J6HgHLNBorEKLwW817SCmw== dependencies: "@eclipse-che/api" "^7.0.0-beta-4.0" - axios "0.20.0" + axios "^0.21.1" qs "^6.9.4" tunnel "0.0.6" websocket "1.0.23" @@ -669,6 +677,34 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@kubernetes/client-node@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.14.0.tgz#a02806f3b6fdb68fb51d451ee8ff01faa446f557" + integrity sha512-/37JHuEUAQ5GQ4kLKBmCYvGgf5W1KZWKreKGWFYH8VvT2Hl/o0aJZasu2w0EHEfmE11JCn0X9arVmOTyVCYvww== + dependencies: + "@types/js-yaml" "^3.12.1" + "@types/node" "^10.12.0" + "@types/request" "^2.47.1" + "@types/stream-buffers" "^3.0.3" + "@types/tar" "^4.0.3" + "@types/underscore" "^1.8.9" + "@types/ws" "^6.0.1" + byline "^5.0.0" + execa "1.0.0" + isomorphic-ws "^4.0.1" + js-yaml "^3.13.1" + jsonpath-plus "^0.19.0" + openid-client "^4.1.1" + request "^2.88.0" + rfc4648 "^1.3.0" + shelljs "^0.8.2" + stream-buffers "^3.0.2" + tar "^6.0.2" + tmp-promise "^3.0.2" + tslib "^1.9.3" + underscore "^1.9.1" + ws "^7.3.1" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -710,6 +746,11 @@ dependencies: mkdirp "^1.0.4" +"@panva/asn1.js@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" + integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== + "@patternfly/patternfly@4.10.31": version "4.10.31" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.10.31.tgz#742852b69d90bb2efe304130f7226d2e356306cf" @@ -786,6 +827,11 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.9.22.tgz#d23488cddf19ad065ef55bd43b47336ee63bf4cc" integrity sha512-hN/8u7mFR62naFB2hdO7nl1p/0lCXtNq+VY+BAbp4UFC2/QyjNP0IOPBR+mR9Pbj5JwxrURI7G5blLp+k9RLvQ== +"@sindresorhus/is@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" + integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ== + "@sinonjs/commons@^1.7.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" @@ -815,6 +861,13 @@ remark "^12.0.0" unist-util-find-all-after "^3.0.1" +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + "@testing-library/dom@^7.14.2", "@testing-library/dom@^7.16.2": version "7.16.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.16.2.tgz#f7a20b5548817e5c7ed26077913372d977be90af" @@ -908,6 +961,21 @@ "@types/connect" "*" "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + +"@types/caseless@*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + "@types/cheerio@*": version "0.22.18" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.18.tgz#19018dceae691509901e339d63edf1e935978fe6" @@ -1011,6 +1079,11 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA== +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + "@types/http-proxy-middleware@*": version "0.19.3" resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" @@ -1070,6 +1143,11 @@ dependencies: "@types/sizzle" "*" +"@types/js-yaml@^3.12.1": + version "3.12.6" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.6.tgz#7f10c926aa41e189a2755c4c7fcf8e4573bd7ac1" + integrity sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ== + "@types/js-yaml@^3.12.4": version "3.12.4" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25" @@ -1085,6 +1163,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + "@types/less@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/less/-/less-3.0.1.tgz#625694093c72f8356c4042754e222407e50d6b08" @@ -1110,11 +1195,23 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= +"@types/minipass@*": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/minipass/-/minipass-2.2.0.tgz#51ad404e8eb1fa961f75ec61205796807b6f9651" + integrity sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg== + dependencies: + "@types/node" "*" + "@types/node@*": version "14.0.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.6.tgz#f9e178b2da31a4b0ec60b64649e244c31ce18daf" integrity sha512-FbNmu4F67d3oZMWBV6Y4MaPER+0EpE9eIYf2yaHhCWovc1dlXCZkqGX4NLHfVVr6umt20TNBdRzrNJIzIKfdbw== +"@types/node@^10.12.0": + version "10.17.54" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.54.tgz#a737488631aca3ec7bd9f6229d77f1079e444793" + integrity sha512-c8Lm7+hXdSPmWH4B9z/P/xIXhFK3mCQin4yCYMd2p1qpMG5AfgyJuYZ+3q2dT7qLiMMMGMd5dnkFpdqJARlvtQ== + "@types/node@^14.0.10": version "14.0.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3" @@ -1243,6 +1340,16 @@ dependencies: redux "^4.0.5" +"@types/request@^2.47.1": + version "2.48.5" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0" + integrity sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/reselect@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/reselect/-/reselect-2.2.0.tgz#c667206cfdc38190e1d379babe08865b2288575f" @@ -1250,6 +1357,13 @@ dependencies: reselect "*" +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/sanitize-html@^1.27.1": version "1.27.1" resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.27.1.tgz#1fc4b67edd6296eeb366960d13cd01f5d6bffdcd" @@ -1280,11 +1394,26 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/stream-buffers@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/stream-buffers/-/stream-buffers-3.0.3.tgz#34e565bf64e3e4bdeee23fd4aa58d4636014a02b" + integrity sha512-NeFeX7YfFZDYsCfbuaOmFQ0OjSmHreKBpp7MQ4alWQBHeh2USLsj7qyMyn9t82kjqIX516CR/5SRHnARduRtbQ== + dependencies: + "@types/node" "*" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/tar@^4.0.3": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.4.tgz#d680de60855e7778a51c672b755869a3b8d2889f" + integrity sha512-0Xv+xcmkTsOZdIF4yCnd7RkOOyfyqPaqJ7RZFKnwdxfDbkN3eAAE9sHl8zJFqBz4VhxolW9EErbjR1oyH7jK2A== + dependencies: + "@types/minipass" "*" + "@types/node" "*" + "@types/testing-library__jest-dom@^5.9.1": version "5.9.1" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.1.tgz#aba5ee062b7880f69c212ef769389f30752806e5" @@ -1292,6 +1421,11 @@ dependencies: "@types/jest" "*" +"@types/tough-cookie@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== + "@types/uglify-js@*": version "3.9.2" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.2.tgz#01992579debba674e1e359cd6bcb1a1d0ab2e02b" @@ -1299,6 +1433,11 @@ dependencies: source-map "^0.6.1" +"@types/underscore@^1.8.9": + version "1.10.24" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.24.tgz#dede004deed3b3f99c4db0bdb9ee21cae25befdd" + integrity sha512-T3NQD8hXNW2sRsSbLNjF/aBo18MyJlbw0lSpQHB/eZZtScPdexN4HSa8cByYwTw9Wy7KuOFr81mlDQcQQaZ79w== + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" @@ -1369,6 +1508,13 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/ws@^6.0.1": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" + integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -1941,7 +2087,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== -axios@*, axios@0.20.0, axios@^0.21.1: +axios@*, axios@^0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== @@ -2276,6 +2422,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -2345,6 +2496,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -2657,6 +2826,13 @@ clone-regexp@^2.1.0: dependencies: is-regexp "^2.0.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3157,6 +3333,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -3192,6 +3375,11 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -3888,7 +4076,7 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== -execa@^1.0.0: +execa@1.0.0, execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== @@ -4320,6 +4508,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -4452,6 +4649,13 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -4484,7 +4688,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4593,6 +4797,23 @@ gonzales-pe@^4.2.3, gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" +got@^11.8.0: + version "11.8.1" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" + integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -4870,6 +5091,11 @@ htmlparser2@^6.0.0: domutils "^2.4.4" entities "^2.0.0" +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -4948,6 +5174,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.0-beta.5.2" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" + integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -5159,6 +5393,11 @@ interpret@1.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + inversify-inject-decorators@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/inversify-inject-decorators/-/inversify-inject-decorators-3.1.0.tgz#d9941080bad77cec8a65ee29d905e4d5d73e1e95" @@ -5279,6 +5518,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -5553,6 +5799,11 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -5984,6 +6235,13 @@ jest@^26.0.1: import-local "^3.0.2" jest-cli "^26.0.1" +jose@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/jose/-/jose-2.0.4.tgz#7838354d28f64466db9fc7f275aa8a96db656f37" + integrity sha512-EArN9f6aq1LT/fIGGsfghOnNXn4noD+3dG5lL/ljY3LcRjw1u9w+4ahu/4ahsN6N0kRLyyW6zqdoYk7LNx3+YQ== + dependencies: + "@panva/asn1.js" "^1.0.0" + jquery@^3.4.1: version "3.5.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" @@ -6049,6 +6307,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -6110,6 +6373,11 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz#b901e57607055933dc9a8bef0cc25160ee9dd64c" + integrity sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg== + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -6136,6 +6404,13 @@ keycloak-js@^10.0.2: base64-js "1.3.1" js-sha256 "0.9.0" +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -6414,6 +6689,11 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -6429,6 +6709,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -6451,7 +6738,7 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" -make-error@1.x: +make-error@1.x, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -6700,6 +6987,16 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -7054,6 +7351,11 @@ normalize-selector@^0.2.0: resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -7112,6 +7414,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" + integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== + object-hash@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" @@ -7201,6 +7508,11 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +oidc-token-hash@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" + integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -7232,6 +7544,19 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== +openid-client@^4.1.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-4.4.0.tgz#f6535ccf387e7d25e0fb9af0953330b52bd77b01" + integrity sha512-FZq6rMaItawQc0mMrxlya96fydO7jlkW4I0Hrke3E4ogLAYcFbSefcJlKFLRvr+S5x9N6PMH6OZl9LHgu7JXvw== + dependencies: + got "^11.8.0" + jose "^2.0.4" + lru-cache "^6.0.0" + make-error "^1.3.6" + object-hash "^2.0.1" + oidc-token-hash "^5.0.1" + p-any "^3.0.0" + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -7289,6 +7614,19 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-any@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-any/-/p-any-3.0.0.tgz#79847aeed70b5d3a10ea625296c0c3d2e90a87b9" + integrity sha512-5rqbqfsRWNb0sukt0awwgJMlaep+8jV45S15SKKB34z4UuzjcofIfnriCBhWjZP2jbVtjt9yRl7buB6RlKsu9w== + dependencies: + p-cancelable "^2.0.0" + p-some "^5.0.0" + +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -7363,6 +7701,14 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" +p-some@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-some/-/p-some-5.0.0.tgz#8b730c74b4fe5169d7264a240ad010b6ebc686a4" + integrity sha512-Js5XZxo6vHjB9NOYAzWDYAIyyiPvva0DWESAIWIK7uhSpGsyg5FwUPxipU/SOQx5x9EqhOh545d1jo6cVkitig== + dependencies: + aggregate-error "^3.0.0" + p-cancelable "^2.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -8066,6 +8412,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -8350,6 +8701,13 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + redent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" @@ -8579,7 +8937,7 @@ request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.2: +request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -8625,6 +8983,11 @@ reselect@*, reselect@^4.0.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== +resolve-alpn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" + integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -8672,6 +9035,14 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@^1.1.6: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + resolve@^1.10.0, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" @@ -8679,6 +9050,13 @@ resolve@^1.10.0, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2: dependencies: path-parse "^1.0.6" +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -8702,6 +9080,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfc4648@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" + integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -9018,6 +9401,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shelljs@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -9336,6 +9728,11 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" +stream-buffers@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" + integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -9910,6 +10307,13 @@ tippy.js@5.1.2: dependencies: popper.js "^1.16.0" +tmp-promise@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.2.tgz#6e933782abff8b00c3119d63589ca1fb9caaa62a" + integrity sha512-OyCLAKU1HzBjL6Ev3gxUeraJNlbNingmi8IrHHEsYH8LTmEuhvYfqvhn2F/je+mjf4N58UmZ96OMEy1JanSCpA== + dependencies: + tmp "^0.2.0" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9917,6 +10321,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -10060,6 +10471,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ== +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -10175,6 +10591,11 @@ umd-compat-loader@2.1.1: loader-utils "^1.0.3" recast "^0.11.17" +underscore@^1.9.1: + version "1.12.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.0.tgz#4814940551fc80587cef7840d1ebb0f16453be97" + integrity sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -10960,6 +11381,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== +ws@^7.3.1: + version "7.4.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd" + integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== + x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" From 56e26ac7d206f06abd63e1850ba67ca75664ed7c Mon Sep 17 00:00:00 2001 From: Josh Pinkney Date: Tue, 23 Feb 2021 11:58:21 -0500 Subject: [PATCH 02/13] Monaco support for devworkspaces Signed-off-by: Josh Pinkney --- src/components/DevfileEditor/index.tsx | 11 +++++++-- .../GetStarted/CustomWorkspaceTab/index.tsx | 12 ++++++---- .../WorkspaceDetails/DevfileTab/index.tsx | 2 ++ src/pages/WorkspacesList/Rows.tsx | 2 +- src/services/helpers/editor.ts | 4 +++- src/store/DevfileRegistries/index.ts | 23 +++++++++++++++++-- 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/components/DevfileEditor/index.tsx b/src/components/DevfileEditor/index.tsx index aa47dd530..1c4ab8ded 100644 --- a/src/components/DevfileEditor/index.tsx +++ b/src/components/DevfileEditor/index.tsx @@ -45,6 +45,7 @@ type Props = devfile: che.WorkspaceDevfile; decorationPattern?: string; onChange: (devfile: che.WorkspaceDevfile, isValid: boolean) => void; + isReadonly?: boolean; }; type State = { errorMessage: string; @@ -100,7 +101,7 @@ export class DevfileEditor extends React.PureComponent { } const jsonSchema = this.props.devfileRegistries.schema || {}; const items = this.props.plugins.plugins; - const components = jsonSchema && jsonSchema.properties ? jsonSchema.properties.components : undefined; + const components = jsonSchema && jsonSchema.oneOf && jsonSchema.oneOf.length > 0 && jsonSchema.oneOf[0].properties ? jsonSchema.oneOf[0].properties.components : undefined; if (components) { const mountSources = components.items.properties.mountSources; // mount sources is specific only for some of component types but always appears @@ -168,6 +169,7 @@ export class DevfileEditor extends React.PureComponent { const element = $('.devfile-editor .monaco').get(0); if (element) { const value = stringify(this.props.devfile); + MONACO_CONFIG.readOnly = this.props.isReadonly !== undefined ? this.props.isReadonly : false; this.editor = monaco.editor.create(element, Object.assign( { value }, MONACO_CONFIG, @@ -224,10 +226,15 @@ export class DevfileEditor extends React.PureComponent { const href = this.props.branding.data.docs.devfile; const { errorMessage } = this.state; + let message = errorMessage; + if (this.props.isReadonly !== undefined && this.props.isReadonly === true) { + message = 'DevWorkspace editor support has not been enabled. Editor is in Readonly mode.'; + } + return (
 
-
{errorMessage}
+
{message}
Devfile Documentation
); diff --git a/src/pages/GetStarted/CustomWorkspaceTab/index.tsx b/src/pages/GetStarted/CustomWorkspaceTab/index.tsx index 113ae6345..0848a1153 100644 --- a/src/pages/GetStarted/CustomWorkspaceTab/index.tsx +++ b/src/pages/GetStarted/CustomWorkspaceTab/index.tsx @@ -120,15 +120,17 @@ export class CustomWorkspaceTab extends React.PureComponent { return; } this.setState({ devfile }); - const storageType = attributesToType(devfile.attributes); - if (storageType !== this.state.storageType) { - this.setState({ storageType }); + if (devfile?.attributes) { + const storageType = attributesToType(devfile.attributes); + if (storageType !== this.state.storageType) { + this.setState({ storageType }); + } } - const workspaceName = devfile.metadata.name || ''; + const workspaceName = devfile?.metadata?.name || ''; if (workspaceName !== this.state.workspaceName) { this.setState({ workspaceName }); } - const generateName = devfile.metadata.generateName; + const generateName = devfile?.metadata?.generateName; if (generateName !== this.state.generateName) { this.setState({ generateName }); } diff --git a/src/pages/WorkspaceDetails/DevfileTab/index.tsx b/src/pages/WorkspaceDetails/DevfileTab/index.tsx index 60403e497..07a36376b 100644 --- a/src/pages/WorkspaceDetails/DevfileTab/index.tsx +++ b/src/pages/WorkspaceDetails/DevfileTab/index.tsx @@ -23,6 +23,7 @@ import DevfileEditor, { DevfileEditor as Editor } from '../../../components/Devf import EditorTools from './EditorTools'; import './DevfileTab.styl'; +import { isDevWorkspace } from '../../../services/helpers/devworkspace'; type Props = { onSave: (workspace: che.Workspace) => Promise; @@ -124,6 +125,7 @@ export class EditorTab extends React.PureComponent { onChange={(devfile, isValid) => { this.onDevfileChange(devfile, isValid); }} + isReadonly={isDevWorkspace(originDevfile)} />