From 34444ca495eb3306a576c52f96552991cf9b63c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Thu, 15 Dec 2022 13:05:20 +0100 Subject: [PATCH 01/16] Rename assets to build_resources --- packages/teleterm/build_resources/README.md | 7 +++++++ .../icon-linux/512x512.png | Bin .../{assets => build_resources}/icon-mac.png | Bin .../{assets => build_resources}/icon-win.ico | Bin packages/teleterm/electron-builder-config.js | 9 ++++----- 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 packages/teleterm/build_resources/README.md rename packages/teleterm/{assets => build_resources}/icon-linux/512x512.png (100%) rename packages/teleterm/{assets => build_resources}/icon-mac.png (100%) rename packages/teleterm/{assets => build_resources}/icon-win.ico (100%) diff --git a/packages/teleterm/build_resources/README.md b/packages/teleterm/build_resources/README.md new file mode 100644 index 000000000..c90ca5e61 --- /dev/null +++ b/packages/teleterm/build_resources/README.md @@ -0,0 +1,7 @@ +This is the directory we use as the `buildResources` dir for electron-builder. + +By default, electron-builder uses the `build` dir at the project root. However, we already use that +directory for webpack output. + +If you see a path in electron-builder docs referring to `build`, you can assume that they meant the +`buildResources` directory. diff --git a/packages/teleterm/assets/icon-linux/512x512.png b/packages/teleterm/build_resources/icon-linux/512x512.png similarity index 100% rename from packages/teleterm/assets/icon-linux/512x512.png rename to packages/teleterm/build_resources/icon-linux/512x512.png diff --git a/packages/teleterm/assets/icon-mac.png b/packages/teleterm/build_resources/icon-mac.png similarity index 100% rename from packages/teleterm/assets/icon-mac.png rename to packages/teleterm/build_resources/icon-mac.png diff --git a/packages/teleterm/assets/icon-win.ico b/packages/teleterm/build_resources/icon-win.ico similarity index 100% rename from packages/teleterm/assets/icon-win.ico rename to packages/teleterm/build_resources/icon-win.ico diff --git a/packages/teleterm/electron-builder-config.js b/packages/teleterm/electron-builder-config.js index d199c0dd8..04a9f5d83 100644 --- a/packages/teleterm/electron-builder-config.js +++ b/packages/teleterm/electron-builder-config.js @@ -45,7 +45,7 @@ module.exports = { gatekeeperAssess: false, // If CONNECT_TSH_APP_PATH is provided, we assume that tsh.app is already signed. signIgnore: env.CONNECT_TSH_APP_PATH && ['tsh.app'], - icon: 'assets/icon-mac.png', + icon: 'build_resources/icon-mac.png', // On macOS, helper apps (such as tsh.app) should be under Contents/MacOS, hence using // `extraFiles` instead of `extraResources`. // https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle @@ -83,7 +83,7 @@ module.exports = { win: { target: ['nsis'], artifactName: '${productName} Setup-${version}.${ext}', - icon: 'assets/icon-win.ico', + icon: 'build_resources/icon-win.ico', extraResources: [ env.CONNECT_TSH_BIN_PATH && { from: env.CONNECT_TSH_BIN_PATH, @@ -105,7 +105,7 @@ module.exports = { target: ['tar.gz', 'rpm', 'deb'], artifactName: '${name}-${version}-${arch}.${ext}', //tar.gz category: 'Development', - icon: 'assets/icon-linux', + icon: 'build_resources/icon-linux', extraResources: [ env.CONNECT_TSH_BIN_PATH && { from: env.CONNECT_TSH_BIN_PATH, @@ -114,8 +114,7 @@ module.exports = { ].filter(Boolean), }, directories: { - buildResources: 'assets', + buildResources: 'build_resources', output: 'build/release', }, - extraResources: ['./assets/**'], }; From 86c8bf2643131db087f055ccc6418848b4780221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Thu, 15 Dec 2022 13:00:50 +0100 Subject: [PATCH 02/16] Add resources\bin to Path during installation on Windows --- .../teleterm/build_resources/installer.nsh | 24 ++++++++++++++++++ .../build_resources/nsis-plugins/EnVar.dll | Bin 0 -> 10240 bytes .../build_resources/nsis-plugins/README.md | 6 +++++ .../services/pty/ptyHost/buildPtyOptions.ts | 5 ++++ 4 files changed, 35 insertions(+) create mode 100644 packages/teleterm/build_resources/installer.nsh create mode 100644 packages/teleterm/build_resources/nsis-plugins/EnVar.dll create mode 100644 packages/teleterm/build_resources/nsis-plugins/README.md diff --git a/packages/teleterm/build_resources/installer.nsh b/packages/teleterm/build_resources/installer.nsh new file mode 100644 index 000000000..1df15aef6 --- /dev/null +++ b/packages/teleterm/build_resources/installer.nsh @@ -0,0 +1,24 @@ +# https://github.com/electron-userland/electron-builder/blob/v24.0.0-alpha.5/docs/configuration/nsis.md#custom-nsis-script + +# electron-builder adds `BUILD_RESOURCES_DIR\x86-unicode` as a plugin dir. +# But that dir name isn't very descriptive, so we add a custom plugin dir. +!addplugindir "${BUILD_RESOURCES_DIR}\nsis-plugins" + +# The EnVar plugin is recommended for env var modification as EnvVarUpdate doesn't handle long +# strings very well. +# https://nsis.sourceforge.io/Environmental_Variables:_append,_prepend,_and_remove_entries +# https://nsis.sourceforge.io/EnVar_plug-in + +!macro customInstall + # Make EnVar define user env vars instead of system env vars. + EnVar::SetHKCU + EnVar::AddValue "Path" $INSTDIR\resources\bin +!macroend + +!macro customUnInstall + EnVar::SetHKCU + # Inside the uninstaller, $INSTDIR is the directory where the uninstaller lies. + # Fortunately, electron-builder puts the uninstaller directly into the actual installation dir. + # https://nsis.sourceforge.io/Docs/Chapter4.html#varother + EnVar::DeleteValue "Path" $INSTDIR\resources\bin +!macroend diff --git a/packages/teleterm/build_resources/nsis-plugins/EnVar.dll b/packages/teleterm/build_resources/nsis-plugins/EnVar.dll new file mode 100644 index 0000000000000000000000000000000000000000..964e3427da199343f8c703a3920abf59bb96cb06 GIT binary patch literal 10240 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 literal 0 HcmV?d00001 diff --git a/packages/teleterm/build_resources/nsis-plugins/README.md b/packages/teleterm/build_resources/nsis-plugins/README.md new file mode 100644 index 000000000..4f25daeac --- /dev/null +++ b/packages/teleterm/build_resources/nsis-plugins/README.md @@ -0,0 +1,6 @@ +By default, electron-builder adds `${buildResources}\x86-unicode` as the plugin dir. But that name +is not really that descriptive, so we put the plugins under nsis-plugins. + +When you download a plugin, its `Plugins` folder is likely to contain .dlls for different +architectures and encodings such as amd64-unicode, x86-ansi, x86-unicode. You should use the .dlls +from the `x86-unicode` dir. diff --git a/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts b/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts index 443c3db0c..55f80d474 100644 --- a/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts +++ b/packages/teleterm/src/services/pty/ptyHost/buildPtyOptions.ts @@ -128,6 +128,11 @@ function prependBinDirToPath( env: typeof process.env, settings: RuntimeSettings ): void { + // On Windows, if settings.binDir is already in Path, this function will simply put in the front, + // guaranteeing that any shell session started from within Connect will use the bundled tsh. + // + // Windows seems to construct Path by first taking the system Path env var and adding to it the + // user Path env var. const pathName = settings.platform === 'win32' ? 'Path' : 'PATH'; env[pathName] = [settings.binDir, env[pathName]] .map(path => path?.trim()) From 69cf815879f66a33348c4f6a4068f918f67be40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Fri, 16 Dec 2022 10:18:14 +0100 Subject: [PATCH 03/16] Adjust docs related to USE_SYSTEM_FPM It turns out you need that for deb packages too. --- packages/teleterm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/teleterm/README.md b/packages/teleterm/README.md index 9e5823b14..3acf453e2 100644 --- a/packages/teleterm/README.md +++ b/packages/teleterm/README.md @@ -145,7 +145,7 @@ node-pty](https://github.com/microsoft/node-pty#dependencies). ### Linux -To create an RPM package for an arm64 target you need to provide `USE_SYSTEM_FPM=1` env var. +To create arm64 deb and RPM packages you need to provide `USE_SYSTEM_FPM=1` env var. ### macOS From c05425455b7d71a6222f180f9d85a2f383cccde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Mon, 19 Dec 2022 11:58:05 +0100 Subject: [PATCH 04/16] Create symlink to bundled tsh on Linux targets --- .../teleterm/build_resources/linux/README.md | 25 ++++++++ .../build_resources/linux/after-install.tpl | 59 +++++++++++++++++++ .../build_resources/linux/after-remove.tpl | 44 ++++++++++++++ packages/teleterm/electron-builder-config.js | 6 +- 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 packages/teleterm/build_resources/linux/README.md create mode 100644 packages/teleterm/build_resources/linux/after-install.tpl create mode 100644 packages/teleterm/build_resources/linux/after-remove.tpl diff --git a/packages/teleterm/build_resources/linux/README.md b/packages/teleterm/build_resources/linux/README.md new file mode 100644 index 000000000..d86f3cd2c --- /dev/null +++ b/packages/teleterm/build_resources/linux/README.md @@ -0,0 +1,25 @@ +## Differences between deb & RPM scripts + +During an upgrade of a deb package from one version to another, the following steps happen: + +1. The new version is unpacked, with the new files overwriting the old files. +2. The after-remove of the old version is called. +3. The old files are removed. +4. The after-install of the new version is called. + +See: + +- [Debian Policy Manual: Upgrading a package](https://www.debian.org/doc/debian-policy/ap-flowcharts.html#id5). +- [Debian Policy Manual: Details of unpack phase of installation or + upgrade](https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#details-of-unpack-phase-of-installation-or-upgrade) + +However, when you upgrade an RPM package, the scriptlets are called in a reverse order: + +1. The new version is unpacked, with the new files overwriting the old files. +2. The after-install of the new version is called. +3. The old files are removed. +4. The after-remove of the old version is called. + +See [Fedora Docs: Scriptlets - Ordering](https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#ordering). + +This has direct consequences when attempting to use the same scripts for both targets. diff --git a/packages/teleterm/build_resources/linux/after-install.tpl b/packages/teleterm/build_resources/linux/after-install.tpl new file mode 100644 index 000000000..767c440a6 --- /dev/null +++ b/packages/teleterm/build_resources/linux/after-install.tpl @@ -0,0 +1,59 @@ +#!/bin/bash +set -eu + +### +# Default after-install.tpl copied from electron-builder. +# https://github.com/electron-userland/electron-builder/blob/v24.0.0-alpha.5/packages/app-builder-lib/templates/linux/after-install.tpl +### + +# SUID chrome-sandbox for Electron 5+ +chmod 4755 "/opt/${sanitizedProductName}/chrome-sandbox" || true + +update-mime-database /usr/share/mime || true +update-desktop-database /usr/share/applications || true + +### +# Custom after-install.tpl script. +### + +APP="/opt/${sanitizedProductName}" +BIN=/usr/local/bin +TSH_SYMLINK_SOURCE=$APP/resources/bin/tsh +TSH_SYMLINK_TARGET=$BIN/tsh + +# Create $BIN if it doesn't exist. +[ ! -d "$BIN" ] && mkdir -p "$BIN" + +# Link to the Electron app binary. +ln -sf "$APP/${executable}" "$BIN/${executable}" + +# Link to the bundled tsh if the symlink doesn't exist already. Otherwise echo a message unless the +# link points to teleport-connect's tsh already. +# Does TSH_SYMLINK_TARGET not exist? +if [ ! -e "$TSH_SYMLINK_TARGET" ]; then + ln -s "$TSH_SYMLINK_SOURCE" "$TSH_SYMLINK_TARGET" +else + message="${executable}: Skipping symlinking $TSH_SYMLINK_TARGET to $TSH_SYMLINK_SOURCE" + + # Is TSH_SYMLINK_TARGET a symlink? + if [ -L "$TSH_SYMLINK_TARGET" ]; then + # Does TSH_SYMLINK_TARGET point at something else than TSH_SYMLINK_SOURCE? + # If TSH_SYMLINK_TARGET exists and it points at TSH_SYMLINK_SOURCE already, don't do anything. + if [ ! "$TSH_SYMLINK_TARGET" -ef "$TSH_SYMLINK_SOURCE" ]; then + message+=" because the symlink already exists and it points to $(realpath $TSH_SYMLINK_TARGET)." + echo "$message" + fi + else + message+=" because $TSH_SYMLINK_TARGET already exists and it isn't a symlink." + echo "$message" + fi +fi + +# DELETE IN 13.0.0 +# Remove the old symlink from times where we used the default after-install.tpl. +OLD_SYMLINK=/usr/bin/teleport-connect +if [ -e "$OLD_SYMLINK" ]; then + rm $OLD_SYMLINK +fi + +# vim: syntax=sh diff --git a/packages/teleterm/build_resources/linux/after-remove.tpl b/packages/teleterm/build_resources/linux/after-remove.tpl new file mode 100644 index 000000000..9656ea68c --- /dev/null +++ b/packages/teleterm/build_resources/linux/after-remove.tpl @@ -0,0 +1,44 @@ +#!/bin/bash +set -eu + +# Do not touch symlinks if the package is being upgraded. +# +# Why? +# +# During an upgrade, RPM packages call after-install of new version first followed by after-remove +# of the old version. deb packages do this in reverse order. See README.md in this directory for +# more details. +# +# So, for RPM packages we should not remove the symlinks if the package is being upgraded, otherwise +# the user would end up with no symlinks after an upgrade. +# +# How? +# +# Both deb and RPM pass arguments to the scripts. rpm passes the number of packages of the given +# name which will be left on the system when the action completes. deb passes "upgrade" during an +# upgrade. We can check those args to determine if the package is being upgraded or removed. +# +# https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#details-of-unpack-phase-of-installation-or-upgrade +# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/#_syntax +# +# Is the first argument "upgrade" or "1"? +if [ "$1" = "upgrade" ] || [ "$1" = "1" ]; then + echo "${executable}: Upgrade detected, skipping symlink operations" + exit 0 +fi + +BIN=/usr/local/bin +TSH_SYMLINK_TARGET=$BIN/tsh + +# Remove the link to the Electron app binary. +rm -f "$BIN/${executable}" + +# At this point, the app has already been removed from disk. If TSH_SYMLINK_TARGET used to point at +# tsh bundled with the teleport-connect package, it is a broken symlink now. +# +# Is TSH_SYMLINK_TARGET a link that points at a file which doesn't exist? +if [ -L "$TSH_SYMLINK_TARGET" ] && [ ! -e "$TSH_SYMLINK_TARGET" ]; then + rm -f "$TSH_SYMLINK_TARGET" +fi + +# vim: syntax=sh diff --git a/packages/teleterm/electron-builder-config.js b/packages/teleterm/electron-builder-config.js index 04a9f5d83..e06eef635 100644 --- a/packages/teleterm/electron-builder-config.js +++ b/packages/teleterm/electron-builder-config.js @@ -93,6 +93,8 @@ module.exports = { }, rpm: { artifactName: '${name}-${version}.${arch}.${ext}', + afterInstall: 'build_resources/linux/after-install.tpl', + afterRemove: 'build_resources/linux/after-remove.tpl', // --rpm-rpmbuild-define "_build_id_links none" fixes the problem with not being able to install // Connect's rpm next to other Electron apps. // https://github.com/gravitational/teleport/issues/18859 @@ -100,10 +102,12 @@ module.exports = { }, deb: { artifactName: '${name}_${version}_${arch}.${ext}', + afterInstall: 'build_resources/linux/after-install.tpl', + afterRemove: 'build_resources/linux/after-remove.tpl', }, linux: { target: ['tar.gz', 'rpm', 'deb'], - artifactName: '${name}-${version}-${arch}.${ext}', //tar.gz + artifactName: '${name}-${version}-${arch}.${ext}', // tar.gz category: 'Development', icon: 'build_resources/icon-linux', extraResources: [ From 324a8426df1b05f6600984de1b415566b3e00984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Thu, 22 Dec 2022 10:51:16 +0100 Subject: [PATCH 05/16] after-install: Get rid of old symlink removal --- packages/teleterm/build_resources/linux/after-install.tpl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/teleterm/build_resources/linux/after-install.tpl b/packages/teleterm/build_resources/linux/after-install.tpl index 767c440a6..9bcd38e9a 100644 --- a/packages/teleterm/build_resources/linux/after-install.tpl +++ b/packages/teleterm/build_resources/linux/after-install.tpl @@ -49,11 +49,4 @@ else fi fi -# DELETE IN 13.0.0 -# Remove the old symlink from times where we used the default after-install.tpl. -OLD_SYMLINK=/usr/bin/teleport-connect -if [ -e "$OLD_SYMLINK" ]; then - rm $OLD_SYMLINK -fi - # vim: syntax=sh From 9132ab84c0b4f7e768004d9a8495a7dbd2b9cc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Thu, 22 Dec 2022 17:23:48 +0100 Subject: [PATCH 06/16] Expand story for QuickInput --- .../src/ui/QuickInput/QuickInput.story.tsx | 179 ++++++++++++++++-- 1 file changed, 164 insertions(+), 15 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx index b1d72f914..5a1e62bde 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx @@ -28,6 +28,35 @@ export default { }; export const Story = () => { + return ( + <> + {/* Extra bottom margin to accommodate the suggestions displayed under the input. */} + + + + + + + ); +}; + +const QuickInputDemo = (props: { + description: string; + inputValue?: string; + mb?: number; +}) => { const appContext = new MockAppContext(); appContext.workspacesService.state = { @@ -45,30 +74,150 @@ export const Story = () => { rootClusterUri: '/clusters/localhost', }; + const cluster = { + uri: '/clusters/localhost' as const, + name: 'Test', + leaf: false, + connected: true, + proxyHost: 'localhost:3080', + loggedInUser: { + activeRequestsList: [], + name: 'admin', + acl: {}, + sshLoginsList: [ + 'root', + 'ubuntu', + 'ansible', + 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', + ], + rolesList: [], + }, + }; + appContext.clustersService.getClusters = () => { - return [ + return [cluster]; + }; + + appContext.clustersService.setState(draftState => { + draftState.clusters = new Map([[cluster.uri, cluster]]); + }); + + appContext.resourcesService.fetchServers = async () => ({ + agentsList: [ { - uri: '/clusters/localhost', - name: 'Test', - leaf: false, - connected: true, - proxyHost: 'localhost:3080', - loggedInUser: { - activeRequestsList: [], - name: 'admin', - acl: {}, - sshLoginsList: [], - rolesList: [], - }, + uri: '/clusters/localhost/servers/foo', + tunnel: false, + name: '2018454d-ef3b-4b15-84f7-61ca213d37e3', + hostname: 'foo', + addr: 'foo.localhost', + labelsList: [ + { name: 'env', value: 'prod' }, + { name: 'kernel', value: '5.15.0-1023-aws' }, + ], }, - ]; - }; + { + uri: '/clusters/localhost/servers/bar', + tunnel: false, + name: '24c7aebe-4741-4464-ab69-f076fe467ebd', + hostname: 'bar', + addr: 'bar.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + ], + }, + { + uri: '/clusters/localhost/servers/lorem', + tunnel: false, + name: '24c7aebe-4741-4464-ab69-f076fe467ebd', + hostname: + 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', + addr: 'lorem.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + { + name: 'lorem', + value: + 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', + }, + { name: 'kernel2', value: '5.14.1-1058-aws' }, + { name: 'env2', value: 'staging' }, + { name: 'kernel3', value: '5.14.1-1058-aws' }, + ], + }, + ], + totalCount: 3, + startKey: 'foo', + }); + + appContext.resourcesService.fetchDatabases = async () => ({ + agentsList: [ + { + uri: '/clusters/localhost/dbs/postgres', + name: 'postgres', + desc: 'A PostgreSQL database', + protocol: 'postgres', + type: 'self-hosted', + hostname: 'postgres.localhost', + addr: 'postgres.localhost', + labelsList: [ + { name: 'env', value: 'prod' }, + { name: 'kernel', value: '5.15.0-1023-aws' }, + ], + }, + { + uri: '/clusters/localhost/dbs/mysql', + name: 'mysql', + desc: 'A MySQL database', + protocol: 'mysql', + type: 'self-hosted', + hostname: 'mysql.localhost', + addr: 'mysql.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + ], + }, + { + uri: '/clusters/localhost/dbs/lorem', + name: 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', + desc: 'Lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet', + protocol: 'mysql', + type: 'self-hosted', + hostname: 'lorem.localhost', + addr: 'lorem.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + { + name: 'lorem', + value: + 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', + }, + { name: 'kernel2', value: '5.14.1-1058-aws' }, + { name: 'env2', value: 'staging' }, + { name: 'kernel3', value: '5.14.1-1058-aws' }, + ], + }, + ], + totalCount: 2, + startKey: 'foo', + }); + + if (props.inputValue !== undefined) { + appContext.quickInputService.setInputValue(props.inputValue); + appContext.quickInputService.show(); + appContext.quickInputService.hide = () => {}; + } return ( +

{props.description}

From 4459f9e78b5fe4037edc942dddc7e91107499bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Fri, 23 Dec 2022 12:19:44 +0100 Subject: [PATCH 07/16] Make command suggestions stay in place --- .../src/ui/QuickInput/QuickInputList/QuickInputList.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx index 74b4dcd4d..2d4b062fe 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx @@ -24,6 +24,10 @@ import { Cli, Server, Person, Database } from 'design/Icon'; import * as types from 'teleterm/ui/services/quickInput/types'; const QuickInputList = React.forwardRef((props, ref) => { + // Ideally, this property would be described by the suggestion object itself rather than depending + // on `kind`. But for now we need it just for a single suggestion kind anyway. + const shouldSuggestionsStayInPlace = + props.items[0]?.kind === 'suggestion.cmd'; const activeItemRef = useRef(); const { items, activeItem } = props; if (items.length === 0) { @@ -60,7 +64,7 @@ const QuickInputList = React.forwardRef((props, ref) => { return ( Date: Fri, 23 Dec 2022 12:20:23 +0100 Subject: [PATCH 08/16] Align suggestion icons to the top rather than center This makes it easier to tell when one suggestion ends and another starts. --- .../src/ui/QuickInput/QuickInputList/QuickInputList.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx index 2d4b062fe..cf8cd1e2e 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx @@ -79,7 +79,7 @@ export default QuickInputList; function CmdItem(props: { item: types.SuggestionCmd }) { return ( - + @@ -90,7 +90,7 @@ function CmdItem(props: { item: types.SuggestionCmd }) { function SshLoginItem(props: { item: types.SuggestionSshLogin }) { return ( - + @@ -108,7 +108,7 @@ function ServerItem(props: { item: types.SuggestionServer }) { )); return ( - + @@ -129,7 +129,7 @@ function DatabaseItem(props: { item: types.SuggestionDatabase }) { )); return ( - + From bef40b0a72eb9e44bfd85f2b50f20ecb0389a66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Fri, 23 Dec 2022 12:31:40 +0100 Subject: [PATCH 09/16] Add install & uninstall cmds to command bar --- .../QuickInputList/QuickInputList.tsx | 6 ++- .../src/ui/QuickInput/useQuickInput.ts | 10 +++++ packages/teleterm/src/ui/commandLauncher.ts | 13 +++++- .../src/ui/services/quickInput/parsers.ts | 42 ++++++++++++++++--- .../services/quickInput/quickInputService.ts | 8 ++++ .../src/ui/services/quickInput/types.ts | 8 +++- 6 files changed, 77 insertions(+), 10 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx index cf8cd1e2e..936488a39 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInputList/QuickInputList.tsx @@ -83,7 +83,11 @@ function CmdItem(props: { item: types.SuggestionCmd }) { - {props.item.data.displayName} + {/* Equivalent of flex-shrink: 0, but styled-system doesn't support flex-shrink. */} + + {props.item.data.displayName} + + {props.item.data.description} ); } diff --git a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts index 2a2f9a0f6..6068c5393 100644 --- a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts +++ b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts @@ -122,6 +122,16 @@ export default function useQuickInput() { }); break; } + case 'command.tsh-install': { + break; + } + case 'command.tsh-uninstall': { + break; + } + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const exhaustiveCheck: never = command; + } } quickInputService.clearInputValueAndHide(); diff --git a/packages/teleterm/src/ui/commandLauncher.ts b/packages/teleterm/src/ui/commandLauncher.ts index 3fa4cc700..c3bcdbd30 100644 --- a/packages/teleterm/src/ui/commandLauncher.ts +++ b/packages/teleterm/src/ui/commandLauncher.ts @@ -172,8 +172,17 @@ const commands = { }, 'autocomplete.tsh-proxy-db': { displayName: 'tsh proxy db', - description: - 'Start local TLS proxy for database connections when using Teleport', + description: 'Start a local proxy for a database connection', + run() {}, + }, + 'autocomplete.tsh-install': { + displayName: 'tsh install', + description: 'Install tsh in PATH', + run() {}, + }, + 'autocomplete.tsh-uninstall': { + displayName: 'tsh uninstall', + description: 'Uninstall tsh from PATH', run() {}, }, }; diff --git a/packages/teleterm/src/ui/services/quickInput/parsers.ts b/packages/teleterm/src/ui/services/quickInput/parsers.ts index bfcfb1b17..352186bf5 100644 --- a/packages/teleterm/src/ui/services/quickInput/parsers.ts +++ b/packages/teleterm/src/ui/services/quickInput/parsers.ts @@ -17,6 +17,7 @@ limitations under the License. import { CommandLauncher } from 'teleterm/ui/commandLauncher'; import { + AutocompleteCommand, AutocompleteToken, SuggestionCmd, QuickInputParser, @@ -136,13 +137,21 @@ export class QuickCommandParser implements QuickInputParser { private mapAutocompleteCommandsToSuggestions( commands: { name: string; displayName: string; description: string }[] ): SuggestionCmd[] { - return commands.map(cmd => ({ - kind: 'suggestion.cmd' as const, - token: cmd.displayName, + return commands.map(cmd => { + const acceptsNoArguments = + this.parserRegistry.get(cmd.displayName) instanceof + QuickNoArgumentsParser; + // If a command accepts arguments, let's append a space when that suggestion is picked. // This allows us to immediately show autocomplete for the first argument of the command. - appendToToken: ' ', - data: cmd, - })); + const appendToToken = acceptsNoArguments ? '' : ' '; + + return { + kind: 'suggestion.cmd' as const, + token: cmd.displayName, + appendToToken: appendToToken, + data: cmd, + }; + }); } } @@ -307,3 +316,24 @@ export class QuickTshProxyDbParser implements QuickInputParser { }; } } + +// QuickNoArgumentsParser is useful in situations where a command does not accept any arguments. +// If QuickNoArgumentsParser is registered as the parser for that command, selecting that command +// from suggestions will simply close autocomplete. Pressing Enter again will execute the command +// passed to the constructor of QuickNoArgumentsParser. +export class QuickNoArgumentsParser implements QuickInputParser { + constructor(private command: AutocompleteCommand) {} + + parse(rawInput: string, startIndex: number): ParseResult { + const targetToken = { + value: '', + startIndex, + }; + + return { + targetToken, + command: this.command, + getSuggestions: noSuggestions, + }; + } +} diff --git a/packages/teleterm/src/ui/services/quickInput/quickInputService.ts b/packages/teleterm/src/ui/services/quickInput/quickInputService.ts index 7e4dca341..1d114c7bc 100644 --- a/packages/teleterm/src/ui/services/quickInput/quickInputService.ts +++ b/packages/teleterm/src/ui/services/quickInput/quickInputService.ts @@ -68,6 +68,14 @@ export class QuickInputService extends Store { 'tsh proxy db', new parsers.QuickTshProxyDbParser(databaseSuggester) ); + this.quickCommandParser.registerParserForCommand( + 'tsh install', + new parsers.QuickNoArgumentsParser({ kind: 'command.tsh-install' }) + ); + this.quickCommandParser.registerParserForCommand( + 'tsh uninstall', + new parsers.QuickNoArgumentsParser({ kind: 'command.tsh-uninstall' }) + ); } state: State = { diff --git a/packages/teleterm/src/ui/services/quickInput/types.ts b/packages/teleterm/src/ui/services/quickInput/types.ts index 77831a7e0..53e2b2b9d 100644 --- a/packages/teleterm/src/ui/services/quickInput/types.ts +++ b/packages/teleterm/src/ui/services/quickInput/types.ts @@ -62,6 +62,12 @@ export type AutocompleteTshSshCommand = CommandBase<'command.tsh-ssh'> & { loginHost: string; }; +export type AutocompleteTshInstallCommand = CommandBase<'command.tsh-install'>; +export type AutocompleteTshUninstallCommand = + CommandBase<'command.tsh-uninstall'>; + export type AutocompleteCommand = | AutocompleteUnknownCommand - | AutocompleteTshSshCommand; + | AutocompleteTshSshCommand + | AutocompleteTshInstallCommand + | AutocompleteTshUninstallCommand; From c331e626de385a7ae1f4f6dc532b923a01374211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Fri, 23 Dec 2022 12:40:18 +0100 Subject: [PATCH 10/16] Exclude new commands from OSes other than macOS --- .../src/mainProcess/fixtures/mocks.ts | 60 ++++++++++--------- .../teleterm/src/ui/commandLauncher.test.ts | 36 +++++++++++ packages/teleterm/src/ui/commandLauncher.ts | 11 ++++ packages/teleterm/src/ui/fixtures/mocks.ts | 5 +- 4 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 packages/teleterm/src/ui/commandLauncher.test.ts diff --git a/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 450356c7b..9504d34d9 100644 --- a/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -5,33 +5,17 @@ import { createMockFileStorage } from 'teleterm/services/fileStorage/fixtures/mo // Importing electron breaks the fixtures if that's done from within storybook. import { createConfigService } from 'teleterm/services/config/configService'; -const platform = 'darwin'; - export class MockMainProcessClient implements MainProcessClient { + configService: ReturnType; + + constructor(private runtimeSettings: Partial = {}) { + this.configService = createConfigService( + createMockFileStorage(), + this.getRuntimeSettings().platform + ); + } getRuntimeSettings(): RuntimeSettings { - return { - platform, - dev: true, - userDataDir: '', - binDir: '', - certsDir: '', - kubeConfigsDir: '', - defaultShell: '', - tshd: { - insecure: true, - requestedNetworkAddress: '', - binaryPath: '', - homeDir: '', - flags: [], - }, - sharedProcess: { - requestedNetworkAddress: '', - }, - tshdEvents: { - requestedNetworkAddress: '', - }, - installationId: '123e4567-e89b-12d3-a456-426614174000', - }; + return { ...defaultRuntimeSettings, ...this.runtimeSettings }; } getResolvedChildProcessAddresses = () => @@ -47,8 +31,6 @@ export class MockMainProcessClient implements MainProcessClient { return Promise.resolve({ canceled: false, filePath: '' }); } - configService = createConfigService(createMockFileStorage(), platform); - fileStorage = createMockFileStorage(); removeKubeConfig(): Promise { @@ -57,3 +39,27 @@ export class MockMainProcessClient implements MainProcessClient { forceFocusWindow() {} } + +const defaultRuntimeSettings = { + platform: 'darwin' as const, + dev: true, + userDataDir: '', + binDir: '', + certsDir: '', + kubeConfigsDir: '', + defaultShell: '', + tshd: { + insecure: true, + requestedNetworkAddress: '', + binaryPath: '', + homeDir: '', + flags: [], + }, + sharedProcess: { + requestedNetworkAddress: '', + }, + tshdEvents: { + requestedNetworkAddress: '', + }, + installationId: '123e4567-e89b-12d3-a456-426614174000', +}; diff --git a/packages/teleterm/src/ui/commandLauncher.test.ts b/packages/teleterm/src/ui/commandLauncher.test.ts new file mode 100644 index 000000000..1abde3cf2 --- /dev/null +++ b/packages/teleterm/src/ui/commandLauncher.test.ts @@ -0,0 +1,36 @@ +import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; + +import { CommandLauncher } from './commandLauncher'; + +it('returns tsh install & uninstall autocomplete command on macOS', () => { + const appContext = new MockAppContext({ platform: 'darwin' }); + const commandLauncher = new CommandLauncher(appContext); + const autocompleteCommandNames = commandLauncher + .getAutocompleteCommands() + .map(c => c.name); + + expect(autocompleteCommandNames).toContain('autocomplete.tsh-install'); + expect(autocompleteCommandNames).toContain('autocomplete.tsh-uninstall'); +}); + +it('does not return tsh install & uninstall autocomplete command on Linux', () => { + const appContext = new MockAppContext({ platform: 'linux' }); + const commandLauncher = new CommandLauncher(appContext); + const autocompleteCommandNames = commandLauncher + .getAutocompleteCommands() + .map(c => c.name); + + expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-install'); + expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-uninstall'); +}); + +it('does not return tsh install & uninstall autocomplete command on Windows', () => { + const appContext = new MockAppContext({ platform: 'win32' }); + const commandLauncher = new CommandLauncher(appContext); + const autocompleteCommandNames = commandLauncher + .getAutocompleteCommands() + .map(c => c.name); + + expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-install'); + expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-uninstall'); +}); diff --git a/packages/teleterm/src/ui/commandLauncher.ts b/packages/teleterm/src/ui/commandLauncher.ts index c3bcdbd30..0fc51bbad 100644 --- a/packages/teleterm/src/ui/commandLauncher.ts +++ b/packages/teleterm/src/ui/commandLauncher.ts @@ -178,11 +178,13 @@ const commands = { 'autocomplete.tsh-install': { displayName: 'tsh install', description: 'Install tsh in PATH', + platforms: ['darwin'], run() {}, }, 'autocomplete.tsh-uninstall': { displayName: 'tsh uninstall', description: 'Uninstall tsh from PATH', + platforms: ['darwin'], run() {}, }, }; @@ -199,8 +201,17 @@ export class CommandLauncher { } getAutocompleteCommands() { + const { platform } = this.appContext.mainProcessClient.getRuntimeSettings(); + return Object.entries(commands) .filter(([key]) => key.startsWith('autocomplete.')) + .filter(([, command]) => { + const platforms = command['platforms']; + return ( + !platforms || + (Array.isArray(platforms) && platforms.includes(platform)) + ); + }) .map(([key, value]) => ({ name: key, ...value })); } } diff --git a/packages/teleterm/src/ui/fixtures/mocks.ts b/packages/teleterm/src/ui/fixtures/mocks.ts index 706df9dbc..d81eb0d13 100644 --- a/packages/teleterm/src/ui/fixtures/mocks.ts +++ b/packages/teleterm/src/ui/fixtures/mocks.ts @@ -2,10 +2,11 @@ import { MockMainProcessClient } from 'teleterm/mainProcess/fixtures/mocks'; import { MockTshClient } from 'teleterm/services/tshd/fixtures/mocks'; import { MockPtyServiceClient } from 'teleterm/services/pty/fixtures/mocks'; import AppContext from 'teleterm/ui/appContext'; +import { RuntimeSettings } from 'teleterm/types'; export class MockAppContext extends AppContext { - constructor() { - const mainProcessClient = new MockMainProcessClient(); + constructor(runtimeSettings?: Partial) { + const mainProcessClient = new MockMainProcessClient(runtimeSettings); const tshdClient = new MockTshClient(); const ptyServiceClient = new MockPtyServiceClient(); From aecddd1ce75801a6908e1781a56f4e3837631ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Tue, 3 Jan 2023 12:22:00 +0100 Subject: [PATCH 11/16] Implement commands for symlinking tsh --- .../src/mainProcess/fixtures/mocks.ts | 9 +++- .../teleterm/src/mainProcess/mainProcess.ts | 50 ++++++++++++++++++- .../src/mainProcess/mainProcessClient.ts | 6 +++ packages/teleterm/src/mainProcess/types.ts | 10 ++++ .../src/ui/QuickInput/useQuickInput.ts | 2 + packages/teleterm/src/ui/commandLauncher.ts | 44 ++++++++++++++++ 6 files changed, 117 insertions(+), 4 deletions(-) diff --git a/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 9504d34d9..24c003b9c 100644 --- a/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -22,9 +22,7 @@ export class MockMainProcessClient implements MainProcessClient { Promise.resolve({ tsh: '', shared: '' }); openTerminalContextMenu() {} - openClusterContextMenu() {} - openTabContextMenu() {} showFileSaveDialog() { @@ -38,6 +36,13 @@ export class MockMainProcessClient implements MainProcessClient { } forceFocusWindow() {} + + async symlinkTsh() { + return true; + } + async removeTshSymlink() { + return true; + } } const defaultRuntimeSettings = { diff --git a/packages/teleterm/src/mainProcess/mainProcess.ts b/packages/teleterm/src/mainProcess/mainProcess.ts index b8cf21bdf..47e477709 100644 --- a/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/packages/teleterm/src/mainProcess/mainProcess.ts @@ -1,8 +1,8 @@ -import { ChildProcess, fork, spawn } from 'child_process'; +import { ChildProcess, fork, spawn, exec } from 'child_process'; import path from 'path'; - import fs from 'fs/promises'; +import { promisify } from 'util'; import { app, dialog, @@ -203,6 +203,52 @@ export default class MainProcess { this.windowsManager.forceFocusWindow(); }); + // Used in the `tsh install` command on macOS to make the bundled tsh available in PATH. + // Returns true if tsh got successfully installed, false if the user closed the osascript + // prompt. Throws an error when osascript fails. + ipcMain.handle('main-process-symlink-tsh', async () => { + const source = this.settings.tshd.binaryPath; + const target = '/usr/local/bin/tsh'; + const prompt = + 'Teleport Connect wants to create a symlink for tsh in /usr/local/bin.'; + const command = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf '${source}' '${target}'\\" with prompt \\"${prompt}\\" with administrator privileges"`; + + try { + await promisify(exec)(command); + this.logger.info(`Created the symlink to ${source} under ${target}`); + return true; + } catch (error) { + // Ignore the error if the user canceled the prompt. + // https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_error_codes.html#//apple_ref/doc/uid/TP40000983-CH220-SW2 + if (error instanceof Error && error.message.includes('-128')) { + return false; + } + this.logger.error(error); + throw error; + } + }); + + ipcMain.handle('main-process-remove-tsh-symlink', async () => { + const target = '/usr/local/bin/tsh'; + const prompt = + 'Teleport Connect wants to remove a symlink for tsh from /usr/local/bin.'; + const command = `osascript -e "do shell script \\"rm '${target}'\\" with prompt \\"${prompt}\\" with administrator privileges"`; + + try { + await promisify(exec)(command); + this.logger.info(`Removed the symlink under ${target}`); + return true; + } catch (error) { + // Ignore the error if the user canceled the prompt. + // https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_error_codes.html#//apple_ref/doc/uid/TP40000983-CH220-SW2 + if (error instanceof Error && error.message.includes('-128')) { + return false; + } + this.logger.error(error); + throw error; + } + }); + subscribeToTerminalContextMenuEvent(); subscribeToTabContextMenuEvent(); subscribeToConfigServiceEvents(this.configService); diff --git a/packages/teleterm/src/mainProcess/mainProcessClient.ts b/packages/teleterm/src/mainProcess/mainProcessClient.ts index 200a557bc..f79c31350 100644 --- a/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -31,5 +31,11 @@ export default function createMainProcessClient(): MainProcessClient { forceFocusWindow() { return ipcRenderer.invoke('main-process-force-focus-window'); }, + symlinkTsh() { + return ipcRenderer.invoke('main-process-symlink-tsh'); + }, + removeTshSymlink() { + return ipcRenderer.invoke('main-process-remove-tsh-symlink'); + }, }; } diff --git a/packages/teleterm/src/mainProcess/types.ts b/packages/teleterm/src/mainProcess/types.ts index 9816427c2..c4f596ce0 100644 --- a/packages/teleterm/src/mainProcess/types.ts +++ b/packages/teleterm/src/mainProcess/types.ts @@ -43,6 +43,16 @@ export type MainProcessClient = { isDirectory?: boolean; }): Promise; forceFocusWindow(): void; + /** + * The promise returns true if tsh got successfully symlinked, false if the user closed the + * osascript prompt. The promise gets rejected if osascript encountered an error. + */ + symlinkTsh(): Promise; + /** + * The promise returns true if the tsh symlink got removed, false if the user closed the osascript + * prompt. The promise gets rejected if osascript encountered an error. + */ + removeTshSymlink(): Promise; }; export type ChildProcessAddresses = { diff --git a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts index 6068c5393..77dac962f 100644 --- a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts +++ b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts @@ -123,9 +123,11 @@ export default function useQuickInput() { break; } case 'command.tsh-install': { + commandLauncher.executeCommand('tsh-install', undefined); break; } case 'command.tsh-uninstall': { + commandLauncher.executeCommand('tsh-uninstall', undefined); break; } default: { diff --git a/packages/teleterm/src/ui/commandLauncher.ts b/packages/teleterm/src/ui/commandLauncher.ts index 0fc51bbad..6899f4886 100644 --- a/packages/teleterm/src/ui/commandLauncher.ts +++ b/packages/teleterm/src/ui/commandLauncher.ts @@ -92,6 +92,50 @@ const commands = { }, }, + 'tsh-install': { + displayName: '', + description: '', + run(ctx: IAppContext) { + ctx.mainProcessClient.symlinkTsh().then( + isSymlinked => { + if (isSymlinked) { + ctx.notificationsService.notifyInfo( + 'tsh successfully installed in PATH' + ); + } + }, + error => { + ctx.notificationsService.notifyError({ + title: 'Could not install tsh in PATH', + description: `Ran into an error: ${error}`, + }); + } + ); + }, + }, + + 'tsh-uninstall': { + displayName: '', + description: '', + run(ctx: IAppContext) { + ctx.mainProcessClient.removeTshSymlink().then( + isRemoved => { + if (isRemoved) { + ctx.notificationsService.notifyInfo( + 'tsh successfully removed from PATH' + ); + } + }, + error => { + ctx.notificationsService.notifyError({ + title: 'Could not remove tsh from PATH', + description: `Ran into an error: ${error}`, + }); + } + ); + }, + }, + 'kube-connect': { displayName: '', description: '', From f0655239cc55ef6112ad28b7c2b847ba91f55a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Wed, 4 Jan 2023 14:52:58 +0100 Subject: [PATCH 12/16] Add macos suffix to symlink IPC handlers --- packages/teleterm/src/mainProcess/mainProcess.ts | 4 ++-- packages/teleterm/src/mainProcess/mainProcessClient.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/teleterm/src/mainProcess/mainProcess.ts b/packages/teleterm/src/mainProcess/mainProcess.ts index 47e477709..ff98342f3 100644 --- a/packages/teleterm/src/mainProcess/mainProcess.ts +++ b/packages/teleterm/src/mainProcess/mainProcess.ts @@ -206,7 +206,7 @@ export default class MainProcess { // Used in the `tsh install` command on macOS to make the bundled tsh available in PATH. // Returns true if tsh got successfully installed, false if the user closed the osascript // prompt. Throws an error when osascript fails. - ipcMain.handle('main-process-symlink-tsh', async () => { + ipcMain.handle('main-process-symlink-tsh-macos', async () => { const source = this.settings.tshd.binaryPath; const target = '/usr/local/bin/tsh'; const prompt = @@ -228,7 +228,7 @@ export default class MainProcess { } }); - ipcMain.handle('main-process-remove-tsh-symlink', async () => { + ipcMain.handle('main-process-remove-tsh-symlink-macos', async () => { const target = '/usr/local/bin/tsh'; const prompt = 'Teleport Connect wants to remove a symlink for tsh from /usr/local/bin.'; diff --git a/packages/teleterm/src/mainProcess/mainProcessClient.ts b/packages/teleterm/src/mainProcess/mainProcessClient.ts index f79c31350..ebc0b9789 100644 --- a/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -32,10 +32,10 @@ export default function createMainProcessClient(): MainProcessClient { return ipcRenderer.invoke('main-process-force-focus-window'); }, symlinkTsh() { - return ipcRenderer.invoke('main-process-symlink-tsh'); + return ipcRenderer.invoke('main-process-symlink-tsh-macos'); }, removeTshSymlink() { - return ipcRenderer.invoke('main-process-remove-tsh-symlink'); + return ipcRenderer.invoke('main-process-remove-tsh-symlink-macos'); }, }; } From 5d6e4f9d4e09c42b2dcbed6a73152c7d7e5c217f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Wed, 4 Jan 2023 16:36:23 +0100 Subject: [PATCH 13/16] Refactor QuickInput story into two separate stories --- .../src/ui/QuickInput/QuickInput.story.tsx | 383 +++++++++++------- 1 file changed, 228 insertions(+), 155 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx index 5a1e62bde..0bee47dad 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx @@ -16,47 +16,27 @@ import React from 'react'; +import Flex from 'design/Flex'; + import AppContextProvider from 'teleterm/ui/appContextProvider'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; - -import { getEmptyPendingAccessRequest } from '../services/workspacesService/accessRequestsService'; +import { getEmptyPendingAccessRequest } from 'teleterm/ui/services/workspacesService/accessRequestsService'; +import * as types from 'teleterm/services/tshd/types'; +import { + SuggestionCmd, + SuggestionDatabase, + SuggestionServer, + SuggestionSshLogin, +} from 'teleterm/ui/services/quickInput'; import QuickInput from './QuickInput'; +import QuickInputList from './QuickInputList'; export default { title: 'Teleterm/QuickInput', }; export const Story = () => { - return ( - <> - {/* Extra bottom margin to accommodate the suggestions displayed under the input. */} - - - - - - - ); -}; - -const QuickInputDemo = (props: { - description: string; - inputValue?: string; - mb?: number; -}) => { const appContext = new MockAppContext(); appContext.workspacesService.state = { @@ -74,26 +54,6 @@ const QuickInputDemo = (props: { rootClusterUri: '/clusters/localhost', }; - const cluster = { - uri: '/clusters/localhost' as const, - name: 'Test', - leaf: false, - connected: true, - proxyHost: 'localhost:3080', - loggedInUser: { - activeRequestsList: [], - name: 'admin', - acl: {}, - sshLoginsList: [ - 'root', - 'ubuntu', - 'ansible', - 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', - ], - rolesList: [], - }, - }; - appContext.clustersService.getClusters = () => { return [cluster]; }; @@ -103,121 +63,22 @@ const QuickInputDemo = (props: { }); appContext.resourcesService.fetchServers = async () => ({ - agentsList: [ - { - uri: '/clusters/localhost/servers/foo', - tunnel: false, - name: '2018454d-ef3b-4b15-84f7-61ca213d37e3', - hostname: 'foo', - addr: 'foo.localhost', - labelsList: [ - { name: 'env', value: 'prod' }, - { name: 'kernel', value: '5.15.0-1023-aws' }, - ], - }, - { - uri: '/clusters/localhost/servers/bar', - tunnel: false, - name: '24c7aebe-4741-4464-ab69-f076fe467ebd', - hostname: 'bar', - addr: 'bar.localhost', - labelsList: [ - { name: 'env', value: 'staging' }, - { name: 'kernel', value: '5.14.1-1058-aws' }, - ], - }, - { - uri: '/clusters/localhost/servers/lorem', - tunnel: false, - name: '24c7aebe-4741-4464-ab69-f076fe467ebd', - hostname: - 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', - addr: 'lorem.localhost', - labelsList: [ - { name: 'env', value: 'staging' }, - { name: 'kernel', value: '5.14.1-1058-aws' }, - { - name: 'lorem', - value: - 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', - }, - { name: 'kernel2', value: '5.14.1-1058-aws' }, - { name: 'env2', value: 'staging' }, - { name: 'kernel3', value: '5.14.1-1058-aws' }, - ], - }, - ], + agentsList: servers, totalCount: 3, - startKey: 'foo', + startKey: '', }); appContext.resourcesService.fetchDatabases = async () => ({ - agentsList: [ - { - uri: '/clusters/localhost/dbs/postgres', - name: 'postgres', - desc: 'A PostgreSQL database', - protocol: 'postgres', - type: 'self-hosted', - hostname: 'postgres.localhost', - addr: 'postgres.localhost', - labelsList: [ - { name: 'env', value: 'prod' }, - { name: 'kernel', value: '5.15.0-1023-aws' }, - ], - }, - { - uri: '/clusters/localhost/dbs/mysql', - name: 'mysql', - desc: 'A MySQL database', - protocol: 'mysql', - type: 'self-hosted', - hostname: 'mysql.localhost', - addr: 'mysql.localhost', - labelsList: [ - { name: 'env', value: 'staging' }, - { name: 'kernel', value: '5.14.1-1058-aws' }, - ], - }, - { - uri: '/clusters/localhost/dbs/lorem', - name: 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', - desc: 'Lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet', - protocol: 'mysql', - type: 'self-hosted', - hostname: 'lorem.localhost', - addr: 'lorem.localhost', - labelsList: [ - { name: 'env', value: 'staging' }, - { name: 'kernel', value: '5.14.1-1058-aws' }, - { - name: 'lorem', - value: - 'lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet', - }, - { name: 'kernel2', value: '5.14.1-1058-aws' }, - { name: 'env2', value: 'staging' }, - { name: 'kernel3', value: '5.14.1-1058-aws' }, - ], - }, - ], - totalCount: 2, - startKey: 'foo', + agentsList: databases, + totalCount: 3, + startKey: '', }); - if (props.inputValue !== undefined) { - appContext.quickInputService.setInputValue(props.inputValue); - appContext.quickInputService.show(); - appContext.quickInputService.hide = () => {}; - } - return ( -

{props.description}

@@ -225,3 +86,215 @@ const QuickInputDemo = (props: { ); }; + +export const Suggestions = () => { + const commandSuggestions: SuggestionCmd[] = [ + { + kind: 'suggestion.cmd', + token: '', + data: { + name: 'tsh foo', + displayName: 'tsh foo', + description: 'Nulla convallis lorem ut ipsum maximus venenatis.', + }, + }, + { + kind: 'suggestion.cmd', + token: '', + data: { + name: 'tsh bar', + displayName: 'tsh bar', + description: 'Vivamus id nulla sed neque efficitur ornare nec in diam.', + }, + }, + { + kind: 'suggestion.cmd', + token: '', + data: { + name: 'tsh quux foo', + displayName: 'tsh quux foo', + description: + 'Sed porta nibh eget lacus suscipit vehicula. Curabitur eget sapien in lacus blandit pretium.', + }, + }, + { + kind: 'suggestion.cmd', + token: '', + data: { + name: 'tsh baz quux', + displayName: 'tsh baz quux', + description: 'Etiam cursus magna at feugiat ornare.', + }, + }, + ]; + + const loginSuggestions: SuggestionSshLogin[] = + cluster.loggedInUser.sshLoginsList.map(login => ({ + kind: 'suggestion.ssh-login', + token: '', + appendToToken: '', + data: login, + })); + + const serverSuggestions: SuggestionServer[] = servers.map(server => ({ + kind: 'suggestion.server', + token: '', + data: server, + })); + + const dbSuggestions: SuggestionDatabase[] = databases.map(db => ({ + kind: 'suggestion.database', + token: '', + data: db, + })); + + return ( + + + + + + + ); +}; + +const defaultWidth = 200; +const defaultHeight = 200; + +const QuickInputListWrapper = ({ + items, + width = defaultWidth, + height = defaultHeight, +}) => { + return ( +
+ {}} + /> +
+ ); +}; + +const longIdentifier = + 'lorem-ipsum-dolor-sit-amet-consectetur-adipiscing-elit-quisque-elementum-nulla'; + +const servers: types.Server[] = [ + { + uri: '/clusters/localhost/servers/foo' as const, + tunnel: false, + name: '2018454d-ef3b-4b15-84f7-61ca213d37e3', + hostname: 'foo', + addr: 'foo.localhost', + labelsList: [ + { name: 'env', value: 'prod' }, + { name: 'kernel', value: '5.15.0-1023-aws' }, + ], + }, + { + uri: '/clusters/localhost/servers/bar' as const, + tunnel: false, + name: '24c7aebe-4741-4464-ab69-f076fe467ebd', + hostname: 'bar', + addr: 'bar.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + ], + }, + { + uri: '/clusters/localhost/servers/lorem' as const, + tunnel: false, + name: '24c7aebe-4741-4464-ab69-f076fe467ebd', + hostname: longIdentifier, + addr: 'lorem.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + { name: 'lorem', value: longIdentifier }, + { name: 'kernel2', value: '5.14.1-1058-aws' }, + { name: 'env2', value: 'staging' }, + { name: 'kernel3', value: '5.14.1-1058-aws' }, + ], + }, +]; + +const databases: types.Database[] = [ + { + uri: '/clusters/localhost/dbs/postgres' as const, + name: 'postgres', + desc: 'A PostgreSQL database', + protocol: 'postgres', + type: 'self-hosted', + hostname: 'postgres.localhost', + addr: 'postgres.localhost', + labelsList: [ + { name: 'env', value: 'prod' }, + { name: 'kernel', value: '5.15.0-1023-aws' }, + ], + }, + { + uri: '/clusters/localhost/dbs/mysql' as const, + name: 'mysql', + desc: 'A MySQL database', + protocol: 'mysql', + type: 'self-hosted', + hostname: 'mysql.localhost', + addr: 'mysql.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + ], + }, + { + uri: '/clusters/localhost/dbs/lorem' as const, + name: longIdentifier, + desc: 'Vestibulum ut blandit est, sed dapibus sem. Pellentesque egestas mi eu scelerisque ultricies.', + protocol: 'mysql', + type: 'self-hosted', + hostname: 'lorem.localhost', + addr: 'lorem.localhost', + labelsList: [ + { name: 'env', value: 'staging' }, + { name: 'kernel', value: '5.14.1-1058-aws' }, + { name: 'lorem', value: longIdentifier }, + { name: 'kernel2', value: '5.14.1-1058-aws' }, + { name: 'env2', value: 'staging' }, + { name: 'kernel3', value: '5.14.1-1058-aws' }, + ], + }, +]; + +const cluster = { + uri: '/clusters/localhost' as const, + name: 'Test', + leaf: false, + connected: true, + proxyHost: 'localhost:3080', + loggedInUser: { + activeRequestsList: [], + name: 'admin', + acl: {}, + sshLoginsList: ['root', 'ubuntu', 'ansible', longIdentifier], + rolesList: [], + }, +}; From d3ed0c39cf53ef8eee29403a4a95567186eb1a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Wed, 4 Jan 2023 16:39:11 +0100 Subject: [PATCH 14/16] Use assertUnreachable --- packages/teleterm/src/ui/QuickInput/useQuickInput.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts index 77dac962f..e5ebe7bfb 100644 --- a/packages/teleterm/src/ui/QuickInput/useQuickInput.ts +++ b/packages/teleterm/src/ui/QuickInput/useQuickInput.ts @@ -31,7 +31,7 @@ import { import { routing } from 'teleterm/ui/uri'; import { KeyboardShortcutType } from 'teleterm/services/config'; -import { retryWithRelogin } from '../utils'; +import { assertUnreachable, retryWithRelogin } from '../utils'; export default function useQuickInput() { const appContext = useAppContext(); @@ -131,8 +131,7 @@ export default function useQuickInput() { break; } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const exhaustiveCheck: never = command; + assertUnreachable(command); } } From 49804dcf309934139fd8c045ed192e220f80c63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Wed, 4 Jan 2023 16:55:19 +0100 Subject: [PATCH 15/16] Refactor autocomplete commands --- .../src/ui/QuickInput/QuickInput.story.tsx | 4 --- .../teleterm/src/ui/commandLauncher.test.ts | 18 +++++----- packages/teleterm/src/ui/commandLauncher.ts | 35 +++++++++---------- .../src/ui/services/quickInput/parsers.ts | 5 +-- .../quickInput/quickInputService.test.ts | 2 -- .../src/ui/services/quickInput/types.ts | 2 +- 6 files changed, 29 insertions(+), 37 deletions(-) diff --git a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx index 0bee47dad..d69064560 100644 --- a/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx +++ b/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx @@ -93,7 +93,6 @@ export const Suggestions = () => { kind: 'suggestion.cmd', token: '', data: { - name: 'tsh foo', displayName: 'tsh foo', description: 'Nulla convallis lorem ut ipsum maximus venenatis.', }, @@ -102,7 +101,6 @@ export const Suggestions = () => { kind: 'suggestion.cmd', token: '', data: { - name: 'tsh bar', displayName: 'tsh bar', description: 'Vivamus id nulla sed neque efficitur ornare nec in diam.', }, @@ -111,7 +109,6 @@ export const Suggestions = () => { kind: 'suggestion.cmd', token: '', data: { - name: 'tsh quux foo', displayName: 'tsh quux foo', description: 'Sed porta nibh eget lacus suscipit vehicula. Curabitur eget sapien in lacus blandit pretium.', @@ -121,7 +118,6 @@ export const Suggestions = () => { kind: 'suggestion.cmd', token: '', data: { - name: 'tsh baz quux', displayName: 'tsh baz quux', description: 'Etiam cursus magna at feugiat ornare.', }, diff --git a/packages/teleterm/src/ui/commandLauncher.test.ts b/packages/teleterm/src/ui/commandLauncher.test.ts index 1abde3cf2..c39a30dce 100644 --- a/packages/teleterm/src/ui/commandLauncher.test.ts +++ b/packages/teleterm/src/ui/commandLauncher.test.ts @@ -7,10 +7,10 @@ it('returns tsh install & uninstall autocomplete command on macOS', () => { const commandLauncher = new CommandLauncher(appContext); const autocompleteCommandNames = commandLauncher .getAutocompleteCommands() - .map(c => c.name); + .map(c => c.displayName); - expect(autocompleteCommandNames).toContain('autocomplete.tsh-install'); - expect(autocompleteCommandNames).toContain('autocomplete.tsh-uninstall'); + expect(autocompleteCommandNames).toContain('tsh install'); + expect(autocompleteCommandNames).toContain('tsh uninstall'); }); it('does not return tsh install & uninstall autocomplete command on Linux', () => { @@ -18,10 +18,10 @@ it('does not return tsh install & uninstall autocomplete command on Linux', () = const commandLauncher = new CommandLauncher(appContext); const autocompleteCommandNames = commandLauncher .getAutocompleteCommands() - .map(c => c.name); + .map(c => c.displayName); - expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-install'); - expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-uninstall'); + expect(autocompleteCommandNames).not.toContain('tsh install'); + expect(autocompleteCommandNames).not.toContain('tsh uninstall'); }); it('does not return tsh install & uninstall autocomplete command on Windows', () => { @@ -29,8 +29,8 @@ it('does not return tsh install & uninstall autocomplete command on Windows', () const commandLauncher = new CommandLauncher(appContext); const autocompleteCommandNames = commandLauncher .getAutocompleteCommands() - .map(c => c.name); + .map(c => c.displayName); - expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-install'); - expect(autocompleteCommandNames).not.toContain('autocomplete.tsh-uninstall'); + expect(autocompleteCommandNames).not.toContain('tsh install'); + expect(autocompleteCommandNames).not.toContain('tsh uninstall'); }); diff --git a/packages/teleterm/src/ui/commandLauncher.ts b/packages/teleterm/src/ui/commandLauncher.ts index 6899f4886..c58c62b11 100644 --- a/packages/teleterm/src/ui/commandLauncher.ts +++ b/packages/teleterm/src/ui/commandLauncher.ts @@ -24,6 +24,7 @@ import { } from 'teleterm/ui/uri'; import { tsh } from 'teleterm/ui/services/clusters/types'; import { TrackedKubeConnection } from 'teleterm/ui/services/connectionTracker'; +import { Platform } from 'teleterm/mainProcess/types'; const commands = { // For handling "tsh ssh" executed from the command bar. @@ -208,30 +209,32 @@ const commands = { } }, }, +}; - 'autocomplete.tsh-ssh': { +const autocompleteCommands: { + displayName: string; + description: string; + platforms?: Array; +}[] = [ + { displayName: 'tsh ssh', description: 'Run shell or execute a command on a remote SSH node', - run() {}, }, - 'autocomplete.tsh-proxy-db': { + { displayName: 'tsh proxy db', description: 'Start a local proxy for a database connection', - run() {}, }, - 'autocomplete.tsh-install': { + { displayName: 'tsh install', description: 'Install tsh in PATH', platforms: ['darwin'], - run() {}, }, - 'autocomplete.tsh-uninstall': { + { displayName: 'tsh uninstall', description: 'Uninstall tsh from PATH', platforms: ['darwin'], - run() {}, }, -}; +]; export class CommandLauncher { appContext: IAppContext; @@ -247,16 +250,10 @@ export class CommandLauncher { getAutocompleteCommands() { const { platform } = this.appContext.mainProcessClient.getRuntimeSettings(); - return Object.entries(commands) - .filter(([key]) => key.startsWith('autocomplete.')) - .filter(([, command]) => { - const platforms = command['platforms']; - return ( - !platforms || - (Array.isArray(platforms) && platforms.includes(platform)) - ); - }) - .map(([key, value]) => ({ name: key, ...value })); + return autocompleteCommands.filter(command => { + const platforms = command.platforms; + return !command.platforms || platforms.includes(platform); + }); } } diff --git a/packages/teleterm/src/ui/services/quickInput/parsers.ts b/packages/teleterm/src/ui/services/quickInput/parsers.ts index 352186bf5..f12b0bce0 100644 --- a/packages/teleterm/src/ui/services/quickInput/parsers.ts +++ b/packages/teleterm/src/ui/services/quickInput/parsers.ts @@ -43,7 +43,6 @@ export class QuickCommandParser implements QuickInputParser { // TODO(ravicious): Handle env vars. parse(rawInput: string): ParseResult { - const autocompleteCommands = this.launcher.getAutocompleteCommands(); // We can safely ignore any whitespace at the start. However, `startIndex` needs to account for // any removed whitespace. const input = rawInput.trimStart(); @@ -54,6 +53,8 @@ export class QuickCommandParser implements QuickInputParser { // Return all commands if there's no input. if (input === '') { + const autocompleteCommands = this.launcher.getAutocompleteCommands(); + return { targetToken, command: { kind: 'command.unknown' }, @@ -135,7 +136,7 @@ export class QuickCommandParser implements QuickInputParser { } private mapAutocompleteCommandsToSuggestions( - commands: { name: string; displayName: string; description: string }[] + commands: { displayName: string; description: string }[] ): SuggestionCmd[] { return commands.map(cmd => { const acceptsNoArguments = diff --git a/packages/teleterm/src/ui/services/quickInput/quickInputService.test.ts b/packages/teleterm/src/ui/services/quickInput/quickInputService.test.ts index dc66d47c7..ae60bfc49 100644 --- a/packages/teleterm/src/ui/services/quickInput/quickInputService.test.ts +++ b/packages/teleterm/src/ui/services/quickInput/quickInputService.test.ts @@ -537,7 +537,6 @@ test('picking a command suggestion in an empty input autocompletes the command', kind: 'suggestion.cmd', token: 'tsh ssh', data: { - name: 'autocomplete.tsh-ssh', displayName: 'tsh ssh', description: '', }, @@ -564,7 +563,6 @@ test('picking a command suggestion in an input with a single space preserves the kind: 'suggestion.cmd', token: 'tsh ssh', data: { - name: 'autocomplete.tsh-ssh', displayName: 'tsh ssh', description: '', }, diff --git a/packages/teleterm/src/ui/services/quickInput/types.ts b/packages/teleterm/src/ui/services/quickInput/types.ts index 53e2b2b9d..930d54b0c 100644 --- a/packages/teleterm/src/ui/services/quickInput/types.ts +++ b/packages/teleterm/src/ui/services/quickInput/types.ts @@ -9,7 +9,7 @@ type SuggestionBase = { export type SuggestionCmd = SuggestionBase< 'suggestion.cmd', - { name: string; displayName: string; description: string } + { displayName: string; description: string } >; export type SuggestionSshLogin = SuggestionBase< From 2daafe917f9b286aa7de23671ccd217dedb1a605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Wed, 4 Jan 2023 17:24:21 +0100 Subject: [PATCH 16/16] Add MacOs suffix to MainProcessClient methods --- packages/teleterm/src/mainProcess/fixtures/mocks.ts | 4 ++-- packages/teleterm/src/mainProcess/mainProcessClient.ts | 4 ++-- packages/teleterm/src/mainProcess/types.ts | 4 ++-- packages/teleterm/src/ui/commandLauncher.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/teleterm/src/mainProcess/fixtures/mocks.ts b/packages/teleterm/src/mainProcess/fixtures/mocks.ts index 24c003b9c..cab95c27f 100644 --- a/packages/teleterm/src/mainProcess/fixtures/mocks.ts +++ b/packages/teleterm/src/mainProcess/fixtures/mocks.ts @@ -37,10 +37,10 @@ export class MockMainProcessClient implements MainProcessClient { forceFocusWindow() {} - async symlinkTsh() { + async symlinkTshMacOs() { return true; } - async removeTshSymlink() { + async removeTshSymlinkMacOs() { return true; } } diff --git a/packages/teleterm/src/mainProcess/mainProcessClient.ts b/packages/teleterm/src/mainProcess/mainProcessClient.ts index ebc0b9789..763af92fb 100644 --- a/packages/teleterm/src/mainProcess/mainProcessClient.ts +++ b/packages/teleterm/src/mainProcess/mainProcessClient.ts @@ -31,10 +31,10 @@ export default function createMainProcessClient(): MainProcessClient { forceFocusWindow() { return ipcRenderer.invoke('main-process-force-focus-window'); }, - symlinkTsh() { + symlinkTshMacOs() { return ipcRenderer.invoke('main-process-symlink-tsh-macos'); }, - removeTshSymlink() { + removeTshSymlinkMacOs() { return ipcRenderer.invoke('main-process-remove-tsh-symlink-macos'); }, }; diff --git a/packages/teleterm/src/mainProcess/types.ts b/packages/teleterm/src/mainProcess/types.ts index c4f596ce0..e06916c8d 100644 --- a/packages/teleterm/src/mainProcess/types.ts +++ b/packages/teleterm/src/mainProcess/types.ts @@ -47,12 +47,12 @@ export type MainProcessClient = { * The promise returns true if tsh got successfully symlinked, false if the user closed the * osascript prompt. The promise gets rejected if osascript encountered an error. */ - symlinkTsh(): Promise; + symlinkTshMacOs(): Promise; /** * The promise returns true if the tsh symlink got removed, false if the user closed the osascript * prompt. The promise gets rejected if osascript encountered an error. */ - removeTshSymlink(): Promise; + removeTshSymlinkMacOs(): Promise; }; export type ChildProcessAddresses = { diff --git a/packages/teleterm/src/ui/commandLauncher.ts b/packages/teleterm/src/ui/commandLauncher.ts index c58c62b11..d9870d8ff 100644 --- a/packages/teleterm/src/ui/commandLauncher.ts +++ b/packages/teleterm/src/ui/commandLauncher.ts @@ -97,7 +97,7 @@ const commands = { displayName: '', description: '', run(ctx: IAppContext) { - ctx.mainProcessClient.symlinkTsh().then( + ctx.mainProcessClient.symlinkTshMacOs().then( isSymlinked => { if (isSymlinked) { ctx.notificationsService.notifyInfo( @@ -119,7 +119,7 @@ const commands = { displayName: '', description: '', run(ctx: IAppContext) { - ctx.mainProcessClient.removeTshSymlink().then( + ctx.mainProcessClient.removeTshSymlinkMacOs().then( isRemoved => { if (isRemoved) { ctx.notificationsService.notifyInfo(