Merge remote-tracking branch 'origin/main' into show-info-message-when-empty

This commit is contained in:
Susmitha Girumala committed 2021-02-12 09:29:20 -05:00
commit d36b3e8833
25 files changed
+318 -231

No files matched your search

+8
View File
@@ -32,6 +32,14 @@ export default class SessionModel extends GeneratedSessionModel {
: null;
}
/**
* The target associated with this session (if loaded).
* @type {TargetModel}
*/
get target() {
return this.store.peekRecord('target', this.target_id);
}
// =methods
/**
+8
View File
@@ -45,6 +45,14 @@ export default class TargetModel extends GeneratedTargetModel {
.filter(s => s && (s.target_id === this.id));
}
/**
* The project associated with this target (if already loaded).
* @type {ScopeModel}
*/
get project() {
return this.store.peekRecord('scope', this.scopeID);
}
/**
* True if any sessions associated with this target are active or pending.
* @type {boolean}
+30 -9
View File
@@ -23,13 +23,16 @@ export default function() {
// Scope resources
this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => {
this.get('/scopes', ({ scopes }, { queryParams: { scope_id, recursive } }) => {
// Default parent scope is global
if (!scope_id) scope_id = 'global';
const results = scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
return results;
if (recursive && scope_id === 'global') {
return scopes.all();
} else {
return scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
}
});
this.post('/scopes', function ({ scopes }) {
// Parent scope comes through the payload via `scope_id`, but this needs
@@ -287,7 +290,16 @@ export default function() {
// target
this.get('/targets', function ({ targets }, { queryParams: { scope_id } }) {
this.get('/targets', function ({ targets }, { queryParams: { scope_id, recursive } }) {
if (recursive && scope_id === 'global') {
return targets.all();
} else if (recursive) {
return targets.where(target => {
const targetModel = targets.find(target.id);
return (target.scopeId === scope_id)
|| (targetModel?.scope?.scope?.id === scope_id)
});
}
return targets.where(target => target.scopeId === scope_id);
});
this.post('/targets', function ({ targets }) {
@@ -325,7 +337,7 @@ export default function() {
// session
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id, recursive } }) {
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// To populate sessions for logged in user,
@@ -333,7 +345,7 @@ export default function() {
// But only if not in testing mode.
// In tests, we need deterministic statuses.
if (!Ember.testing) {
sessions.where(session => session.scopeId === scope_id)
sessions.all()
.models
.forEach(session => {
session.update({
@@ -344,7 +356,16 @@ export default function() {
});
});
}
return sessions.where(session => session.scopeId === scope_id)
if (recursive && scope_id === 'global') {
return sessions.all();
} else if (recursive) {
return sessions.where(session => {
const sessionModel = sessions.find(session.id);
return (session.scopeId === scope_id)
|| (sessionModel?.scope?.scope?.id === scope_id)
});
}
return sessions.where(session => session.scopeId === scope_id);
});
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
const session = sessions.find(id);
@@ -17,3 +17,5 @@ generic:
title: Error
authentication-failed:
title: Authentication Failed
origin-verification-failed:
description: Unable to connect to the specified origin. Please check the URL and try again.
@@ -0,0 +1,35 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeProjectsSessionsIndexController extends Controller {
// =sessions
@service session;
// =attributes
/**
* A list of sessions filtered to the current user and sorted by created time,
* 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;
const sortedSessions = sessions
// filter sessions by current user
.filter(session => session.user_id === userId)
// sort by created time
.sortBy('session.created_time').reverse();
return [
// then move active sessions to the top
...sortedSessions.filter((session) => session.isCancelable),
// and all others to the end
...sortedSessions.filter((session) => !session.isCancelable),
];
}
}
@@ -0,0 +1,36 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { A } from '@ember/array';
export default class ScopesScopeProjectsTargetsTargetSessionsController extends Controller {
// =services
@service session;
/**
* 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;
const userId = this.session.data.authenticated.user_id;
const filteredSessions =
sessions.filter(session =>
session.isCancelable &&
session.user_id === userId
);
// 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)
]
}
}
-11
View File
@@ -88,15 +88,4 @@ export default class ScopesScopeRoute extends Route {
outlet: 'header-nav',
});
}
/**
* In global scope, redirect to first org on authentication.
*/
redirect(model) {
const { isAuthenticated } = this.session;
const { isGlobal } = model;
if (isAuthenticated && isGlobal && this.scopes.orgs?.length) {
this.replaceWith('scopes.scope', this.scopes.orgs.firstObject.id);
}
}
}
+19 -6
View File
@@ -1,6 +1,20 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
/**
* TODO: This route is now vestigial. Desktop does not provide project-based
* navigation. This route formerly provided a way to aggregate
* all project scopes, which was necessary to then aggregate targets and
* sessions under those projects. Since the API now provides recursive listing,
* it's possible to load all targets and sessions with a single call, no client-
* side aggregation required.
*
* However, it _is_ necessary to prime the store with all project scopes.
* This allows the UI to display project names on associated resources.
*
* The priming function of this route could be moved to scopes or scopes/scope.
* It no longer makes sense as a dedicated route.
*/
export default class ScopesScopeProjectsRoute extends Route {
// =services
@@ -16,14 +30,13 @@ export default class ScopesScopeProjectsRoute extends Route {
}
/**
* Loads project scopes for the current scope.
* @return {Promise
* Primes the store with _all project scopes_ under global.
* @return {Promise{ScopeModel}}
*/
model() {
const currentScope = this.modelFor('scopes.scope');
return this.store.query('scope', {
scope_id: currentScope.id,
});
return this.store
.query('scope', { recursive: true, scope_id: 'global' })
.filter(({ isProject }) => isProject);
}
}
@@ -1,8 +1,6 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all, hash } from 'rsvp';
import { task, timeout } from 'ember-concurrency';
import { A } from '@ember/array';
import config from '../../../../config/environment';
import { action } from '@ember/object';
import { notifySuccess, notifyError } from 'core/decorators/notify';
@@ -49,43 +47,9 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
* @return {Promise{[{session: SessionModel, target: TargetModel}]}}
*/
async model() {
const projects = this.modelFor('scopes.scope.projects');
const userId = this.session.data.authenticated.user_id;
let projectSessions = await all(
projects.map(({ id: scope_id }) =>
this.store.query('session', { scope_id })
)
);
// Filter sessions to current user
let sessions = projectSessions.map(
sessions => sessions.filter(
session => session.user_id === userId
)
).flat();
const sessionAggregates = await all(
sessions.map(session => hash({
session,
target: session.target_id
? (
this.store.peekRecord('target', session.target_id) ||
this.store.findRecord('target', session.target_id)
)
: null,
}))
);
// Sort sessions by time created...
let sortedSessionAggregates =
A(sessionAggregates).sortBy('session.created_time').reverse();
// Then move active sessions to the top...
sortedSessionAggregates = [
...sortedSessionAggregates.filter((aggregate) => aggregate.session.status === 'active'),
...sortedSessionAggregates.filter((aggregate) => aggregate.session.status !== 'active'),
];
return sortedSessionAggregates;
const { id: scope_id } = this.modelFor('scopes.scope');
await this.store.query('target', { recursive: true, scope_id });
return await this.store.query('session', { recursive: true, scope_id });
}
/**
@@ -1,6 +1,5 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all } from 'rsvp';
import { action } from '@ember/object';
import { task, timeout } from 'ember-concurrency';
import { later } from '@ember/runloop';
@@ -51,20 +50,9 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
* @return {Promise{[TargetModel]}}
*/
async model() {
const projects = this.modelFor('scopes.scope.projects');
let projectTargets = await all(
projects.map(({ id: scope_id }) =>
this.store.query('target', { scope_id })
)
);
let targets = projectTargets.map((target) => target.toArray()).flat();
const { id: scope_id } = this.modelFor('scopes.scope');
await this.refreshSessions();
return targets.map((target) => {
return {
target,
project: this.store.peekRecord('scope', target.scopeID),
};
});
return this.store.query('target', { recursive: true, scope_id });
}
/**
@@ -73,13 +61,8 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
* TODO: query only for sessions associated with the current user.
*/
async refreshSessions() {
const projects = this.modelFor('scopes.scope.projects');
//const userId = this.session.data.authenticated.user_id;
await all(
projects.map(({ id: scope_id }) =>
this.store.query('session', { scope_id })
)
);
const { id: scope_id } = this.modelFor('scopes.scope');
await this.store.query('session', { recursive: true, scope_id });
}
/**
@@ -115,7 +98,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
target_id: model.id,
token: this.session.data.authenticated.token
};
if (host) options.host_id = host.id;
// Create target session
@@ -1,36 +1,17 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { A } from '@ember/array';
export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
// =services
@service session;
// =methods
/**
* Load a target and it's active sessions
* Load a target
* @param {object} params
* @param {string} params.target_id
* @return {{TargetModel, [SessionModel]}}
* @return {TargetModel}
*/
async model({ target_id }) {
const userId = this.session.data.authenticated.user_id;
const target = await this.store.findRecord('target', target_id, { reload: true });
const sessions = await this.store.query('session', { scope_id: target.scopeID });
// Filter to current user, current target, and active state
const filteredSessions = sessions.filter(
session => session.user_id === userId &&
session.target_id === target.id &&
session.isCancelable
)
// Sort sessions by time created
const sortedSessions =
A(filteredSessions).sortBy('session.created_time').reverse();
return { target, sessions: sortedSessions };
model({ target_id }) {
return this.store.findRecord('target', target_id, { reload: true });
}
/**
@@ -18,7 +18,7 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
* @return {Promise{[HostModel]}}
*/
async model() {
const { target } = this.modelFor('scopes.scope.projects.targets.target');
const target = this.modelFor('scopes.scope.projects.targets.target');
// Load all host-sets
const hostSets = await all(
target.host_sets.map(({ host_set_id }) =>
@@ -3,6 +3,16 @@ import { action } from '@ember/object';
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
/**
+2 -2
View File
@@ -77,7 +77,7 @@
@icon="check-circle-fill"
@style="success"
@modal={{true}}
@dismiss={{accept}}
@dismiss={{deny}}
as |dialog|>
<dialog.body>
@@ -107,7 +107,7 @@
@icon="cancel-square-fill"
@style="error"
@modal={{true}}
@dismiss={{accept}}
@dismiss={{deny}}
as |dialog|>
<dialog.body>
<p>
+7
View File
@@ -0,0 +1,7 @@
{{page-title (t "errors.generic.title") }}
<main>
<Rose::Layout::Centered>
<Error::Message />
</Rose::Layout::Centered>
</main>
@@ -1,65 +1,58 @@
{{page-title (t "actions.authenticate")}}
{{!--
<Rose::Form::Input
@type="text"
@name=""
@value={{this.origin.rendererOrigin}}
@label={{t "form.origin.label"}}
@disabled={{true}}
/>
<Rose::Button @style="ghost">Change</Rose::Button> --}}
<BrandedCard
@title={{t 'actions.authenticate'}}
@description={{t 'auth.descriptions.authenticating-with-org' name=@model.scope.displayName}}
>
<main>
<BrandedCard
@title={{t 'actions.authenticate'}}
@description={{t 'auth.descriptions.authenticating-with-org' name=@model.scope.displayName}}
>
<Rose::Dropdown
@text={{t "actions.choose-different-org"}}
@icon="user-organization"
as |dropdown|>
<dropdown.link @route="scopes.scope.authenticate" @model="global">
{{t "titles.global"}}
</dropdown.link>
{{#each this.sortedScopes as |scope|}}
<dropdown.link @route="scopes.scope.authenticate" @model={{scope.id}}>
{{scope.displayName}}
<Rose::Dropdown
@text={{t "actions.choose-different-org"}}
@icon="user-organization"
as |dropdown|>
<dropdown.link @route="scopes.scope.authenticate" @model="global">
{{t "titles.global"}}
</dropdown.link>
{{/each}}
</Rose::Dropdown>
{{#each this.sortedScopes as |scope|}}
<dropdown.link @route="scopes.scope.authenticate" @model={{scope.id}}>
{{scope.displayName}}
</dropdown.link>
{{/each}}
</Rose::Dropdown>
<Rose::Nav::Tabs as |nav|>
{{#each @model.authMethods as |authMethod|}}
<nav.link @route="scopes.scope.authenticate.method" @model={{authMethod.id}}>
{{t (concat "resources.auth-method.types." authMethod.type)}}
</nav.link>
{{/each}}
</Rose::Nav::Tabs>
<Rose::Nav::Tabs as |nav|>
{{#each @model.authMethods as |authMethod|}}
<nav.link @route="scopes.scope.authenticate.method" @model={{authMethod.id}}>
{{t (concat "resources.auth-method.types." authMethod.type)}}
</nav.link>
{{/each}}
</Rose::Nav::Tabs>
{{#if @model.authMethods}}
{{outlet}}
{{#if @model.authMethods}}
{{outlet}}
<p class="change-origin">
{{t "origin.connected-to"}}:
<code>{{this.origin}}</code>
<br>
<LinkTo @route="origin">
{{t "actions.change-origin"}}
</LinkTo>
</p>
<p class="change-origin">
{{t "origin.connected-to"}}:
<code>{{this.origin}}</code>
<br>
<LinkTo @route="origin">
{{t "actions.change-origin"}}
</LinkTo>
</p>
{{/if}}
{{#unless @model.authMethods}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t "resources.auth-method.messages.none.title"}}
@icon="alert-circle-outline"
as |message|>
<message.description>
{{t "resources.auth-method.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
{{/if}}
{{#unless @model.authMethods}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t "resources.auth-method.messages.none.title"}}
@icon="alert-circle-outline"
as |message|>
<message.description>
{{t "resources.auth-method.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
</BrandedCard>
</BrandedCard>
</main>
@@ -6,7 +6,7 @@
<page.body>
{{#if @model}}
{{#if this.sorted}}
<Rose::Table as |table|>
<table.header as |header|>
@@ -20,22 +20,22 @@
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |sessionAggregate|}}
{{#each this.sorted as |model|}}
<body.row as |row|>
<row.headerCell class="nowrap">
<Rose::Icon
class={{concat "session-status-" sessionAggregate.session.status}}
class={{concat "session-status-" model.status}}
@name="app-icons/action-session"
@size="medium" />
<Copyable
@text={{sessionAggregate.session.id}}
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{sessionAggregate.session.id}}</code>
<code>{{model.id}}</code>
</Copyable>
</row.headerCell>
<row.cell>{{sessionAggregate.target.displayName}}</row.cell>
<row.cell>{{model.target.displayName}}</row.cell>
<row.cell>
{{!--
Two if's here because:
@@ -48,29 +48,29 @@
from other devices (or even another tab), in which case we
won't have the local proxy info available here.
--}}
{{#if sessionAggregate.session.isCancelable}}
{{#if sessionAggregate.session.proxy}}
{{#if model.isCancelable}}
{{#if model.proxy}}
<Copyable
@text={{sessionAggregate.session.proxy}}
@text={{model.proxy}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{sessionAggregate.session.proxy}}</code>
<code>{{model.proxy}}</code>
</Copyable>
{{/if}}
{{/if}}
</row.cell>
<row.cell>
<time datetime={{format-date-iso sessionAggregate.session.created_time}}>
{{format-date-iso-human sessionAggregate.session.created_time}}
<time datetime={{format-date-iso model.created_time}}>
{{format-date-iso-human model.created_time}}
</time>
</row.cell>
<row.cell>
<Rose::Badge>{{sessionAggregate.session.status}}</Rose::Badge>
<Rose::Badge>{{model.status}}</Rose::Badge>
</row.cell>
<row.cell>
{{#if sessionAggregate.session.isCancelable}}
<Rose::Button @style="secondary" {{on "click" (route-action "cancelSession" sessionAggregate.session)}}>
{{#if model.isCancelable}}
<Rose::Button @style="secondary" {{on "click" (route-action "cancelSession" model)}}>
{{t "actions.cancel"}}
</Rose::Button>
{{/if}}
@@ -8,7 +8,6 @@
<page.body>
{{#if @model}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
@@ -23,44 +22,45 @@
{{#each @model as |model|}}
<body.row as |row|>
<row.headerCell>
<Rose::Icon
class={{concat "session-status-" (if model.target.isActive 'active' 'canceled')}}
@name="app-icons/action-session"
@size="medium"
title={{if model.target.isActive (t 'states.active')}}
/>
<LinkTo
@route="scopes.scope.projects.targets.target"
@model={{model.target.id}}
@model={{model.id}}
>
{{model.target.displayName}}
{{model.displayName}}
</LinkTo>
<p>{{model.target.description}}</p>
{{#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.target.id}}
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.target.id}}</code>
<code>{{model.id}}</code>
</Copyable>
</row.cell>
<row.cell>
{{#if model.target.type}}
{{#if model.type}}
<Rose::Badge>
{{t (concat "resources.target.types." model.target.type)}}
{{t (concat "resources.target.types." model.type)}}
</Rose::Badge>
{{/if}}
</row.cell>
<row.cell>
<div>
<Copyable
@text={{model.project.id}}
@text={{model.target.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.project.id}}</code>
<code>{{model.target.id}}</code>
</Copyable>
</div>
<Rose::Badge @style='informational'>
@@ -68,7 +68,7 @@
</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model.target)}}>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
@@ -76,7 +76,6 @@
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
@@ -1,3 +1,3 @@
<Rose::Button @style="primary" {{on "click" (route-action "connect" @model.target)}}>
<Rose::Button @style="primary" {{on "click" (route-action "connect" @model)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
@@ -1,18 +1,19 @@
<h2>
{{@model.target.displayName}}
{{#if @model.target.isActive}}
{{@model.displayName}}
{{#if @model.isActive}}
&nbsp;
<Rose::Badge @style="success">
{{t "states.active"}}
</Rose::Badge>
{{/if}}
</h2>
<p>{{@model.target.description}}</p>
<p>{{@model.description}}</p>
<p>
<Copyable
@text={{@model.target.id}}
@text={{@model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{@model.target.id}}</code>
<code>{{@model.id}}</code>
</Copyable>
</p>
@@ -1,12 +1,7 @@
{{#if @model.target.sessions}}
{{#if this.selfActiveSessions}}
<Rose::Table as |table|>
<table.body as |body|>
{{!--
TODO: sort these via the controller so that active and pending appear first
similar to the way the sessions routes are sorted.
--}}
{{#each @model.target.sessions as |session|}}
{{#each this.selfActiveSessions as |session|}}
<body.row as |row|>
<row.cell class="nowrap">
<Rose::Icon
@@ -22,18 +17,26 @@
</Copyable>
</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>
{{!--
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 session.proxy}}
<Copyable
@text={{session.proxy}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{session.proxy}}</code>
</Copyable>
{{/if}}
</row.cell>
</body.row>
+12 -2
View File
@@ -3,6 +3,15 @@ const { version } = require('../src/boundary-cli.js');
const formattedCLIVersion = version().formatted;
const appVersion = process.env.VERSION
? `Version: ${process.env.VERSION}\n`
: '';
const appCommit = process.env.COMMIT
? `Commit: ${process.env.COMMIT}\n`
: '';
const formattedAppVersion = `${appVersion}${appCommit}`;
module.exports = {
hooks: {
prePackage: () => {
@@ -19,7 +28,7 @@ module.exports = {
name: "Boundary Desktop",
appBundleId: "com.electron.boundary",
// TODO: where should the client version number come from?
appVersion: `1.0.0 ${formattedCLIVersion}`,
appVersion: `${formattedAppVersion}${formattedCLIVersion}\n`,
appCopyright: "Copyright © 2021 HashiCorp, Inc.",
icon: "./config/macos/icon.icns",
osxSign: {
@@ -34,7 +43,8 @@ module.exports = {
{
name: "@electron-forge/maker-dmg",
config: {
name: "Boundary Desktop"
name: "boundary-desktop",
title: "Boundary Desktop"
}
}
]
+2 -2
View File
@@ -49,8 +49,8 @@ module.exports = {
if (gitRevision) gitRevision = gitRevision.groups.rev;
if (versionNumber) versionNumber = versionNumber.groups.ver;
const formatted = versionNumber
? `CLI Version ${versionNumber}; CLI Git Rev ${gitRevision}`
: `CLI Git Rev ${gitRevision}`;
? `CLI Version: ${versionNumber}\nCLI Commit: ${gitRevision}`
: `CLI Commit: ${gitRevision}`;
return { gitRevision, versionNumber, formatted };
return rawOutput;
}
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/projects/sessions/index', function(hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/projects/sessions/index');
assert.ok(controller);
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/projects/targets/target/sessions', function(hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/projects/targets/target/sessions');
assert.ok(controller);
});
});