diff --git a/addons/core/tests/integration/components/error/message-test.js b/addons/core/tests/integration/components/error/message-test.js index f7e92a534..47651bd89 100644 --- a/addons/core/tests/integration/components/error/message-test.js +++ b/addons/core/tests/integration/components/error/message-test.js @@ -60,7 +60,7 @@ module('Integration | Component | error/message', function (hooks) { ); assert.equal( find('.rose-message-description').textContent.trim(), - 'Please contact your administrator or try again later.' + 'We\'re not sure what happened. Please contact your administrator or try again later.' ); }); }); diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index 89165233a..1c4c2483a 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -3,6 +3,7 @@ cancel: Cancel delete: Delete remove: Remove discard: Discard +disconnect: Disconnect dismiss: Dismiss save: Save submit: Submit diff --git a/addons/core/translations/errors/en-us.yaml b/addons/core/translations/errors/en-us.yaml index c814611d0..0f05b94a7 100644 --- a/addons/core/translations/errors/en-us.yaml +++ b/addons/core/translations/errors/en-us.yaml @@ -12,7 +12,7 @@ description: We ran into a problem and could not continue. You can ask your administrator or try again later. unknown: title: Something went wrong - description: Please contact your administrator or try again later. + description: We're not sure what happened. Please contact your administrator or try again later. generic: title: Error authentication-failed: diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index ff2dce9eb..e670a70d7 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -95,6 +95,9 @@ host: none: title: No Hosts description: No hosts available in this scope. + none-friendly: + title: No Hosts Available + description: No hosts available to display yet. actions: add: Add Host create: New Host @@ -110,6 +113,12 @@ session: none: title: No Sessions description: There are no active sessions within the current scope. + none-friendly: + title: No Sessions Available + description: No sessions available to display yet. + none-active-friendly: + title: No Active Sessions Available + description: No active sessions available to display yet. actions: connect: Connect org: @@ -195,6 +204,9 @@ target: messages: welcome: title: Welcome to Targets + none: + title: No Targets Available + description: No Targets to display yet. Contact your Boundary admin if you don't see a Target you expect access to. actions: create: New Target delete: Delete Target diff --git a/ui/desktop/app/routes/application.js b/ui/desktop/app/routes/application.js index 45c73b24a..f32dbf9bc 100644 --- a/ui/desktop/app/routes/application.js +++ b/ui/desktop/app/routes/application.js @@ -71,6 +71,16 @@ export default class ApplicationRoute extends Route.extend( this.session.invalidate(); } + /** + * Disconnects from origin and invalidates session, thereby resetting + * the client and reloading to the onboarding origin screen. + */ + @action + disconnect() { + this.origin.resetOrigin(); + this.invalidateSession(); + } + /** * Hooks into ember-loading to kick off loading indicator in the * application template. diff --git a/ui/desktop/app/services/origin.js b/ui/desktop/app/services/origin.js index ba280a1ae..e72a738f0 100644 --- a/ui/desktop/app/services/origin.js +++ b/ui/desktop/app/services/origin.js @@ -1,6 +1,7 @@ import Service from '@ember/service'; import { assert } from '@ember/debug'; import { inject as service } from '@ember/service'; +import { notifyError } from 'core/decorators/notify'; export default class OriginService extends Service { @@ -72,4 +73,13 @@ export default class OriginService extends Service { } } + /** + * Resets the origin. + */ + @notifyError(({ message }) => message, { catch: true }) + async resetOrigin() { + this.rendererOrigin = null; + await this.ipc.invoke('resetOrigin'); + } + } diff --git a/ui/desktop/app/templates/error.hbs b/ui/desktop/app/templates/error.hbs new file mode 100644 index 000000000..f5fcb9fbf --- /dev/null +++ b/ui/desktop/app/templates/error.hbs @@ -0,0 +1,15 @@ +{{page-title (t "errors.generic.title") }} + +
+ + + + + + + + {{t "actions.disconnect"}} + + + +
diff --git a/ui/desktop/app/templates/scopes/scope/projects/error.hbs b/ui/desktop/app/templates/scopes/scope/projects/error.hbs new file mode 100644 index 000000000..57529c9f8 --- /dev/null +++ b/ui/desktop/app/templates/scopes/scope/projects/error.hbs @@ -0,0 +1,23 @@ +{{page-title (t "errors.generic.title") }} + +{{! Most error types will land here }} +{{#if @model.errors}} + + {{#each @model.errors as |error|}} + + {{/each}} + +{{/if}} + +{{! But a disconnected state doesn't get normalized errors }} +{{#unless @model.errors}} + + + + + + + {{t "actions.disconnect"}} + + +{{/unless}} diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs index 3f6dccb72..fc6e412c2 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs @@ -83,9 +83,9 @@ {{else}} - + - {{t "resources.session.messages.none.description"}} + {{t "resources.session.messages.none-friendly.description"}} 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 af224473c..46ba29ed8 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -7,74 +7,86 @@ - - - - {{t "form.name.label"}} - {{t "form.id.label"}} - {{t "form.type.label"}} - {{t "resources.project.title"}} - - - - - {{#each @model as |model|}} - - - - {{model.displayName}} - - {{#if model.target.description}} -

{{model.target.description}}

- {{/if}} - {{#if model.target.isActive}} - - {{t "states.active"}} - - {{/if}} -
- - - {{model.id}} - - - - {{#if model.type}} - - {{t (concat "resources.target.types." model.type)}} - - {{/if}} - - -
+ {{#if @model}} + + + + {{t "form.name.label"}} + {{t "form.id.label"}} + {{t "form.type.label"}} + {{t "resources.project.title"}} + + + + + {{#each @model as |model|}} + + + + {{model.displayName}} + + {{#if model.target.description}} +

{{model.target.description}}

+ {{/if}} + {{#if model.target.isActive}} + + {{t "states.active"}} + + {{/if}} +
+ - {{model.project.id}} + {{model.id}} -
- - {{model.project.name}} - -
- - - {{t "resources.session.actions.connect"}} - - -
- {{/each}} -
-
+ + + {{#if model.type}} + + {{t (concat "resources.target.types." model.type)}} + + {{/if}} + + +
+ + {{model.target.id}} + +
+ + {{model.project.name}} + +
+ + + {{t "resources.session.actions.connect"}} + + + + {{/each}} + + + {{else}} + + + + + {{t "resources.target.messages.none.description"}} + + + + + {{/if}}
diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs index bb76d5e56..92ae3eebe 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs @@ -1,32 +1,46 @@ {{page-title (t "resources.host-set.title_plural")}} - - - {{#each @model.hosts as |host|}} - - - {{host.displayName}} -

{{host.description}}

-
- - - {{host.id}} - - - {{host.attributes.address}} - - {{t (concat 'resources.host.types.' host.type)}} - - - - {{t "resources.session.actions.connect"}} - - -
- {{/each}} -
-
+{{#if @model.hosts}} + + + + {{#each @model.hosts as |host|}} + + + {{host.displayName}} +

{{host.description}}

+
+ + + {{host.id}} + + + {{host.attributes.address}} + + {{t (concat 'resources.host.types.' host.type)}} + + + + {{t "resources.session.actions.connect"}} + + +
+ {{/each}} +
+
+ +{{else}} + + + + + {{t "resources.host.messages.none-friendly.description"}} + + + + +{{/if}} diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target/sessions.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target/sessions.hbs index 3dfa9e4e4..8593787fe 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target/sessions.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target/sessions.hbs @@ -1,34 +1,35 @@ - - - - {{#each this.cancelableUserSessions as |session|}} - - - - - {{session.id}} - - - - {{!-- - Two if's here because: - 1) Local proxy info is only relevant if the session is - active or pending (aka isCancelable). Once the session - is canceled or in the process thereof, the local proxy - may no longer be used. - 2) The session actually has proxy information associated - with it. It's possible for the user to start sessions - from other devices (or even another tab), in which case we - won't have the local proxy info available here. - --}} - {{#if session.isCancelable}} +{{#if this.cancelableUserSessions}} + + + + + {{t "form.id.label"}} + {{t "form.proxy.label"}} + {{t "form.started.label"}} + + + + + + {{#each this.cancelableUserSessions as |session|}} + + + + + + {{session.id}} + + {{session.status}} + + + {{#if session.proxy}} {{session.proxy}} {{/if}} - {{/if}} - - - - - - {{session.status}} - - - {{#if session.isCancelable}} - - {{t "actions.cancel"}} - - {{/if}} - - - {{/each}} - - + + + + + + + + {{#if session.isCancelable}} + + {{t "actions.cancel"}} + + {{/if}} + + + + {{/each}} + + + + +{{else}} + + + + + {{t "resources.session.messages.none-active-friendly.description"}} + + + + +{{/if}} diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index f4b9a62b4..948e82c51 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -17,6 +17,13 @@ handle('setOrigin', async (requestOrigin) => { origin.origin = requestOrigin; }); +/** + * Resets the origin. + */ +handle('resetOrigin', async (requestOrigin) => { + origin.resetOrigin(); +}); + /** * Check for boundary cli existence */ diff --git a/ui/desktop/electron-app/src/index.js b/ui/desktop/electron-app/src/index.js index d811bcf9e..5b95e658f 100644 --- a/ui/desktop/electron-app/src/index.js +++ b/ui/desktop/electron-app/src/index.js @@ -4,12 +4,14 @@ const { default: installExtension, EMBER_INSPECTOR, } = require('electron-devtools-installer'); -const { session, app, protocol, BrowserWindow, ipcMain } = require('electron'); +const { session, app, protocol, BrowserWindow, ipcMain, Menu } = require('electron'); require('./handlers.js'); const origin = require('./origin.js'); const { generateCSPHeader } = require('./content-security-policy.js'); +const menu = require('./menu.js'); + const isDev = require('electron-is-dev'); // Register the custom file protocol @@ -88,6 +90,12 @@ app.on('ready', async () => { } } + // Configure menu in prod env + if(!isDev) { + const menuTemplate = Menu.buildFromTemplate(menu.generateMenuTemplate()); + Menu.setApplicationMenu(menuTemplate); + } + mainWindow = new BrowserWindow({ width: 1024, height: 768, diff --git a/ui/desktop/electron-app/src/menu.js b/ui/desktop/electron-app/src/menu.js new file mode 100644 index 000000000..8e37595bc --- /dev/null +++ b/ui/desktop/electron-app/src/menu.js @@ -0,0 +1,89 @@ +const { shell } = require('electron') + +const generateMenuTemplate = () => { + return [ + // { role: 'appMenu' } + { + label: 'Boundary Desktop', + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideothers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }, + // { role: 'fileMenu' } + { + label: 'File', + submenu: [ + { role: 'close' } + ] + }, + // { role: 'editMenu' } + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + { type: 'separator' }, + { + label: 'Speech', + submenu: [ + { role: 'startSpeaking' }, + { role: 'stopSpeaking' } + ] + } + ] + }, + // { role: 'viewMenu' } + { + label: 'View', + submenu: [ + { role: 'reload' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + { type: 'separator' }, + { role: 'togglefullscreen' } + ] + }, + // { role: 'windowMenu' } + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + { type: 'separator' }, + { role: 'front' }, + { type: 'separator' }, + { role: 'window' } + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Documentation', + click: () => shell.openExternal('https://www.boundaryproject.io') + } + ] + } + ] +} + +module.exports = { + generateMenuTemplate: generateMenuTemplate +}; \ No newline at end of file diff --git a/ui/desktop/electron-app/src/origin.js b/ui/desktop/electron-app/src/origin.js index f4461e8f0..7d85e4466 100644 --- a/ui/desktop/electron-app/src/origin.js +++ b/ui/desktop/electron-app/src/origin.js @@ -24,7 +24,7 @@ class RuntimeSettings { } /** - * + * Validates the origin is reachable and has a scopes endpoint. */ async validateOrigin(origin) { // If the origin is the Electron origin, it is automatically valid. @@ -40,6 +40,13 @@ class RuntimeSettings { } } + /** + * Sets the origin to null. + */ + async resetOrigin() { + this.origin = null; + } + // Quick and dirty event handler pattern to enable the application to respond // when the origin is changed. #originWatchers = [];