diff --git a/web/.eslintignore b/.eslintignore similarity index 100% rename from web/.eslintignore rename to .eslintignore diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000000..f2fb2bf41c58a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,2 @@ +const eslint = require('./web/packages/build/.eslintrc'); +module.exports = eslint; diff --git a/package.json b/package.json index 47cd0bd77680a..bc3f24b05f0eb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,11 @@ "build-native-deps-for-term": "yarn workspace @gravitational/namespaces build-native-deps-for-term", "build-and-package-term-ci": "yarn workspace @gravitational/namespaces build-and-package-term-ci", "test": "yarn workspace @gravitational/namespaces test", - "type-check": "yarn workspace @gravitational/namespaces type-check" + "lint": "yarn prettier-check && yarn eslint", + "eslint": "eslint --quiet --ext .js,.jsx,.ts,.tsx web/", + "type-check": "tsc --noEmit", + "prettier-check": "yarn prettier --check 'web/**/*.{ts,tsx,js,jsx,md}'", + "prettier-write": "yarn prettier --write 'web/**/*.{ts,tsx,js,jsx,md}'" }, "private": true, "resolutions": { @@ -34,4 +38,4 @@ ] } -} \ No newline at end of file +} diff --git a/web/.eslintrc.js b/web/.eslintrc.js deleted file mode 100644 index 8a95852ccbfe6..0000000000000 --- a/web/.eslintrc.js +++ /dev/null @@ -1,2 +0,0 @@ -const eslint = require('./packages/build/.eslintrc'); -module.exports = eslint; diff --git a/web/.storybook/public/mockServiceWorker.js b/web/.storybook/public/mockServiceWorker.js index ab63a84955e80..a4ea781ce0bdd 100644 --- a/web/.storybook/public/mockServiceWorker.js +++ b/web/.storybook/public/mockServiceWorker.js @@ -8,121 +8,121 @@ * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = 'b3066ef78c2f9090b4ce87e874965995' -const activeClientIds = new Set() +const INTEGRITY_CHECKSUM = 'b3066ef78c2f9090b4ce87e874965995'; +const activeClientIds = new Set(); self.addEventListener('install', function () { - self.skipWaiting() -}) + self.skipWaiting(); +}); self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) + event.waitUntil(self.clients.claim()); +}); self.addEventListener('message', async function (event) { - const clientId = event.source.id + const clientId = event.source.id; if (!clientId || !self.clients) { - return + return; } - const client = await self.clients.get(clientId) + const client = await self.clients.get(clientId); if (!client) { - return + return; } const allClients = await self.clients.matchAll({ type: 'window', - }) + }); switch (event.data) { case 'KEEPALIVE_REQUEST': { sendToClient(client, { type: 'KEEPALIVE_RESPONSE', - }) - break + }); + break; } case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', payload: INTEGRITY_CHECKSUM, - }) - break + }); + break; } case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) + activeClientIds.add(clientId); sendToClient(client, { type: 'MOCKING_ENABLED', payload: true, - }) - break + }); + break; } case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break + activeClientIds.delete(clientId); + break; } case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) + activeClientIds.delete(clientId); - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister() + self.registration.unregister(); } - break + break; } } -}) +}); self.addEventListener('fetch', function (event) { - const { request } = event - const accept = request.headers.get('accept') || '' + const { request } = event; + const accept = request.headers.get('accept') || ''; // Bypass server-sent events. if (accept.includes('text/event-stream')) { - return + return; } // Bypass navigation requests. if (request.mode === 'navigate') { - return + return; } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return + return; } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been deleted (still remains active until the next reload). if (activeClientIds.size === 0) { - return + return; } // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) + const requestId = Math.random().toString(16).slice(2); event.respondWith( - handleRequest(event, requestId).catch((error) => { + handleRequest(event, requestId).catch(error => { if (error.name === 'NetworkError') { console.warn( '[MSW] Successfully emulated a network error for the "%s %s" request.', request.method, - request.url, - ) - return + request.url + ); + return; } // At this point, any exception indicates an issue with the original request/response. @@ -131,22 +131,22 @@ self.addEventListener('fetch', function (event) { [MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, request.method, request.url, - `${error.name}: ${error.message}`, - ) - }), - ) -}) + `${error.name}: ${error.message}` + ); + }) + ); +}); async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - ;(async function () { - const clonedResponse = response.clone() + (async function () { + const clonedResponse = response.clone(); sendToClient(client, { type: 'RESPONSE', payload: { @@ -160,11 +160,11 @@ async function handleRequest(event, requestId) { headers: Object.fromEntries(clonedResponse.headers.entries()), redirected: clonedResponse.redirected, }, - }) - })() + }); + })(); } - return response + return response; } // Resolve the main client for the given event. @@ -172,49 +172,49 @@ async function handleRequest(event, requestId) { // that registered the worker. It's with the latter the worker should // communicate with during the response resolving phase. async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) + const client = await self.clients.get(event.clientId); if (client.frameType === 'top-level') { - return client + return client; } const allClients = await self.clients.matchAll({ type: 'window', - }) + }); return allClients - .filter((client) => { + .filter(client => { // Get only those clients that are currently visible. - return client.visibilityState === 'visible' + return client.visibilityState === 'visible'; }) - .find((client) => { + .find(client => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) + return activeClientIds.has(client.id); + }); } async function getResponse(event, client, requestId) { - const { request } = event - const clonedRequest = request.clone() + const { request } = event; + const clonedRequest = request.clone(); function passthrough() { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) + const headers = Object.fromEntries(clonedRequest.headers.entries()); // Remove MSW-specific request headers so the bypassed requests // comply with the server's CORS preflight check. // Operate with the headers as an object because request "Headers" // are immutable. - delete headers['x-msw-bypass'] + delete headers['x-msw-bypass']; - return fetch(clonedRequest, { headers }) + return fetch(clonedRequest, { headers }); } // Bypass mocking when the client is not active. if (!client) { - return passthrough() + return passthrough(); } // Bypass initial page load requests (i.e. static assets). @@ -222,13 +222,13 @@ async function getResponse(event, client, requestId) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough() + return passthrough(); } // Bypass requests with the explicit bypass header. // Such requests can be issued by "ctx.fetch()". if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() + return passthrough(); } // Notify the client that a request has been intercepted. @@ -251,53 +251,53 @@ async function getResponse(event, client, requestId) { bodyUsed: request.bodyUsed, keepalive: request.keepalive, }, - }) + }); switch (clientMessage.type) { case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) + return respondWithMock(clientMessage.data); } case 'MOCK_NOT_FOUND': { - return passthrough() + return passthrough(); } case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; // Rejecting a "respondWith" promise emulates a network error. - throw networkError + throw networkError; } } - return passthrough() + return passthrough(); } function sendToClient(client, message) { return new Promise((resolve, reject) => { - const channel = new MessageChannel() + const channel = new MessageChannel(); - channel.port1.onmessage = (event) => { + channel.port1.onmessage = event => { if (event.data && event.data.error) { - return reject(event.data.error) + return reject(event.data.error); } - resolve(event.data) - } + resolve(event.data); + }; - client.postMessage(message, [channel.port2]) - }) + client.postMessage(message, [channel.port2]); + }); } function sleep(timeMs) { - return new Promise((resolve) => { - setTimeout(resolve, timeMs) - }) + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); } async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) + await sleep(response.delay); + return new Response(response.body, response); } diff --git a/web/README.md b/web/README.md index 6ca4b88056327..39591d96a15dd 100644 --- a/web/README.md +++ b/web/README.md @@ -73,7 +73,7 @@ By default, Webpack will store a cache in `node_modules/.cache/webpack` during d makes starting `webpack-dev-server` really quick after having ran it once, as it will re-use the cache from the last time it was running. -If you want to change the location of the cache, you can set `WEBPACK_CACHE_DIRECTORY` to an +If you want to change the location of the cache, you can set `WEBPACK_CACHE_DIRECTORY` to an absolute file path of the folder where you want to store Webpack's cache. If you wish to disable the cache, you can set `WEBPACK_CACHE_DISABLED` to `yes`. diff --git a/web/babel.config.js b/web/babel.config.js index 62dc2e9ba749c..7ae725ec5ca2f 100644 --- a/web/babel.config.js +++ b/web/babel.config.js @@ -1,5 +1,5 @@ const baseCfg = require('@gravitational/build/.babelrc'); -module.exports = function(api) { +module.exports = function (api) { api.cache(true); return baseCfg; }; diff --git a/web/package.json b/web/package.json index 5334caa3aaff4..528bd9e4dc07d 100644 --- a/web/package.json +++ b/web/package.json @@ -23,11 +23,6 @@ "build-teleport-e": "yarn workspace @gravitational/teleport.e build --output-path=../../../webassets/e/teleport/app", "build-oss": "yarn build-teleport-oss", "build-e": "yarn build-teleport-e", - "lint": "yarn prettier-check && yarn eslint", - "eslint": "eslint --quiet --ext .js,.jsx,.ts,.tsx packages/", - "type-check": "tsc --noEmit", - "prettier-check": "yarn prettier --check 'packages/**/*.{ts,tsx,js,jsx,md}'", - "prettier-write": "yarn prettier --write 'packages/**/*.{ts,tsx,js,jsx,md}'", "nop": "exit 0" }, "private": true, diff --git a/web/packages/build/jest/config.js b/web/packages/build/jest/config.js index c635abda48202..dfdefbcae32ea 100644 --- a/web/packages/build/jest/config.js +++ b/web/packages/build/jest/config.js @@ -26,7 +26,7 @@ module.exports = { '^design($|/.*)': '/packages/design/src/$1', '^teleport($|/.*)': '/packages/teleport/src/$1', '^teleterm($|/.*)': '/packages/teleterm/src/$1', - '^e-teleport/(.*)$': '/packages/webapps.e/teleport/src/$1', - '^e-teleterm/(.*)$': '/packages/webapps.e/teleterm/src/$1', + '^e-teleport/(.*)$': '/../e/web/teleport/src/$1', + '^e-teleterm/(.*)$': '/../e/web/teleterm/src/$1', }, }; diff --git a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx index 0422d587acbd2..00acadeccdffe 100644 --- a/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx +++ b/web/packages/teleport/src/AppLauncher/AppLauncher.test.tsx @@ -15,21 +15,24 @@ */ import React from 'react'; -import { render, screen, fireEvent } from 'design/utils/testing'; +import { render } from 'design/utils/testing'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router'; + import { Route } from 'teleport/components/Router'; import cfg from 'teleport/config'; -import { AppLauncher } from './AppLauncher'; import service from 'teleport/services/apps'; +import { AppLauncher } from './AppLauncher'; + test('arn is url decoded', () => { jest.spyOn(service, 'createAppSession'); - const launcherPath = '/web/launch/test-app.test.teleport/test.teleport/test-app.test.teleport/arn:aws:iam::joe123:role%2FEC2FullAccess'; + const launcherPath = + '/web/launch/test-app.test.teleport/test.teleport/test-app.test.teleport/arn:aws:iam::joe123:role%2FEC2FullAccess'; const mockHistory = createMemoryHistory({ initialEntries: [launcherPath], });