Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
798cfc6828 | ||
|
|
d93986c4e2 | ||
|
|
879c196887 | ||
|
|
98170ed01e | ||
|
|
47a218cf8f | ||
|
|
879a58dcb8 | ||
|
|
f100190ac1 | ||
|
|
0902e42cb0 | ||
|
|
6b0958e4d8 | ||
|
|
82ea70f44b | ||
|
|
0bb72040db | ||
|
|
9d9a38510b | ||
|
|
a54aecee07 | ||
|
|
7dab184bb9 | ||
|
|
f068ac0b0c | ||
|
|
d07c33ca41 | ||
|
|
f073de774d | ||
|
|
1040a05880 |
No files matched your search
@@ -19,7 +19,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn install
|
||||
@@ -37,17 +37,17 @@ jobs:
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:oss
|
||||
- name: Upload artifact admin-ui
|
||||
uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: admin-ui
|
||||
path: ./ui/admin/dist/
|
||||
- name: Upload artifact OSS admin UI
|
||||
uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: admin-ui-oss
|
||||
path: ./ui/admin/dist/
|
||||
@@ -65,12 +65,12 @@ jobs:
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:enterprise
|
||||
- name: Upload artifact enterprise admin UI
|
||||
uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: admin-ui-ent
|
||||
path: ./ui/admin/dist/
|
||||
@@ -88,12 +88,12 @@ jobs:
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:hcp
|
||||
- name: Upload artifact HCP admin UI
|
||||
uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: admin-ui-hcp
|
||||
path: ./ui/admin/dist/
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e6a47e25652c5dbefe5b65d3b8e8dab0e9364b21 # codeql-bundle-v2.16.0
|
||||
uses: github/codeql-action/init@592977e6ae857384aa79bb31e7a1d62d63449ec5 # codeql-bundle-v2.16.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -54,4 +54,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e6a47e25652c5dbefe5b65d3b8e8dab0e9364b21 # codeql-bundle-v2.16.0
|
||||
uses: github/codeql-action/analyze@592977e6ae857384aa79bb31e7a1d62d63449ec5 # codeql-bundle-v2.16.3
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn install
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run compliance:licenses
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn lint
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn test
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import BaseModel from '../../models/base';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
/**
|
||||
* Alias contains all fields related to an Alias resource
|
||||
*/
|
||||
export default class GeneratedAliasModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource, to help differentiate schemas',
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional name for identification purposes.',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional user-set description for identification purposes.',
|
||||
})
|
||||
description;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was created\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
created_time;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was last updated\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
updated_time;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
@attr('string', {
|
||||
description: 'This is the id of the resource that this Alias points to.',
|
||||
})
|
||||
destination_id;
|
||||
|
||||
@attr('object', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'This contains a host_id which is the id of the host that the session will be authorized for.',
|
||||
})
|
||||
authorize_session_arguments;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import GeneratedAliasModel from '../generated/models/alias';
|
||||
|
||||
export const TYPE_ALIAS_TARGET = 'target';
|
||||
export const TYPES_ALIAS = Object.freeze([TYPE_ALIAS_TARGET]);
|
||||
export default class AliasModel extends GeneratedAliasModel {}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/models/alias';
|
||||
@@ -753,6 +753,22 @@ function routes() {
|
||||
return policies.create(attrs);
|
||||
});
|
||||
|
||||
// Alias
|
||||
this.get(
|
||||
'/aliases',
|
||||
({ aliases }, { queryParams: { scope_id: scopeId } }) => {
|
||||
return aliases.where({ scopeId });
|
||||
},
|
||||
);
|
||||
this.get('/aliases/:id');
|
||||
this.del('/aliases/:id');
|
||||
this.patch('/aliases/:id');
|
||||
this.post('/aliases', function ({ aliases }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
|
||||
return aliases.create(attrs);
|
||||
});
|
||||
|
||||
// session recordings
|
||||
this.get(
|
||||
'/session-recordings',
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import factory from '../generated/factories/alias';
|
||||
import permissions from '../helpers/permissions';
|
||||
import generateId from '../helpers/id';
|
||||
|
||||
const destinationIDs = ['', generateId('t_')];
|
||||
|
||||
export default factory.extend({
|
||||
id: () => generateId('alt_'),
|
||||
destination_id: (i) => destinationIDs[i % destinationIDs.length],
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('alias') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
});
|
||||
@@ -33,12 +33,13 @@ export default factory.extend({
|
||||
roles: ['create', 'list'],
|
||||
};
|
||||
|
||||
// Worker permissions only available on the global scope
|
||||
// Resources that are available only on the global scope
|
||||
if (this.type === 'global') {
|
||||
collectionActions.workers = ['create:worker-led', 'list'];
|
||||
collectionActions.aliases = ['create', 'list'];
|
||||
}
|
||||
|
||||
// Session recording resources are only available on global or org scope
|
||||
// Resources that are only available on the global or the org scope
|
||||
if (this.type === 'global' || this.type === 'org') {
|
||||
collectionActions['storage-buckets'] = ['create', 'list'];
|
||||
collectionActions['session-recordings'] = ['list'];
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { Factory } from 'miragejs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { TYPE_ALIAS_TARGET } from 'api/models/alias';
|
||||
|
||||
/**
|
||||
* GeneratedAliasModelFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
name: () => faker.word.words(),
|
||||
type: TYPE_ALIAS_TARGET,
|
||||
value: () => faker.word.words(),
|
||||
description: () => faker.word.words(),
|
||||
created_time: () => faker.date.recent(),
|
||||
updated_time: () => faker.date.recent(),
|
||||
version: () => faker.number.int(),
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { belongsTo } from 'miragejs';
|
||||
import Model from './base';
|
||||
|
||||
export default Model.extend({
|
||||
scope: belongsTo(),
|
||||
});
|
||||
@@ -108,6 +108,9 @@ export default function (server) {
|
||||
server.createList('policy', 3, { scope: globalScope });
|
||||
server.createList('policy', 3, { scope: orgScope });
|
||||
|
||||
// Aliases
|
||||
server.createList('alias', 3, { scope: globalScope });
|
||||
|
||||
// Other resources
|
||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||
server.createList('host-catalog', 8, { scope }, 'withChildren');
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | alias', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('alias', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -999,3 +999,8 @@ policy:
|
||||
label: Do not delete
|
||||
custom:
|
||||
label: Custom
|
||||
alias:
|
||||
title: Alias
|
||||
title_plural: Aliases
|
||||
titles:
|
||||
new: New Alias
|
||||
@@ -12,7 +12,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'deleteAccount' @model)}}
|
||||
{{on 'click' (fn @delete @model)}}
|
||||
>
|
||||
{{t 'resources.account.actions.delete'}}
|
||||
</dropdown.button>
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'deleteManagedGroup' @model)}}
|
||||
{{on 'click' (fn @delete @model)}}
|
||||
>
|
||||
{{t 'resources.managed-group.actions.delete'}}
|
||||
</dropdown.button>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'delete' @model)}}
|
||||
{{on 'click' @delete}}
|
||||
>
|
||||
{{t 'resources.credential-store.actions.delete'}}
|
||||
</dropdown.button>
|
||||
|
||||
@@ -10,58 +10,48 @@
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<InfoField @value={{@model.type}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.type.label'}}</F.Label>
|
||||
</InfoField>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.email}}
|
||||
@label={{t 'form.email.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.full_name}}
|
||||
@label={{t 'form.full_name.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
<InfoField @value={{@model.issuer}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.issuer.label'}}</F.Label>
|
||||
</InfoField>
|
||||
|
||||
<InfoField @value={{@model.subject}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.subject.label'}}</F.Label>
|
||||
</InfoField>
|
||||
|
||||
<InfoField @value={{@model.email}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.email.label'}}</F.Label>
|
||||
</InfoField>
|
||||
|
||||
<InfoField @value={{@model.full_name}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.full_name.label'}}</F.Label>
|
||||
</InfoField>
|
||||
{{/unless}}
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
@isInvalid={{@model.errors.name}}
|
||||
@isOptional={{true}}
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message data-test-error-message-name>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Textarea::Field
|
||||
@isOptional={{true}}
|
||||
@@ -83,41 +73,41 @@
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<form.input
|
||||
@name='issuer'
|
||||
@type='text'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='issuer'
|
||||
@value={{@model.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
@error={{@model.errors.issuer}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
@isInvalid={{@model.errors.issuer}}
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'issuer')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.issuer.label'}}</F.Label>
|
||||
{{#if @model.errors.issuer}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.issuer as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<form.input
|
||||
@name='subject'
|
||||
@type='text'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='subject'
|
||||
@value={{@model.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
@error={{@model.errors.subject}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
@isInvalid={{@model.errors.subject}}
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'subject')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.subject.label'}}</F.Label>
|
||||
{{#if @model.errors.subject}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.subject as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
|
||||
@@ -10,37 +10,35 @@
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<InfoField @value={{@model.type}} readonly={{true}} as |F|>
|
||||
<F.Label>{{t 'form.type.label'}}</F.Label>
|
||||
</InfoField>
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
@isInvalid={{@model.errors.name}}
|
||||
@isOptional={{true}}
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message data-test-error-message-name>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Textarea::Field
|
||||
@isOptional={{true}}
|
||||
name='description'
|
||||
@value={{@model.description}}
|
||||
@isInvalid={{@model.errors.description}}
|
||||
name='description'
|
||||
@isOptional={{true}}
|
||||
disabled={{form.disabled}}
|
||||
as |F|
|
||||
>
|
||||
@@ -55,43 +53,45 @@
|
||||
{{/if}}
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
||||
<form.input
|
||||
@name='login_name'
|
||||
@type='text'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='login_name'
|
||||
@value={{@model.login_name}}
|
||||
@label={{t 'form.login_name.label'}}
|
||||
@error={{@model.errors.login_name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
@isInvalid={{@model.errors.login_name}}
|
||||
@isRequired={{true}}
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'login_name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.login_name.label'}}</F.Label>
|
||||
{{#if @model.errors.login_name}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.login_name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<form.input
|
||||
@name='password'
|
||||
<Hds::Form::TextInput::Field
|
||||
name='password'
|
||||
@type='password'
|
||||
@value={{this.password}}
|
||||
@label={{t 'form.password.label'}}
|
||||
@error={{@model.errors.password}}
|
||||
readonly={{false}}
|
||||
@value={{@model.password}}
|
||||
@isInvalid={{@model.errors.password}}
|
||||
disabled={{form.disabled}}
|
||||
autocomplete='new-password'
|
||||
as |field|
|
||||
{{on 'input' (set-from-event @model 'password')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.password.label'}}</F.Label>
|
||||
{{#if @model.errors.password}}
|
||||
<field.errors as |errors|>
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.password as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'delete' @model)}}
|
||||
{{on 'click' @delete}}
|
||||
>
|
||||
{{t 'resources.group.actions.delete'}}
|
||||
</dropdown.button>
|
||||
|
||||
@@ -5,9 +5,45 @@
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class AccountChangePasswordController extends Controller {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Changes the password on the account and then redirects to the index route.
|
||||
* @param {AccountModel} account
|
||||
* @param {string} currentPassword
|
||||
* @param {string} newPassword
|
||||
*/
|
||||
@action
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.save-success')
|
||||
async changePassword(account, currentPassword, newPassword) {
|
||||
await account.changePassword(currentPassword, newPassword);
|
||||
// Transition to index after success. We ignore errors in the transition,
|
||||
// since the index route is responsible for additional
|
||||
// redirects depending on the authentication context. These additional
|
||||
// redirects may abort this initial transition, resulting in a
|
||||
// TransitionAborted error, which we do not want to show to the user.
|
||||
try {
|
||||
await this.router.replaceWith('index');
|
||||
} catch (e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On cancel, redirects to index route for further processing.
|
||||
*/
|
||||
@action
|
||||
cancel() {
|
||||
this.router.replaceWith('index');
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,73 @@
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ApplicationController extends Controller {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service flashMessages;
|
||||
@service features;
|
||||
@service featureEdition;
|
||||
@service flashMessages;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Delegates invalidation to the session service.
|
||||
*/
|
||||
@action
|
||||
invalidateSession() {
|
||||
this.session.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to specified edition.
|
||||
* Options are oss, enterprise, and hcp.
|
||||
* @param {string} edition
|
||||
*/
|
||||
@action
|
||||
toggleEdition(edition) {
|
||||
this.featureEdition.setEdition(edition);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} feature
|
||||
*/
|
||||
@action
|
||||
toggleFeature(feature) {
|
||||
if (this.features.isEnabled(feature)) {
|
||||
this.features.disable(feature);
|
||||
} else {
|
||||
this.features.enable(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAliasesAliasIndexController extends Controller {
|
||||
@controller('scopes/scope/aliases/index') aliases;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAliasesIndexController extends Controller {
|
||||
// =services
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAliasesNewController extends Controller {
|
||||
@controller('scopes/scope/aliases/index') aliases;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountIndexController extends Controller {
|
||||
@controller('scopes/scope/auth-methods/auth-method/accounts/index') accounts;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountSetPasswordController extends Controller {
|
||||
// =controllers
|
||||
|
||||
@controller('scopes/scope/auth-methods/auth-method/accounts/index') accounts;
|
||||
|
||||
// =services
|
||||
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Set password for the specified account.
|
||||
* @param {AccountModel} account
|
||||
* @param {string} password
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.save-success')
|
||||
async setPassword(account, password) {
|
||||
await account.setPassword(password);
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.accounts.account.set-password',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsIndexController extends Controller {
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on an account.
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
async cancel(account) {
|
||||
const { isNew } = account;
|
||||
account.rollbackAttributes();
|
||||
if (isNew) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an account in current scope.
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(account, password) {
|
||||
const { isNew } = account;
|
||||
const adapterOptions = {};
|
||||
if (isNew) {
|
||||
adapterOptions.password = password;
|
||||
}
|
||||
await account.save({ adapterOptions });
|
||||
if (this.can.can('read model', account)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts.account',
|
||||
account,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an account in current scope and redirect to index
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(account) {
|
||||
await account.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
await this.router.refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsNewController extends Controller {
|
||||
@controller('scopes/scope/auth-methods/auth-method/accounts/index') accounts;
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsIndexController extends Controller {
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a managed group.
|
||||
* @param {ManagedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
async cancel(managedGroup) {
|
||||
const { isNew } = managedGroup;
|
||||
managedGroup.rollbackAttributes();
|
||||
if (isNew) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a managed group in current scope.
|
||||
* @param {ManagedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(managedGroup) {
|
||||
await managedGroup.save();
|
||||
if (this.can.can('read model', managedGroup)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups.managed-group',
|
||||
managedGroup,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a managed group in current scope and redirect to index
|
||||
* @param {ManagedGroupModel} managed group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(managedGroup) {
|
||||
await managedGroup.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of string array fields in order to force the instance
|
||||
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
|
||||
* to the original expected array.
|
||||
*
|
||||
* The deep copy implemented here is required to ensure that both the
|
||||
* array itself and its members are all new.
|
||||
*
|
||||
* @param {managedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
edit(managedGroup) {
|
||||
if (managedGroup.group_names) {
|
||||
managedGroup.group_names = structuredClone(managedGroup.group_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupIndexController extends Controller {
|
||||
@controller('scopes/scope/auth-methods/index') authMethods;
|
||||
@controller('scopes/scope/auth-methods/auth-method/managed-groups/index')
|
||||
managedGroups;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupMembersController extends Controller {
|
||||
@controller('scopes/scope/auth-methods/auth-method/managed-groups/index')
|
||||
managedGroups;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsNewController extends Controller {
|
||||
@controller('scopes/scope/auth-methods/index') authMethods;
|
||||
@controller('scopes/scope/auth-methods/auth-method/managed-groups/index')
|
||||
managedGroups;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeAuthMethodsIndexController extends Controller {
|
||||
// =attributes
|
||||
@@ -15,4 +16,35 @@ export default class ScopesScopeAuthMethodsIndexController extends Controller {
|
||||
get hasPrimaryAuthMethod() {
|
||||
return Boolean(this.scopeModel.primary_auth_method_id);
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Removes an item from array `property` at `index` on the
|
||||
* passed `authMethod`. This is used to manage entries in fragment array
|
||||
* fields such as `signing_algorithms`.
|
||||
* @param {AuthMethodModel} authMethod
|
||||
* @param {string} property
|
||||
* @param {number} index
|
||||
*/
|
||||
@action
|
||||
async removeItemByIndex(authMethod, property, index) {
|
||||
const array = authMethod.get(property).filter((item, i) => i !== index);
|
||||
authMethod.set(property, array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string item to array `property` on the passed `authMethod`.
|
||||
* This is used to manage entries in fragment OIDC string array fields such
|
||||
* as `signing_algorithms`.
|
||||
* @param {AuthMethodModel} authMethod
|
||||
* @param {string} property
|
||||
* @param {string} value
|
||||
*/
|
||||
@action
|
||||
async addStringItem(authMethod, property, value) {
|
||||
const existingArray = authMethod[property] ?? [];
|
||||
const array = [...existingArray, { value }];
|
||||
authMethod.set(property, array);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesCredentialLibraryIndexController extends Controller {
|
||||
@controller(
|
||||
'scopes/scope/credential-stores/credential-store/credential-libraries/index',
|
||||
)
|
||||
credentialLibraries;
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesIndexController extends Controller {
|
||||
@controller('scopes/scope/credential-stores/index') credentialStores;
|
||||
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a credential library.
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
async cancel(credentialLibrary) {
|
||||
const { isNew } = credentialLibrary;
|
||||
credentialLibrary.rollbackAttributes();
|
||||
if (isNew)
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save of a credential library
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(credentialLibrary) {
|
||||
await credentialLibrary.save();
|
||||
if (this.can.can('read model', credentialLibrary)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries.credential-library',
|
||||
credentialLibrary,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete of a credential library
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(credentialLibrary) {
|
||||
await credentialLibrary.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of array fields in order to force the instance
|
||||
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
|
||||
* to the original expected array.
|
||||
*
|
||||
* The deep copy implemented here is required to ensure that both the
|
||||
* array itself and its members are all new.
|
||||
*
|
||||
* @param {credentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
edit(credentialLibrary) {
|
||||
const { critical_options, extensions } = credentialLibrary;
|
||||
credentialLibrary.critical_options = structuredClone(critical_options);
|
||||
credentialLibrary.extensions = structuredClone(extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential library
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
async changeType(type) {
|
||||
await this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -3,10 +3,13 @@
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewController extends Controller {
|
||||
// =services
|
||||
@controller(
|
||||
'scopes/scope/credential-stores/credential-store/credential-libraries/index',
|
||||
)
|
||||
credentialLibraries;
|
||||
|
||||
// =attributes
|
||||
queryParams = ['type'];
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialsCredentialIndexController extends Controller {
|
||||
@controller(
|
||||
'scopes/scope/credential-stores/credential-store/credentials/index',
|
||||
)
|
||||
credentials;
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialsIndexController extends Controller {
|
||||
@controller('scopes/scope/credential-stores/index') credentialStores;
|
||||
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a credential.
|
||||
* @param {CredentialModel} credential
|
||||
*/
|
||||
@action
|
||||
async cancel(credential) {
|
||||
const { isNew } = credential;
|
||||
credential.rollbackAttributes();
|
||||
if (isNew)
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save of a credential
|
||||
* @param {CredentialModel} credential
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(credential) {
|
||||
await credential.save();
|
||||
if (this.can.can('read credential', credential)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials.credential',
|
||||
credential,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
/**
|
||||
* Handle delete of a credential
|
||||
* @param {Credential} credential
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(credential) {
|
||||
await credential.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
async changeType(type) {
|
||||
await this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -3,10 +3,13 @@
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewController extends Controller {
|
||||
// =services
|
||||
@controller(
|
||||
'scopes/scope/credential-stores/credential-store/credentials/index',
|
||||
)
|
||||
credentials;
|
||||
|
||||
// =attributes
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreIndexController extends Controller {
|
||||
@controller('scopes/scope/credential-stores/index') credentialStores;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeCredentialStoresIndexController extends Controller {
|
||||
// =services
|
||||
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Handle save
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('notifications.save-success')
|
||||
async save(credentialStore) {
|
||||
await credentialStore.save();
|
||||
if (this.can.can('read model', credentialStore)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store',
|
||||
credentialStore,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo('scopes.scope.credential-stores');
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback changes on credential stores.
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
async cancel(credentialStore) {
|
||||
const { isNew } = credentialStore;
|
||||
credentialStore.rollbackAttributes();
|
||||
if (isNew) {
|
||||
await this.router.transitionTo('scopes.scope.credential-stores');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the credential store and redirects to index
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(credentialStore) {
|
||||
await credentialStore.destroyRecord();
|
||||
await this.router.replaceWith('scopes.scope.credential-stores');
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential store
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
async changeType(type) {
|
||||
await this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresNewController extends Controller {
|
||||
// =services
|
||||
@controller('scopes/scope/credential-stores/index') credentialStores;
|
||||
|
||||
// =attributes
|
||||
queryParams = ['type'];
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
*/
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeGroupsGroupAddMembersController extends Controller {
|
||||
// =services
|
||||
|
||||
@service router;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
@@ -26,4 +32,29 @@ export default class ScopesScopeGroupsGroupAddMembersController extends Controll
|
||||
const sorted = [global, ...orgsAndProjects].flat();
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Adds members to the group and saves, replaces with the members index
|
||||
* route, and notifies the user of success or error.
|
||||
* @param {GroupModel} group
|
||||
* @param {[string]} userIDs
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.add-success')
|
||||
async addMembers(group, userIDs) {
|
||||
await group.addMembers(userIDs);
|
||||
await this.router.replaceWith('scopes.scope.groups.group.members');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to group members as if nothing ever happened.
|
||||
*/
|
||||
@action
|
||||
async cancel() {
|
||||
await this.router.replaceWith('scopes.scope.groups.group.members');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeGroupsGroupIndexController extends Controller {
|
||||
@controller('scopes/scope/groups/index') groups;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeGroupsGroupMembersController extends Controller {
|
||||
@controller('scopes/scope/groups/index') groups;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeGroupsIndexController extends Controller {
|
||||
// =services
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a group.
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
async cancel(group) {
|
||||
const { isNew } = group;
|
||||
group.rollbackAttributes();
|
||||
if (isNew) await this.router.transitionTo('scopes.scope.groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a group in current scope.
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(group) {
|
||||
await group.save();
|
||||
if (this.can.can('read model', group)) {
|
||||
await this.router.transitionTo('scopes.scope.groups.group', group);
|
||||
} else {
|
||||
await this.router.transitionTo('scopes.scope.groups');
|
||||
}
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a group in current scope and redirect to index
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(group) {
|
||||
await group.destroyRecord();
|
||||
await this.router.replaceWith('scopes.scope.groups');
|
||||
await this.router.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from the current role and redirect to members index.
|
||||
* @param {GroupModel} group
|
||||
* @param {UserModel} member
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.remove-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.remove-success')
|
||||
async removeMember(group, member) {
|
||||
await group.removeMember(member.id);
|
||||
await this.router.refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeGroupsNewController extends Controller {
|
||||
@controller('scopes/scope/groups/index') groups;
|
||||
}
|
||||
@@ -95,6 +95,11 @@ Router.map(function () {
|
||||
this.route('new');
|
||||
});
|
||||
|
||||
this.route('aliases', function () {
|
||||
this.route('new');
|
||||
this.route('alias', { path: ':alias_id' }, function () {});
|
||||
});
|
||||
|
||||
this.route('host-catalogs', function () {
|
||||
this.route('new');
|
||||
this.route('host-catalog', { path: ':host_catalog_id' }, function () {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
/**
|
||||
* The change password flow is associated only with password-type auth methods.
|
||||
@@ -30,33 +28,4 @@ export default class AccountChangePasswordRoute extends Route {
|
||||
const account_id = this.session?.data?.authenticated?.account_id;
|
||||
return this.store.findRecord('account', account_id);
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Changes the password on the account and then redirects to the index route.
|
||||
* @param {AccountModel} account
|
||||
* @param {string} currentPassword
|
||||
* @param {string} newPassword
|
||||
*/
|
||||
@action
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.save-success')
|
||||
async changePassword(account, currentPassword, newPassword) {
|
||||
await account.changePassword(currentPassword, newPassword);
|
||||
// Transition to index after success. We ignore errors in the transition,
|
||||
// since the index route is responsible for additional
|
||||
// redirects depending on the authentication context. These additional
|
||||
// redirects may abort this initial transition, resulting in a
|
||||
// TransitionAborted error, which we do not want to show to the user.
|
||||
await this.router.replaceWith('index').catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* On cancel, redirects to index route for further processing.
|
||||
*/
|
||||
@action
|
||||
cancel() {
|
||||
this.router.replaceWith('index');
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,6 @@ export default class ApplicationRoute extends Route {
|
||||
@service confirm;
|
||||
@service router;
|
||||
@service intl;
|
||||
@service features;
|
||||
@service featureEdition;
|
||||
|
||||
// =attributes
|
||||
|
||||
@@ -64,33 +62,6 @@ export default class ApplicationRoute extends Route {
|
||||
this.toggleTheme(theme);
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Delegates invalidation to the session service.
|
||||
*/
|
||||
@action
|
||||
invalidateSession() {
|
||||
this.session.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the session if a 401 error occurs and returns false to
|
||||
* prevent further error handling.
|
||||
* Returns true in all other cases, allowing error handling to occur (such
|
||||
* as displaying the `error.hbs` template, if one exists).
|
||||
* @param {Error} e
|
||||
*/
|
||||
@action
|
||||
error(e) {
|
||||
const isUnauthenticated = A(e?.errors)?.[0]?.isUnauthenticated;
|
||||
if (isUnauthenticated) {
|
||||
this.session.invalidate();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the specified color theme to the root ember element.
|
||||
* @param {string} theme - "light", "dark", or nullish (system default)
|
||||
@@ -117,17 +88,20 @@ export default class ApplicationRoute extends Route {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the session if a 401 error occurs and returns false to
|
||||
* prevent further error handling.
|
||||
* Returns true in all other cases, allowing error handling to occur (such
|
||||
* as displaying the `error.hbs` template, if one exists).
|
||||
* @param {Error} e
|
||||
*/
|
||||
@action
|
||||
toggleEdition(edition) {
|
||||
this.featureEdition.setEdition(edition);
|
||||
}
|
||||
|
||||
@action
|
||||
toggleFeature(feature) {
|
||||
if (this.features.isEnabled(feature)) {
|
||||
this.features.disable(feature);
|
||||
} else {
|
||||
this.features.enable(feature);
|
||||
error(e) {
|
||||
const isUnauthenticated = A(e?.errors)?.[0]?.isUnauthenticated;
|
||||
if (isUnauthenticated) {
|
||||
this.session.invalidate();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeAliasesRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service session;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* If arriving here unauthenticated, redirect to index for further processing.
|
||||
*/
|
||||
beforeModel() {
|
||||
if (!this.session.isAuthenticated) this.router.transitionTo('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all aliases in current scope
|
||||
* @return {Promise<[AliasModel]>}
|
||||
*/
|
||||
async model() {
|
||||
const scope = this.modelFor('scopes.scope');
|
||||
const { id: scope_id } = scope;
|
||||
|
||||
if (
|
||||
this.can.can('list model', scope, {
|
||||
collection: 'aliases',
|
||||
})
|
||||
) {
|
||||
return this.store.query('alias', { scope_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeAliasesAliasRoute extends Route {
|
||||
@service store;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Load an alias in current scope.
|
||||
* @param {object} params
|
||||
* @param {string} params.alias_id
|
||||
* @return {Promise{AliasModel}}
|
||||
*/
|
||||
async model({ alias_id }) {
|
||||
return this.store.findRecord('alias', alias_id, {
|
||||
reload: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ScopesScopeAliasesIndexRoute extends Route {
|
||||
// =methods
|
||||
|
||||
setupController(controller) {
|
||||
const scope = this.modelFor('scopes.scope');
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties({ scope });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { TYPE_ALIAS_TARGET } from 'api/models/alias';
|
||||
|
||||
export default class ScopesScopeAliasesNewRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Redirect to parent route when scope does not have create authorized action.
|
||||
*/
|
||||
beforeModel() {
|
||||
const scopeModel = this.modelFor('scopes.scope');
|
||||
if (
|
||||
this.can.cannot('create model', scopeModel, { collection: 'aliases' })
|
||||
) {
|
||||
this.router.replaceWith('scopes.scope.aliases');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new unsaved alias.
|
||||
* @return {AliasModel}
|
||||
*/
|
||||
model() {
|
||||
const scopeModel = this.modelFor('scopes.scope');
|
||||
const record = this.store.createRecord('alias', {
|
||||
type: TYPE_ALIAS_TARGET,
|
||||
});
|
||||
record.scopeModel = scopeModel;
|
||||
return record;
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,13 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service intl;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -42,67 +36,4 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsRoute extends Route
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on an account.
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
cancel(account) {
|
||||
const { isNew } = account;
|
||||
account.rollbackAttributes();
|
||||
if (isNew)
|
||||
this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an account in current scope.
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(account, password) {
|
||||
const { isNew } = account;
|
||||
const adapterOptions = {};
|
||||
if (isNew) {
|
||||
adapterOptions.password = password;
|
||||
}
|
||||
await account.save({ adapterOptions });
|
||||
if (this.can.can('read model', account)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts.account',
|
||||
account,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an account in current scope and redirect to index
|
||||
* @param {AccountModel} account
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async deleteAccount(account) {
|
||||
await account.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.accounts',
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
+1
-29
@@ -4,33 +4,5 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountSettingsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
@service router;
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Set password for the specified account.
|
||||
* @param {AccountModel} account
|
||||
* @param {string} password
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.save-success')
|
||||
async setPassword(account, password) {
|
||||
await account.setPassword(password);
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.accounts.account.set-password',
|
||||
);
|
||||
}
|
||||
}
|
||||
export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountSetPasswordRoute extends Route {}
|
||||
@@ -4,19 +4,13 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service intl;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
//=methods
|
||||
|
||||
@@ -43,62 +37,4 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsRoute extends
|
||||
managedGroups,
|
||||
};
|
||||
}
|
||||
|
||||
// =actions
|
||||
/**
|
||||
* Rollback changes on a managed group.
|
||||
* @param {ManagedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
cancel(managedGroup) {
|
||||
const { isNew } = managedGroup;
|
||||
managedGroup.rollbackAttributes();
|
||||
if (isNew) {
|
||||
this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a managed group in current scope.
|
||||
* @param {ManagedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(managedGroup) {
|
||||
await managedGroup.save();
|
||||
if (this.can.can('read model', managedGroup)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups.managed-group',
|
||||
managedGroup,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a managed group in current scope and redirect to index
|
||||
* @param {ManagedGroupModel} managed group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async deleteManagedGroup(managedGroup) {
|
||||
await managedGroup.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.auth-methods.auth-method.managed-groups',
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
-19
@@ -4,14 +4,12 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
@@ -44,21 +42,4 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of string array fields in order to force the instance
|
||||
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
|
||||
* to the original expected array.
|
||||
*
|
||||
* The deep copy implemented here is required to ensure that both the
|
||||
* array itself and its members are all new.
|
||||
*
|
||||
* @param {managedGroupModel} managedGroup
|
||||
*/
|
||||
@action
|
||||
edit(managedGroup) {
|
||||
if (managedGroup.group_names) {
|
||||
managedGroup.group_names = structuredClone(managedGroup.group_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,12 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeCredentialStoresRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -34,49 +29,4 @@ export default class ScopesScopeCredentialStoresRoute extends Route {
|
||||
return this.store.query('credential-store', { scope_id });
|
||||
}
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Handle save
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('notifications.save-success')
|
||||
async save(credentialStore) {
|
||||
await credentialStore.save();
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store',
|
||||
credentialStore,
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback changes on credential stores.
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
cancel(credentialStore) {
|
||||
const { isNew } = credentialStore;
|
||||
credentialStore.rollbackAttributes();
|
||||
if (isNew) this.router.transitionTo('scopes.scope.credential-stores');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the credential store and redirects to index
|
||||
* @param {CredentialStoreModel} credentialStore
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(credentialStore) {
|
||||
await credentialStore.destroyRecord();
|
||||
await this.router.replaceWith('scopes.scope.credential-stores');
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
-63
@@ -4,10 +4,6 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route {
|
||||
@@ -15,7 +11,6 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -36,62 +31,4 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
return this.store.query('credential-library', { credential_store_id });
|
||||
}
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a credential library.
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
cancel(credentialLibrary) {
|
||||
const { isNew } = credentialLibrary;
|
||||
credentialLibrary.rollbackAttributes();
|
||||
if (isNew)
|
||||
this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save of a credential library
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(credentialLibrary) {
|
||||
await credentialLibrary.save();
|
||||
if (this.can.can('read model', credentialLibrary)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries.credential-library',
|
||||
credentialLibrary,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete of a credential library
|
||||
* @param {CredentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async deleteCredentialLibrary(credentialLibrary) {
|
||||
await credentialLibrary.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.credential-stores.credential-store.credential-libraries',
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
-19
@@ -5,13 +5,11 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesCredentialLibraryRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
@@ -45,21 +43,4 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of array fields in order to force the instance
|
||||
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
|
||||
* to the original expected array.
|
||||
*
|
||||
* The deep copy implemented here is required to ensure that both the
|
||||
* array itself and its members are all new.
|
||||
*
|
||||
* @param {credentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
edit(credentialLibrary) {
|
||||
const { critical_options, extensions } = credentialLibrary;
|
||||
credentialLibrary.critical_options = structuredClone(critical_options);
|
||||
credentialLibrary.extensions = structuredClone(extensions);
|
||||
}
|
||||
}
|
||||
-10
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library';
|
||||
|
||||
@@ -64,13 +63,4 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
credential_store_id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential library
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
changeType(type) {
|
||||
this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,6 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialsRoute extends Route {
|
||||
@@ -15,7 +11,6 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsRoute
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -36,61 +31,4 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsRoute
|
||||
return this.store.query('credential', { credential_store_id });
|
||||
}
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a credential.
|
||||
* @param {CredentialModel} credential
|
||||
*/
|
||||
@action
|
||||
cancel(credential) {
|
||||
const { isNew } = credential;
|
||||
credential.rollbackAttributes();
|
||||
if (isNew)
|
||||
this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save of a credential
|
||||
* @param {CredentialModel} credential
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(credential) {
|
||||
await credential.save();
|
||||
if (this.can.can('read model', credential)) {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials.credential',
|
||||
credential,
|
||||
);
|
||||
} else {
|
||||
await this.router.transitionTo(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
/**
|
||||
* Handle delete of a credential
|
||||
* @param {Credential} credential
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async deleteCredential(credential) {
|
||||
await credential.destroyRecord();
|
||||
await this.router.replaceWith(
|
||||
'scopes.scope.credential-stores.credential-store.credentials',
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
-10
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRoute extends Route {
|
||||
@@ -77,13 +76,4 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou
|
||||
...typeSpecificAttrs,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
changeType(type) {
|
||||
this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresNewRoute extends Route {
|
||||
@@ -63,13 +62,4 @@ export default class ScopesScopeCredentialStoresNewRoute extends Route {
|
||||
record.scopeModel = scopeModel;
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential store
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
changeType(type) {
|
||||
this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,11 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeGroupsRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service intl;
|
||||
@service session;
|
||||
@service can;
|
||||
@service router;
|
||||
@@ -39,51 +34,4 @@ export default class ScopesScopeGroupsRoute extends Route {
|
||||
return this.store.query('group', { scope_id });
|
||||
}
|
||||
}
|
||||
|
||||
// =actions
|
||||
/**
|
||||
* Rollback changes on a group.
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
cancel(group) {
|
||||
const { isNew } = group;
|
||||
group.rollbackAttributes();
|
||||
if (isNew) this.router.transitionTo('scopes.scope.groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a group in current scope.
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess(({ isNew }) =>
|
||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||
)
|
||||
async save(group) {
|
||||
await group.save();
|
||||
if (this.can.can('read model', group)) {
|
||||
await this.router.transitionTo('scopes.scope.groups.group', group);
|
||||
} else {
|
||||
await this.router.transitionTo('scopes.scope.groups');
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a group in current scope and redirect to index
|
||||
* @param {GroupModel} group
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.delete-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.delete-success')
|
||||
async delete(group) {
|
||||
await group.destroyRecord();
|
||||
await this.router.replaceWith('scopes.scope.groups');
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { hash } from 'rsvp';
|
||||
import { loading } from 'ember-loading';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { resourceFilter } from 'core/decorators/resource-filter';
|
||||
|
||||
export default class ScopesScopeGroupsGroupAddMembersRoute extends Route {
|
||||
@@ -67,29 +65,6 @@ export default class ScopesScopeGroupsGroupAddMembersRoute extends Route {
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Adds members to the group and saves, replaces with the members index
|
||||
* route, and notifies the user of success or error.
|
||||
* @param {GroupModel} group
|
||||
* @param {[string]} userIDs
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.add-success')
|
||||
async addMembers(group, userIDs) {
|
||||
await group.addMembers(userIDs);
|
||||
await this.router.replaceWith('scopes.scope.groups.group.members');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to group members as if nothing ever happened.
|
||||
*/
|
||||
@action
|
||||
cancel() {
|
||||
this.router.replaceWith('scopes.scope.groups.group.members');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified resource filter field to the specified value.
|
||||
* @param {string} field
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { loading } from 'ember-loading';
|
||||
import { confirm } from 'core/decorators/confirm';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class ScopesScopeGroupsGroupMembersRoute extends Route {
|
||||
// =services
|
||||
@@ -37,21 +33,4 @@ export default class ScopesScopeGroupsGroupMembersRoute extends Route {
|
||||
: [],
|
||||
});
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Remove a member from the current role and redirect to members index.
|
||||
* @param {GroupModel} group
|
||||
* @param {UserModel} member
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@confirm('questions.remove-confirm')
|
||||
@notifyError(({ message }) => message, { catch: true })
|
||||
@notifySuccess('notifications.remove-success')
|
||||
async removeMember(group, member) {
|
||||
await group.removeMember(member.id);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
<page.body>
|
||||
<Form::Account::Password::ChangePassword
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'changePassword' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.changePassword @model}}
|
||||
@cancel={{fn this.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</dropdown.link>
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.button {{on 'click' (route-action 'invalidateSession')}}>
|
||||
<dropdown.button {{on 'click' this.invalidateSession}}>
|
||||
{{t 'actions.deauthenticate'}}
|
||||
</dropdown.button>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
this.session.data.theme
|
||||
'system-default-theme'
|
||||
}}
|
||||
@changed={{route-action 'toggleTheme'}}
|
||||
@changed={{this.toggleTheme}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<dropdown.item>
|
||||
@@ -83,7 +83,7 @@
|
||||
<form.radioGroup
|
||||
@name='edition'
|
||||
@selectedValue={{this.featureEdition.edition}}
|
||||
@changed={{route-action 'toggleEdition'}}
|
||||
@changed={{this.toggleEdition}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each this.featureEdition.editions as |edition|}}
|
||||
@@ -105,7 +105,7 @@
|
||||
<form.checkbox
|
||||
@label={{feature}}
|
||||
@checked={{feature-flag feature}}
|
||||
@onChange={{route-action 'toggleFeature' feature}}
|
||||
@onChange={{fn this.toggleFeature feature}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
{{t 'resources.policy.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if (can 'navigate scope' @model collection='aliases')}}
|
||||
<nav.link @route='scopes.scope.aliases'>
|
||||
<Rose::Icon @name='flight-icons/svg/at-sign-16' @size='medium' />
|
||||
{{t 'resources.alias.title_plural'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
{{#if
|
||||
(and
|
||||
(can 'navigate worker' @model collection='workers')
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
{{page-title (t 'resources.alias.title_plural')}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{t 'resources.alias.title_plural'}}
|
||||
@route='scopes.scope.aliases'
|
||||
/>
|
||||
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
{{page-title @model.displayName}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{@model.displayName}}
|
||||
@route='scopes.scope.aliases.alias'
|
||||
@model={{@model}}
|
||||
/>
|
||||
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,11 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,27 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.alias.title'}}
|
||||
{{! TODO: Add doc link for alias }}
|
||||
</h2>
|
||||
{{! TODO: Add desc for alias }}
|
||||
</page.header>
|
||||
<page.actions>
|
||||
{{#if (can 'create model' this.scope collection='aliases')}}
|
||||
{{#if @model}}
|
||||
<Hds::Button
|
||||
@route='scopes.scope.aliases.new'
|
||||
@text={{t 'resources.alias.titles.new'}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</page.actions>
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,27 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
{{page-title (t 'titles.new')}}
|
||||
<Breadcrumbs::Item
|
||||
@text={{t 'resources.alias.titles.new'}}
|
||||
@route='scopes.scope.aliases.new'
|
||||
/>
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.alias.titles.new'}}
|
||||
{{! TODO: Add doc link for alias }}
|
||||
|
||||
</h2>
|
||||
{{! TODO: Add desc for alias }}
|
||||
|
||||
</page.header>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
+6
-3
@@ -18,14 +18,17 @@
|
||||
</page.navigation>
|
||||
|
||||
<page.actions>
|
||||
<AuthMethods::AuthMethod::Accounts::Account::Actions @model={{@model}} />
|
||||
<AuthMethods::AuthMethod::Accounts::Account::Actions
|
||||
@model={{@model}}
|
||||
@delete={{this.accounts.delete}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::Account
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.accounts.save @model}}
|
||||
@cancel={{fn this.accounts.cancel @model}}
|
||||
/>
|
||||
|
||||
</page.body>
|
||||
|
||||
+6
-3
@@ -26,14 +26,17 @@
|
||||
</page.navigation>
|
||||
|
||||
<page.actions>
|
||||
<AuthMethods::AuthMethod::Accounts::Account::Actions @model={{@model}} />
|
||||
<AuthMethods::AuthMethod::Accounts::Account::Actions
|
||||
@model={{@model}}
|
||||
@delete={{this.accounts.delete}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::Account::Password::SetPassword
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'setPassword' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.setPassword @model}}
|
||||
@cancel={{fn this.accounts.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<page.body>
|
||||
<Form::Account
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.accounts.save @model}}
|
||||
@cancel={{fn this.accounts.cancel @model}}
|
||||
/>
|
||||
|
||||
</page.body>
|
||||
|
||||
+6
-5
@@ -24,17 +24,18 @@
|
||||
<page.actions>
|
||||
<AuthMethods::AuthMethod::ManagedGroups::ManagedGroup::Actions
|
||||
@model={{@model}}
|
||||
@delete={{this.managedGroups.delete}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::ManagedGroup
|
||||
@model={{@model}}
|
||||
@edit={{route-action 'edit' @model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@removeItemByIndex={{route-action 'removeItemByIndex' @model}}
|
||||
@addStringItem={{route-action 'addStringItem' @model}}
|
||||
@edit={{fn this.managedGroups.edit @model}}
|
||||
@submit={{fn this.managedGroups.save @model}}
|
||||
@cancel={{fn this.managedGroups.cancel @model}}
|
||||
@removeItemByIndex={{fn this.authMethods.removeItemByIndex @model}}
|
||||
@addStringItem={{fn this.authMethods.addStringItem @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
+1
@@ -30,6 +30,7 @@
|
||||
<page.actions>
|
||||
<AuthMethods::AuthMethod::ManagedGroups::ManagedGroup::Actions
|
||||
@model={{@model.managedGroup}}
|
||||
@delete={{this.managedGroups.delete}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
<page.body>
|
||||
<Form::ManagedGroup
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@removeItemByIndex={{route-action 'removeItemByIndex' @model}}
|
||||
@addStringItem={{route-action 'addStringItem' @model}}
|
||||
@submit={{fn this.managedGroups.save @model}}
|
||||
@cancel={{fn this.managedGroups.cancel @model}}
|
||||
@removeItemByIndex={{fn this.authMethods.removeItemByIndex @model}}
|
||||
@addStringItem={{fn this.authMethods.addStringItem @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
+4
-4
@@ -38,7 +38,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'deleteCredentialLibrary' @model)}}
|
||||
{{on 'click' (fn this.credentialLibraries.delete @model)}}
|
||||
>
|
||||
{{t 'resources.credential-library.actions.delete'}}
|
||||
</dropdown.button>
|
||||
@@ -49,9 +49,9 @@
|
||||
<page.body>
|
||||
<Form::CredentialLibrary
|
||||
@model={{@model}}
|
||||
@edit={{route-action 'edit' @model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@edit={{fn this.credentialLibraries.edit @model}}
|
||||
@submit={{fn this.credentialLibraries.save @model}}
|
||||
@cancel={{fn this.credentialLibraries.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
+1
@@ -22,6 +22,7 @@
|
||||
<page.actions>
|
||||
<CredentialStores::CredentialStore::Actions
|
||||
@model={{this.credentialStore}}
|
||||
@delete={{fn this.credentialStores.delete this.credentialStore}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
|
||||
+3
-3
@@ -26,9 +26,9 @@
|
||||
<page.body>
|
||||
<Form::CredentialLibrary
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
@submit={{fn this.credentialLibraries.save @model}}
|
||||
@cancel={{fn this.credentialLibraries.cancel @model}}
|
||||
@changeType={{this.credentialLibraries.changeType}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
+3
-3
@@ -38,7 +38,7 @@
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'deleteCredential' @model)}}
|
||||
{{on 'click' (fn this.credentials.delete @model)}}
|
||||
>
|
||||
{{t 'resources.credential.actions.delete'}}
|
||||
</dropdown.button>
|
||||
@@ -49,8 +49,8 @@
|
||||
<page.body>
|
||||
<Form::Credential
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.credentials.save @model}}
|
||||
@cancel={{fn this.credentials.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
+1
@@ -22,6 +22,7 @@
|
||||
<page.actions>
|
||||
<CredentialStores::CredentialStore::Actions
|
||||
@model={{this.credentialStore}}
|
||||
@delete={{fn this.credentialStores.delete this.credentialStore}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
|
||||
+3
-3
@@ -26,9 +26,9 @@
|
||||
<page.body>
|
||||
<Form::Credential
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
@submit={{fn this.credentials.save @model}}
|
||||
@cancel={{fn this.credentials.cancel @model}}
|
||||
@changeType={{this.credentials.changeType}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -18,14 +18,17 @@
|
||||
</page.navigation>
|
||||
|
||||
<page.actions>
|
||||
<CredentialStores::CredentialStore::Actions @model={{@model}} />
|
||||
<CredentialStores::CredentialStore::Actions
|
||||
@model={{@model}}
|
||||
@delete={{fn this.credentialStores.delete @model}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::CredentialStore
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.credentialStores.save @model}}
|
||||
@cancel={{fn this.credentialStores.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -25,9 +25,9 @@
|
||||
<page.body>
|
||||
<Form::CredentialStore
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
@submit={{fn this.credentialStores.save @model}}
|
||||
@cancel={{fn this.credentialStores.cancel @model}}
|
||||
@changeType={{this.credentialStores.changeType}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@
|
||||
<Form::Group::AddMembers
|
||||
@model={{@model.group}}
|
||||
@users={{@model.users}}
|
||||
@submit={{route-action 'addMembers' @model.group}}
|
||||
@cancel={{route-action 'cancel'}}
|
||||
@submit={{fn this.addMembers @model.group}}
|
||||
@cancel={{this.cancel}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -18,14 +18,17 @@
|
||||
</page.navigation>
|
||||
|
||||
<page.actions>
|
||||
<Groups::Group::Actions @model={{@model}} />
|
||||
<Groups::Group::Actions
|
||||
@model={{@model}}
|
||||
@delete={{fn this.groups.delete @model}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
<Form::Group
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.groups.save @model}}
|
||||
@cancel={{fn this.groups.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
</page.navigation>
|
||||
|
||||
<page.actions>
|
||||
<Groups::Group::Actions @model={{@model.group}} />
|
||||
<Groups::Group::Actions
|
||||
@model={{@model.group}}
|
||||
@delete={{fn this.groups.delete @model.group}}
|
||||
/>
|
||||
</page.actions>
|
||||
|
||||
<page.body>
|
||||
@@ -75,7 +78,7 @@
|
||||
@text={{t 'actions.remove'}}
|
||||
{{on
|
||||
'click'
|
||||
(route-action 'removeMember' @model.group B.data)
|
||||
(fn this.groups.removeMember @model.group B.data)
|
||||
}}
|
||||
/>
|
||||
</Hds::Dropdown>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<page.body>
|
||||
<Form::Group
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@submit={{fn this.groups.save @model}}
|
||||
@cancel={{fn this.groups.cancel @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -134,18 +134,18 @@ module('Acceptance | accounts | create', function (hooks) {
|
||||
const accountsCount = this.server.db.accounts.length;
|
||||
await visit(urls.newAccount);
|
||||
await fillIn('[name="name"]', 'Account name');
|
||||
await click('form button:not([type="submit"])');
|
||||
assert.strictEqual(this.server.db.accounts.length, accountsCount);
|
||||
await click('.rose-form-actions [type="button"]');
|
||||
assert.strictEqual(currentURL(), urls.accounts);
|
||||
assert.strictEqual(this.server.db.accounts.length, accountsCount);
|
||||
});
|
||||
|
||||
test('can cancel a new LDAP account creation', async function (assert) {
|
||||
const accountsCount = this.server.db.accounts.length;
|
||||
await visit(urls.newAccount);
|
||||
await fillIn('[name="name"]', 'Account name');
|
||||
await click('form button:not([type="submit"])');
|
||||
assert.strictEqual(this.server.db.accounts.length, accountsCount);
|
||||
await click('.rose-form-actions [type="button"]');
|
||||
assert.strictEqual(currentURL(), urls.accounts);
|
||||
assert.strictEqual(this.server.db.accounts.length, accountsCount);
|
||||
});
|
||||
|
||||
test('saving a new account with invalid fields displays error messages', async function (assert) {
|
||||
@@ -178,7 +178,7 @@ module('Acceptance | accounts | create', function (hooks) {
|
||||
'Displays primary error message.',
|
||||
);
|
||||
assert.strictEqual(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
find('[data-test-error-message-name]').textContent.trim(),
|
||||
'Name is required.',
|
||||
'Displays field-level errors.',
|
||||
);
|
||||
|
||||
@@ -158,7 +158,7 @@ module('Acceptance | accounts | update', function (hooks) {
|
||||
'Displays primary error message.',
|
||||
);
|
||||
assert.strictEqual(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
find('[data-test-error-message-name]').textContent.trim(),
|
||||
'Name is required.',
|
||||
'Displays field-level errors.',
|
||||
);
|
||||
|
||||
@@ -23,6 +23,8 @@ module(
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
let featuresService;
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
@@ -83,6 +85,8 @@ module(
|
||||
urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`;
|
||||
urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`;
|
||||
urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`;
|
||||
|
||||
featuresService = this.owner.lookup('service:features');
|
||||
authenticateSession({});
|
||||
});
|
||||
|
||||
@@ -117,6 +121,7 @@ module(
|
||||
});
|
||||
|
||||
test('can save changes to existing JSON credential', async function (assert) {
|
||||
featuresService.enable('json-credentials');
|
||||
const mockInput = 'random string';
|
||||
assert.notEqual(instances.jsonCredential.name, mockInput);
|
||||
await visit(urls.jsonCredential);
|
||||
|
||||
@@ -20,18 +20,25 @@ exports.checkBoundaryCli = async () => {
|
||||
|
||||
/**
|
||||
* Uses the boundary CLI to authenticate to the specified Boundary instance
|
||||
* @param {string} addr Address of the Boundary instance to be authenticated to
|
||||
* @param {string} authMethodId ID of the auth method to be used for authentication
|
||||
* @param {string} loginName Login name to be used for authentication
|
||||
* @param {string} password Password to be used for authentication
|
||||
*/
|
||||
exports.authenticateBoundaryCli = async () => {
|
||||
exports.authenticateBoundaryCli = async (
|
||||
addr,
|
||||
authMethodId,
|
||||
loginName,
|
||||
password,
|
||||
) => {
|
||||
try {
|
||||
execSync(
|
||||
'boundary authenticate password' +
|
||||
' -addr=' +
|
||||
process.env.BOUNDARY_ADDR +
|
||||
' -auth-method-id=' +
|
||||
process.env.E2E_PASSWORD_AUTH_METHOD_ID +
|
||||
' -login-name=' +
|
||||
process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME +
|
||||
' -password=env://E2E_PASSWORD_ADMIN_PASSWORD',
|
||||
` -addr=${addr}` +
|
||||
` -auth-method-id=${authMethodId}` +
|
||||
` -login-name=${loginName}` +
|
||||
' -password=env://BPASS',
|
||||
{ env: { ...process.env, BPASS: password } },
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
@@ -41,25 +48,24 @@ exports.authenticateBoundaryCli = async () => {
|
||||
/**
|
||||
* Uses the boundary CLI to connect to the specified target
|
||||
* @param {string} targetId ID of the target to be connected to
|
||||
* @param {string} sshUser User to be used for the ssh connection
|
||||
* @param {string} sshKeyPath Path to the ssh key to be used for the ssh connection
|
||||
* @returns ChildProcess representing the result of the command execution
|
||||
*/
|
||||
exports.connectToTarget = async (targetId) => {
|
||||
exports.connectToTarget = async (targetId, sshUser, sshKeyPath) => {
|
||||
let connect;
|
||||
try {
|
||||
connect = exec(
|
||||
'boundary connect' +
|
||||
' -target-id=' +
|
||||
targetId +
|
||||
' -exec /usr/bin/ssh --' +
|
||||
' -l ' +
|
||||
process.env.E2E_SSH_USER +
|
||||
' -i ' +
|
||||
process.env.E2E_SSH_KEY_PATH +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes' + // forces the use of the provided key
|
||||
' -p {{boundary.port}}' +
|
||||
' {{boundary.ip}}',
|
||||
` -target-id=${targetId}` +
|
||||
' -exec /usr/bin/ssh --' +
|
||||
` -l ${sshUser}` +
|
||||
` -i ${sshKeyPath}` +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes' + // forces the use of the provided key
|
||||
' -p {{boundary.port}}' +
|
||||
' {{boundary.ip}}',
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
@@ -77,12 +83,12 @@ exports.connectSshToTarget = async (targetId) => {
|
||||
try {
|
||||
connect = exec(
|
||||
'boundary connect ssh' +
|
||||
' -target-id=' +
|
||||
targetId +
|
||||
' --' +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
' -target-id=' +
|
||||
targetId +
|
||||
' --' +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
@@ -124,6 +130,30 @@ exports.deleteOrgCli = async (orgId) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the boundary CLI to delete the specified storage bucket
|
||||
* @param {string} storageBucketId ID of the storage bucket to be deleted
|
||||
*/
|
||||
exports.deleteStorageBucketCli = async (storageBucketId) => {
|
||||
try {
|
||||
exec('boundary storage-buckets delete -id=' + storageBucketId);
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the boundary CLI to delete the specified policy
|
||||
* @param {string} policyId ID of the policy to be deleted
|
||||
*/
|
||||
exports.deletePolicyCli = async (policyId) => {
|
||||
try {
|
||||
exec('boundary policies delete -id=' + policyId);
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the boundary CLI to create a new project.
|
||||
* @param {string} orgId ID of the organization under which the project will be created.
|
||||
@@ -217,7 +247,6 @@ exports.makeAuthMethodPrimaryCli = async (scopeId, authMethodId) => {
|
||||
exports.createNewPasswordAccountCli = async (authMethodId) => {
|
||||
let passwordAccount;
|
||||
const login = 'test-login';
|
||||
process.env.ACCOUNT_PASSWORD = 'test-password';
|
||||
try {
|
||||
passwordAccount = JSON.parse(
|
||||
execSync(
|
||||
@@ -226,6 +255,7 @@ exports.createNewPasswordAccountCli = async (authMethodId) => {
|
||||
-login-name ${login} \
|
||||
-password env://ACCOUNT_PASSWORD \
|
||||
-format json`,
|
||||
{ env: { ...process.env, ACCOUNT_PASSWORD: 'test-password' } },
|
||||
),
|
||||
).item;
|
||||
} catch (e) {
|
||||
@@ -427,12 +457,14 @@ exports.createNewStaticCredentialStoreCli = async (projectId) => {
|
||||
/**
|
||||
* Uses the boundary CLI to create a new vault credential store.
|
||||
* @param {string} projectId ID of the project under which the credential store will be created.
|
||||
* @param {string} vaultAddr Address of the vault that the credential store will be created for.
|
||||
* @param {string} secretPolicyName Name of the secret policy that's used to create vault token.
|
||||
* @param {string} boundaryPolicyName Name of the boundary policy that's used to create vault token.
|
||||
* @returns {Promise<string>} new credential store's ID
|
||||
*/
|
||||
exports.createNewVaultCredentialStoreCli = async (
|
||||
projectId,
|
||||
vaultAddr,
|
||||
secretPolicyName,
|
||||
boundaryPolicyName,
|
||||
) => {
|
||||
@@ -463,7 +495,7 @@ exports.createNewVaultCredentialStoreCli = async (
|
||||
`boundary credential-stores create vault \
|
||||
-scope-id ${projectId} \
|
||||
-name ${credentialStoreName} \
|
||||
-vault-address ${process.env.E2E_VAULT_ADDR} \
|
||||
-vault-address ${vaultAddr} \
|
||||
-vault-token ${clientToken} \
|
||||
-format json`,
|
||||
),
|
||||
@@ -482,7 +514,6 @@ exports.createNewVaultCredentialStoreCli = async (
|
||||
exports.createNewUsernamePasswordCredentialCli = async (credentialStoreId) => {
|
||||
let usernamePasswordCredential;
|
||||
const login = 'test-login';
|
||||
process.env.CREDENTIALS_PASSWORD = 'credentials-password';
|
||||
try {
|
||||
usernamePasswordCredential = JSON.parse(
|
||||
execSync(
|
||||
@@ -492,6 +523,9 @@ exports.createNewUsernamePasswordCredentialCli = async (credentialStoreId) => {
|
||||
-username ${login} \
|
||||
-password env://CREDENTIALS_PASSWORD \
|
||||
-format json`,
|
||||
{
|
||||
env: { ...process.env, CREDENTIALS_PASSWORD: 'credentials-password' },
|
||||
},
|
||||
),
|
||||
).item;
|
||||
} catch (e) {
|
||||
@@ -580,3 +614,29 @@ exports.getSessionCli = async (orgName, projectName, targetName) => {
|
||||
}
|
||||
return session;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the boundary CLI to wait for a session recording to be available.
|
||||
* @param {string} storageBucketId ID of storage bucket that the session recording is associated with
|
||||
* @returns An object representing a session recording
|
||||
*/
|
||||
exports.waitForSessionRecordingCli = async (storageBucketId) => {
|
||||
let i = 0;
|
||||
let filteredSessionRecording = [];
|
||||
do {
|
||||
i = i + 1;
|
||||
const sessionRecordings = JSON.parse(
|
||||
execSync('boundary session-recordings list -format json'),
|
||||
);
|
||||
filteredSessionRecording = sessionRecordings.items.filter(
|
||||
(obj) =>
|
||||
obj.storage_bucket_id == storageBucketId && obj.state == 'available',
|
||||
);
|
||||
if (filteredSessionRecording.length > 0) {
|
||||
break;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} while (i < 5);
|
||||
|
||||
return filteredSessionRecording[0];
|
||||
};
|
||||
@@ -118,15 +118,16 @@ exports.createNewHostSet = async (page) => {
|
||||
/**
|
||||
* Uses the UI to create a new host in a host set. Assumes you have just created a new host set.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} address Address of the host
|
||||
* @returns Name of the host
|
||||
*/
|
||||
exports.createNewHostInHostSet = async (page) => {
|
||||
exports.createNewHostInHostSet = async (page, address) => {
|
||||
const hostName = 'Host ' + nanoid();
|
||||
await page.getByText('Manage').click();
|
||||
await page.getByRole('link', { name: 'Create and Add Host' }).click();
|
||||
await page.getByLabel('Name').fill(hostName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByLabel('Address').fill(process.env.E2E_TARGET_ADDRESS);
|
||||
await page.getByLabel('Address').fill(address);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
@@ -138,7 +139,7 @@ exports.createNewHostInHostSet = async (page) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a credential store. Assumes you have selected the desired project.
|
||||
* Uses the UI to create a static credential store. Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @returns Name of the credential store
|
||||
*/
|
||||
@@ -166,12 +167,47 @@ exports.createStaticCredentialStore = async (page) => {
|
||||
return credentialStoreName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a vault credential store. Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} vaultAddr address of the vault server
|
||||
* @param {string} clientToken vault token to connect to boundary
|
||||
* @returns Name of the credential store
|
||||
*/
|
||||
exports.createVaultCredentialStore = async (page, vaultAddr, clientToken) => {
|
||||
const credentialStoreName = 'Credential Store ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Credential Stores' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name', { exact: true }).fill(credentialStoreName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByRole('group', { name: 'Type' }).getByLabel('Vault').click();
|
||||
await page.getByLabel('Address', { exact: true }).fill(vaultAddr);
|
||||
await page.getByLabel('Token', { exact: true }).fill(clientToken);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(credentialStoreName),
|
||||
).toBeVisible();
|
||||
|
||||
return credentialStoreName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a static key pair credential . Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} username username for the credential
|
||||
* @param {string} keyPath path to the private key
|
||||
* @returns Name of the credential
|
||||
*/
|
||||
exports.createStaticCredentialKeyPair = async (page) => {
|
||||
exports.createStaticCredentialKeyPair = async (page, username, keyPath) => {
|
||||
const credentialName = 'Credential ' + nanoid();
|
||||
await page.getByRole('link', { name: 'Credentials', exact: true }).click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
@@ -181,10 +217,8 @@ exports.createStaticCredentialKeyPair = async (page) => {
|
||||
.getByRole('group', { name: 'Type' })
|
||||
.getByLabel('Username & Key Pair')
|
||||
.click();
|
||||
await page
|
||||
.getByLabel('Username', { exact: true })
|
||||
.fill(process.env.E2E_SSH_USER);
|
||||
const keyData = await readFile(process.env.E2E_SSH_KEY_PATH, {
|
||||
await page.getByLabel('Username', { exact: true }).fill(username);
|
||||
const keyData = await readFile(keyPath, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
await page.getByLabel('SSH Private Key', { exact: true }).fill(keyData);
|
||||
@@ -202,12 +236,107 @@ exports.createStaticCredentialKeyPair = async (page) => {
|
||||
return credentialName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a vault-generic credential library. Assumes you have selected
|
||||
* the desired credential store.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} vaultPath path to secret in vault
|
||||
* @param {string} credentialType type of credential for credential injection
|
||||
* @returns Name of the credential library
|
||||
*/
|
||||
exports.createVaultGenericCredentialLibrary = async (
|
||||
page,
|
||||
vaultPath,
|
||||
credentialType,
|
||||
) => {
|
||||
const credentialLibraryName = 'Credential Library ' + nanoid();
|
||||
await page.getByRole('link', { name: 'Credential Libraries' }).click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page
|
||||
.getByLabel('Name (Optional)', { exact: true })
|
||||
.fill(credentialLibraryName);
|
||||
await page
|
||||
.getByLabel('Description (Optional)')
|
||||
.fill('This is an automated test');
|
||||
await page
|
||||
.getByRole('group', { name: 'Type' })
|
||||
.getByLabel('Generic Secrets')
|
||||
.click();
|
||||
await page.getByLabel('Vault Path').fill(vaultPath);
|
||||
await page
|
||||
.getByRole('combobox', { name: 'Credential Type' })
|
||||
.selectOption(credentialType);
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
|
||||
return credentialLibraryName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a vault-ssh-certificate credential library. Assumes you have selected
|
||||
* the desired credential store.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} vaultPath path to secret in vault
|
||||
* @param {string} username username for the credential
|
||||
* @returns Name of the credential library
|
||||
*/
|
||||
exports.createVaultSshCertificateCredentialLibrary = async (
|
||||
page,
|
||||
vaultPath,
|
||||
username,
|
||||
) => {
|
||||
const credentialLibraryName = 'Credential Library ' + nanoid();
|
||||
|
||||
await page.getByRole('link', { name: 'Credential Libraries' }).click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
|
||||
// Temporarily putting the Group selection first due to a bug where
|
||||
// Name and Description fields get cleared when the Group is selected
|
||||
await page
|
||||
.getByRole('group', { name: 'Type' })
|
||||
.getByLabel('SSH Certificates')
|
||||
.click();
|
||||
|
||||
await page
|
||||
.getByLabel('Name (Optional)', { exact: true })
|
||||
.fill(credentialLibraryName);
|
||||
await page
|
||||
.getByLabel('Description (Optional)')
|
||||
.fill('This is an automated test');
|
||||
await page.getByLabel('Vault Path').fill(vaultPath);
|
||||
await page.getByLabel('Username').fill(username);
|
||||
await page.getByLabel('Key Type').selectOption('ecdsa');
|
||||
await page.getByLabel('Key Bits').fill('521');
|
||||
|
||||
await page
|
||||
.getByRole('group', { name: 'Extensions' })
|
||||
.getByLabel('Key')
|
||||
.fill('permity-pty');
|
||||
await page
|
||||
.getByRole('group', { name: 'Extensions' })
|
||||
.getByRole('button', { name: 'Add' })
|
||||
.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
|
||||
return credentialLibraryName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a new target. Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} port Port of the target
|
||||
* @returns Name of the target
|
||||
*/
|
||||
exports.createNewTarget = async (page) => {
|
||||
exports.createNewTarget = async (page, port) => {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
@@ -216,7 +345,7 @@ exports.createNewTarget = async (page) => {
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name').fill(targetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByLabel('Default Port').fill(process.env.E2E_TARGET_PORT);
|
||||
await page.getByLabel('Default Port').fill(port);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
@@ -232,9 +361,11 @@ exports.createNewTarget = async (page) => {
|
||||
/**
|
||||
* Uses the UI to create a new target with address. Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} address Address of the target
|
||||
* @param {string} port Port of the target
|
||||
* @returns Name of the target
|
||||
*/
|
||||
exports.createNewTargetWithAddress = async (page) => {
|
||||
exports.createNewTargetWithAddress = async (page, address, port) => {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
@@ -243,8 +374,8 @@ exports.createNewTargetWithAddress = async (page) => {
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name').fill(targetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByLabel('Target Address').fill(process.env.E2E_TARGET_ADDRESS);
|
||||
await page.getByLabel('Default Port').fill(process.env.E2E_TARGET_PORT);
|
||||
await page.getByLabel('Target Address').fill(address);
|
||||
await page.getByLabel('Default Port').fill(port);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
@@ -261,9 +392,11 @@ exports.createNewTargetWithAddress = async (page) => {
|
||||
* Uses the UI to create a new TCP target with address in boundary-enterprise
|
||||
* Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} address Address of the target
|
||||
* @param {string} port Port of the target
|
||||
* @returns Name of the target
|
||||
*/
|
||||
exports.createTcpTargetWithAddressEnt = async (page) => {
|
||||
exports.createTcpTargetWithAddressEnt = async (page, address, port) => {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
@@ -273,8 +406,8 @@ exports.createTcpTargetWithAddressEnt = async (page) => {
|
||||
await page.getByLabel('Name').fill(targetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByRole('group', { name: 'Type' }).getByLabel('TCP').click();
|
||||
await page.getByLabel('Target Address').fill(process.env.E2E_TARGET_ADDRESS);
|
||||
await page.getByLabel('Default Port').fill(process.env.E2E_TARGET_PORT);
|
||||
await page.getByLabel('Target Address').fill(address);
|
||||
await page.getByLabel('Default Port').fill(port);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
@@ -291,9 +424,11 @@ exports.createTcpTargetWithAddressEnt = async (page) => {
|
||||
* Uses the UI to create a new SSH target with address in boundary-enterprise
|
||||
* Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} address Address of the target
|
||||
* @param {string} port Port of the target
|
||||
* @returns Name of the target
|
||||
*/
|
||||
exports.createSshTargetWithAddressEnt = async (page) => {
|
||||
exports.createSshTargetWithAddressEnt = async (page, address, port) => {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
@@ -303,8 +438,48 @@ exports.createSshTargetWithAddressEnt = async (page) => {
|
||||
await page.getByLabel('Name').fill(targetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByRole('group', { name: 'Type' }).getByLabel('SSH').click();
|
||||
await page.getByLabel('Target Address').fill(process.env.E2E_TARGET_ADDRESS);
|
||||
await page.getByLabel('Default Port').fill(process.env.E2E_TARGET_PORT);
|
||||
await page.getByLabel('Target Address').fill(address);
|
||||
await page.getByLabel('Default Port').fill(port);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(
|
||||
page.getByRole('navigation', { name: 'breadcrumbs' }).getByText(targetName),
|
||||
).toBeVisible();
|
||||
|
||||
return targetName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a new SSH target with address in boundary-enterprise
|
||||
* Assumes you have selected the desired project.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} address Address of the target
|
||||
* @param {string} port Port of the target
|
||||
* @param {string} workerFilterEgress Egress worker filter
|
||||
* @returns Name of the target
|
||||
*/
|
||||
exports.createSshTargetWithAddressAndWorkerFilterEnt = async (
|
||||
page,
|
||||
address,
|
||||
port,
|
||||
workerFilterEgress,
|
||||
) => {
|
||||
const targetName = 'Target ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name').fill(targetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByRole('group', { name: 'Type' }).getByLabel('SSH').click();
|
||||
await page.getByLabel('Target Address').fill(address);
|
||||
await page.getByLabel('Default Port').fill(port);
|
||||
await page.getByLabel('Egress worker filter').click();
|
||||
await page.getByRole('textbox', { name: 'Filter' }).fill(workerFilterEgress);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
@@ -372,7 +547,8 @@ exports.addHostSourceToTarget = async (page, hostSourceName) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to navigate to Sessions and waits for the session to appear.
|
||||
* Uses the UI to navigate to Sessions and waits for the session to appear
|
||||
* and be in Active state.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} targetName Name of the target associated with the session
|
||||
*/
|
||||
@@ -383,12 +559,16 @@ exports.waitForSessionToBeVisible = async (page, targetName) => {
|
||||
.click();
|
||||
let i = 0;
|
||||
let sessionIsVisible = false;
|
||||
let sessionIsActive = false;
|
||||
do {
|
||||
i = i + 1;
|
||||
sessionIsVisible = await page
|
||||
.getByRole('cell', { name: targetName })
|
||||
.isVisible();
|
||||
if (sessionIsVisible) {
|
||||
sessionIsActive = await page
|
||||
.getByRole('cell', { name: 'Active' })
|
||||
.isVisible();
|
||||
if (sessionIsVisible && sessionIsActive) {
|
||||
break;
|
||||
}
|
||||
await page.getByRole('button', { name: 'Refresh' }).click();
|
||||
@@ -668,9 +848,10 @@ exports.addMemberToGroup = async (page, userName) => {
|
||||
/**
|
||||
* Uses the UI to create a new role. Assumes you have selected the desired scope.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} roleName Name of the new role
|
||||
*/
|
||||
exports.createNewRole = async (page, roleName) => {
|
||||
exports.createRole = async (page) => {
|
||||
const roleName = 'Role ' + nanoid();
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: 'IAM' })
|
||||
.getByRole('link', { name: 'Roles' })
|
||||
@@ -685,6 +866,8 @@ exports.createNewRole = async (page, roleName) => {
|
||||
await expect(
|
||||
page.getByRole('navigation', { name: 'breadcrumbs' }).getByText(roleName),
|
||||
).toBeVisible();
|
||||
|
||||
return roleName;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -729,3 +912,116 @@ exports.addGrantsToGroup = async (page, grants) => {
|
||||
page.getByRole('textbox', { name: 'Grant', exact: true }),
|
||||
).toHaveValue(grants);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a new Storage Bucket. Assumes you have selected the desired scope.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} bucketName Name of the Storage Bucket
|
||||
* @param {string} region Region of the Storage Bucket
|
||||
* @param {string} accessKeyId Access Key ID for the Storage Bucket
|
||||
* @param {string} secretAccessKey Secret Access Key for the Storage Bucket
|
||||
* @param {string} workerFilter Worker filter for the Storage Bucket
|
||||
* @returns Name of the Storage Bucket
|
||||
*/
|
||||
exports.createStorageBucket = async (
|
||||
page,
|
||||
bucketName,
|
||||
region,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
workerFilter,
|
||||
) => {
|
||||
const storageBucketName = 'Bucket ' + nanoid();
|
||||
await page
|
||||
.getByRole('link', { name: 'Storage Buckets', exact: true })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New Storage Bucket' }).click();
|
||||
await page.getByLabel(new RegExp('Name*')).fill(storageBucketName);
|
||||
await page.getByLabel('Bucket name').fill(bucketName);
|
||||
await page.getByLabel('Region').fill(region);
|
||||
await page.getByLabel('Access key ID').fill(accessKeyId);
|
||||
await page.getByLabel('Secret access key').fill(secretAccessKey);
|
||||
await page.getByLabel('Worker filter').fill(workerFilter);
|
||||
await page.getByLabel('Disable credential rotation').click();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(storageBucketName),
|
||||
).toBeVisible();
|
||||
|
||||
return storageBucketName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to enable session recording for a target. Assumes you have selected the desired target.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} storageBucketName name of the Storage Bucket used for session recording
|
||||
*/
|
||||
exports.enableSessionRecording = async (page, storageBucketName) => {
|
||||
await page.getByText('Enable recording').click();
|
||||
await page.getByLabel('Record sessions for this target').click();
|
||||
await page
|
||||
.getByLabel('AWS storage buckets')
|
||||
.selectOption({ label: storageBucketName });
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(
|
||||
page.getByRole('listitem').getByText(storageBucketName),
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to create a Storage Policy. Assumes you have selected the desired scope.
|
||||
* @param {Page} page Playwright page object
|
||||
* @returns Name of the Storage Policy
|
||||
*/
|
||||
exports.createStoragePolicy = async (page) => {
|
||||
const storagePolicyName = 'Policy ' + nanoid();
|
||||
await page
|
||||
.getByRole('link', { name: 'Storage Policies', exact: true })
|
||||
.click();
|
||||
await page
|
||||
.getByRole('link', { name: 'Create a new storage policy', exact: true })
|
||||
.click();
|
||||
await page.getByLabel('Name').fill(storagePolicyName);
|
||||
await page.getByLabel('Retention Policy').selectOption({ label: 'Forever' });
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(storagePolicyName),
|
||||
).toBeVisible();
|
||||
|
||||
return storagePolicyName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses the UI to attach Storage Policy to a scope. Assumes you have selected the desired scope.
|
||||
* @param {Page} page Playwright page object
|
||||
* @param {string} policyName name of the Policy to be attached to the scope
|
||||
*/
|
||||
exports.attachStoragePolicy = async (page, policyName) => {
|
||||
await page
|
||||
.getByRole('link', { name: new RegExp('/*Settings'), exact: true })
|
||||
.click();
|
||||
await page.getByText('Add Storage Policy').click();
|
||||
await page.getByLabel('Storage Policy').selectOption({ label: policyName });
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(page.getByRole('listitem').getByText(policyName)).toBeVisible();
|
||||
};
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user