diff --git a/plugins/web-terminal/README.md b/plugins/web-terminal/README.md index f865a94884..749e6e462c 100644 --- a/plugins/web-terminal/README.md +++ b/plugins/web-terminal/README.md @@ -1,38 +1,100 @@ -# Web terminal plugin for Backstage +# Web Terminal plugin for Backstage + +This plugin provides a frontend for [`webterminal proxy`](https://github.com/janus-idp/webterminal-proxy) and shows a terminal for catalog entities with an Kubernetes API-Server annotation (`kubernetes.io/api-server`). + +Users first enter their user token from the cluster, and then the plugin setups environment. Once it is set up, it connects to `webterminal-proxy`, which finishes setups and passes data between the frontend plugin and pod. -This plugin provides a frontend for [`webterminal proxy`](https://github.com/janus-idp/webterminal-proxy). Users first enter their user token from the cluster, and then the plugin setups environment. Once it is set up, it connects to `webterminal-proxy`, which finishes setups and passes data between the frontend plugin and pod. This plugin uses [`xterm.js`](http://xtermjs.org/) to simulate a regular terminal. ## Prerequisites Before we can install this plugin, we need to fulfill the following requirements: -1. Deployed [`webterminal-proxy`](https://github.com/janus-idp/webterminal-proxy) -2. Installed [Web terminal operator](https://docs.openshift.com/container-platform/4.8/web_console/odc-about-web-terminal.html#odc-installing-web-terminal_odc-about-web-terminal) +1. Installed [Web Terminal operator](https://docs.openshift.com/container-platform/latest/web_console/web_terminal/installing-web-terminal.html) +2. Deployed [`webterminal-proxy`](https://github.com/janus-idp/webterminal-proxy) + +## Installation + +1. Install the Web Terminal plugin using the following command: + + ```console + yarn workspace app add @janus-idp/backstage-plugin-web-terminal + ``` + +2. Enable an additional tab on the entity view page using the `packages/app/src/components/catalog/EntityPage.tsx` file as follows: + + ```tsx title="packages/app/src/components/catalog/EntityPage.tsx" + /* highlight-add-start */ + import { + isWebTerminalAvailable, + WebTerminal, + } from '@janus-idp/backstage-plugin-web-terminal'; + + /* highlight-add-end */ + + const serviceEntityPage = ( + + // ... + {/* highlight-add-start */} + + + + {/* highlight-add-end */} + + ); + ``` -## Usage +3. Alternative you can add the WebTerminal to an existing page: + + ```tsx title="packages/app/src/components/catalog/EntityPage.tsx" + /* highlight-add-start */ + import { + isWebTerminalAvailable, + WebTerminal, + } from '@janus-idp/backstage-plugin-web-terminal'; + + /* highlight-add-end */ + + + {/* highlight-add-start */} + + + + + + + + {/* highlight-add-end */} + ; + ``` + +4. Annotate your entity using the following annotations: + + ```yaml + metadata: + annotations: + 'kubernetes.io/api-server': `', + ``` + +## Configuration You have to define the location of the `webterminal-proxy` in `app-config.yaml`: ```yaml webTerminal: - webSocketUrl: 'wss://example.com:3000' - restServerUrl: 'https://example.com:3000/rest' + webSocketUrl: 'wss://example.com:3000/webterminal' + restServerUrl: 'https://example.com:3000/webterminal/rest' ``` Optionally, you can also define the default namespace for the terminal; otherwise, `openshift-terminal` will be used: ```yaml webTerminal: - webSocketUrl: 'wss://example.com:3000' - restServerUrl: 'https://example.com:3000/rest' + webSocketUrl: 'wss://example.com:3000/webterminal' + restServerUrl: 'https://example.com:3000/webterminal/rest' defaultNamespace: 'default' ``` - -Next, you can include the `WebTerminal` component in your catalog resource page within the entity context: - -```typescript - - - -``` diff --git a/plugins/web-terminal/package.json b/plugins/web-terminal/package.json index 181327568b..3c1515b826 100644 --- a/plugins/web-terminal/package.json +++ b/plugins/web-terminal/package.json @@ -26,6 +26,7 @@ }, "configSchema": "schema.d.ts", "dependencies": { + "@backstage/catalog-model": "^1.4.3", "@backstage/config": "^1.1.1", "@backstage/core-components": "^0.13.6", "@backstage/core-plugin-api": "^1.7.0", diff --git a/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.test.tsx b/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.test.tsx index ad96c830c1..440f07e04f 100644 --- a/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.test.tsx +++ b/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.test.tsx @@ -13,6 +13,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { TerminalComponent } from './TerminalComponent'; +import { KUBERNETES_API_SERVER } from './utils/annotations'; const DOMAIN_URL = 'mock-domain.com/webterminal'; const API_URL = 'https://api.cluster.com'; @@ -20,10 +21,11 @@ const NAMESPACES_URL = `${API_URL}/api/v1/namespaces`; const NAMESPACE = 'web-terminal-service-catalog'; const WORKSPACES_URL = `${API_URL}/apis/workspace.devfile.io/v1alpha2/namespaces/${NAMESPACE}/devworkspaces`; const CREATED_WORKSPACE_URL = `${WORKSPACES_URL}/web-terminal-c5e12`; + const entityMock = { metadata: { annotations: { - 'kubernetes.io/api-server': API_URL, + [KUBERNETES_API_SERVER]: API_URL, }, name: 'cluster', }, diff --git a/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.tsx b/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.tsx index d35f23c2e3..98a507b64d 100644 --- a/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.tsx +++ b/plugins/web-terminal/src/components/TerminalComponent/TerminalComponent.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useRef } from 'react'; -import { InfoCard, Progress } from '@backstage/core-components'; +import { InfoCard, Progress, WarningPanel } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { useEntity } from '@backstage/plugin-catalog-react'; @@ -14,11 +14,13 @@ import { getDefaultNamespace, getNamespaces, getWorkspace, + waitBetweenRetries, } from './utils'; import './static/xterm.css'; import { NamespacePickerDialog } from '../NamespacePickerDialog'; +import { KUBERNETES_API_SERVER } from './utils/annotations'; const useStyles = makeStyles({ term: { @@ -53,9 +55,10 @@ export const TerminalComponent = () => { ); const { entity } = useEntity(); - const cluster = entity.metadata.annotations?.[ - 'kubernetes.io/api-server' - ]?.replace(/(https?:\/\/)/, ''); + const cluster = entity.metadata.annotations?.[KUBERNETES_API_SERVER]?.replace( + /(https?:\/\/)/, + '', + ); const classes = useStyles(); const tokenRef = React.useRef(null); const termRef = React.useRef(null); @@ -109,7 +112,13 @@ export const TerminalComponent = () => { } let workspaceID; let phase; - while (phase !== 'Running') { + const waitUntil = Date.now() + 5 * 60 * 1000; // wait max 5 minutes + for ( + let retry = 0; + retry < 1000 && Date.now() < waitUntil && phase !== 'Running'; + retry++ + ) { + await waitBetweenRetries(retry); [workspaceID, phase] = await getWorkspace( restServerUrl, link, @@ -173,24 +182,32 @@ export const TerminalComponent = () => { return (
-
- + + + + ) : ( + - - + )} {displayModal && cluster && token && ( getNamespaces(restServerUrl, cluster, token)} diff --git a/plugins/web-terminal/src/components/TerminalComponent/utils/annotations.ts b/plugins/web-terminal/src/components/TerminalComponent/utils/annotations.ts new file mode 100644 index 0000000000..66f4c60bff --- /dev/null +++ b/plugins/web-terminal/src/components/TerminalComponent/utils/annotations.ts @@ -0,0 +1,7 @@ +import { Entity } from '@backstage/catalog-model'; + +export const KUBERNETES_API_SERVER = 'kubernetes.io/api-server'; + +/** @public */ +export const isWebTerminalAvailable = (entity: Entity): boolean => + Boolean(entity.metadata.annotations?.[KUBERNETES_API_SERVER]); diff --git a/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.test.ts b/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.test.ts new file mode 100644 index 0000000000..cf706592ee --- /dev/null +++ b/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.test.ts @@ -0,0 +1,19 @@ +import { getTimeInMsBetweenRetries } from './helpers'; + +describe('getTimeInMsBetweenRetries', () => { + it('should return 0 for retry 0', () => { + expect(getTimeInMsBetweenRetries(0)).toBe(0); + }); + + it('should return more then 0 for the next retries', () => { + expect(getTimeInMsBetweenRetries(1)).toBeGreaterThan(0); + expect(getTimeInMsBetweenRetries(2)).toBeGreaterThan(0); + }); + + it('should return not go above 5 seconds', () => { + expect(getTimeInMsBetweenRetries(9)).toBe(3000); + expect(getTimeInMsBetweenRetries(10)).toBe(5000); + expect(getTimeInMsBetweenRetries(11)).toBe(5000); + expect(getTimeInMsBetweenRetries(100)).toBe(5000); + }); +}); diff --git a/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.ts b/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.ts index 1d7df21696..adbc00cb16 100644 --- a/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.ts +++ b/plugins/web-terminal/src/components/TerminalComponent/utils/helpers.ts @@ -5,3 +5,19 @@ const OPENSHIFT_TERMINAL_DEFAULT_NAMESPACE = 'openshift-terminal'; export const getDefaultNamespace = (config: Config) => config.getOptionalString('webTerminal.defaultNamespace') ?? OPENSHIFT_TERMINAL_DEFAULT_NAMESPACE; + +const timeInMsBetweenRetries = [ + 0, 100, 500, 1000, 1000, 1000, 2000, 2000, 2000, 3000, 5000, +]; + +export const getTimeInMsBetweenRetries = (retry: number) => { + return timeInMsBetweenRetries[ + Math.min(retry, timeInMsBetweenRetries.length - 1) + ]; +}; + +export const waitBetweenRetries = (retry: number) => { + return new Promise(resolve => { + setTimeout(() => resolve(), getTimeInMsBetweenRetries(retry)); + }); +}; diff --git a/plugins/web-terminal/src/components/TerminalComponent/utils/index.ts b/plugins/web-terminal/src/components/TerminalComponent/utils/index.ts index b42e526f3f..44f1b9ca57 100644 --- a/plugins/web-terminal/src/components/TerminalComponent/utils/index.ts +++ b/plugins/web-terminal/src/components/TerminalComponent/utils/index.ts @@ -1,2 +1,2 @@ export { createWorkspace, getWorkspace, getNamespaces } from './requests'; -export { getDefaultNamespace } from './helpers'; +export { getDefaultNamespace, waitBetweenRetries } from './helpers'; diff --git a/plugins/web-terminal/src/components/TerminalComponent/utils/requests.ts b/plugins/web-terminal/src/components/TerminalComponent/utils/requests.ts index 59cf4ee35c..f2f3859786 100644 --- a/plugins/web-terminal/src/components/TerminalComponent/utils/requests.ts +++ b/plugins/web-terminal/src/components/TerminalComponent/utils/requests.ts @@ -83,7 +83,7 @@ export const getWorkspace = async ( }, ); const data = await response.json(); - return [data.status.devworkspaceId, data.status.phase]; + return [data.status?.devworkspaceId, data.status?.phase]; }; export const getNamespaces = async ( diff --git a/plugins/web-terminal/src/index.ts b/plugins/web-terminal/src/index.ts index a45bf62b9b..f62460949d 100644 --- a/plugins/web-terminal/src/index.ts +++ b/plugins/web-terminal/src/index.ts @@ -1 +1,2 @@ export { webTerminalPlugin, WebTerminal } from './plugin'; +export { isWebTerminalAvailable } from './components/TerminalComponent/utils/annotations';