Merge branch 'main' into show-session-proxy-details-in-active-sessions-in-a-target
This commit is contained in:
11 files changed
+136
-77
No files matched your search
@@ -98,4 +98,30 @@ export default class ApplicationRoute extends Route.extend(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the specified color theme to the root ember element.
|
||||
* @param {string} theme - "light", "dark", or nullish (system default)
|
||||
*/
|
||||
@action
|
||||
toggleTheme(theme) {
|
||||
const rootElementSelector = getOwner(this).rootElement;
|
||||
const rootEl = getOwner(this)
|
||||
.lookup('service:-document')
|
||||
.querySelector(rootElementSelector);
|
||||
this.session.set('data.theme', theme);
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
rootEl.classList.add('rose-theme-light');
|
||||
rootEl.classList.remove('rose-theme-dark');
|
||||
break;
|
||||
case 'dark':
|
||||
rootEl.classList.add('rose-theme-dark');
|
||||
rootEl.classList.remove('rose-theme-light');
|
||||
break;
|
||||
default:
|
||||
rootEl.classList.remove('rose-theme-dark');
|
||||
rootEl.classList.remove('rose-theme-light');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,21 +101,25 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
|
||||
/**
|
||||
* Establish a session to current target.
|
||||
* @param {TargetModel} model
|
||||
* @param {HostModel} host
|
||||
*/
|
||||
@action
|
||||
async connect(model) {
|
||||
async connect(model, host) {
|
||||
// TODO: Connect: Refactor into an addon
|
||||
try {
|
||||
// Check for CLI
|
||||
const cliExists = await this.ipc.invoke('cliExists');
|
||||
if (!cliExists) throw new Error('Cannot find Boundary CLI.');
|
||||
|
||||
const options = {
|
||||
target_id: model.id,
|
||||
token: this.session.data.authenticated.token
|
||||
};
|
||||
|
||||
if (host) options.host_id = host.id;
|
||||
|
||||
// Create target session
|
||||
const connectionDetails = await this.ipc.invoke('connect', {
|
||||
target_id: model.target.id,
|
||||
token: this.session.data.authenticated.token,
|
||||
addr: this.origin.rendererOrigin
|
||||
});
|
||||
const connectionDetails = await this.ipc.invoke('connect', options);
|
||||
|
||||
// Associate the connection details with the session
|
||||
const { session_id, address, port } = connectionDetails;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { all, hash } from 'rsvp';
|
||||
import { later } from '@ember/runloop';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
|
||||
@@ -23,7 +21,7 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
|
||||
const { target } = this.modelFor('scopes.scope.projects.targets.target');
|
||||
// Load all host-sets
|
||||
const hostSets = await all(
|
||||
target.host_sets.map(({ host_set_id }) =>
|
||||
target.host_sets.map(({ host_set_id }) =>
|
||||
this.store.findRecord('host-set', host_set_id)
|
||||
)
|
||||
);
|
||||
@@ -32,7 +30,7 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
|
||||
const hostIds = hostSets.map(({host_ids}) =>
|
||||
host_ids.content.map((hostId) =>
|
||||
hostId.value
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Load unique hosts
|
||||
@@ -41,65 +39,10 @@ export default class ScopesScopeProjectsTargetsTargetHostsRoute extends Route {
|
||||
return hash({
|
||||
target,
|
||||
hosts: all(
|
||||
[...uniqueHostIds].map((hostId) =>
|
||||
[...uniqueHostIds].map((hostId) =>
|
||||
this.store.findRecord('host', hostId)
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a session to host within target.
|
||||
* @param {TargetModel} target
|
||||
* @param {HostModel} host
|
||||
*/
|
||||
@action
|
||||
async connect(target, host) {
|
||||
// TODO: Connect: Refactor into an addon
|
||||
try {
|
||||
// Check for CLI
|
||||
const cliExists = await this.ipc.invoke('cliExists');
|
||||
if (!cliExists) throw new Error('Cannot find Boundary CLI.');
|
||||
|
||||
// Create target session
|
||||
const connectionDetails = await this.ipc.invoke('connect', {
|
||||
host_id: host.id,
|
||||
target_id: target.id,
|
||||
token: this.session.data.authenticated.token,
|
||||
addr: this.origin.rendererOrigin
|
||||
});
|
||||
|
||||
// Associate the connection details with the session
|
||||
const { session_id, address, port } = connectionDetails;
|
||||
await this.store.findRecord('session', session_id);
|
||||
|
||||
// Update the session record with proxy information from the CLI
|
||||
// This is read-only information that shouldn't dirty the session,
|
||||
// so we use store.push here. In the future, it may make sense to push
|
||||
// this off to the API so that we don't have to manually persist the
|
||||
// proxy details.
|
||||
later(() => {
|
||||
this.store.push({
|
||||
data: {
|
||||
id: session_id,
|
||||
type: 'session',
|
||||
attributes: {
|
||||
proxy_address: address,
|
||||
proxy_port: port
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 150);
|
||||
|
||||
// Show the user a modal with basic connection info.
|
||||
// We don't await because this modal is purely informational.
|
||||
this.confirm.confirm(connectionDetails, { isConnectSuccess: true });
|
||||
|
||||
} catch(e) {
|
||||
this.confirm.confirm(e.message, { isConnectError: true })
|
||||
// Retry
|
||||
.then(() => this.connect(target, host))
|
||||
.catch(() => null /* no op */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,29 @@
|
||||
>
|
||||
{{t 'actions.deauthenticate'}}
|
||||
</dropdown.button>
|
||||
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title={{t "titles.toggle-color-theme"}}>
|
||||
<Rose::Form as |form|>
|
||||
<form.radioGroup
|
||||
@name="theme"
|
||||
@selectedValue={{this.session.data.theme}}
|
||||
@changed={{route-action "toggleTheme"}}
|
||||
as |radioGroup|>
|
||||
<dropdown.item>
|
||||
<radioGroup.radio @label={{t "themes.system"}} />
|
||||
</dropdown.item>
|
||||
<dropdown.item>
|
||||
<radioGroup.radio @label={{t "themes.light"}} @value="light" />
|
||||
</dropdown.item>
|
||||
<dropdown.item>
|
||||
<radioGroup.radio @label={{t "themes.dark"}} @value="dark" />
|
||||
</dropdown.item>
|
||||
</form.radioGroup>
|
||||
</Rose::Form>
|
||||
</dropdown.section>
|
||||
|
||||
</utilities.dropdown>
|
||||
</header.utilities>
|
||||
{{/if}}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
|
||||
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model.target)}}>
|
||||
{{t "resources.session.actions.connect"}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<Rose::Button @style="primary" {{on "click" (route-action "connect" @model)}}>
|
||||
<Rose::Button @style="primary" {{on "click" (route-action "connect" @model.target)}}>
|
||||
{{t "resources.session.actions.connect"}}
|
||||
</Rose::Button>
|
||||
@@ -25,7 +25,8 @@
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-is-dev": "^1.2.0",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"lookpath": "^1.1.0"
|
||||
"lookpath": "^1.1.0",
|
||||
"shell-quote": "^1.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "6.0.0-beta.53",
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
const path = require('path');
|
||||
const { spawnAsyncJSONPromise, spawnSync } = require('./spawn-promise');
|
||||
const origin = require('./origin.js');
|
||||
|
||||
const cliPath = async () => path.resolve(__dirname, '..', 'cli', 'boundary');
|
||||
|
||||
/**
|
||||
* Super paranoid shell quote/escape and validation. Input must be base62.
|
||||
* @param {string} str
|
||||
*/
|
||||
const escapeAndValidateBase62 = (str) => {
|
||||
const candidate = str.toString();
|
||||
if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate;
|
||||
throw new Error(`
|
||||
Could not invoke command:
|
||||
input contained unsafe characters.
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// Check boundary cli existence
|
||||
exists: () => Boolean(cliPath()),
|
||||
// Initiate connection and return output
|
||||
connect: (target_id, token, addr, host_id) => {
|
||||
connect: (target_id, token, host_id) => {
|
||||
const addr = origin.origin;
|
||||
const sanitized = {
|
||||
target_id: escapeAndValidateBase62(target_id),
|
||||
token: escapeAndValidateBase62(token)
|
||||
};
|
||||
if (host_id) sanitized.host_id = escapeAndValidateBase62(host_id);
|
||||
const command = [
|
||||
'connect',
|
||||
`-target-id=${target_id}`,
|
||||
`-token=${token}`,
|
||||
`-target-id=${sanitized.target_id}`,
|
||||
`-token=${sanitized.token}`,
|
||||
`-addr=${addr}`,
|
||||
'-format=json',
|
||||
'--output-json-errors'
|
||||
];
|
||||
|
||||
if(host_id) command.push(`-host-id=${host_id}`)
|
||||
if (host_id) command.push(`-host-id=${sanitized.host_id}`)
|
||||
return spawnAsyncJSONPromise(command);
|
||||
},
|
||||
// Returns JSON-formatted version information from the CLI
|
||||
|
||||
@@ -25,4 +25,6 @@ handle('cliExists', () => boundaryCli.exists());
|
||||
/**
|
||||
* Establishes a boundary session and returns session details.
|
||||
*/
|
||||
handle('connect', ({ target_id, token, addr, host_id }) => boundaryCli.connect(target_id, token, addr, host_id));
|
||||
handle('connect', ({ target_id, token, host_id }) =>
|
||||
boundaryCli.connect(target_id, token, host_id)
|
||||
);
|
||||
@@ -3108,6 +3108,11 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
shell-quote@^1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||
|
||||
@@ -3,19 +3,20 @@ import {
|
||||
visit,
|
||||
currentURL,
|
||||
//fillIn,
|
||||
//click,
|
||||
click,
|
||||
find,
|
||||
//findAll,
|
||||
//getRootElement
|
||||
getRootElement,
|
||||
//setupOnerror,
|
||||
} from '@ember/test-helpers';
|
||||
import { run, later } from '@ember/runloop';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
//import { Response } from 'miragejs';
|
||||
//import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import {
|
||||
currentSession,
|
||||
//authenticateSession,
|
||||
authenticateSession,
|
||||
invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
|
||||
@@ -159,4 +160,38 @@ module('Acceptance | authentication', function (hooks) {
|
||||
assert.equal(currentURL(), urls.authenticate.global);
|
||||
assert.ok(find('.rose-message'));
|
||||
});
|
||||
|
||||
test('color theme is applied from session data', async function (assert) {
|
||||
assert.expect(12);
|
||||
authenticateSession({
|
||||
scope: {
|
||||
id: instances.scopes.global.id,
|
||||
type: instances.scopes.global.type
|
||||
},
|
||||
});
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
// system default
|
||||
assert.notOk(currentSession().get('data.theme'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
// toggle light mode
|
||||
await click('[name="theme"][value="light"]');
|
||||
assert.equal(currentSession().get('data.theme'), 'light');
|
||||
assert.ok(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
// toggle dark mode
|
||||
await click('[name="theme"][value="dark"]');
|
||||
assert.equal(currentSession().get('data.theme'), 'dark');
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.ok(getRootElement().classList.contains('rose-theme-dark'));
|
||||
// toggle system default
|
||||
await click('[name="theme"][value=""]');
|
||||
assert.notOk(currentSession().get('data.theme'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
}, 750);
|
||||
|
||||
await visit(urls.scopes.org);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user