From cc24e5e03eee430a50c833aa1c21d76c54fc9205 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Tue, 8 Dec 2020 10:11:24 -0500 Subject: [PATCH 1/9] feat: Add node spawn promise wrapper --- .../routes/scopes/scope/projects/targets.js | 9 ++- ui/desktop/app/services/ipc.js | 2 +- ui/desktop/electron-app/src/handlers.js | 56 ++++------------ ui/desktop/electron-app/src/spawn-promise.js | 65 +++++++++++++++++++ yarn.lock | 27 ++------ 5 files changed, 91 insertions(+), 68 deletions(-) create mode 100644 ui/desktop/electron-app/src/spawn-promise.js diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index 199a09878..a42d2bb54 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -47,10 +47,17 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { @action async connect(model) { try { - await this.ipc.invoke('connect', { + await this.ipc.invoke('cli'); + const connect = this.ipc.invoke('connect', { target_id: model.target.id, token: this.session.data.authenticated.token, }); + + connect.then((data) => { + this.notify.success(JSON.stringify(data)); + }, (e) => { + this.notify.error(e.message, { closeAfter: null }); + }); } catch(e) { this.notify.error(e.message, { closeAfter: null }); } diff --git a/ui/desktop/app/services/ipc.js b/ui/desktop/app/services/ipc.js index d1295d052..8be63359f 100644 --- a/ui/desktop/app/services/ipc.js +++ b/ui/desktop/app/services/ipc.js @@ -17,7 +17,7 @@ import { Promise } from 'rsvp'; * knowledge of the main process or the Electron context. In fact a main * process and Electron context are not even required. * - * In an Electron context, the `preload.js` script sets up a `window.postMessge` + * In an Electron context, the `preload.js` script sets up a `window.postMessage` * listener which acts as proxy layer between the main and renderer processes. * It waits for incoming requests from the renderer and forwards them to main. * Responses from the main process are forwarded back via a response port diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 8803ddd6e..99b859a65 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -1,6 +1,6 @@ const handle = require('./ipc-handler'); const { lookpath } = require('lookpath'); -const { exec } = require('child_process'); +const spawnPromise = require('./spawn-promise'); let origin = ''; @@ -20,51 +20,19 @@ handle('getOrigin', () => origin); */ handle('setOrigin', (requestOrigin) => origin = requestOrigin); +/** + * Detect boundary cli in env path + */ +handle('cli', async (event, request) => { + const cliPath = await lookpath('boundary'); + if(!cliPath) throw new Error({ message: 'Cannot find boundary cli.'}) + return cliPath; +}); + /** * Establishes a boundary session and returns session details. - * TODO: Return session details. */ handle('connect', async ({ target_id, token }) => { - const cliAvailable = await lookpath('boundary'); - if(!cliAvailable) { throw new Error('CLI unavailable.'); } - - const connectCmd = `boundary connect -target-id=${target_id} -token=${token}`; - - // TODO: this isn't the *exact* promise, because apparently exec doesn't - // call the success callback until the process exits. We need a way to - // resolve the promise if the process doesn't exit AND we receive valid - // JSON on stdout, since this indicates the connection was successful. - // - // TODO: if there is an existing promiserified exec tool, we might consider - // using it to help make this cleaner. - - // You can throw exceptions, or allow them to occur, and this is supported. - // Exceptions thrown in this way will be returned to the UI as an - // IPC rejection. However in most cases, you probably want to return a - // specific exception object of your own design, since Error instances - // originating from the underlying system will be noisey and not very helpful - // to users. - // - //throw new Error('Random error! Should still work!'); - //throw { message: 'Random POJO exception! Should still work!' }; - - return new Promise((resolve, reject) => { - exec(connectCmd, (error, stdout, stderr) => { - if (error) { - // For example, you could just reject(error) here, but this is the raw - // error from the underlying exec implementation. It's not a very - // helpful message for users, so it's recommended to craft nice - // POJO representation and throw it or promise->reject it. - // - // TODO: connection errors may now return JSON on stderr, which we - // would attempt to parse here to use a basis for rejection. - const { code } = error; - return reject({ - code, - message: `OH NOSE! Command failed with code ${code}` - }); - } - return resolve(stdout); - }); - }); + const connectCmd = ['connect', `-target-id=${target_id}`, `-token=${token}`, '-format=json']; + return spawnPromise(connectCmd); }); diff --git a/ui/desktop/electron-app/src/spawn-promise.js b/ui/desktop/electron-app/src/spawn-promise.js new file mode 100644 index 000000000..4cae62e46 --- /dev/null +++ b/ui/desktop/electron-app/src/spawn-promise.js @@ -0,0 +1,65 @@ +const { spawn } = require('child_process'); + +/** + * Attempt to parse output string to JSON. + * When JSON is successfully parsed, consider stdout fully read. +*/ +// TODO: Potential use for BufferList? +const resolveOutput = (resolve, outputStream) => { + try { + resolve(JSON.parse(outputStream)); + } catch (e) { + // Ignore JSON parse errors while stdout buffers. + } +}; + +// You can throw exceptions, or allow them to occur, and this is supported. +// Exceptions thrown in this way will be returned to the UI as an +// IPC rejection. However in most cases, you probably want to return a +// specific exception object of your own design, since Error instances +// originating from the underlying system will be noisey and not very helpful +// to users. +// +//throw new Error('Random error! Should still work!'); +//throw { message: 'Random POJO exception! Should still work!' }; + +// For example, you could just reject(error) here, but this is the raw +// error from the underlying exec implementation. It's not a very +// helpful message for users, so it's recommended to craft nice +// POJO representation and throw it or promise->reject it. +// +// TODO: connection errors may now return JSON on stderr, which we +// would attempt to parse here to use a basis for rejection. +const rejectError = (reject, message, code=10001) => { + const error = new Error(`Connect error: ${message}`); + error.code = code; + reject(error); +}; + +module.exports = function spawnPromise (command) { + return new Promise((resolve, reject) => { + const childProcess = spawn('boundary', command); + let outputStream = ''; + let errorStream = ''; + + childProcess.stdout.on('data', (data) => { + outputStream += data.toString(); + resolveOutput(resolve, outputStream); + }); + + // Handle exit of spawned process + childProcess.on('close', (code) => { + if(code === 0) { + resolveOutput(resolve, outputStream); + }else { + rejectError(reject, errorStream); + } + }); + + // Handle error + childProcess.on('error', (err) => rejectError(reject, err)); + + // TODO: Figure out what scenarios this is needed in addition to error event listener + childProcess.stderr.on('data', (data) => errorStream += data.toString()); + }); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6e151364c..1414b408a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7507,7 +7507,7 @@ debug@~4.1.0: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -12204,7 +12204,7 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -13911,11 +13911,6 @@ lodash._baseflatten@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -13924,16 +13919,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*, lodash._bindcallback@^3.0.0: +lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - lodash._createassigner@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" @@ -13943,19 +13933,12 @@ lodash._createassigner@^3.0.0: lodash._isiterateecall "^3.0.0" lodash.restparam "^3.0.0" -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: +lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= @@ -14168,7 +14151,7 @@ lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.restparam@*, lodash.restparam@^3.0.0: +lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= From 7d0876b7adff7e61edc2321e13289b91b204e21e Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 8 Dec 2020 11:19:05 -0500 Subject: [PATCH 2/9] chore: remove API_HOST env var support for desktop and update ENABLE_MIRAGE var --- ui/desktop/config/environment.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index e4b94e3fc..05aaad491 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -1,8 +1,7 @@ 'use strict'; const APP_NAME = process.env.APP_NAME || 'Boundary'; -const API_HOST = process.env.API_HOST || ''; -const ENABLE_MIRAGE = process.env.API_HOST ? false : true; +const ENABLE_MIRAGE = process.env.ENABLE_MIRAGE === 'false' ? false : true; module.exports = function (environment) { let ENV = { @@ -35,7 +34,8 @@ module.exports = function (environment) { }, api: { - host: API_HOST, + // there is no default API host in desktop + //host: API_HOST, namespace: 'v1', }, From 3077488a638122c79d457a9f6d05e4f598c0d0c1 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Tue, 8 Dec 2020 12:21:41 -0500 Subject: [PATCH 3/9] feat: Remove spawn error event listener Remove spawn error event listener is favour of using stderr stream. --- .../routes/scopes/scope/projects/targets.js | 2 +- ui/desktop/electron-app/src/handlers.js | 10 ++++-- ui/desktop/electron-app/src/spawn-promise.js | 32 +++++++------------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index a42d2bb54..b98e9f416 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -56,7 +56,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { connect.then((data) => { this.notify.success(JSON.stringify(data)); }, (e) => { - this.notify.error(e.message, { closeAfter: null }); + this.notify.error(JSON.stringify(e.message), { closeAfter: null }); }); } catch(e) { this.notify.error(e.message, { closeAfter: null }); diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 99b859a65..d747c556c 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -23,7 +23,7 @@ handle('setOrigin', (requestOrigin) => origin = requestOrigin); /** * Detect boundary cli in env path */ -handle('cli', async (event, request) => { +handle('cli', async () => { const cliPath = await lookpath('boundary'); if(!cliPath) throw new Error({ message: 'Cannot find boundary cli.'}) return cliPath; @@ -33,6 +33,12 @@ handle('cli', async (event, request) => { * Establishes a boundary session and returns session details. */ handle('connect', async ({ target_id, token }) => { - const connectCmd = ['connect', `-target-id=${target_id}`, `-token=${token}`, '-format=json']; + const connectCmd = [ + 'connect', + `-target-id=${target_id}`, + `-token=${token}`, + '-format=json', + '--output-json-errors' + ]; return spawnPromise(connectCmd); }); diff --git a/ui/desktop/electron-app/src/spawn-promise.js b/ui/desktop/electron-app/src/spawn-promise.js index 4cae62e46..1fdac9a6e 100644 --- a/ui/desktop/electron-app/src/spawn-promise.js +++ b/ui/desktop/electron-app/src/spawn-promise.js @@ -27,13 +27,13 @@ const resolveOutput = (resolve, outputStream) => { // error from the underlying exec implementation. It's not a very // helpful message for users, so it's recommended to craft nice // POJO representation and throw it or promise->reject it. -// -// TODO: connection errors may now return JSON on stderr, which we -// would attempt to parse here to use a basis for rejection. -const rejectError = (reject, message, code=10001) => { - const error = new Error(`Connect error: ${message}`); - error.code = code; - reject(error); +const rejectError = (reject, message) => { + try { + const errorMessage = JSON.parse(message); + reject(new Error(errorMessage.error)); + } catch (e) { + // Ignore JSON parse errors while stderr buffers. + } }; module.exports = function spawnPromise (command) { @@ -46,20 +46,10 @@ module.exports = function spawnPromise (command) { outputStream += data.toString(); resolveOutput(resolve, outputStream); }); - - // Handle exit of spawned process - childProcess.on('close', (code) => { - if(code === 0) { - resolveOutput(resolve, outputStream); - }else { - rejectError(reject, errorStream); - } + + childProcess.stderr.on('data', (data) => { + errorStream += data.toString(); + rejectError(reject, errorStream); }); - - // Handle error - childProcess.on('error', (err) => rejectError(reject, err)); - - // TODO: Figure out what scenarios this is needed in addition to error event listener - childProcess.stderr.on('data', (data) => errorStream += data.toString()); }); } \ No newline at end of file From 9bfe3d530e2238910f44df9260636aff11a24797 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 8 Dec 2020 12:27:41 -0500 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20confirmations=20?= =?UTF-8?q?should=20be=20enabled=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/core/addon/services/confirm.js | 4 +++- addons/core/tests/dummy/config/environment.js | 4 +++- ui/admin/config/environment.js | 1 - ui/desktop/config/environment.js | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/core/addon/services/confirm.js b/addons/core/addon/services/confirm.js index df271f219..e63a76fdf 100644 --- a/addons/core/addon/services/confirm.js +++ b/addons/core/addon/services/confirm.js @@ -19,9 +19,11 @@ export default class ConfirmationsService extends Service { /** * When the confirm service is disabled, it always returns resolving promises. + * By default the service is enabled but it is useful to disable it for test + * purposes. * @type {boolean} */ - enabled = config.enableConfirmService; + enabled = config.enableConfirmService ?? true; /** * @type {Array} diff --git a/addons/core/tests/dummy/config/environment.js b/addons/core/tests/dummy/config/environment.js index 84720c90a..2a5061f98 100644 --- a/addons/core/tests/dummy/config/environment.js +++ b/addons/core/tests/dummy/config/environment.js @@ -39,7 +39,9 @@ module.exports = function (environment) { 'ember-cli-mirage': { directory: '../api/mirage' - } + }, + + enableConfirmService: false }; if (environment === "development") { diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index c071cd17c..7cd8efa3e 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -36,7 +36,6 @@ module.exports = function (environment) { notifyTimeout: 4000, sessionPollingTimeoutSeconds: 2.5, - enableConfirmService: true, documentation: { baseURL: 'https://boundaryproject.io/help/admin-ui', diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index 05aaad491..ed64f6253 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -70,6 +70,8 @@ module.exports = function (environment) { // Notification timeout should be 0 for fast tests ENV.notifyTimeout = 0; + + ENV.enableConfirmService = false; } if (environment === 'production') { From dd94fca50f1f2e1b87c663ebf80e91b8d7b56846 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 8 Dec 2020 15:20:59 -0500 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20initial=20success/er?= =?UTF-8?q?ror=20modals=20on=20connect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/core/translations/actions/en-us.yaml | 1 + addons/core/translations/en-us.yaml | 1 + .../addon/styles/rose/components/_dialog.scss | 14 ++++ .../routes/scopes/scope/projects/targets.js | 15 ++-- ui/desktop/app/templates/application.hbs | 73 +++++++++++++++++++ 5 files changed, 96 insertions(+), 8 deletions(-) diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index 638f3b3a9..1a378edc3 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -12,6 +12,7 @@ ok: OK deny: Deny help: Need help? back: Back +retry: Retry get-topic-help: Get help for this topic copy-to-clipboard: Copy to Clipboard authenticate: Authenticate diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index 0af3c50e3..e611da6b5 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -30,6 +30,7 @@ questions: delete-confirm: Are you sure you want to delete this resource? remove-confirm: Are you sure you want to remove this association? abandon-confirm: Are you sure you want to navigate away? Your unsaved changes will be lost. + retry-confirm: Would you like to retry? themes: system: System Default light: Light diff --git a/addons/rose/addon/styles/rose/components/_dialog.scss b/addons/rose/addon/styles/rose/components/_dialog.scss index 08763098e..1ff395f63 100644 --- a/addons/rose/addon/styles/rose/components/_dialog.scss +++ b/addons/rose/addon/styles/rose/components/_dialog.scss @@ -20,6 +20,13 @@ $xxxxs: sizing.rems(xxxxs); // 1 box-shadow: 0 $xs $s rgba(var(--stark-components), var(--opacity-2)), 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-3)); + &.rose-dialog-success { + --headerColor: var(--success-starker-1); + --headerIconColor: var(--success); + --headerBackgroundColor: var(--success-subtler-2); + --headerBorderColor: var(--success); + } + &.rose-dialog-warning { --headerColor: var(--warning-starker-1); --headerIconColor: var(--warning); @@ -27,6 +34,13 @@ $xxxxs: sizing.rems(xxxxs); // 1 --headerBorderColor: var(--warning); } + &.rose-dialog-error { + --headerColor: var(--failure-starker-1); + --headerIconColor: var(--failure); + --headerBackgroundColor: var(--failure-subtler-2); + --headerBorderColor: var(--failure); + } + .rose-dialog-header { display: flex; align-items: center; diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index a42d2bb54..9d98bb1ab 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -9,6 +9,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { @service ipc; @service session; @service notify; + @service confirm; // =methods @@ -48,18 +49,16 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { async connect(model) { try { await this.ipc.invoke('cli'); - const connect = this.ipc.invoke('connect', { + const connection = await this.ipc.invoke('connect', { target_id: model.target.id, token: this.session.data.authenticated.token, }); - - connect.then((data) => { - this.notify.success(JSON.stringify(data)); - }, (e) => { - this.notify.error(e.message, { closeAfter: null }); - }); + // Show the user a modal with basic connection info. + this.confirm.confirm(connection, { isConnectSuccess: true }); } catch(e) { - this.notify.error(e.message, { closeAfter: null }); + this.confirm.confirm(e.message, { isConnectError: true }) + .then(() => this.connect(model)) + .catch(() => null /* no op */); } } } diff --git a/ui/desktop/app/templates/application.hbs b/ui/desktop/app/templates/application.hbs index dacda7d01..07e8c79dc 100644 --- a/ui/desktop/app/templates/application.hbs +++ b/ui/desktop/app/templates/application.hbs @@ -45,3 +45,76 @@ {{message.text}} + + + {{#if confirmation.options.isConnectSuccess}} + + +

+ {{confirmation.text.address}} +
+ {{confirmation.text.port}} +

+
+ + + {{t "actions.ok"}} + + +
+ {{else if confirmation.options.isConnectError}} + + +

+ {{confirmation.text}} +

+

+ {{t "questions.retry-confirm"}} +

+
+ + + {{t "actions.retry"}} + + + {{t "actions.cancel"}} + + +
+ {{else}} + + +

{{confirmation.text}}

+
+ + + {{t "actions.ok"}} + + + {{t "actions.cancel"}} + + +
+ {{/if}} +
From b7fe0c23347753764740e010bdd74c6c4f61c487 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 9 Dec 2020 16:31:14 -0500 Subject: [PATCH 6/9] refactor: spawn promise cleanup --- .../routes/scopes/scope/projects/targets.js | 10 +++--- .../scopes/scope/projects/targets/index.hbs | 1 + ui/desktop/electron-app/src/handlers.js | 4 +-- ui/desktop/electron-app/src/spawn-promise.js | 33 +++++++------------ 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index b98e9f416..0ffa21a19 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -47,16 +47,18 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { @action async connect(model) { try { - await this.ipc.invoke('cli'); + // Check for cli + const cliPath = await this.ipc.invoke('cliPath'); + if(!cliPath) throw new Error('Cannot find boundary cli.') + + // Create target session const connect = this.ipc.invoke('connect', { target_id: model.target.id, token: this.session.data.authenticated.token, }); - connect.then((data) => { + await connect.then((data) => { this.notify.success(JSON.stringify(data)); - }, (e) => { - this.notify.error(JSON.stringify(e.message), { closeAfter: null }); }); } catch(e) { this.notify.error(e.message, { closeAfter: null }); diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs index 7d2ea2383..8c3c463e5 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -34,6 +34,7 @@ {{model.project.name}} +

{{model.project.id}}

diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index d747c556c..3dc473c23 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -21,9 +21,9 @@ handle('getOrigin', () => origin); handle('setOrigin', (requestOrigin) => origin = requestOrigin); /** - * Detect boundary cli in env path + * Detect boundary cli path */ -handle('cli', async () => { +handle('cliPath', async () => { const cliPath = await lookpath('boundary'); if(!cliPath) throw new Error({ message: 'Cannot find boundary cli.'}) return cliPath; diff --git a/ui/desktop/electron-app/src/spawn-promise.js b/ui/desktop/electron-app/src/spawn-promise.js index 1fdac9a6e..5fa22cf4c 100644 --- a/ui/desktop/electron-app/src/spawn-promise.js +++ b/ui/desktop/electron-app/src/spawn-promise.js @@ -1,17 +1,17 @@ const { spawn } = require('child_process'); -/** - * Attempt to parse output string to JSON. - * When JSON is successfully parsed, consider stdout fully read. -*/ -// TODO: Potential use for BufferList? -const resolveOutput = (resolve, outputStream) => { +// Verify json +const isJSON = (data) => { + let jsonData; + if(typeof data !== 'string') data = JSON.stringify(data) try { - resolve(JSON.parse(outputStream)); + jsonData = JSON.parse(data); } catch (e) { - // Ignore JSON parse errors while stdout buffers. + // Ignore parse errors } -}; + + return (typeof jsonData === 'object'); +} // You can throw exceptions, or allow them to occur, and this is supported. // Exceptions thrown in this way will be returned to the UI as an @@ -27,15 +27,6 @@ const resolveOutput = (resolve, outputStream) => { // error from the underlying exec implementation. It's not a very // helpful message for users, so it's recommended to craft nice // POJO representation and throw it or promise->reject it. -const rejectError = (reject, message) => { - try { - const errorMessage = JSON.parse(message); - reject(new Error(errorMessage.error)); - } catch (e) { - // Ignore JSON parse errors while stderr buffers. - } -}; - module.exports = function spawnPromise (command) { return new Promise((resolve, reject) => { const childProcess = spawn('boundary', command); @@ -44,12 +35,12 @@ module.exports = function spawnPromise (command) { childProcess.stdout.on('data', (data) => { outputStream += data.toString(); - resolveOutput(resolve, outputStream); + if(isJSON(outputStream)) resolve(JSON.parse(outputStream)); }); childProcess.stderr.on('data', (data) => { errorStream += data.toString(); - rejectError(reject, errorStream); + if(isJSON(errorStream)) reject(new Error(JSON.parse(errorStream)?.error)); }); }); -} \ No newline at end of file +} From 0039e5d2e987dbde00f816b4cafd7006c838c649 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 9 Dec 2020 17:16:38 -0500 Subject: [PATCH 7/9] feat: Add intl connection details --- addons/core/translations/states/en-us.yaml | 3 +++ ui/desktop/app/routes/scopes/scope/projects/targets.js | 6 ++++-- ui/desktop/app/templates/application.hbs | 10 +++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/addons/core/translations/states/en-us.yaml b/addons/core/translations/states/en-us.yaml index 96b1fd92c..1edb1e6ce 100644 --- a/addons/core/translations/states/en-us.yaml +++ b/addons/core/translations/states/en-us.yaml @@ -3,3 +3,6 @@ success: Success warning: Warning error: Error copied: Copied +connection: + success: + description: "Local proxy address ({protocol}): {address}:{port}" diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index a88a5e5e1..f8c23251e 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -58,8 +58,10 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { token: this.session.data.authenticated.token, }); - // Show the user a modal with basic connection info. - this.confirm.confirm(connection, { isConnectSuccess: true }); + await connection.then((details) => { + // Show the user a modal with basic connection info. + this.confirm.confirm(details, { isConnectSuccess: true }); + }); } catch(e) { this.confirm.confirm(e.message, { isConnectError: true }) .then(() => this.connect(model)) diff --git a/ui/desktop/app/templates/application.hbs b/ui/desktop/app/templates/application.hbs index 07e8c79dc..6dfa45877 100644 --- a/ui/desktop/app/templates/application.hbs +++ b/ui/desktop/app/templates/application.hbs @@ -57,11 +57,11 @@ @dismiss={{accept}} as |dialog|> -

- {{confirmation.text.address}} -
- {{confirmation.text.port}} -

+ {{t 'states.connection.success.description' + port=confirmation.text.port + address=confirmation.text.address + protocol=confirmation.text.protocol + }}
From 99282f5bf939460d29f8b34b1f81a00d24c417b6 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 10 Dec 2020 16:41:26 -0500 Subject: [PATCH 8/9] refactor: Move cli commands to a new module Separate cli interaction with ipc handlers using new boundary-cli module. --- .../routes/scopes/scope/projects/targets.js | 13 ++++----- ui/desktop/config/environment.js | 1 - ui/desktop/electron-app/src/boundary-cli.js | 22 +++++++++++++++ ui/desktop/electron-app/src/handlers.js | 26 +++++++----------- ui/desktop/electron-app/src/spawn-promise.js | 17 ++++++------ yarn.lock | 27 ++++--------------- 6 files changed, 51 insertions(+), 55 deletions(-) create mode 100644 ui/desktop/electron-app/src/boundary-cli.js diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets.js b/ui/desktop/app/routes/scopes/scope/projects/targets.js index f8c23251e..55f9dbdf0 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets.js @@ -48,22 +48,23 @@ export default class ScopesScopeProjectsTargetsRoute extends Route { @action async connect(model) { try { + //eslint-disable-next-line no-debugger + debugger; // Check for cli - const cliPath = await this.ipc.invoke('cliPath'); + const cliPath = await this.ipc.invoke('cliExists'); if(!cliPath) throw new Error('Cannot find boundary cli.') // Create target session - const connection = this.ipc.invoke('connect', { + const connectionDetails = await this.ipc.invoke('connect', { target_id: model.target.id, token: this.session.data.authenticated.token, }); - await connection.then((details) => { - // Show the user a modal with basic connection info. - this.confirm.confirm(details, { isConnectSuccess: true }); - }); + // Show the user a modal with basic connection info. + this.confirm.confirm(connectionDetails, { isConnectSuccess: true }); } catch(e) { this.confirm.confirm(e.message, { isConnectError: true }) + // Retry .then(() => this.connect(model)) .catch(() => null /* no op */); } diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index e22aeb9b7..0ef3f3a3b 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -1,7 +1,6 @@ 'use strict'; const APP_NAME = process.env.APP_NAME || 'Boundary'; -const API_HOST = process.env.API_HOST || ''; const ENABLE_MIRAGE = process.env.ENABLE_MIRAGE ? false : true; module.exports = function (environment) { diff --git a/ui/desktop/electron-app/src/boundary-cli.js b/ui/desktop/electron-app/src/boundary-cli.js new file mode 100644 index 000000000..d3112bd79 --- /dev/null +++ b/ui/desktop/electron-app/src/boundary-cli.js @@ -0,0 +1,22 @@ +const { lookpath } = require('lookpath'); +const spawnPromise = require('./spawn-promise'); + +const cliPath = async () => await lookpath('boundary'); + +module.exports = { + // Find boundary cli path + path: () => cliPath(), + // Check boundary cli existence + exists: () => Boolean(cliPath()), + // Initiate connection and return output + connect: (target_id, token) => { + const command = [ + 'connect', + `-target-id=${target_id}`, + `-token=${token}`, + '-format=json', + '--output-json-errors' + ] + return spawnPromise(command); + } +} \ No newline at end of file diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 3dc473c23..4ee3466b8 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -1,6 +1,6 @@ const handle = require('./ipc-handler'); -const { lookpath } = require('lookpath'); -const spawnPromise = require('./spawn-promise'); +const boundaryCli = require('./boundary-cli'); + let origin = ''; @@ -20,25 +20,17 @@ handle('getOrigin', () => origin); */ handle('setOrigin', (requestOrigin) => origin = requestOrigin); +/** + * Check for boundary cli existence + */ +handle('cliExists', () => boundaryCli.exists()); + /** * Detect boundary cli path */ -handle('cliPath', async () => { - const cliPath = await lookpath('boundary'); - if(!cliPath) throw new Error({ message: 'Cannot find boundary cli.'}) - return cliPath; -}); +handle('cliPath', () => boundaryCli.path()); /** * Establishes a boundary session and returns session details. */ -handle('connect', async ({ target_id, token }) => { - const connectCmd = [ - 'connect', - `-target-id=${target_id}`, - `-token=${token}`, - '-format=json', - '--output-json-errors' - ]; - return spawnPromise(connectCmd); -}); +handle('connect', ({ target_id, token }) => boundaryCli.connect(target_id, token)); diff --git a/ui/desktop/electron-app/src/spawn-promise.js b/ui/desktop/electron-app/src/spawn-promise.js index 5fa22cf4c..7daf188e1 100644 --- a/ui/desktop/electron-app/src/spawn-promise.js +++ b/ui/desktop/electron-app/src/spawn-promise.js @@ -1,16 +1,13 @@ const { spawn } = require('child_process'); -// Verify json -const isJSON = (data) => { - let jsonData; - if(typeof data !== 'string') data = JSON.stringify(data) +// Convert to json +const jsonify = (data) => { + if(typeof data !== 'string') data = JSON.stringify(data); try { - jsonData = JSON.parse(data); + return JSON.parse(data); } catch (e) { // Ignore parse errors } - - return (typeof jsonData === 'object'); } // You can throw exceptions, or allow them to occur, and this is supported. @@ -35,12 +32,14 @@ module.exports = function spawnPromise (command) { childProcess.stdout.on('data', (data) => { outputStream += data.toString(); - if(isJSON(outputStream)) resolve(JSON.parse(outputStream)); + const jsonData = jsonify(outputStream); + if(jsonData) resolve(jsonData); }); childProcess.stderr.on('data', (data) => { errorStream += data.toString(); - if(isJSON(errorStream)) reject(new Error(JSON.parse(errorStream)?.error)); + const jsonData = jsonify(errorStream); + if(jsonData) reject(new Error(jsonData?.error)); }); }); } diff --git a/yarn.lock b/yarn.lock index 651d9475e..ed7cfd948 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7507,7 +7507,7 @@ debug@~4.1.0: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -12204,7 +12204,7 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -13911,11 +13911,6 @@ lodash._baseflatten@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -13924,16 +13919,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*, lodash._bindcallback@^3.0.0: +lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - lodash._createassigner@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" @@ -13943,19 +13933,12 @@ lodash._createassigner@^3.0.0: lodash._isiterateecall "^3.0.0" lodash.restparam "^3.0.0" -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: +lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= @@ -14168,7 +14151,7 @@ lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.restparam@*, lodash.restparam@^3.0.0: +lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= From bc9a68b320f40f464563943e5dfb5b254f4b87d5 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 10 Dec 2020 16:44:01 -0500 Subject: [PATCH 9/9] style: Fix sass sort-order warnings in admin notify styles --- ui/admin/app/styles/_notify.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/admin/app/styles/_notify.scss b/ui/admin/app/styles/_notify.scss index 102df123b..26ef1675a 100644 --- a/ui/admin/app/styles/_notify.scss +++ b/ui/admin/app/styles/_notify.scss @@ -61,19 +61,19 @@ .ember-notify-show { -webkit-animation-duration: 500ms; - animation-duration: 500ms; -webkit-animation-name: ember-notify-show; - animation-name: ember-notify-show; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); + animation-duration: 500ms; + animation-name: ember-notify-show; animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); } .ember-notify-hide { -webkit-animation-duration: 250ms; - animation-duration: 250ms; -webkit-animation-name: ember-notify-hide; - animation-name: ember-notify-hide; -webkit-animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); + animation-duration: 250ms; + animation-name: ember-notify-hide; animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); &:hover {