Merge remote-tracking branch 'origin/main' into dmg-background

This commit is contained in:
Susmitha Girumala committed 2021-02-12 12:18:44 -05:00
commit 7be9948f5a
19 files changed
+381 -171

No files matched your search

@@ -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.'
);
});
});
@@ -3,6 +3,7 @@ cancel: Cancel
delete: Delete
remove: Remove
discard: Discard
disconnect: Disconnect
dismiss: Dismiss
save: Save
submit: Submit
+1 -1
View File
@@ -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:
@@ -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
@@ -1,5 +1,4 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeProjectsSessionsIndexController extends Controller {
@@ -15,7 +14,6 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
* and active/pending.
* @type {SessionModel[]}
*/
@computed('model.@each.{created_time,active}', 'session.data.authenticated.user_id')
get sorted() {
const userId = this.session.data.authenticated.user_id;
const sessions = this.model;
@@ -1,5 +1,4 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { A } from '@ember/array';
@@ -9,14 +8,21 @@ export default class ScopesScopeProjectsTargetsTargetSessionsController extends
@service session;
/**
* Sort and filter to active sessions of current user
* Sessions belonging to the target.
* @type {SessionModel[]}
*/
get sessions() {
return this.model.sessions;
}
/**
* Sort and filter to active sessions of current user.
* @param {[SessionModel]} model
* @param {object} session
* @type {[SessionModel]}
*/
@computed('model.@each.{created_time,active}', 'session.data.authenticated.user_id')
get selfActiveSessions() {
const sessions = this.model;
get cancelableUserSessions() {
const sessions = this.sessions;
const userId = this.session.data.authenticated.user_id;
const filteredSessions =
sessions.filter(session =>
@@ -27,10 +33,6 @@ export default class ScopesScopeProjectsTargetsTargetSessionsController extends
// Sort sessions
const sortedSessions = A(filteredSessions).sortBy('created_time').reverse();
// Move active sessions to top
return [
...sortedSessions.filter(session => session.isActive),
...sortedSessions.filter(session => !session.isActive)
]
return sortedSessions;
}
}
+10
View File
@@ -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.
@@ -4,15 +4,6 @@ import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeProjectsTargetsTargetSessionsRoute extends Route {
// =methods
/**
* @return {[SessionModel]}
*/
model() {
return this.modelFor('scopes.scope.projects.targets.target').sessions;
}
// =actions
/**
@@ -25,4 +16,5 @@ export default class ScopesScopeProjectsTargetsTargetSessionsRoute extends Route
async cancelSession(session) {
await session.cancelSession();
}
}
+10
View File
@@ -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');
}
}
+8
View File
@@ -1,7 +1,15 @@
{{page-title (t "errors.generic.title") }}
<main>
<Rose::Layout::Centered>
<Error::Message />
</Rose::Layout::Centered>
<Rose::Layout::Centered>
<Rose::Button @style="primary" {{on "click" (route-action "disconnect")}}>
{{t "actions.disconnect"}}
</Rose::Button>
</Rose::Layout::Centered>
</main>
@@ -0,0 +1,23 @@
{{page-title (t "errors.generic.title") }}
{{! Most error types will land here }}
{{#if @model.errors}}
<Rose::Layout::Centered>
{{#each @model.errors as |error|}}
<Error::Message @status={{error.status}}/>
{{/each}}
</Rose::Layout::Centered>
{{/if}}
{{! But a disconnected state doesn't get normalized errors }}
{{#unless @model.errors}}
<Rose::Layout::Centered>
<Error::Message />
</Rose::Layout::Centered>
<Rose::Layout::Centered>
<Rose::Button @style="primary" {{on "click" (route-action "disconnect")}}>
{{t "actions.disconnect"}}
</Rose::Button>
</Rose::Layout::Centered>
{{/unless}}
@@ -83,9 +83,9 @@
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "resources.session.messages.none.title"}} as |message|>
<Rose::Message @title={{t "resources.session.messages.none-friendly.title"}} as |message|>
<message.description>
{{t "resources.session.messages.none.description"}}
{{t "resources.session.messages.none-friendly.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
@@ -7,74 +7,86 @@
</page.header>
<page.body>
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t "form.name.label"}}</row.headerCell>
<row.headerCell>{{t "form.id.label"}}</row.headerCell>
<row.headerCell>{{t "form.type.label"}}</row.headerCell>
<row.headerCell>{{t "resources.project.title"}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |model|}}
<body.row as |row|>
<row.headerCell>
<LinkTo
@route="scopes.scope.projects.targets.target"
@model={{model.id}}
>
{{model.displayName}}
</LinkTo>
{{#if model.target.description}}
<p>{{model.target.description}}</p>
{{/if}}
{{#if model.target.isActive}}
<Rose::Badge @style="success">
{{t "states.active"}}
</Rose::Badge>
{{/if}}
</row.headerCell>
<row.cell>
<Copyable
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.id}}</code>
</Copyable>
</row.cell>
<row.cell>
{{#if model.type}}
<Rose::Badge>
{{t (concat "resources.target.types." model.type)}}
</Rose::Badge>
{{/if}}
</row.cell>
<row.cell>
<div>
{{#if @model}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t "form.name.label"}}</row.headerCell>
<row.headerCell>{{t "form.id.label"}}</row.headerCell>
<row.headerCell>{{t "form.type.label"}}</row.headerCell>
<row.headerCell>{{t "resources.project.title"}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |model|}}
<body.row as |row|>
<row.headerCell>
<LinkTo
@route="scopes.scope.projects.targets.target"
@model={{model.id}}
>
{{model.displayName}}
</LinkTo>
{{#if model.target.description}}
<p>{{model.target.description}}</p>
{{/if}}
{{#if model.target.isActive}}
<Rose::Badge @style="success">
{{t "states.active"}}
</Rose::Badge>
{{/if}}
</row.headerCell>
<row.cell>
<Copyable
@text={{model.project.id}}
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.project.id}}</code>
<code>{{model.id}}</code>
</Copyable>
</div>
<Rose::Badge @style='informational'>
{{model.project.name}}
</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
</row.cell>
<row.cell>
{{#if model.type}}
<Rose::Badge>
{{t (concat "resources.target.types." model.type)}}
</Rose::Badge>
{{/if}}
</row.cell>
<row.cell>
<div>
<Copyable
@text={{model.target.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.target.id}}</code>
</Copyable>
</div>
<Rose::Badge @style='informational'>
{{model.project.name}}
</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "resources.target.messages.none.title"}} as |message|>
<message.description>
{{t "resources.target.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
</page.body>
</Rose::Layout::Page>
@@ -1,32 +1,46 @@
{{page-title (t "resources.host-set.title_plural")}}
<Rose::Table as |table|>
<table.body as |body|>
{{#each @model.hosts as |host|}}
<body.row as |row|>
<row.cell>
{{host.displayName}}
<p>{{host.description}}</p>
</row.cell>
<row.cell>
<Copyable
@text={{host.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{host.id}}</code>
</Copyable>
</row.cell>
<row.cell>{{host.attributes.address}}</row.cell>
<row.cell>
<Rose::Badge>{{t (concat 'resources.host.types.' host.type)}}</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" @model.target host)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{#if @model.hosts}}
<Rose::Table as |table|>
<table.body as |body|>
{{#each @model.hosts as |host|}}
<body.row as |row|>
<row.cell>
{{host.displayName}}
<p>{{host.description}}</p>
</row.cell>
<row.cell>
<Copyable
@text={{host.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{host.id}}</code>
</Copyable>
</row.cell>
<row.cell>{{host.attributes.address}}</row.cell>
<row.cell>
<Rose::Badge>{{t (concat 'resources.host.types.' host.type)}}</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" @model.target host)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "resources.host.messages.none-friendly.title"}} as |message|>
<message.description>
{{t "resources.host.messages.none-friendly.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
@@ -1,33 +1,35 @@
<Rose::Table as |table|>
<table.body as |body|>
{{#each this.selfActiveSessions as |session|}}
<body.row as |row|>
<row.cell class="nowrap">
<Rose::Icon
class={{concat "session-status-" session.status}}
@name="app-icons/action-session"
@size="medium" />
<Copyable
@text={{session.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{session.id}}</code>
</Copyable>
</row.cell>
<row.cell>
{{!--
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}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t "form.id.label"}}</row.headerCell>
<row.headerCell>{{t "form.proxy.label"}}</row.headerCell>
<row.headerCell>{{t "form.started.label"}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each this.cancelableUserSessions as |session|}}
<body.row as |row|>
<row.cell class="nowrap">
<Rose::Icon
class={{concat "session-status-" session.status}}
@name="app-icons/action-session"
@size="medium" />
<Copyable
@text={{session.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{session.id}}</code>
</Copyable>
<Rose::Badge>{{session.status}}</Rose::Badge>
</row.cell>
<row.cell>
{{#if session.proxy}}
<Copyable
@text={{session.proxy}}
@@ -37,24 +39,39 @@
<code>{{session.proxy}}</code>
</Copyable>
{{/if}}
{{/if}}
</row.cell>
<row.cell>
<time datetime={{format-date-iso session.created_time}}>
{{format-date-iso-human session.created_time}}
</time>
</row.cell>
<row.cell>
<Rose::Badge>{{session.status}}</Rose::Badge>
</row.cell>
<row.cell>
{{#if session.isCancelable}}
<Rose::Button @style="secondary" {{on "click" (route-action "cancelSession" session)}}>
{{t "actions.cancel"}}
</Rose::Button>
{{/if}}
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
</row.cell>
<row.cell>
<time datetime={{format-date-iso session.created_time}}>
{{format-date-iso-human session.created_time}}
</time>
</row.cell>
<row.cell>
{{#if session.isCancelable}}
<Rose::Button
@style="secondary"
{{on "click" (route-action "cancelSession" session)}}
>
{{t "actions.cancel"}}
</Rose::Button>
{{/if}}
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t "resources.session.messages.none-active-friendly.title"}} as |message|>
<message.description>
{{t "resources.session.messages.none-active-friendly.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
+7
View File
@@ -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
*/
+9 -1
View File
@@ -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,
+89
View File
@@ -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
};
+8 -1
View File
@@ -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 = [];