Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dde2f259e8 | ||
|
|
e38c28c288 | ||
|
|
0ff91f9182 | ||
|
|
857bf01596 | ||
|
|
965e82473d | ||
|
|
0d55812f64 | ||
|
|
e807e1a3af | ||
|
|
64e568bd72 | ||
|
|
401ff354e8 | ||
|
|
9c31154941 | ||
|
|
1947365c91 | ||
|
|
be528dae47 | ||
|
|
d3c789c66c | ||
|
|
15727f61fd | ||
|
|
486d8b116a | ||
|
|
91ffbfb315 | ||
|
|
9b33812f9e | ||
|
|
b40c53fb45 | ||
|
|
97258f7525 | ||
|
|
ace65eab3a | ||
|
|
20e6df522a | ||
|
|
c8abc3845a | ||
|
|
6cd2f0f7d8 | ||
|
|
1266930647 | ||
|
|
695d9d413d | ||
|
|
edaf19f59b | ||
|
|
de5defeab2 | ||
|
|
63ec418cf0 | ||
|
|
e4f4394197 | ||
|
|
0b9357e9b3 | ||
|
|
777380e19f | ||
|
|
25b3a940a3 | ||
|
|
a97f66e011 | ||
|
|
fac7d2b128 | ||
|
|
ad1bb34453 | ||
|
|
8605a54221 | ||
|
|
98cd9dc1ec | ||
|
|
bd857836ee | ||
|
|
4974273bd0 | ||
|
|
c4e58fb780 | ||
|
|
2515efead6 | ||
|
|
fda3ca9d4b | ||
|
|
9648e1fa3b | ||
|
|
e395b1196c | ||
|
|
b7e6492d26 | ||
|
|
6311d7587e | ||
|
|
07573d6b2c | ||
|
|
90890441e1 | ||
|
|
1430adce7e | ||
|
|
89afee7297 | ||
|
|
78f89da348 | ||
|
|
9bacb55e87 | ||
|
|
dea4a62955 | ||
|
|
ef163709f2 | ||
|
|
0cfa814cdd | ||
|
|
694c43296a | ||
|
|
c98bfb5622 | ||
|
|
7cebece600 | ||
|
|
a9cfc43404 | ||
|
|
c38ba294cc | ||
|
|
ce2649272f | ||
|
|
3d60d047e3 | ||
|
|
a3082e97e6 | ||
|
|
333eebd731 | ||
|
|
320af9f6f7 | ||
|
|
af9b832d38 | ||
|
|
bea791b2e6 | ||
|
|
e31ac72531 | ||
|
|
075d8c6d8f | ||
|
|
56f42c3f5a | ||
|
|
af7196215b | ||
|
|
80d2898e56 | ||
|
|
f607e77274 | ||
|
|
007f5d8b46 | ||
|
|
a9d110a1b6 | ||
|
|
79b06ee565 | ||
|
|
0af81d9bc1 | ||
|
|
9e668e8467 | ||
|
|
69da1fa04c | ||
|
|
392792eb92 | ||
|
|
9c018f131f | ||
|
|
0542218537 | ||
|
|
7ac904a31f | ||
|
|
a7d9a7e348 | ||
|
|
0d0dadf398 | ||
|
|
45c16252a7 | ||
|
|
9b3cbaa6b3 | ||
|
|
89cfb14d6e | ||
|
|
a68d7d103a | ||
|
|
4dc5e13f24 |
No files matched your search
@@ -19,7 +19,7 @@ jobs:
|
|||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@@ -37,17 +37,17 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:oss
|
- run: yarn run build:ui:admin:oss
|
||||||
- name: Upload artifact admin-ui
|
- name: Upload artifact admin-ui
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui
|
name: admin-ui
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
- name: Upload artifact OSS admin UI
|
- name: Upload artifact OSS admin UI
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-oss
|
name: admin-ui-oss
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -65,12 +65,12 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:enterprise
|
- run: yarn run build:ui:admin:enterprise
|
||||||
- name: Upload artifact enterprise admin UI
|
- name: Upload artifact enterprise admin UI
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-ent
|
name: admin-ui-ent
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -88,12 +88,12 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:hcp
|
- run: yarn run build:ui:admin:hcp
|
||||||
- name: Upload artifact HCP admin UI
|
- name: Upload artifact HCP admin UI
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-hcp
|
name: admin-ui-hcp
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@8f1a6fed33af5212fab8a999d004627ae8901d1b # codeql-bundle-v2.17.5
|
uses: github/codeql-action/init@5618c9fc1e675841ca52c1c6b1304f5255a905a0 # codeql-bundle-v2.19.0
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -54,4 +54,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@8f1a6fed33af5212fab8a999d004627ae8901d1b # codeql-bundle-v2.17.5
|
uses: github/codeql-action/analyze@5618c9fc1e675841ca52c1c6b1304f5255a905a0 # codeql-bundle-v2.19.0
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
name: Check Copyright Headers
|
name: Add Copyright Headers
|
||||||
on: pull_request
|
on: pull_request
|
||||||
jobs:
|
jobs:
|
||||||
copywrite:
|
copywrite:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
|
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
|
||||||
- name: Check Header Compliance
|
- name: Add missing Copyright Headers
|
||||||
run: copywrite headers --plan
|
run: copywrite headers
|
||||||
|
- uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
|
||||||
|
with:
|
||||||
|
commit_message: Add missing copyright headers
|
||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run compliance:licenses
|
- run: yarn run compliance:licenses
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
node-modules-
|
node-modules-
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
path: '**/node_modules'
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
@@ -98,7 +98,7 @@ List of available project commands. `yarn run <command-name>`
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------------------|---|
|
|---------------------|---|
|
||||||
| build | Builds Admin UI. |
|
| build | Builds Admin UI for production. |
|
||||||
| test | Runs tests in CI mode. |
|
| test | Runs tests in CI mode. |
|
||||||
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
|
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
|
||||||
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
|
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
|
||||||
@@ -119,9 +119,9 @@ of README.
|
|||||||
|
|
||||||
### Building Admin UI for Production
|
### Building Admin UI for Production
|
||||||
|
|
||||||
See ui/admin/README.md Building for Production for more information on how to
|
See [Admin UI README](ui/admin/README.md#building-for-production) for more information on how to
|
||||||
build the Admin UI. Be sure to set build-related environment variables as
|
build the Admin UI for production. Be sure to set build-related environment variables as
|
||||||
necessary for your target environment, as described in the Admin UI readme.
|
necessary for your target environment, as described in the [Environment Variables](ui/admin/README.md#environment-variables) section of README.
|
||||||
|
|
||||||
To quickly produce a production build of Admin UI, run from this folder:
|
To quickly produce a production build of Admin UI, run from this folder:
|
||||||
|
|
||||||
@@ -144,9 +144,9 @@ Assets are saved to `ui/admin/dist/`.
|
|||||||
|
|
||||||
### Building Desktop UI for Production
|
### Building Desktop UI for Production
|
||||||
|
|
||||||
See ui/desktop/README.md Building for Production for more information on how to
|
See [Desktop UI README](ui/desktop/README.md#building-for-production) for more information on how to
|
||||||
build the Desktop UI. Be sure to set build-related environment variables as
|
build the Desktop UI. Be sure to set build-related environment variables as
|
||||||
necessary for your target environment, as described in the Desktop UI readme.
|
necessary for your target environment, as described in the [Environment Variables](ui/desktop/README.md#environment-variables-prod) section of README.
|
||||||
|
|
||||||
To quickly produce a production build of Desktop UI, run from this folder:
|
To quickly produce a production build of Desktop UI, run from this folder:
|
||||||
|
|
||||||
@@ -174,8 +174,9 @@ EXE archived desktop UI is available at asset location under `zip` folder.
|
|||||||
|
|
||||||
This describes how to connect local Boundary UI to your local instance of Boundary.
|
This describes how to connect local Boundary UI to your local instance of Boundary.
|
||||||
|
|
||||||
This assumes you already have up and running a Boundary dev instance in your local environment with a listener `127.0.0.1:9200` (default behaviour). [Learn how to start a local Boundary dev instance](https://learn.hashicorp.com/tutorials/boundary/getting-started-dev?in=boundary/getting-started#start-a-dev-environment).
|
This assumes you already have up and running a Boundary dev instance in your local environment with a listener `127.0.0.1:9200` (default behaviour). [Learn how to start a local Boundary dev instance](https://developer.hashicorp.com/boundary/tutorials/get-started-community/community-get-started-dev)
|
||||||
#### For admin
|
|
||||||
|
#### For Admin
|
||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/admin`.
|
This assumes you are within `boundary-ui/ui/admin`.
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
|||||||
|
|
||||||
Once you open the UI you will see the login screen, authenticate using generated admin login name and password.
|
Once you open the UI you will see the login screen, authenticate using generated admin login name and password.
|
||||||
|
|
||||||
#### For desktop
|
#### For Desktop
|
||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/desktop`.
|
This assumes you are within `boundary-ui/ui/desktop`.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import sinon from 'sinon';
|
|||||||
import { typeOf } from '@ember/utils';
|
import { typeOf } from '@ember/utils';
|
||||||
import { resourceNames } from 'api/handlers/cache-daemon-handler';
|
import { resourceNames } from 'api/handlers/cache-daemon-handler';
|
||||||
import { singularize } from 'ember-inflector';
|
import { singularize } from 'ember-inflector';
|
||||||
|
import { underscore } from '@ember/string';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test helper can be used to help setup your sinon stubs in your tests.
|
* This test helper can be used to help setup your sinon stubs in your tests.
|
||||||
@@ -69,16 +70,18 @@ export default function setupStubs(hooks) {
|
|||||||
.withArgs('searchCacheDaemon')
|
.withArgs('searchCacheDaemon')
|
||||||
.onCall(i)
|
.onCall(i)
|
||||||
.returns({
|
.returns({
|
||||||
[resourceName]: models.map((model) => {
|
[underscore(resourceNames[singularize(resourceName)])]: models.map(
|
||||||
// Use internal serializer to serialize the model correctly
|
(model) => {
|
||||||
// according to our mirage serializers
|
// Use internal serializer to serialize the model correctly
|
||||||
const modelData =
|
// according to our mirage serializers
|
||||||
this.server.serializerOrRegistry.serialize(model);
|
const modelData =
|
||||||
|
this.server.serializerOrRegistry.serialize(model);
|
||||||
|
|
||||||
// Serialize the data properly to standard JSON as that is what
|
// Serialize the data properly to standard JSON as that is what
|
||||||
// we're expecting from the cache daemon response
|
// we're expecting from the cache daemon response
|
||||||
return JSON.parse(JSON.stringify(modelData));
|
return JSON.parse(JSON.stringify(modelData));
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,12 +65,19 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
|
|
||||||
// AWS specific
|
// AWS specific
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
description:
|
||||||
|
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
|
||||||
|
})
|
||||||
|
worker_filter;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
region;
|
region;
|
||||||
|
|
||||||
|
// AWS static credentials
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedSecret: true,
|
isNestedSecret: true,
|
||||||
description: '',
|
description: '',
|
||||||
@@ -83,6 +90,31 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
})
|
})
|
||||||
secret_access_key;
|
secret_access_key;
|
||||||
|
|
||||||
|
// AWS dynamic credentials
|
||||||
|
@attr('string', {
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role ARN to use.',
|
||||||
|
})
|
||||||
|
role_arn;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role external ID to use.',
|
||||||
|
})
|
||||||
|
role_external_id;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role session to use.',
|
||||||
|
})
|
||||||
|
role_session_name;
|
||||||
|
|
||||||
|
@attr('object-as-array', {
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role tags to use.',
|
||||||
|
})
|
||||||
|
role_tags;
|
||||||
|
|
||||||
// Azure specific
|
// Azure specific
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { inject as service } from '@ember/service';
|
|||||||
import { getOwner, setOwner } from '@ember/application';
|
import { getOwner, setOwner } from '@ember/application';
|
||||||
import { generateMQLExpression } from '../utils/mql-query';
|
import { generateMQLExpression } from '../utils/mql-query';
|
||||||
import { paginateResults } from '../utils/paginate-results';
|
import { paginateResults } from '../utils/paginate-results';
|
||||||
|
import { underscore } from '@ember/string';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not all types are yet supported by the cache daemon so we'll
|
* Not all types are yet supported by the cache daemon so we'll
|
||||||
@@ -94,12 +95,14 @@ export default class CacheDaemonHandler {
|
|||||||
const sessionData = this.session.data?.authenticated;
|
const sessionData = this.session.data?.authenticated;
|
||||||
const auth_token_id = sessionData?.id;
|
const auth_token_id = sessionData?.id;
|
||||||
const token = sessionData?.token;
|
const token = sessionData?.token;
|
||||||
|
const resourceName = resourceNames[type];
|
||||||
|
|
||||||
remainingQuery = {
|
remainingQuery = {
|
||||||
...remainingQuery,
|
...remainingQuery,
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
auth_token_id,
|
auth_token_id,
|
||||||
token,
|
token,
|
||||||
resource: resourceNames[type],
|
resource: resourceName,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cacheDaemonResults = {};
|
let cacheDaemonResults = {};
|
||||||
@@ -130,7 +133,7 @@ export default class CacheDaemonHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
__electronLog?.error(
|
__electronLog?.error(
|
||||||
'Failed to search cache daemon; falling back to search controller',
|
`Failed to search cache daemon for ${resourceName}. Error:`,
|
||||||
e.message,
|
e.message,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -138,10 +141,7 @@ export default class CacheDaemonHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently returns with a singular top level field with resource name
|
const results = cacheDaemonResults[underscore(resourceName)];
|
||||||
// e.g. { targets: [...] } or { sessions: [..] }
|
|
||||||
// So this just unwraps to the array, or undefined
|
|
||||||
const [results] = Object.values(cacheDaemonResults);
|
|
||||||
const payload = { items: paginateResults(results, page, pageSize) };
|
const payload = { items: paginateResults(results, page, pageSize) };
|
||||||
|
|
||||||
const schema = store.modelFor(type);
|
const schema = store.modelFor(type);
|
||||||
@@ -167,7 +167,16 @@ export default class CacheDaemonHandler {
|
|||||||
// This isn't conventional but is better than returning an ArrayProxy
|
// This isn't conventional but is better than returning an ArrayProxy
|
||||||
// or EmberArray since the ember store query method asserts it has to be an array
|
// or EmberArray since the ember store query method asserts it has to be an array
|
||||||
// so we can't just return an object.
|
// so we can't just return an object.
|
||||||
records.meta = { totalItems: results?.length ?? 0 };
|
// Also include whether the results are incomplete or is still refreshing
|
||||||
|
// as part of the response
|
||||||
|
records.meta = {
|
||||||
|
totalItems: results?.length ?? 0,
|
||||||
|
isLoadIncomplete: cacheDaemonResults.incomplete ?? false,
|
||||||
|
// Sometimes the refresh status returns an error status if it takes too long but it's still refreshing
|
||||||
|
isCacheRefreshing:
|
||||||
|
cacheDaemonResults.refresh_status !== 'not-refreshing',
|
||||||
|
};
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { registerDeprecationHandler } from '@ember/debug';
|
|
||||||
|
|
||||||
const deprecationList = ['ember-data:deprecate-early-static'];
|
|
||||||
|
|
||||||
export function initialize() {
|
|
||||||
registerDeprecationHandler((message, options, next) => {
|
|
||||||
// TODO: Remove deprecation handling for `deprecate-early-static` warning
|
|
||||||
// once we upgrade to ember-cli-mirage v3.0.0 which fixes this issue.
|
|
||||||
// More info about this issue here: https://github.com/miragejs/ember-cli-mirage/issues/2441
|
|
||||||
if (options?.id && deprecationList.includes(options.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (options?.url) {
|
|
||||||
message += options.url;
|
|
||||||
}
|
|
||||||
next(message, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
initialize,
|
|
||||||
};
|
|
||||||
@@ -19,9 +19,18 @@ export const TYPES_HOST_CATALOG_PLUGIN = [
|
|||||||
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TYPE_CREDENTIAL_STATIC = 'static-credential';
|
||||||
|
export const TYPE_CREDENTIAL_DYNAMIC = 'dynamic-credential';
|
||||||
|
|
||||||
|
export const TYPES_CREDENTIALS = Object.freeze([
|
||||||
|
TYPE_CREDENTIAL_STATIC,
|
||||||
|
TYPE_CREDENTIAL_DYNAMIC,
|
||||||
|
]);
|
||||||
|
|
||||||
export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
||||||
// =attributes
|
// =attributes
|
||||||
|
|
||||||
|
#credentialType;
|
||||||
/**
|
/**
|
||||||
* True if the host catalog is static.
|
* True if the host catalog is static.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -38,6 +47,29 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
return this.type === 'plugin';
|
return this.type === 'plugin';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value from credentialType
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get credentialType() {
|
||||||
|
if (!this.#credentialType) {
|
||||||
|
if (this.role_arn) {
|
||||||
|
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
|
||||||
|
} else {
|
||||||
|
this.#credentialType = TYPE_CREDENTIAL_STATIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.#credentialType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value to credentialType
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
set credentialType(type) {
|
||||||
|
this.#credentialType = type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the host catalog is an unknown type.
|
* True if the host catalog is an unknown type.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|||||||
@@ -4,29 +4,59 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ApplicationSerializer from './application';
|
import ApplicationSerializer from './application';
|
||||||
|
import {
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
TYPE_CREDENTIAL_DYNAMIC,
|
||||||
|
} from '../models/host-catalog';
|
||||||
|
|
||||||
const fieldsByType = {
|
// These fields are Azure specific
|
||||||
aws: [
|
const azureFields = [
|
||||||
'disable_credential_rotation',
|
'disable_credential_rotation',
|
||||||
'region',
|
'tenant_id',
|
||||||
|
'client_id',
|
||||||
|
'subscription_id',
|
||||||
|
'secret_id',
|
||||||
|
'secret_value',
|
||||||
|
];
|
||||||
|
|
||||||
|
// These fields are AWS specific, AWS provider now has two credentialTypes: static and dynamic [not to be confused with static and dynamic host-catalogs]
|
||||||
|
const AWSfieldsWithCredentialType = {
|
||||||
|
'static-credential': [
|
||||||
'access_key_id',
|
'access_key_id',
|
||||||
'secret_access_key',
|
'secret_access_key',
|
||||||
],
|
'region',
|
||||||
azure: [
|
|
||||||
'disable_credential_rotation',
|
'disable_credential_rotation',
|
||||||
'tenant_id',
|
'worker_filter',
|
||||||
'client_id',
|
],
|
||||||
'subscription_id',
|
'dynamic-credential': [
|
||||||
'secret_id',
|
'role_arn',
|
||||||
'secret_value',
|
'role_external_id',
|
||||||
|
'role_session_name',
|
||||||
|
'role_tags',
|
||||||
|
'region',
|
||||||
|
'disable_credential_rotation',
|
||||||
|
'worker_filter',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class HostCatalogSerializer extends ApplicationSerializer {
|
export default class HostCatalogSerializer extends ApplicationSerializer {
|
||||||
// =methods
|
|
||||||
serialize(snapshot) {
|
serialize(snapshot) {
|
||||||
|
const { compositeType, credentialType } = snapshot.record;
|
||||||
const serialized = super.serialize(...arguments);
|
const serialized = super.serialize(...arguments);
|
||||||
switch (snapshot.record.compositeType) {
|
|
||||||
|
// This is a hack to remove the worker_filter field from the serialized data if the compositeType is not AWS
|
||||||
|
// Ideally we should refactor the model to include the compositeType using `for` attribute
|
||||||
|
if (compositeType !== TYPE_HOST_CATALOG_PLUGIN_AWS) {
|
||||||
|
delete serialized.worker_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, disable credential rotation for dynamic credentials
|
||||||
|
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
|
||||||
|
serialized.attributes.disable_credential_rotation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (compositeType) {
|
||||||
case 'static':
|
case 'static':
|
||||||
return this.serializeStatic(...arguments);
|
return this.serializeStatic(...arguments);
|
||||||
default:
|
default:
|
||||||
@@ -36,21 +66,30 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
|
|||||||
|
|
||||||
serializeAttribute(snapshot, json, key, attribute) {
|
serializeAttribute(snapshot, json, key, attribute) {
|
||||||
const value = super.serializeAttribute(...arguments);
|
const value = super.serializeAttribute(...arguments);
|
||||||
const { isPlugin, compositeType } = snapshot.record;
|
const { compositeType, credentialType, isPlugin } = snapshot.record;
|
||||||
const { options } = attribute;
|
const { options } = attribute;
|
||||||
// Nested conditionals for readability
|
|
||||||
// This deletes any fields that don't belong to the record type
|
const fields =
|
||||||
|
compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE
|
||||||
|
? azureFields
|
||||||
|
: AWSfieldsWithCredentialType[credentialType];
|
||||||
|
|
||||||
|
// Delete any nested attribute fields that don't belong to the record type
|
||||||
if (isPlugin && options.isNestedAttribute && json.attributes) {
|
if (isPlugin && options.isNestedAttribute && json.attributes) {
|
||||||
// The key must be included in the fieldsByType list above
|
if (!fields.includes(key)) {
|
||||||
if (!fieldsByType[compositeType].includes(key))
|
|
||||||
delete json.attributes[key];
|
delete json.attributes[key];
|
||||||
}
|
}
|
||||||
// This deletes any fields that don't belong to the record type
|
|
||||||
if (isPlugin && options.isNestedSecret && json.secrets) {
|
|
||||||
if (!fieldsByType[compositeType].includes(key)) delete json.secrets[key];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// new one for isNestedSecret
|
// Delete any secret fields that don't belong to the record type
|
||||||
|
if (isPlugin && options.isNestedSecret && json.secrets) {
|
||||||
|
if (!fields.includes(key)) {
|
||||||
|
delete json.secrets[key];
|
||||||
|
if (json['secrets'] && Object.keys(json.secrets).length === 0) {
|
||||||
|
delete json.secrets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +98,7 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
|
|||||||
// Delete unnecessary fields for static host-catalog
|
// Delete unnecessary fields for static host-catalog
|
||||||
delete serialized.attributes;
|
delete serialized.attributes;
|
||||||
delete serialized.secrets;
|
delete serialized.secrets;
|
||||||
|
delete serialized.worker_filter;
|
||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,10 @@ export const modelIndexes = {
|
|||||||
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.scope.scope_id, attributes.plugin.name',
|
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.scope.scope_id, attributes.plugin.name',
|
||||||
'auth-method':
|
'auth-method':
|
||||||
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.is_primary, attributes.scope.scope_id',
|
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.is_primary, attributes.scope.scope_id',
|
||||||
|
'session-recording':
|
||||||
|
'&id, attributes.created_time, attributes.type, attributes.state, attributes.start_time, attributes.end_time, attributes.duration, attributes.scope.scope_id, attributes.create_time_values.user.id, attributes.create_time_values.user.name, attributes.create_time_values.target.id, attributes.create_time_values.target.name, attributes.create_time_values.target.scope.id, attributes.create_time_values.target.scope.name',
|
||||||
|
alias:
|
||||||
|
'&id, attributes.created_time, attributes.type, attributes.value, attributes.name, attributes.description, attributes.destination_id, attributes.scope.scope_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatDbName = (userId, clusterUrl) =>
|
export const formatDbName = (userId, clusterUrl) =>
|
||||||
@@ -109,7 +113,7 @@ export default class IndexedDbService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.#db = new Dexie(dbName);
|
this.#db = new Dexie(dbName);
|
||||||
this.#db.version(1).stores(modelIndexes);
|
this.#db.version(2).stores(modelIndexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ const isTesting = environmentConfig.environment === 'test';
|
|||||||
export default function (mirageConfig) {
|
export default function (mirageConfig) {
|
||||||
let finalConfig = {
|
let finalConfig = {
|
||||||
...mirageConfig,
|
...mirageConfig,
|
||||||
models: { ...discoverEmberDataModels(), ...mirageConfig.models },
|
models: {
|
||||||
|
...discoverEmberDataModels(mirageConfig.store),
|
||||||
|
...mirageConfig.models,
|
||||||
|
},
|
||||||
serializers: applyEmberDataSerializers(mirageConfig.serializers),
|
serializers: applyEmberDataSerializers(mirageConfig.serializers),
|
||||||
routes,
|
routes,
|
||||||
};
|
};
|
||||||
@@ -775,18 +778,7 @@ function routes() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// session recordings
|
// session recordings
|
||||||
this.get(
|
this.get('/session-recordings');
|
||||||
'/session-recordings',
|
|
||||||
(
|
|
||||||
{ sessionRecordings },
|
|
||||||
{ queryParams: { scope_id: scopeId, recursive } },
|
|
||||||
) => {
|
|
||||||
if (recursive && scopeId === 'global') {
|
|
||||||
return sessionRecordings.all();
|
|
||||||
}
|
|
||||||
return sessionRecordings.where({ scopeId });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.get(
|
this.get(
|
||||||
'/session-recordings/:idMethod',
|
'/session-recordings/:idMethod',
|
||||||
async ({ sessionRecordings }, { params: { idMethod } }) => {
|
async ({ sessionRecordings }, { params: { idMethod } }) => {
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ export default factory.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
worker_filter: function () {
|
||||||
|
if (this.type === 'plugin' && this.plugin?.name === 'aws') {
|
||||||
|
return `"${faker.word.noun()}" in "${faker.system.directoryPath()}"`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
plugin: function (i) {
|
plugin: function (i) {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === 'plugin') {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -190,6 +190,27 @@ export default function initializeMockIPC(server, config) {
|
|||||||
isClientAgentRunning() {
|
isClientAgentRunning() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
cacheDaemonStatus() {
|
||||||
|
return { version: 'Boundary v1.0.0' };
|
||||||
|
}
|
||||||
|
getCliVersion() {
|
||||||
|
return { versionNumber: '1.0.0' };
|
||||||
|
}
|
||||||
|
getDesktopVersion() {
|
||||||
|
return { desktopVersion: '1.0.0' };
|
||||||
|
}
|
||||||
|
getLogLevel() {
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
getLogPath() {
|
||||||
|
return '~/.config/Boundary/logs/desktop-client.log';
|
||||||
|
}
|
||||||
|
setLogLevel() {}
|
||||||
|
clientAgentStatus() {
|
||||||
|
return { version: '0.0.1-dev', status: 'running' };
|
||||||
|
}
|
||||||
|
pauseClientAgent() {}
|
||||||
|
resumeClientAgent() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+10
-10
@@ -26,35 +26,36 @@
|
|||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.6.3",
|
||||||
"ember-can": "^4.2.0",
|
"ember-can": "^4.2.0",
|
||||||
"ember-cli-babel": "^7.26.10",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.2.0",
|
||||||
"ember-cli-mirage": "^2.4.0",
|
"ember-cli-mirage": "^3.0.3",
|
||||||
|
"miragejs": "^0.1.48",
|
||||||
"ember-data": "~4.12.0",
|
"ember-data": "~4.12.0",
|
||||||
"ember-fetch": "^8.1.1",
|
|
||||||
"tracked-built-ins": "^3.1.1"
|
"tracked-built-ins": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.0",
|
"@babel/core": "^7.25.2",
|
||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/eslint-parser": "^7.21.3",
|
||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/optional-features": "^2.0.0",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/string": "^3.0.1",
|
||||||
"@ember/test-helpers": "^2.9.3",
|
"@ember/test-helpers": "^3.3.1",
|
||||||
"@embroider/test-setup": "^2.1.1",
|
"@embroider/test-setup": "^2.1.1",
|
||||||
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^4.12.1",
|
"ember-cli": "^4.12.2",
|
||||||
"ember-cli-dependency-checker": "^3.3.1",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-exam": "^8.0.0",
|
"ember-exam": "^8.0.0",
|
||||||
"ember-load-initializers": "^2.1.2",
|
"ember-load-initializers": "^2.1.2",
|
||||||
"ember-page-title": "^7.0.0",
|
"ember-page-title": "^7.0.0",
|
||||||
"ember-qunit": "^6.2.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^10.0.0",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~4.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
@@ -64,13 +65,12 @@
|
|||||||
"eslint-plugin-ember": "^11.11.1",
|
"eslint-plugin-ember": "^11.11.1",
|
||||||
"eslint-plugin-n": "^16.3.1",
|
"eslint-plugin-n": "^16.3.1",
|
||||||
"eslint-plugin-qunit": "^8.0.1",
|
"eslint-plugin-qunit": "^8.0.1",
|
||||||
"@faker-js/faker": "^8.0.2",
|
|
||||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"qunit": "^2.19.4",
|
"qunit": "^2.19.4",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^2.0.0",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ember-source": ">4.0.0"
|
"ember-source": ">4.0.0"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function setupApplicationTest(hooks, options) {
|
|||||||
// This is also a good place to call test setup functions coming
|
// This is also a good place to call test setup functions coming
|
||||||
// from other addons:
|
// from other addons:
|
||||||
//
|
//
|
||||||
// setupIntl(hooks); // ember-intl
|
// setupIntl(hooks, 'en-us'); // ember-intl
|
||||||
// setupMirage(hooks); // ember-cli-mirage
|
// setupMirage(hooks); // ember-cli-mirage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Application from '@ember/application';
|
|
||||||
|
|
||||||
import config from 'dummy/config/environment';
|
|
||||||
import { initialize } from 'dummy/initializers/deprecations';
|
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import Resolver from 'ember-resolver';
|
|
||||||
import { run } from '@ember/runloop';
|
|
||||||
|
|
||||||
module('Unit | Initializer | deprecations', function (hooks) {
|
|
||||||
hooks.beforeEach(function () {
|
|
||||||
this.TestApplication = class TestApplication extends Application {
|
|
||||||
modulePrefix = config.modulePrefix;
|
|
||||||
podModulePrefix = config.podModulePrefix;
|
|
||||||
Resolver = Resolver;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.TestApplication.initializer({
|
|
||||||
name: 'initializer under test',
|
|
||||||
initialize,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.application = this.TestApplication.create({
|
|
||||||
autoboot: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
hooks.afterEach(function () {
|
|
||||||
run(this.application, 'destroy');
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Replace this with your real tests.
|
|
||||||
test('it works', async function (assert) {
|
|
||||||
await this.application.boot();
|
|
||||||
|
|
||||||
assert.ok(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import {
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_CREDENTIAL_STATIC,
|
||||||
|
TYPE_CREDENTIAL_DYNAMIC,
|
||||||
|
} from 'api/models/host-catalog';
|
||||||
|
|
||||||
module('Unit | Serializer | host catalog', function (hooks) {
|
module('Unit | Serializer | host catalog', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
@@ -31,13 +36,103 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
assert.deepEqual(record.serialize(), expectedResult);
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it serializes a new aws plugin as expected, ignoring azure fields', async function (assert) {
|
test('it serializes a new aws static plugin as expected', async function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
const record = store.createRecord('host-catalog', {
|
const record = store.createRecord('host-catalog', {
|
||||||
compositeType: 'aws',
|
compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
credentialType: TYPE_CREDENTIAL_STATIC,
|
||||||
name: 'AWS',
|
name: 'AWS',
|
||||||
description: 'this is a Aws plugin host-catalog',
|
description: 'this is a Aws plugin host-catalog',
|
||||||
disable_credential_rotation: true,
|
disable_credential_rotation: true,
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
// these are AWS fields and should be included
|
||||||
|
region: 'west',
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
// these are Azure fields and should be excluded
|
||||||
|
tenant_id: 'a1b2c3',
|
||||||
|
client_id: 'a1b2c3',
|
||||||
|
subscription_id: 'a1b2c3',
|
||||||
|
secret_id: 'a1b2c3',
|
||||||
|
secret_value: 'a1b2c3',
|
||||||
|
role_arn: 'test',
|
||||||
|
role_external_id: 'test',
|
||||||
|
role_session_name: 'test',
|
||||||
|
role_tags: [
|
||||||
|
{ key: 'Project', value: 'Automation' },
|
||||||
|
{ key: 'foo', value: 'bar' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const expectedResult = {
|
||||||
|
name: 'AWS',
|
||||||
|
description: 'this is a Aws plugin host-catalog',
|
||||||
|
type: 'plugin',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
attributes: {
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
region: 'west',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it serializes a new aws dynamic plugin as expected', async function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const record = store.createRecord('host-catalog', {
|
||||||
|
compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
name: 'AWS',
|
||||||
|
credentialType: TYPE_CREDENTIAL_DYNAMIC,
|
||||||
|
description: 'this is a Aws plugin host-catalog',
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
// these are AWS fields and should be included
|
||||||
|
region: 'west',
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
// these are Azure fields and should be excluded
|
||||||
|
tenant_id: 'a1b2c3',
|
||||||
|
client_id: 'a1b2c3',
|
||||||
|
subscription_id: 'a1b2c3',
|
||||||
|
secret_id: 'a1b2c3',
|
||||||
|
secret_value: 'a1b2c3',
|
||||||
|
role_arn: 'test',
|
||||||
|
role_external_id: 'test',
|
||||||
|
role_session_name: 'test',
|
||||||
|
role_tags: [
|
||||||
|
{ key: 'Project', value: 'Automation' },
|
||||||
|
{ key: 'foo', value: 'bar' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const expectedResult = {
|
||||||
|
name: 'AWS',
|
||||||
|
description: 'this is a Aws plugin host-catalog',
|
||||||
|
type: 'plugin',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
attributes: {
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
region: 'west',
|
||||||
|
role_external_id: 'test',
|
||||||
|
role_session_name: 'test',
|
||||||
|
role_arn: 'test',
|
||||||
|
role_tags: { Project: 'Automation', foo: 'bar' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it serializes a new aws static plugin as expected, ignoring azure fields', async function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const record = store.createRecord('host-catalog', {
|
||||||
|
compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
name: 'AWS',
|
||||||
|
credentialType: TYPE_CREDENTIAL_STATIC,
|
||||||
|
description: 'this is a Aws plugin host-catalog',
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
// these are AWS fields and should be included
|
// these are AWS fields and should be included
|
||||||
region: 'spain',
|
region: 'spain',
|
||||||
access_key_id: 'foobars',
|
access_key_id: 'foobars',
|
||||||
@@ -53,6 +148,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
name: 'AWS',
|
name: 'AWS',
|
||||||
description: 'this is a Aws plugin host-catalog',
|
description: 'this is a Aws plugin host-catalog',
|
||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
attributes: {
|
attributes: {
|
||||||
disable_credential_rotation: true,
|
disable_credential_rotation: true,
|
||||||
region: 'spain',
|
region: 'spain',
|
||||||
@@ -75,6 +171,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
region: 'spain',
|
region: 'spain',
|
||||||
access_key_id: 'foobars',
|
access_key_id: 'foobars',
|
||||||
secret_access_key: 'testing',
|
secret_access_key: 'testing',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
// these are Azure fields and should be excluded
|
// these are Azure fields and should be excluded
|
||||||
tenant_id: 'a1b2c3',
|
tenant_id: 'a1b2c3',
|
||||||
client_id: 'a1b2c3',
|
client_id: 'a1b2c3',
|
||||||
@@ -106,6 +203,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
data: {
|
data: {
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'host-catalog',
|
type: 'host-catalog',
|
||||||
|
credentialType: 'static',
|
||||||
attributes: {
|
attributes: {
|
||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
@@ -113,6 +211,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
plugin: {
|
plugin: {
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
},
|
},
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
region: 'andorra',
|
region: 'andorra',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,6 +221,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
description: 'test description',
|
description: 'test description',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
attributes: {
|
attributes: {
|
||||||
disable_credential_rotation: false,
|
disable_credential_rotation: false,
|
||||||
region: 'andorra',
|
region: 'andorra',
|
||||||
@@ -215,6 +315,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
name: 'aws',
|
name: 'aws',
|
||||||
description: 'aws host catalog',
|
description: 'aws host catalog',
|
||||||
},
|
},
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
attributes: {
|
attributes: {
|
||||||
disable_credential_rotation: false,
|
disable_credential_rotation: false,
|
||||||
region: 'Illinois',
|
region: 'Illinois',
|
||||||
@@ -238,6 +339,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
description: 'Test description',
|
description: 'Test description',
|
||||||
authorized_actions: ['no-op'],
|
authorized_actions: ['no-op'],
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
plugin: {
|
plugin: {
|
||||||
id: 'plugin-id-5',
|
id: 'plugin-id-5',
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
|||||||
import { pluralize } from 'ember-inflector';
|
import { pluralize } from 'ember-inflector';
|
||||||
import { camelize } from '@ember/string';
|
import { camelize } from '@ember/string';
|
||||||
import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target';
|
import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
const seedIndexDb = async (resource, store, indexedDb, server) => {
|
const seedIndexDb = async (resource, store, indexedDb, server) => {
|
||||||
const resourceData =
|
const resourceData =
|
||||||
@@ -529,4 +530,80 @@ module('Unit | Utility | indexed-db-query', function (hooks) {
|
|||||||
|
|
||||||
assert.strictEqual(result.length, 0);
|
assert.strictEqual(result.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it filters on date type fields and returns results', async function (assert) {
|
||||||
|
const pastDate = new Date('2024-09-19T10:00:00.000Z');
|
||||||
|
const createdTime = new Date('2024-09-23T10:00:00.000Z');
|
||||||
|
this.server.create('session-recording', {
|
||||||
|
created_time: createdTime,
|
||||||
|
});
|
||||||
|
await seedIndexDb('session-recording', store, indexedDb, this.server);
|
||||||
|
const query = { filters: { created_time: [{ gte: pastDate }] } };
|
||||||
|
|
||||||
|
const result = await queryIndexedDb(
|
||||||
|
indexedDb.db,
|
||||||
|
'session-recording',
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it filters on date type fields and returns results - from and to', async function (assert) {
|
||||||
|
const from = new Date('2024-09-19T10:00:00.000Z');
|
||||||
|
const to = new Date('2024-09-25T10:00:00.000Z');
|
||||||
|
const createdTime = new Date('2024-09-23T10:00:00.000Z');
|
||||||
|
const createdTime2 = new Date('2024-09-28T10:00:00.000Z');
|
||||||
|
this.server.create('session-recording', {
|
||||||
|
created_time: createdTime,
|
||||||
|
});
|
||||||
|
this.server.create('session-recording', {
|
||||||
|
created_time: createdTime2,
|
||||||
|
});
|
||||||
|
await seedIndexDb('session-recording', store, indexedDb, this.server);
|
||||||
|
const query = {
|
||||||
|
filters: {
|
||||||
|
created_time: {
|
||||||
|
logicalOperator: 'and',
|
||||||
|
values: [{ gte: from }, { lte: to }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await queryIndexedDb(
|
||||||
|
indexedDb.db,
|
||||||
|
'session-recording',
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.length, 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
result[0].attributes.created_time.toISOString(),
|
||||||
|
createdTime.toISOString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it filters on date type fields and returns no results', async function (assert) {
|
||||||
|
const createdTime = faker.date.between({
|
||||||
|
from: '2024-09-19T00:00:00.000Z',
|
||||||
|
to: '2024-09-20T00:00:00.000Z',
|
||||||
|
});
|
||||||
|
const pastDate = faker.date.between({
|
||||||
|
from: '2024-09-23T00:00:00.000Z',
|
||||||
|
to: '2024-09-24T00:00:00.000Z',
|
||||||
|
});
|
||||||
|
this.server.create('session-recording', {
|
||||||
|
created_time: createdTime,
|
||||||
|
});
|
||||||
|
await seedIndexDb('session-recording', store, indexedDb, this.server);
|
||||||
|
const query = { filters: { created_time: [{ gte: pastDate }] } };
|
||||||
|
|
||||||
|
const result = await queryIndexedDb(
|
||||||
|
indexedDb.db,
|
||||||
|
'session-recording',
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.length, 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import SimpleAuthBaseAuthenticator from 'ember-simple-auth/authenticators/base';
|
import SimpleAuthBaseAuthenticator from 'ember-simple-auth/authenticators/base';
|
||||||
import { resolve, reject } from 'rsvp';
|
import { resolve, reject } from 'rsvp';
|
||||||
import fetch from 'fetch';
|
import { waitForPromise } from '@ember/test-waiters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates common authenticator functionality.
|
* Encapsulates common authenticator functionality.
|
||||||
@@ -58,10 +58,14 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
|||||||
*/
|
*/
|
||||||
async validateToken(token, tokenID) {
|
async validateToken(token, tokenID) {
|
||||||
const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID);
|
const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID);
|
||||||
const response = await fetch(tokenValidationURL, {
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
||||||
method: 'get',
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
const response = await waitForPromise(
|
||||||
});
|
fetch(tokenValidationURL, {
|
||||||
|
method: 'get',
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
}),
|
||||||
|
);
|
||||||
// 401 and 404 responses mean the token is invalid, whereas other types of
|
// 401 and 404 responses mean the token is invalid, whereas other types of
|
||||||
// error responses do not tell us about the validity of the token.
|
// error responses do not tell us about the validity of the token.
|
||||||
if (response.status === 401 || response.status === 404) return reject();
|
if (response.status === 401 || response.status === 404) return reject();
|
||||||
@@ -103,7 +107,7 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts to the `deauthEntpoint` on a best-effort basis and then returns.
|
* Posts to the `deauthEndpoint` on a best-effort basis and then returns.
|
||||||
* Deauthentication with the server is not guaranteed and request failures
|
* Deauthentication with the server is not guaranteed and request failures
|
||||||
* are ignored.
|
* are ignored.
|
||||||
*
|
*
|
||||||
@@ -113,12 +117,14 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
|||||||
async invalidate(options) {
|
async invalidate(options) {
|
||||||
const { token } = options;
|
const { token } = options;
|
||||||
const deauthEndpointURL = this.buildDeauthEndpointURL(options);
|
const deauthEndpointURL = this.buildDeauthEndpointURL(options);
|
||||||
await fetch(deauthEndpointURL, {
|
await waitForPromise(
|
||||||
method: 'delete',
|
fetch(deauthEndpointURL, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
method: 'delete',
|
||||||
}).catch(() => {
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
/* no op */
|
}).catch(() => {
|
||||||
});
|
/* no op */
|
||||||
|
}),
|
||||||
|
);
|
||||||
return super.invalidate(...arguments);
|
return super.invalidate(...arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import BaseAuthenticator from './base';
|
import BaseAuthenticator from './base';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { reject } from 'rsvp';
|
import { reject } from 'rsvp';
|
||||||
import fetch from 'fetch';
|
import { waitForPromise } from '@ember/test-waiters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The OIDC base authenticator encapsulates the multistep OIDC flow.
|
* The OIDC base authenticator encapsulates the multistep OIDC flow.
|
||||||
@@ -47,7 +47,9 @@ export default class OIDCAuthenticator extends BaseAuthenticator {
|
|||||||
async startAuthentication(options) {
|
async startAuthentication(options) {
|
||||||
const url = this.buildAuthEndpointURL(options);
|
const url = this.buildAuthEndpointURL(options);
|
||||||
const body = JSON.stringify({ command: 'start' });
|
const body = JSON.stringify({ command: 'start' });
|
||||||
const response = await fetch(url, { method: 'post', body });
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
||||||
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
||||||
|
const response = await waitForPromise(fetch(url, { method: 'post', body }));
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (response.status < 400) {
|
if (response.status < 400) {
|
||||||
// Store meta about the pending OIDC flow
|
// Store meta about the pending OIDC flow
|
||||||
@@ -94,7 +96,7 @@ export default class OIDCAuthenticator extends BaseAuthenticator {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Fetch the endpoint and get the response JSON
|
// Fetch the endpoint and get the response JSON
|
||||||
const response = await fetch(url, { method: 'post', body });
|
const response = await waitForPromise(fetch(url, { method: 'post', body }));
|
||||||
if (response.status === 202) {
|
if (response.status === 202) {
|
||||||
// The token isn't ready yet, keep trying.
|
// The token isn't ready yet, keep trying.
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import BaseAuthenticator from './base';
|
import BaseAuthenticator from './base';
|
||||||
import { resolve, reject } from 'rsvp';
|
import { resolve, reject } from 'rsvp';
|
||||||
import fetch from 'fetch';
|
import { waitForPromise } from '@ember/test-waiters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -66,7 +66,11 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
|||||||
attributes: { login_name, password },
|
attributes: { login_name, password },
|
||||||
});
|
});
|
||||||
const authEndpointURL = this.buildAuthEndpointURL(options);
|
const authEndpointURL = this.buildAuthEndpointURL(options);
|
||||||
const response = await fetch(authEndpointURL, { method: 'post', body });
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
||||||
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
||||||
|
const response = await waitForPromise(
|
||||||
|
fetch(authEndpointURL, { method: 'post', body }),
|
||||||
|
);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
return response.status < 400
|
return response.status < 400
|
||||||
? resolve(this.normalizeData(json, login_name))
|
? resolve(this.normalizeData(json, login_name))
|
||||||
|
|||||||
@@ -21,9 +21,8 @@
|
|||||||
"doc:toc": "doctoc README.md"
|
"doc:toc": "doctoc README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ember-cli-babel": "^7.26.11",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.2.0",
|
||||||
"ember-fetch": "^8.1.1",
|
|
||||||
"ember-simple-auth": "^4.2.2"
|
"ember-simple-auth": "^4.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -31,7 +30,8 @@
|
|||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/optional-features": "^2.0.0",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/string": "^3.0.1",
|
||||||
"@ember/test-helpers": "^2.9.3",
|
"@ember/test-helpers": "^3.3.1",
|
||||||
|
"@ember/test-waiters": "^3.1.0",
|
||||||
"@embroider/test-setup": "^2.1.1",
|
"@embroider/test-setup": "^2.1.1",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.6.3",
|
||||||
"ember-cli": "^4.12.2",
|
"ember-cli": "^4.12.2",
|
||||||
"ember-cli-dependency-checker": "^3.3.1",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"ember-export-application-global": "^2.0.1",
|
"ember-export-application-global": "^2.0.1",
|
||||||
"ember-load-initializers": "^2.1.2",
|
"ember-load-initializers": "^2.1.2",
|
||||||
"ember-page-title": "^7.0.0",
|
"ember-page-title": "^7.0.0",
|
||||||
"ember-qunit": "^6.2.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^10.0.0",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~4.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"qunit": "^2.19.4",
|
"qunit": "^2.19.4",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^2.0.0",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.* || 20.*"
|
"node": "18.* || 20.*"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function setupApplicationTest(hooks, options) {
|
|||||||
// This is also a good place to call test setup functions coming
|
// This is also a good place to call test setup functions coming
|
||||||
// from other addons:
|
// from other addons:
|
||||||
//
|
//
|
||||||
// setupIntl(hooks); // ember-intl
|
// setupIntl(hooks, 'en-us'); // ember-intl
|
||||||
// setupMirage(hooks); // ember-cli-mirage
|
// setupMirage(hooks); // ember-cli-mirage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,10 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<Rose::Button
|
<Hds::Button
|
||||||
...attributes
|
@text={{t 'actions.refresh'}}
|
||||||
@style='inline-link-action'
|
@color='secondary'
|
||||||
@iconLeft={{if
|
@icon={{if this.isLoading 'loading' 'reload'}}
|
||||||
this.isLoading
|
|
||||||
'flight-icons/svg/loading-16'
|
|
||||||
'flight-icons/svg/reload-16'
|
|
||||||
}}
|
|
||||||
class={{if this.isLoading 'loading-button is-loading' 'loading-button'}}
|
|
||||||
@disabled={{this.isLoading}}
|
@disabled={{this.isLoading}}
|
||||||
{{on 'click' this.toggleRefresh}}
|
{{on 'click' this.toggleRefresh}}
|
||||||
>
|
/>
|
||||||
{{yield}}
|
|
||||||
</Rose::Button>
|
|
||||||
@@ -3,12 +3,17 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if this.lastRefreshed}}
|
<div class='toolbar-refresher'>
|
||||||
<Rose::Toolbar::Info>
|
{{#if (has-block)}}
|
||||||
{{relative-datetime-live this.lastRefreshed}}
|
{{yield}}
|
||||||
</Rose::Toolbar::Info>
|
{{else}}
|
||||||
{{/if}}
|
{{#if this.lastRefreshed}}
|
||||||
|
<Hds::Text::Body @tag='p' @color='faint'>
|
||||||
<LoadingButton @onClick={{this.onClick}}>
|
{{relative-datetime-live this.lastRefreshed}}
|
||||||
{{t 'actions.refresh'}}
|
</Hds::Text::Body>
|
||||||
</LoadingButton>
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
<LoadingButton @onClick={{this.onClick}}>
|
||||||
|
{{t 'actions.refresh'}}
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
@@ -15,7 +15,7 @@ export default class ToolbarRefresherComponent extends Component {
|
|||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the click handler argument and trackes the time it completed.
|
* Executes the click handler argument and tracks the time it completed.
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async onClick() {
|
async onClick() {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import Helper from '@ember/component/helper';
|
import Helper from '@ember/component/helper';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import DurationUnitFormat from 'intl-unofficial-duration-unit-format';
|
import { DurationFormat } from '@formatjs/intl-durationformat';
|
||||||
|
import { secondsToDuration } from 'core/utils/seconds-to-duration';
|
||||||
|
|
||||||
export default class extends Helper {
|
export default class extends Helper {
|
||||||
// =services
|
// =services
|
||||||
@@ -19,12 +20,10 @@ export default class extends Helper {
|
|||||||
*/
|
*/
|
||||||
compute([durationInMs]) {
|
compute([durationInMs]) {
|
||||||
const durationInSeconds = durationInMs / 1000;
|
const durationInSeconds = durationInMs / 1000;
|
||||||
const durationFormat = new DurationUnitFormat(this.intl.primaryLocale, {
|
const durationFormat = new DurationFormat(this.intl.primaryLocale, {
|
||||||
style: 'narrow',
|
style: 'narrow',
|
||||||
format: '{days} {hours} {minutes} {seconds}',
|
|
||||||
round: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return durationFormat.format(durationInSeconds);
|
return durationFormat.format(secondsToDuration(durationInSeconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import Helper from '@ember/component/helper';
|
import Helper from '@ember/component/helper';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import DurationUnitFormat from 'intl-unofficial-duration-unit-format';
|
import { DurationFormat } from '@formatjs/intl-durationformat';
|
||||||
|
import { secondsToDuration } from 'core/utils/seconds-to-duration';
|
||||||
|
|
||||||
export default class extends Helper {
|
export default class extends Helper {
|
||||||
// =services
|
// =services
|
||||||
@@ -29,14 +30,13 @@ export default class extends Helper {
|
|||||||
difference > 0 ? Math.floor(difference / 1000) : 0;
|
difference > 0 ? Math.floor(difference / 1000) : 0;
|
||||||
|
|
||||||
// Format the duration
|
// Format the duration
|
||||||
const duration = new DurationUnitFormat(this.intl.primaryLocale, {
|
const duration = new DurationFormat(this.intl.primaryLocale, {
|
||||||
style: 'timer',
|
style: 'digital',
|
||||||
format: `{hours}:{minutes}:{seconds} ${this.intl.t(
|
|
||||||
'resources.session.remaining',
|
|
||||||
)}`,
|
|
||||||
});
|
});
|
||||||
|
const formattedTime = duration.format(
|
||||||
|
secondsToDuration(differenceInSeconds),
|
||||||
|
);
|
||||||
|
|
||||||
// Return the formatted duration
|
return `${formattedTime} ${this.intl.t('resources.session.remaining')}`;
|
||||||
return duration.format(differenceInSeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
.filter-tag {
|
.filter-tag {
|
||||||
@@ -27,33 +26,20 @@
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hds-segmented-group {
|
> div,
|
||||||
|
table {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hds-segmented-group {
|
||||||
.hds-dropdown-toggle-button__text {
|
.hds-dropdown-toggle-button__text {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hds-table {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-filtering-toolbar {
|
.search-filtering-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
// TODO: Remove these copied styles by migrating to HDS for the toolbar refresher component.
|
|
||||||
// The original styles assumed we were using the old toolbar component under a "rose-toolbar" class.
|
|
||||||
.rose-button-inline-link-action {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
line-height: 1.8461538462;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--stark);
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 0.375rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> :last-child {
|
> :last-child {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
@@ -67,3 +53,15 @@
|
|||||||
color: var(--token-color-foreground-primary);
|
color: var(--token-color-foreground-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-refresher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
> .hds-text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in the number of seconds and converts to an object with corresponding
|
||||||
|
* values for each time duration. For example:
|
||||||
|
* { weeks: 0, days: 0, hours: 22, minutes: 15, seconds: 11 }
|
||||||
|
* @param {number} value
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export const secondsToDuration = (value) => {
|
||||||
|
const second = 1;
|
||||||
|
const minute = second * 60;
|
||||||
|
const hour = minute * 60;
|
||||||
|
const day = hour * 24;
|
||||||
|
const week = day * 7;
|
||||||
|
|
||||||
|
const weeks = Math.max(Math.floor(value / week), 0);
|
||||||
|
const days = Math.max(Math.floor((value % week) / day), 0);
|
||||||
|
const hours = Math.max(Math.floor((value % day) / hour), 0);
|
||||||
|
const minutes = Math.max(Math.floor((value % hour) / minute), 0);
|
||||||
|
const seconds = Math.max(Math.floor((value % minute) / second), 0);
|
||||||
|
|
||||||
|
return { weeks, days, hours, minutes, seconds };
|
||||||
|
};
|
||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default, initialize } from 'api/initializers/deprecations';
|
export { default } from 'core/utils/seconds-to-duration';
|
||||||
@@ -31,19 +31,19 @@
|
|||||||
"doc:toc": "doctoc README.md"
|
"doc:toc": "doctoc README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@formatjs/intl-durationformat": "^0.2.4",
|
||||||
"api": "*",
|
"api": "*",
|
||||||
"auth": "*",
|
"auth": "*",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.6.3",
|
||||||
"ember-browser-services": "^4.0.3",
|
"ember-browser-services": "^4.0.3",
|
||||||
"ember-cli-babel": "^7.26.11",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.2.0",
|
||||||
"ember-cli-sass": "^11.0.1",
|
"ember-cli-sass": "^11.0.1",
|
||||||
"ember-feature-flags": "^6.0.0",
|
"ember-feature-flags": "^6.0.0",
|
||||||
"ember-intl": "^6.0.0-beta.6",
|
"ember-intl": "^7.0.5",
|
||||||
"ember-loading": "^2.0.0",
|
"ember-loading": "^2.0.0",
|
||||||
"ember-truth-helpers": "^3.0.0",
|
"ember-truth-helpers": "^3.0.0",
|
||||||
"filesize": "^10.0.7",
|
"filesize": "^10.0.7",
|
||||||
"intl-unofficial-duration-unit-format": "^3.1.0",
|
|
||||||
"sass": "^1.69.5"
|
"sass": "^1.69.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/optional-features": "^2.0.0",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/string": "^3.0.1",
|
||||||
"@ember/test-helpers": "^2.9.3",
|
"@ember/test-helpers": "^3.3.1",
|
||||||
"@embroider/test-setup": "^2.1.1",
|
"@embroider/test-setup": "^2.1.1",
|
||||||
"@faker-js/faker": "^8.0.2",
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^4.12.2",
|
"ember-cli": "^4.12.2",
|
||||||
"ember-cli-dependency-checker": "^3.3.1",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-inline-svg": "^1.0.1",
|
"ember-inline-svg": "^1.0.1",
|
||||||
"ember-load-initializers": "^2.1.2",
|
"ember-load-initializers": "^2.1.2",
|
||||||
"ember-page-title": "^7.0.0",
|
"ember-page-title": "^7.0.0",
|
||||||
"ember-qunit": "^6.2.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^10.0.0",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~4.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
"qunit": "^2.19.4",
|
"qunit": "^2.19.4",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^2.0.0",
|
||||||
"rose": "*",
|
"rose": "*",
|
||||||
"sinon": "^17.0.1",
|
"sinon": "^18.0.0",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ember-source": "^4.0.0",
|
"ember-source": "^4.0.0",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function setupApplicationTest(hooks, options) {
|
|||||||
// This is also a good place to call test setup functions coming
|
// This is also a good place to call test setup functions coming
|
||||||
// from other addons:
|
// from other addons:
|
||||||
//
|
//
|
||||||
// setupIntl(hooks); // ember-intl
|
// setupIntl(hooks, 'en-us'); // ember-intl
|
||||||
// setupMirage(hooks); // ember-cli-mirage
|
// setupMirage(hooks); // ember-cli-mirage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { hbs } from 'ember-cli-htmlbars';
|
|||||||
|
|
||||||
module('Integration | Component | dropdown/index', function (hooks) {
|
module('Integration | Component | dropdown/index', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
const firstItem = {
|
const firstItem = {
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Component | error/message', function (hooks) {
|
module('Integration | Component | error/message', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders predefined message for known error status', async function (assert) {
|
test('it renders predefined message for known error status', async function (assert) {
|
||||||
await render(hbs`<Error::Message @status='401' />`);
|
await render(hbs`<Error::Message @status='401' />`);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { hbs } from 'ember-cli-htmlbars';
|
|||||||
|
|
||||||
module('Integration | Component | filter-tags/index', function (hooks) {
|
module('Integration | Component | filter-tags/index', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders', async function (assert) {
|
test('it renders', async function (assert) {
|
||||||
this.set('filter', {
|
this.set('filter', {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
|
|
||||||
module('Integration | Component | form | authenticate', function (hooks) {
|
module('Integration | Component | form | authenticate', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it shows login name and password when LDAP is selected from the list', async function (assert) {
|
test('it shows login name and password when LDAP is selected from the list', async function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
|
|||||||
@@ -7,19 +7,22 @@ import { module, test } from 'qunit';
|
|||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, find, click } from '@ember/test-helpers';
|
import { render, find, click } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
import { setupIntl } from 'ember-intl/test-support';
|
||||||
|
|
||||||
module('Integration | Component | loading-button', function (hooks) {
|
module('Integration | Component | loading-button', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders', async function (assert) {
|
test('it renders', async function (assert) {
|
||||||
await render(hbs`<LoadingButton />`);
|
await render(hbs`<LoadingButton />`);
|
||||||
assert.ok(find('.loading-button'));
|
assert.ok(find('.hds-button'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it executes a function on refresh button click', async function (assert) {
|
test('it executes a function on refresh button click', async function (assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
this.onClick = () => assert.ok(true, 'refresh was clicked');
|
this.onClick = () => assert.ok(true, 'refresh was clicked');
|
||||||
await render(hbs`<LoadingButton @onClick={{this.onClick}} />`);
|
await render(hbs`<LoadingButton @onClick={{this.onClick}} />`);
|
||||||
assert.ok(find('.loading-button'));
|
assert.ok(find('.hds-button'));
|
||||||
await click('button');
|
await click('button');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -11,7 +11,7 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Helper | format-day-year', function (hooks) {
|
module('Integration | Helper | format-day-year', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders correctly when it is 1 year', async function (assert) {
|
test('it renders correctly when it is 1 year', async function (assert) {
|
||||||
this.set('inputValue', 365);
|
this.set('inputValue', 365);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Helper | format-time-duration', function (hooks) {
|
module('Integration | Helper | format-time-duration', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('It renders in human-readable duration strings given different durations in milliseconds', async function (assert) {
|
test('It renders in human-readable duration strings given different durations in milliseconds', async function (assert) {
|
||||||
// Set mock input (in milliseconds) durations
|
// Set mock input (in milliseconds) durations
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Helper | relative-datetime-live', function (hooks) {
|
module('Integration | Helper | relative-datetime-live', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
let now;
|
let now;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Helper | reverse-indexed-display-name', function (hooks) {
|
module('Integration | Helper | reverse-indexed-display-name', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders the resource name with its position in parent array reversed', async function (assert) {
|
test('it renders the resource name with its position in parent array reversed', async function (assert) {
|
||||||
this.set('array', ['Boundary', 'is', 'cool!']);
|
this.set('array', ['Boundary', 'is', 'cool!']);
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ import { setupIntl } from 'ember-intl/test-support';
|
|||||||
|
|
||||||
module('Integration | Helper | time-remaining', function (hooks) {
|
module('Integration | Helper | time-remaining', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it calculates the correct time remaining', async function (assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
|
test('it calculates the correct time remainin with less than 24 hours', async function (assert) {
|
||||||
this.set('expirationTime', new Date(Date.now() + 1000 * 60 * 60 * 24));
|
this.set('expirationTime', new Date(Date.now() + 1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
await render(hbs`{{time-remaining this.expirationTime}}`);
|
await render(hbs`{{time-remaining this.expirationTime}}`);
|
||||||
@@ -23,9 +21,29 @@ module('Integration | Helper | time-remaining', function (hooks) {
|
|||||||
assert.strictEqual(this.element.textContent.trim(), '23:59:59 remaining');
|
assert.strictEqual(this.element.textContent.trim(), '23:59:59 remaining');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it handles negative time remaining', async function (assert) {
|
test('it calculates the correct time remaining with more than 24 hours', async function (assert) {
|
||||||
assert.expect(1);
|
this.set('expirationTime', new Date(Date.now() + 1001 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
await render(hbs`{{time-remaining this.expirationTime}}`);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.element.textContent.trim(),
|
||||||
|
'1 day, 0:01:26 remaining',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it calculates the correct time remaining with more than a week', async function (assert) {
|
||||||
|
this.set('expirationTime', new Date(Date.now() + 1001 * 60 * 60 * 24 * 8));
|
||||||
|
|
||||||
|
await render(hbs`{{time-remaining this.expirationTime}}`);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.element.textContent.trim(),
|
||||||
|
'1 wk, 1 day, 0:11:31 remaining',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it handles negative time remaining', async function (assert) {
|
||||||
this.set('expirationTime', new Date(Date.now() - 1000 * 60 * 60 * 24));
|
this.set('expirationTime', new Date(Date.now() - 1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
await render(hbs`{{time-remaining this.expirationTime}}`);
|
await render(hbs`{{time-remaining this.expirationTime}}`);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const listWithOneItem = [{ key: 1, value: 'boundary' }];
|
|||||||
const limit = 3;
|
const limit = 3;
|
||||||
module('Integration | Helper | truncate-list', function (hooks) {
|
module('Integration | Helper | truncate-list', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupIntl(hooks);
|
setupIntl(hooks, 'en-us');
|
||||||
|
|
||||||
test('it renders correctly when there is list with more than the limit specificed', async function (assert) {
|
test('it renders correctly when there is list with more than the limit specificed', async function (assert) {
|
||||||
this.set('inputValue', list);
|
this.set('inputValue', list);
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { secondsToDuration } from 'core/utils/seconds-to-duration';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
|
||||||
|
module('Unit | Utility | seconds-to-duration', function () {
|
||||||
|
test('it handles 0 correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(0);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 0);
|
||||||
|
assert.strictEqual(result.minutes, 0);
|
||||||
|
assert.strictEqual(result.hours, 0);
|
||||||
|
assert.strictEqual(result.days, 0);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it converts seconds correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(45);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 45);
|
||||||
|
assert.strictEqual(result.minutes, 0);
|
||||||
|
assert.strictEqual(result.hours, 0);
|
||||||
|
assert.strictEqual(result.days, 0);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it converts minutes correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(100);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 40);
|
||||||
|
assert.strictEqual(result.minutes, 1);
|
||||||
|
assert.strictEqual(result.hours, 0);
|
||||||
|
assert.strictEqual(result.days, 0);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it converts hours correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(5000);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 20);
|
||||||
|
assert.strictEqual(result.minutes, 23);
|
||||||
|
assert.strictEqual(result.hours, 1);
|
||||||
|
assert.strictEqual(result.days, 0);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it converts days correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(500000);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 20);
|
||||||
|
assert.strictEqual(result.minutes, 53);
|
||||||
|
assert.strictEqual(result.hours, 18);
|
||||||
|
assert.strictEqual(result.days, 5);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it converts weeks correctly', function (assert) {
|
||||||
|
const result = secondsToDuration(800000);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 20);
|
||||||
|
assert.strictEqual(result.minutes, 13);
|
||||||
|
assert.strictEqual(result.hours, 6);
|
||||||
|
assert.strictEqual(result.days, 2);
|
||||||
|
assert.strictEqual(result.weeks, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it handles negative values', function (assert) {
|
||||||
|
const result = secondsToDuration(-800000);
|
||||||
|
|
||||||
|
assert.strictEqual(result.seconds, 0);
|
||||||
|
assert.strictEqual(result.minutes, 0);
|
||||||
|
assert.strictEqual(result.hours, 0);
|
||||||
|
assert.strictEqual(result.days, 0);
|
||||||
|
assert.strictEqual(result.weeks, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,6 +22,8 @@ back: Back
|
|||||||
view: View
|
view: View
|
||||||
retry: Retry
|
retry: Retry
|
||||||
play: Play
|
play: Play
|
||||||
|
pause: Pause
|
||||||
|
resume: Resume
|
||||||
get-topic-help: Get help for this topic
|
get-topic-help: Get help for this topic
|
||||||
copy-to-clipboard: Copy to Clipboard
|
copy-to-clipboard: Copy to Clipboard
|
||||||
copy-error-detail-to-clipboard: Copy error detail to clipboard
|
copy-error-detail-to-clipboard: Copy error detail to clipboard
|
||||||
@@ -60,3 +62,5 @@ yes: Yes
|
|||||||
no: No
|
no: No
|
||||||
narrow-results: Narrow results
|
narrow-results: Narrow results
|
||||||
overflow-options: Overflow Options
|
overflow-options: Overflow Options
|
||||||
|
show-errors: Show Errors
|
||||||
|
hide-errors: Hide Errors
|
||||||
@@ -105,3 +105,31 @@ unsupported-controller:
|
|||||||
releases-page: Go to releases page
|
releases-page: Go to releases page
|
||||||
change-cluster-url: Change cluster URL
|
change-cluster-url: Change cluster URL
|
||||||
error: Unable to download supported version of desktop app. You’ll need to download version 1.7.1 from the releases page
|
error: Unable to download supported version of desktop app. You’ll need to download version 1.7.1 from the releases page
|
||||||
|
settings:
|
||||||
|
application: Application
|
||||||
|
cli: CLI
|
||||||
|
cache-daemon: Cache Daemon
|
||||||
|
client-agent:
|
||||||
|
title: Boundary Client Agent
|
||||||
|
description: Required to use Transparent Sessions
|
||||||
|
client: Client
|
||||||
|
version: Version
|
||||||
|
color-theme: Color Theme
|
||||||
|
provider: Provider
|
||||||
|
hcp: HashiCorp Cloud Platform
|
||||||
|
self-managed: Self-managed
|
||||||
|
server:
|
||||||
|
title: Server
|
||||||
|
description: Boundary connects you to targets using this server managed by your organization.
|
||||||
|
logs:
|
||||||
|
title: Logs
|
||||||
|
logging-level: Logging level
|
||||||
|
log-location: Log location
|
||||||
|
levels:
|
||||||
|
error: Error
|
||||||
|
warn: Warn
|
||||||
|
info: Info
|
||||||
|
debug: Debug
|
||||||
|
alerts:
|
||||||
|
cache-daemon: There may be a problem with the cache daemon
|
||||||
|
client-agent: There may be a problem with the client agent
|
||||||
@@ -212,12 +212,25 @@ host-catalog:
|
|||||||
help:
|
help:
|
||||||
static: Provide a list of hosts and host sets with static addresses
|
static: Provide a list of hosts and host sets with static addresses
|
||||||
plugin: Automate discovery of target hosts and services on Azure or AWS
|
plugin: Automate discovery of target hosts and services on Azure or AWS
|
||||||
|
role_arn: The ARN (Amazon Resource Name) used by this instance.
|
||||||
|
disable_credential_rotation: Credential rotation is automatically disabled when you use dynamic credentials.
|
||||||
types:
|
types:
|
||||||
static: Static
|
static: Static
|
||||||
unknown: Plugin
|
unknown: Plugin
|
||||||
aws: AWS
|
aws: AWS
|
||||||
azure: Azure
|
azure: Azure
|
||||||
plugin: Dynamic
|
plugin: Dynamic
|
||||||
|
credential:
|
||||||
|
label: Credential type
|
||||||
|
help: Select the credential type that you want to use.
|
||||||
|
static-credential:
|
||||||
|
edit: Static Credentials
|
||||||
|
label: Use an access key (Static Credentials)
|
||||||
|
help: Use an access key that you have generated in AWS to use with this AWS Host Catalog.
|
||||||
|
dynamic-credential:
|
||||||
|
edit: Dynamic Credentials
|
||||||
|
label: Use Assume Role (Dynamic Credentials)
|
||||||
|
help: Specify a role used to generate credentials to use with this AWS Host Catalog.
|
||||||
form:
|
form:
|
||||||
aws_region:
|
aws_region:
|
||||||
label: AWS Region
|
label: AWS Region
|
||||||
@@ -225,6 +238,9 @@ host-catalog:
|
|||||||
azure_client_id:
|
azure_client_id:
|
||||||
label: Client/Application ID
|
label: Client/Application ID
|
||||||
help: The client (application) ID of an Azure service principal that Boundary will use to authenticate and discover hosts from Azure.
|
help: The client (application) ID of an Azure service principal that Boundary will use to authenticate and discover hosts from Azure.
|
||||||
|
worker_filter:
|
||||||
|
label: Worker Filter
|
||||||
|
help: Insert a worker filter in order to route requests to the correct worker.
|
||||||
host-set:
|
host-set:
|
||||||
title: Host Set
|
title: Host Set
|
||||||
title_plural: Host Sets
|
title_plural: Host Sets
|
||||||
@@ -357,6 +373,12 @@ session-recording:
|
|||||||
duration: '{time} duration'
|
duration: '{time} duration'
|
||||||
questions:
|
questions:
|
||||||
delete: Are you sure you want to delete this recording?
|
delete: Are you sure you want to delete this recording?
|
||||||
|
filters:
|
||||||
|
time:
|
||||||
|
title: Time
|
||||||
|
last-twenty-four-hours: Last 24 hours
|
||||||
|
last-three-days: Last 3 days
|
||||||
|
last-seven-days: Last 7 days
|
||||||
connection:
|
connection:
|
||||||
title: Connection
|
title: Connection
|
||||||
title_index: Connection {index}
|
title_index: Connection {index}
|
||||||
@@ -384,9 +406,9 @@ session-recording:
|
|||||||
messages:
|
messages:
|
||||||
none:
|
none:
|
||||||
title: Channel Playback
|
title: Channel Playback
|
||||||
missing:
|
error:
|
||||||
title: We can't play back this channel because the file is missing
|
title: Playback error
|
||||||
description: This likely means the connected storage bucket is misconfigured or the file was deleted.
|
description: This may mean the connected storage bucket is misconfigured, the file was deleted, or integrity validation failed.
|
||||||
link: Retrieving lost session recordings
|
link: Retrieving lost session recordings
|
||||||
not-supported:
|
not-supported:
|
||||||
title: Playback is not supported for this channel
|
title: Playback is not supported for this channel
|
||||||
@@ -439,6 +461,7 @@ storage-bucket:
|
|||||||
plugin-types:
|
plugin-types:
|
||||||
aws: Amazon S3
|
aws: Amazon S3
|
||||||
minio: MinIO
|
minio: MinIO
|
||||||
|
unknown: Unknown
|
||||||
form:
|
form:
|
||||||
scope:
|
scope:
|
||||||
label: Scope
|
label: Scope
|
||||||
@@ -447,7 +470,6 @@ storage-bucket:
|
|||||||
help_org: This storage bucket will be available for use with targets within this org.
|
help_org: This storage bucket will be available for use with targets within this org.
|
||||||
provider:
|
provider:
|
||||||
label: Provider
|
label: Provider
|
||||||
help: Boundary currently only supports Amazon S3 storage.
|
|
||||||
endpoint_url:
|
endpoint_url:
|
||||||
label: Endpoint URL
|
label: Endpoint URL
|
||||||
help: Cannot be changed later.
|
help: Cannot be changed later.
|
||||||
@@ -459,7 +481,7 @@ storage-bucket:
|
|||||||
help: The base path where session recordings will be stored.
|
help: The base path where session recordings will be stored.
|
||||||
role_arn:
|
role_arn:
|
||||||
label: Role ARN
|
label: Role ARN
|
||||||
help: The ARN (Assume Role Name) used by this instance that the worker runs on.
|
help: The ARN (Amazon Resource Name) used by this instance that the worker runs on.
|
||||||
role_external_id:
|
role_external_id:
|
||||||
label: Role external ID
|
label: Role external ID
|
||||||
help: Used to provide access to this account if you have this configured.
|
help: Used to provide access to this account if you have this configured.
|
||||||
@@ -615,6 +637,10 @@ role:
|
|||||||
help: Customize which scopes are associated with this role.
|
help: Customize which scopes are associated with this role.
|
||||||
view-projects: '{selected}/{total} total projects'
|
view-projects: '{selected}/{total} total projects'
|
||||||
apply: Apply changes to org
|
apply: Apply changes to org
|
||||||
|
remove-org-and-projects: Remove org and projects
|
||||||
|
remove-org-only: Remove just the org
|
||||||
|
remove-orgs-and-projects: Remove orgs and projects
|
||||||
|
remove-orgs-only: Remove just the orgs
|
||||||
form:
|
form:
|
||||||
this:
|
this:
|
||||||
label: Add this scope
|
label: Add this scope
|
||||||
@@ -627,6 +653,12 @@ role:
|
|||||||
descendants:
|
descendants:
|
||||||
label: Add all descendants
|
label: Add all descendants
|
||||||
help: Add all of the orgs and the projects in the orgs that are underneath this scope.
|
help: Add all of the orgs and the projects in the orgs that are underneath this scope.
|
||||||
|
remove-org:
|
||||||
|
title: Remove this org?
|
||||||
|
description: You're about to remove {orgDisplayName}. Do you want to remove the projects under {orgDisplayName} as well? Or do you just want to remove {orgDisplayName} from this project?
|
||||||
|
remove-all-orgs:
|
||||||
|
title: Remove all selected orgs?
|
||||||
|
description: You're about to remove all the selected orgs. Do you want to remove the projects under these orgs as well? Or do you just want to remove just the orgs?
|
||||||
target:
|
target:
|
||||||
title: Target
|
title: Target
|
||||||
title_plural: Targets
|
title_plural: Targets
|
||||||
@@ -689,7 +721,7 @@ target:
|
|||||||
help: The local proxy port on which to listen by default when a session is started on a client.
|
help: The local proxy port on which to listen by default when a session is started on a client.
|
||||||
sidebar:
|
sidebar:
|
||||||
label: Session Recording
|
label: Session Recording
|
||||||
help: Record SSH sessions on this target and save them to an Amazon S3 bucket.
|
help: Record SSH sessions on this target and save them to a storage bucket.
|
||||||
link: Session Recording settings
|
link: Session Recording settings
|
||||||
questions:
|
questions:
|
||||||
delete-host-sources:
|
delete-host-sources:
|
||||||
@@ -873,7 +905,7 @@ credential-library:
|
|||||||
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
|
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
|
||||||
username:
|
username:
|
||||||
label: Username
|
label: Username
|
||||||
help: The username to use when making an SSH connection. This can be templated to use the requesting user's name. This will be included in the valid_principles when making the request to Vault.
|
help: The username to use when making an SSH connection. This can be templated to use the requesting user's name. This will be included in the valid_principals when making the request to Vault.
|
||||||
key_type:
|
key_type:
|
||||||
label: Key Type
|
label: Key Type
|
||||||
help: Specifies the desired key type to use when generating a private key.
|
help: Specifies the desired key type to use when generating a private key.
|
||||||
|
|||||||
@@ -14,7 +14,18 @@ active-public: Public
|
|||||||
recording: Recording
|
recording: Recording
|
||||||
completed: Completed
|
completed: Completed
|
||||||
failed: Failed
|
failed: Failed
|
||||||
|
installed: Installed
|
||||||
|
not-running: Not Running
|
||||||
|
running: Running
|
||||||
|
paused: Paused
|
||||||
|
authenticated: Authenticated
|
||||||
|
unauthenticated: Unauthenticated
|
||||||
loading:
|
loading:
|
||||||
title: Loading all items...
|
title: Loading all items...
|
||||||
description: All of your {resource} are still loading. Please wait while we finish loading everything on screen.
|
description: All of your {resource} are still loading. Please wait while we finish loading everything on screen.
|
||||||
generic-description: All of your resources are still loading. Please wait while we finish loading everything on screen.
|
generic-description: All of your resources are still loading. Please wait while we finish loading everything on screen.
|
||||||
|
incomplete-loading:
|
||||||
|
limit: Loaded the first {resultCount} results. Please narrow down your search or filters if the result you're looking for is not shown.
|
||||||
|
refreshing:
|
||||||
|
description: Updating cache...
|
||||||
|
tooltip: Some items may not appear until the cache is finished updating
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"@hashicorp/design-system-components": "^4.4.1",
|
"@hashicorp/design-system-components": "^4.4.1",
|
||||||
"codemirror": "5.65.7",
|
"codemirror": "5.65.7",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.6.3",
|
||||||
"ember-cli-babel": "^7.26.11",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-clipboard": "^1.0.0",
|
"ember-cli-clipboard": "^1.0.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.2.0",
|
||||||
"ember-cli-sass": "^11.0.1",
|
"ember-cli-sass": "^11.0.1",
|
||||||
@@ -46,12 +46,12 @@
|
|||||||
"sass": "^1.69.5"
|
"sass": "^1.69.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.5",
|
"@babel/core": "^7.25.2",
|
||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/eslint-parser": "^7.21.3",
|
||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/optional-features": "^2.0.0",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/string": "^3.0.1",
|
||||||
"@ember/test-helpers": "^2.9.3",
|
"@ember/test-helpers": "^3.3.1",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^4.12.2",
|
"ember-cli": "^4.12.2",
|
||||||
"ember-cli-dependency-checker": "^3.3.1",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
"ember-maybe-import-regenerator": "^1.0.0",
|
"ember-maybe-import-regenerator": "^1.0.0",
|
||||||
"ember-modifier": "^3.2.7",
|
"ember-modifier": "^3.2.7",
|
||||||
"ember-page-title": "^7.0.0",
|
"ember-page-title": "^7.0.0",
|
||||||
"ember-qunit": "^6.2.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^10.0.0",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~4.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"eslint-plugin-ember": "^11.11.1",
|
"eslint-plugin-ember": "^11.11.1",
|
||||||
"eslint-plugin-n": "^16.3.1",
|
"eslint-plugin-n": "^16.3.1",
|
||||||
"eslint-plugin-qunit": "^8.0.1",
|
"eslint-plugin-qunit": "^8.0.1",
|
||||||
"jest": "^27.4.7",
|
"jest": "^29.7.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"stylelint": "^15.10.1",
|
"stylelint": "^15.10.1",
|
||||||
"stylelint-config-prettier-scss": "^0.0.1",
|
"stylelint-config-prettier-scss": "^0.0.1",
|
||||||
"stylelint-config-standard-scss": "^11.0.0",
|
"stylelint-config-standard-scss": "^11.0.0",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ember-source": "^4.0.0"
|
"ember-source": "^4.0.0"
|
||||||
|
|||||||
+9
-6
@@ -38,21 +38,22 @@
|
|||||||
"test:ui-desktop": "yarn --cwd ui/desktop test",
|
"test:ui-desktop": "yarn --cwd ui/desktop test",
|
||||||
"compliance:licenses": "license-checker --onlyAllow 'Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;BUSL-1.1;Public Domain;Python-2.0;Unicode-TOU;Unlicense;WTFPL' --excludePackages 'boundary-ui;admin@0.0.0;desktop@0.0.0;base64-js@0.0.2;cycle@1.0.3;cldr-core@43.1.0;make-plural@7.2.0;@hashicorp/ember-asciinema-player@0.0.0;path-scurry@1.10.1'",
|
"compliance:licenses": "license-checker --onlyAllow 'Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;BUSL-1.1;Public Domain;Python-2.0;Unicode-TOU;Unlicense;WTFPL' --excludePackages 'boundary-ui;admin@0.0.0;desktop@0.0.0;base64-js@0.0.2;cycle@1.0.3;cldr-core@43.1.0;make-plural@7.2.0;@hashicorp/ember-asciinema-player@0.0.0;path-scurry@1.10.1'",
|
||||||
"doc:toc": "doctoc README.md",
|
"doc:toc": "doctoc README.md",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cz-conventional-changelog": "^3.1.0",
|
"cz-conventional-changelog": "^3.1.0",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"git-cz": "^4.8.0",
|
"git-cz": "^4.9.0",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lint-staged": "^13.0.2"
|
"lint-staged": "^13.0.2",
|
||||||
|
"patch-package": "^8.0.0",
|
||||||
|
"postinstall-postinstall": "^2.1.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"request": "^2.88.0",
|
|
||||||
"loader-utils": "^2.0.4",
|
"loader-utils": "^2.0.4",
|
||||||
"**/ember-electron/**/minimatch": "^3.0.5",
|
"**/ember-electron/**/minimatch": "^3.0.5",
|
||||||
"**/babel-core/json5": "^2.2.2",
|
|
||||||
"**/react-syntax-highlighter/refractor/prismjs": "^1.27.0",
|
"**/react-syntax-highlighter/refractor/prismjs": "^1.27.0",
|
||||||
"**/nomnom/underscore": "^1.12.1",
|
"**/nomnom/underscore": "^1.12.1",
|
||||||
"**/remark-parse/trim": "^1.0.1",
|
"**/remark-parse/trim": "^1.0.1",
|
||||||
@@ -72,7 +73,9 @@
|
|||||||
"**/ember-cli/**/ansi-html": "^0.0.9",
|
"**/ember-cli/**/ansi-html": "^0.0.9",
|
||||||
"**/ember-cli/testem/fireworm/async": "^2.6.4",
|
"**/ember-cli/testem/fireworm/async": "^2.6.4",
|
||||||
"**/desktop/@doyensec/electronegativity/winston/async": "^2.6.4",
|
"**/desktop/@doyensec/electronegativity/winston/async": "^2.6.4",
|
||||||
"**/@hashicorp/design-system-components/ember-stargate": "^0.6.0"
|
"**/@hashicorp/design-system-components/ember-stargate": "^0.6.0",
|
||||||
|
"micromatch": "^4.0.8",
|
||||||
|
"send": "^0.19.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
diff --git a/node_modules/lodash.template/index.js b/node_modules/lodash.template/index.js
|
||||||
|
index f051141..0c45a8d 100644
|
||||||
|
--- a/node_modules/lodash.template/index.js
|
||||||
|
+++ b/node_modules/lodash.template/index.js
|
||||||
|
@@ -17,6 +17,9 @@ var HOT_COUNT = 800,
|
||||||
|
var INFINITY = 1 / 0,
|
||||||
|
MAX_SAFE_INTEGER = 9007199254740991;
|
||||||
|
|
||||||
|
+/** Error message constants. */
|
||||||
|
+var INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
|
||||||
|
+
|
||||||
|
/** `Object#toString` result references. */
|
||||||
|
var argsTag = '[object Arguments]',
|
||||||
|
arrayTag = '[object Array]',
|
||||||
|
@@ -1343,6 +1346,18 @@ function keysIn(object) {
|
||||||
|
return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * Used to validate the `validate` option in `_.template` variable.
|
||||||
|
+ *
|
||||||
|
+ * Forbids characters which could potentially change the meaning of the function argument definition:
|
||||||
|
+ * - "()," (modification of function parameters)
|
||||||
|
+ * - "=" (default value)
|
||||||
|
+ * - "[]{}" (destructuring of function parameters)
|
||||||
|
+ * - "/" (beginning of a comment)
|
||||||
|
+ * - whitespace
|
||||||
|
+ */
|
||||||
|
+var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Creates a compiled template function that can interpolate data properties
|
||||||
|
* in "interpolate" delimiters, HTML-escape interpolated data properties in
|
||||||
|
@@ -1522,6 +1537,11 @@ function template(string, options, guard) {
|
||||||
|
if (!variable) {
|
||||||
|
source = 'with (obj) {\n' + source + '\n}\n';
|
||||||
|
}
|
||||||
|
+ // Throw an error if a forbidden character was found in `variable`, to prevent
|
||||||
|
+ // potential command injection attacks.
|
||||||
|
+ else if (reForbiddenIdentifierChars.test(variable)) {
|
||||||
|
+ throw new Error(INVALID_TEMPL_VAR_ERROR_TEXT);
|
||||||
|
+ }
|
||||||
|
// Cleanup code by stripping empty strings.
|
||||||
|
source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
|
||||||
|
.replace(reEmptyStringMiddle, '$1')
|
||||||
@@ -64,5 +64,19 @@ module.exports = {
|
|||||||
'qunit/require-expect': [2, 'except-simple'],
|
'qunit/require-expect': [2, 'except-simple'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// e2e files
|
||||||
|
files: ['tests/e2e/**'],
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', 'prettier'],
|
||||||
|
rules: {
|
||||||
|
'no-empty-pattern': [
|
||||||
|
'error',
|
||||||
|
{ allowObjectPatternsAsParameters: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
# Admin UI
|
# Admin UI
|
||||||
|
|
||||||
The admin UI for Boundary.
|
The Admin UI for Boundary.
|
||||||
|
|
||||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
@@ -39,8 +39,8 @@ List of available project commands. `yarn run <command-name>`
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------- | ----------- |
|
| ------- | ----------- |
|
||||||
| build:development | Builds the UI in development mode. |
|
| build:development | Builds the Admin UI in development mode. |
|
||||||
| build | Builds the UI for production. |
|
| build | Builds the Admin UI for production. |
|
||||||
| lint | Runs all lint commands. |
|
| lint | Runs all lint commands. |
|
||||||
| lint:hbs | Lints `hbs` template files. |
|
| lint:hbs | Lints `hbs` template files. |
|
||||||
| lint:js | Lints `js` files. |
|
| lint:js | Lints `js` files. |
|
||||||
|
|||||||
+7
-12
@@ -4,17 +4,12 @@
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if (can 'delete account' @model)}}
|
{{#if (can 'delete account' @model)}}
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-manage-account-auth-methods as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
<dd.Interactive
|
||||||
as |dropdown|
|
@text={{t 'resources.account.actions.delete'}}
|
||||||
>
|
@color='critical'
|
||||||
<dropdown.button
|
|
||||||
@style='danger'
|
|
||||||
@disabled={{@model.canSave}}
|
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.account.actions.delete'}}
|
</Hds::Dropdown>
|
||||||
</dropdown.button>
|
|
||||||
</Rose::Dropdown>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -40,55 +40,42 @@
|
|||||||
</Rose::Dropdown>
|
</Rose::Dropdown>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-manage-auth-methods-dropdown as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
|
||||||
as |dropdown|
|
|
||||||
>
|
|
||||||
{{#if @model.isPrimary}}
|
{{#if @model.isPrimary}}
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@disabled={{@model.canSave}}
|
@text={{t 'resources.auth-method.actions.remove-as-primary'}}
|
||||||
{{on 'click' (fn @removeAsPrimary @model)}}
|
{{on 'click' (fn @removeAsPrimary @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.auth-method.actions.remove-as-primary'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@disabled={{@model.canSave}}
|
@text={{t 'resources.auth-method.actions.make-primary'}}
|
||||||
{{on 'click' (fn @makePrimary @model)}}
|
{{on 'click' (fn @makePrimary @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.auth-method.actions.make-primary'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'create model' @model collection='accounts')}}
|
{{#if (can 'create model' @model collection='accounts')}}
|
||||||
|
<dd.Interactive
|
||||||
<dropdown.link
|
@text={{t 'resources.account.actions.create'}}
|
||||||
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
||||||
@model={{@model}}
|
@model={{@model}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.account.actions.create'}}
|
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (can 'create model' @model collection='managed-groups')}}
|
{{#if (can 'create model' @model collection='managed-groups')}}
|
||||||
{{#if (or @model.isOIDC @model.isLDAP)}}
|
{{#if (or @model.isOIDC @model.isLDAP)}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
|
@text={{t 'resources.managed-group.actions.create'}}
|
||||||
@route='scopes.scope.auth-methods.auth-method.managed-groups.new'
|
@route='scopes.scope.auth-methods.auth-method.managed-groups.new'
|
||||||
@model={{@model}}
|
@model={{@model}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.managed-group.actions.create'}}
|
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (can 'delete auth-method' @model)}}
|
{{#if (can 'delete auth-method' @model)}}
|
||||||
<dropdown.separator />
|
<dd.Separator />
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@style='danger'
|
@text={{t 'resources.auth-method.actions.delete'}}
|
||||||
@disabled={{@model.canSave}}
|
@color='critical'
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.auth-method.actions.delete'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Dropdown>
|
</Hds::Dropdown>
|
||||||
+7
-12
@@ -4,17 +4,12 @@
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-managed-group-dropdown as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
<dd.Interactive
|
||||||
as |dropdown|
|
@text={{t 'resources.managed-group.actions.delete'}}
|
||||||
>
|
@color='critical'
|
||||||
<dropdown.button
|
|
||||||
@style='danger'
|
|
||||||
@disabled={{@model.canSave}}
|
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.managed-group.actions.delete'}}
|
</Hds::Dropdown>
|
||||||
</dropdown.button>
|
|
||||||
</Rose::Dropdown>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -2,37 +2,26 @@
|
|||||||
Copyright (c) HashiCorp, Inc.
|
Copyright (c) HashiCorp, Inc.
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
<Hds::Dropdown data-test-manage-credential-stores-dropdown as |dd|>
|
||||||
<Rose::Dropdown
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@text={{t 'actions.manage'}}
|
|
||||||
@dropdownRight={{true}}
|
|
||||||
as |dropdown|
|
|
||||||
>
|
|
||||||
{{#if (can 'create model' @model collection='credential-libraries')}}
|
{{#if (can 'create model' @model collection='credential-libraries')}}
|
||||||
|
<dd.Interactive
|
||||||
<dropdown.link
|
|
||||||
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
|
@route='scopes.scope.credential-stores.credential-store.credential-libraries.new'
|
||||||
@model={{@model}}
|
@text={{t 'resources.credential-library.actions.create'}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.credential-library.actions.create'}}
|
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'create model' @model collection='credentials')}}
|
{{#if (can 'create model' @model collection='credentials')}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
@route='scopes.scope.credential-stores.credential-store.credentials.new'
|
@route='scopes.scope.credential-stores.credential-store.credentials.new'
|
||||||
@model={{@model}}
|
@text={{t 'resources.credential.actions.create'}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.credential.actions.create'}}
|
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<dropdown.separator />
|
<dd.Separator />
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@style='danger'
|
@text={{t 'resources.credential-store.actions.delete'}}
|
||||||
@disabled={{@model.canSave}}
|
@color='critical'
|
||||||
{{on 'click' @delete}}
|
{{on 'click' @delete}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.credential-store.actions.delete'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Dropdown>
|
</Hds::Dropdown>
|
||||||
@@ -19,12 +19,18 @@
|
|||||||
{{on 'input' (set-from-event @model 'name')}}
|
{{on 'input' (set-from-event @model 'name')}}
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
<F.Label>
|
||||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
{{t 'form.name.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'form.name.help'}}
|
||||||
|
</F.HelperText>
|
||||||
{{#if @model.errors.name}}
|
{{#if @model.errors.name}}
|
||||||
<F.Error as |E|>
|
<F.Error as |E|>
|
||||||
{{#each @model.errors.name as |error|}}
|
{{#each @model.errors.name as |error|}}
|
||||||
<E.Message>{{error.message}}</E.Message>
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</F.Error>
|
</F.Error>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -38,21 +44,28 @@
|
|||||||
disabled={{form.disabled}}
|
disabled={{form.disabled}}
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.Label>{{t 'form.description.label'}}</F.Label>
|
<F.Label>
|
||||||
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
|
{{t 'form.description.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'form.description.help'}}
|
||||||
|
</F.HelperText>
|
||||||
{{#if @model.errors.description}}
|
{{#if @model.errors.description}}
|
||||||
<F.Error as |E|>
|
<F.Error as |E|>
|
||||||
{{#each @model.errors.description as |error|}}
|
{{#each @model.errors.description as |error|}}
|
||||||
<E.Message>{{error.message}}</E.Message>
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</F.Error>
|
</F.Error>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Hds::Form::Textarea::Field>
|
</Hds::Form::Textarea::Field>
|
||||||
|
|
||||||
{{#if @model.isNew}}
|
{{#if @model.isNew}}
|
||||||
|
|
||||||
<Hds::Form::RadioCard::Group @name='type' as |G|>
|
<Hds::Form::RadioCard::Group @name='type' as |G|>
|
||||||
<G.Legend>{{t 'form.type.label'}}</G.Legend>
|
<G.Legend>
|
||||||
|
{{t 'form.type.label'}}
|
||||||
|
</G.Legend>
|
||||||
{{#each this.hostCatalogTypes as |hostCatalogType|}}
|
{{#each this.hostCatalogTypes as |hostCatalogType|}}
|
||||||
<G.RadioCard
|
<G.RadioCard
|
||||||
@value={{hostCatalogType}}
|
@value={{hostCatalogType}}
|
||||||
@@ -61,19 +74,25 @@
|
|||||||
{{on 'input' (fn @changeType hostCatalogType)}}
|
{{on 'input' (fn @changeType hostCatalogType)}}
|
||||||
as |R|
|
as |R|
|
||||||
>
|
>
|
||||||
<R.Label>{{t
|
<R.Label>
|
||||||
(concat 'resources.host-catalog.types.' hostCatalogType)
|
{{t (concat 'resources.host-catalog.types.' hostCatalogType)}}
|
||||||
}}</R.Label>
|
</R.Label>
|
||||||
<R.Description>{{t
|
<R.Description>
|
||||||
(concat 'resources.host-catalog.help.' hostCatalogType)
|
{{t (concat 'resources.host-catalog.help.' hostCatalogType)}}
|
||||||
}}</R.Description>
|
</R.Description>
|
||||||
</G.RadioCard>
|
</G.RadioCard>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</Hds::Form::RadioCard::Group>
|
</Hds::Form::RadioCard::Group>
|
||||||
<Hds::Form::RadioCard::Group @name='plugin_type' @alignment='center' as |G|>
|
<Hds::Form::RadioCard::Group @name='plugin_type' @alignment='center' as |G|>
|
||||||
<G.Legend>{{t 'titles.provider'}}</G.Legend>
|
<G.Legend>
|
||||||
<G.Legend>{{t 'titles.choose-a-provider'}}</G.Legend>
|
{{t 'titles.provider'}}
|
||||||
<G.HelperText>{{t 'descriptions.choose-a-provider'}}</G.HelperText>
|
</G.Legend>
|
||||||
|
<G.Legend>
|
||||||
|
{{t 'titles.choose-a-provider'}}
|
||||||
|
</G.Legend>
|
||||||
|
<G.HelperText>
|
||||||
|
{{t 'descriptions.choose-a-provider'}}
|
||||||
|
</G.HelperText>
|
||||||
|
|
||||||
{{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}}
|
{{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}}
|
||||||
<G.RadioCard
|
<G.RadioCard
|
||||||
@@ -83,11 +102,10 @@
|
|||||||
{{on 'input' (fn @changeType pluginType)}}
|
{{on 'input' (fn @changeType pluginType)}}
|
||||||
as |R|
|
as |R|
|
||||||
>
|
>
|
||||||
<R.Label>{{t
|
<R.Label>
|
||||||
(concat 'resources.host-catalog.types.' pluginType)
|
{{t (concat 'resources.host-catalog.types.' pluginType)}}
|
||||||
}}</R.Label>
|
</R.Label>
|
||||||
<R.Icon @name={{icon}} />
|
<R.Icon @name={{icon}} />
|
||||||
|
|
||||||
</G.RadioCard>
|
</G.RadioCard>
|
||||||
{{/each-in}}
|
{{/each-in}}
|
||||||
</Hds::Form::RadioCard::Group>
|
</Hds::Form::RadioCard::Group>
|
||||||
@@ -98,8 +116,11 @@
|
|||||||
readonly={{true}}
|
readonly={{true}}
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.Label>{{t 'titles.provider'}}</F.Label>
|
<F.Label>
|
||||||
<F.HelperText>{{t 'descriptions.provider'}}
|
{{t 'titles.provider'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'descriptions.provider'}}
|
||||||
</F.HelperText>
|
</F.HelperText>
|
||||||
</InfoField>
|
</InfoField>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -112,7 +133,9 @@
|
|||||||
{{on 'input' (set-from-event @model 'region')}}
|
{{on 'input' (set-from-event @model 'region')}}
|
||||||
as |F|
|
as |F|
|
||||||
>
|
>
|
||||||
<F.Label>{{t 'resources.host-catalog.form.aws_region.label'}}</F.Label>
|
<F.Label>
|
||||||
|
{{t 'resources.host-catalog.form.aws_region.label'}}
|
||||||
|
</F.Label>
|
||||||
<F.HelperText>
|
<F.HelperText>
|
||||||
{{t 'resources.host-catalog.form.aws_region.help'}}
|
{{t 'resources.host-catalog.form.aws_region.help'}}
|
||||||
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws.region'}}>
|
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws.region'}}>
|
||||||
@@ -121,50 +144,289 @@
|
|||||||
</F.HelperText>
|
</F.HelperText>
|
||||||
</Hds::Form::TextInput::Field>
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
<Hds::Form::TextInput::Field
|
{{#if @model.isNew}}
|
||||||
name='access_key_id'
|
<Hds::Form::Radio::Group
|
||||||
@value={{@model.access_key_id}}
|
class='dynamic-credential-selection'
|
||||||
@isRequired={{true}}
|
@layout='vertical'
|
||||||
disabled={{form.disabled}}
|
@name='credential_type'
|
||||||
{{on 'input' (set-from-event @model 'access_key_id')}}
|
as |G|
|
||||||
as |F|
|
>
|
||||||
>
|
<G.Legend>
|
||||||
<F.Label>{{t 'form.access_key_id.label'}}</F.Label>
|
{{t 'resources.host-catalog.types.credential.label'}}
|
||||||
<F.HelperText>
|
</G.Legend>
|
||||||
{{t 'form.access_key_id.help'}}
|
<G.HelperText>
|
||||||
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws'}}>
|
{{t 'resources.host-catalog.types.credential.help'}}
|
||||||
{{t 'actions.learn-more'}}
|
</G.HelperText>
|
||||||
</Hds::Link::Inline>
|
|
||||||
</F.HelperText>
|
|
||||||
</Hds::Form::TextInput::Field>
|
|
||||||
|
|
||||||
<Hds::Form::TextInput::Field
|
{{#each this.credentials as |credentialType|}}
|
||||||
name='secret_access_key'
|
<G.RadioField
|
||||||
@value={{@model.secret_access_key}}
|
name={{credentialType}}
|
||||||
@isRequired={{true}}
|
@value={{credentialType}}
|
||||||
disabled={{form.disabled}}
|
checked={{eq credentialType this.selectedCredentialType}}
|
||||||
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
disabled={{form.disabled}}
|
||||||
as |F|
|
{{on 'input' (fn this.updateCredentialTypeSelection credentialType)}}
|
||||||
>
|
as |F|
|
||||||
<F.Label>{{t 'form.secret_access_key.label'}}</F.Label>
|
>
|
||||||
<F.HelperText>
|
<F.Label>
|
||||||
{{t 'form.secret_access_key.help'}}
|
{{t
|
||||||
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws'}}>
|
(concat
|
||||||
{{t 'actions.learn-more'}}
|
'resources.host-catalog.types.credential.'
|
||||||
</Hds::Link::Inline>
|
credentialType
|
||||||
</F.HelperText>
|
'.label'
|
||||||
</Hds::Form::TextInput::Field>
|
)
|
||||||
|
}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t
|
||||||
|
(concat
|
||||||
|
'resources.host-catalog.types.credential.'
|
||||||
|
credentialType
|
||||||
|
'.help'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</F.HelperText>
|
||||||
|
</G.RadioField>
|
||||||
|
{{/each}}
|
||||||
|
</Hds::Form::Radio::Group>
|
||||||
|
{{else}}
|
||||||
|
<Hds::Form::Fieldset @layout='horizontal' as |F|>
|
||||||
|
<F.Legend>
|
||||||
|
{{t 'resources.host-catalog.types.credential.label'}}
|
||||||
|
</F.Legend>
|
||||||
|
<F.Control>
|
||||||
|
<Hds::Badge
|
||||||
|
@text={{t
|
||||||
|
(concat
|
||||||
|
'resources.host-catalog.types.credential.'
|
||||||
|
this.selectedCredentialType
|
||||||
|
'.edit'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</F.Control>
|
||||||
|
</Hds::Form::Fieldset>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<Hds::Form::Checkbox::Field
|
{{#if this.showDynamicCredentials}}
|
||||||
name='disable_credential_rotation'
|
{{! role_arn }}
|
||||||
checked={{@model.disable_credential_rotation}}
|
<Hds::Form::TextInput::Field
|
||||||
disabled={{form.disabled}}
|
name='role_arn'
|
||||||
{{on 'change' (fn this.toggleDisableCredentialRotation @model)}}
|
@isRequired={{true}}
|
||||||
as |F|
|
@value={{@model.role_arn}}
|
||||||
>
|
@isInvalid={{@model.errors.role_arn}}
|
||||||
<F.Label>{{t 'form.disable_credential_rotation.label'}}</F.Label>
|
disabled={{form.disabled}}
|
||||||
</Hds::Form::Checkbox::Field>
|
{{on 'input' (set-from-event @model 'role_arn')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'resources.storage-bucket.form.role_arn.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.host-catalog.help.role_arn'}}
|
||||||
|
</F.HelperText>
|
||||||
|
{{#if @model.errors.role_arn}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.role_arn as |error|}}
|
||||||
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
|
{{! role_external_id }}
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
name='role_external_id'
|
||||||
|
@isOptional={{true}}
|
||||||
|
@value={{@model.role_external_id}}
|
||||||
|
@isInvalid={{@model.errors.role_external_id}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'input' (set-from-event @model 'role_external_id')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'resources.storage-bucket.form.role_external_id.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.storage-bucket.form.role_external_id.help'}}
|
||||||
|
</F.HelperText>
|
||||||
|
{{#if @model.errors.role_external_id}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.role_external_id as |error|}}
|
||||||
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
|
{{! role_session_name }}
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
name='role_session_name'
|
||||||
|
@isOptional={{true}}
|
||||||
|
@value={{@model.role_session_name}}
|
||||||
|
@isInvalid={{@model.errors.role_session_name}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'input' (set-from-event @model 'role_session_name')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'resources.storage-bucket.form.role_session_name.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.storage-bucket.form.role_session_name.help'}}
|
||||||
|
</F.HelperText>
|
||||||
|
{{#if @model.errors.role_session_name}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.role_session_name as |error|}}
|
||||||
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
|
{{! role_tags }}
|
||||||
|
<Form::Field::ListWrapper
|
||||||
|
@layout='horizontal'
|
||||||
|
@isOptional={{true}}
|
||||||
|
@disabled={{form.disabled}}
|
||||||
|
>
|
||||||
|
<:fieldset as |F|>
|
||||||
|
<F.Legend>
|
||||||
|
{{t 'resources.storage-bucket.form.role_tags.label'}}
|
||||||
|
</F.Legend>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.storage-bucket.form.role_tags.help'}}
|
||||||
|
</F.HelperText>
|
||||||
|
|
||||||
|
{{#if @model.errors.role_tags}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.role_tags as |error|}}
|
||||||
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</:fieldset>
|
||||||
|
<:field as |F|>
|
||||||
|
<F.KeyValue
|
||||||
|
@name='role_tags'
|
||||||
|
@options={{@model.role_tags}}
|
||||||
|
@model={{@model}}
|
||||||
|
>
|
||||||
|
<:key as |K|>
|
||||||
|
<K.text />
|
||||||
|
</:key>
|
||||||
|
<:value as |V|>
|
||||||
|
<V.text />
|
||||||
|
</:value>
|
||||||
|
</F.KeyValue>
|
||||||
|
</:field>
|
||||||
|
</Form::Field::ListWrapper>
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
name='access_key_id'
|
||||||
|
@value={{@model.access_key_id}}
|
||||||
|
@isRequired={{true}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'input' (set-from-event @model 'access_key_id')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'form.access_key_id.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'form.access_key_id.help'}}
|
||||||
|
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws'}}>
|
||||||
|
{{t 'actions.learn-more'}}
|
||||||
|
</Hds::Link::Inline>
|
||||||
|
</F.HelperText>
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
|
||||||
|
<Hds::Form::TextInput::Field
|
||||||
|
name='secret_access_key'
|
||||||
|
@value={{@model.secret_access_key}}
|
||||||
|
@isRequired={{true}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'form.secret_access_key.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'form.secret_access_key.help'}}
|
||||||
|
<Hds::Link::Inline @href={{doc-url 'host-catalog.aws'}}>
|
||||||
|
{{t 'actions.learn-more'}}
|
||||||
|
</Hds::Link::Inline>
|
||||||
|
</F.HelperText>
|
||||||
|
</Hds::Form::TextInput::Field>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (feature-flag 'dynamic-credentials-worker-filter')}}
|
||||||
|
<Hds::Form::Textarea::Field
|
||||||
|
data-test-dynamic-credential-worker-filter
|
||||||
|
name='worker_filter'
|
||||||
|
@value={{@model.worker_filter}}
|
||||||
|
@isOptional={{true}}
|
||||||
|
@isInvalid={{@model.errors.worker_filter}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'resources.host-catalog.form.worker_filter.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.host-catalog.form.worker_filter.help'}}
|
||||||
|
<Hds::Link::Inline @href={{doc-url 'worker.create-tags'}}>
|
||||||
|
{{t 'actions.learn-more'}}
|
||||||
|
</Hds::Link::Inline>
|
||||||
|
</F.HelperText>
|
||||||
|
{{#if @model.errors.worker_filter}}
|
||||||
|
<F.Error as |E|>
|
||||||
|
{{#each @model.errors.worker_filter as |error|}}
|
||||||
|
<E.Message>
|
||||||
|
{{error.message}}
|
||||||
|
</E.Message>
|
||||||
|
{{/each}}
|
||||||
|
</F.Error>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Form::Textarea::Field>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.showDynamicCredentials}}
|
||||||
|
<Hds::Form::Checkbox::Field
|
||||||
|
name='disable_credential_rotation'
|
||||||
|
checked={{true}}
|
||||||
|
disabled={{true}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'form.disable_credential_rotation.label'}}
|
||||||
|
</F.Label>
|
||||||
|
<F.HelperText>
|
||||||
|
{{t 'resources.host-catalog.help.disable_credential_rotation'}}
|
||||||
|
</F.HelperText>
|
||||||
|
</Hds::Form::Checkbox::Field>
|
||||||
|
{{else}}
|
||||||
|
<Hds::Form::Checkbox::Field
|
||||||
|
name='disable_credential_rotation'
|
||||||
|
checked={{@model.disable_credential_rotation}}
|
||||||
|
disabled={{form.disabled}}
|
||||||
|
{{on 'change' (fn this.toggleDisableCredentialRotation @model)}}
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Label>
|
||||||
|
{{t 'form.disable_credential_rotation.label'}}
|
||||||
|
</F.Label>
|
||||||
|
</Hds::Form::Checkbox::Field>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
|
||||||
{{#if (can 'save model' @model)}}
|
{{#if (can 'save model' @model)}}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { action } from '@ember/object';
|
|||||||
import {
|
import {
|
||||||
TYPES_HOST_CATALOG,
|
TYPES_HOST_CATALOG,
|
||||||
TYPES_HOST_CATALOG_PLUGIN,
|
TYPES_HOST_CATALOG_PLUGIN,
|
||||||
|
TYPES_CREDENTIALS,
|
||||||
|
TYPE_CREDENTIAL_DYNAMIC,
|
||||||
} from 'api/models/host-catalog';
|
} from 'api/models/host-catalog';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
//Note: this is a temporary solution till we have resource type helper in place
|
//Note: this is a temporary solution till we have resource type helper in place
|
||||||
const icons = ['aws-color', 'azure-color'];
|
const icons = ['aws-color', 'azure-color'];
|
||||||
@@ -16,6 +19,21 @@ const icons = ['aws-color', 'azure-color'];
|
|||||||
export default class FormHostCatalogAwsComponent extends Component {
|
export default class FormHostCatalogAwsComponent extends Component {
|
||||||
// =properties
|
// =properties
|
||||||
hostCatalogTypes = TYPES_HOST_CATALOG;
|
hostCatalogTypes = TYPES_HOST_CATALOG;
|
||||||
|
|
||||||
|
// =attributes
|
||||||
|
@tracked selectedCredentialType = this.args.model.credentialType;
|
||||||
|
|
||||||
|
@tracked showDynamicCredentials =
|
||||||
|
this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns an array of available credential types
|
||||||
|
* @type {array}
|
||||||
|
*/
|
||||||
|
get credentials() {
|
||||||
|
return TYPES_CREDENTIALS;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* maps resource type with icon
|
* maps resource type with icon
|
||||||
* @type {object}
|
* @type {object}
|
||||||
@@ -32,4 +50,19 @@ export default class FormHostCatalogAwsComponent extends Component {
|
|||||||
toggleDisableCredentialRotation(model) {
|
toggleDisableCredentialRotation(model) {
|
||||||
model.disable_credential_rotation = !model.disable_credential_rotation;
|
model.disable_credential_rotation = !model.disable_credential_rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to update the credential type
|
||||||
|
* @param type {string}
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
updateCredentialTypeSelection(type) {
|
||||||
|
this.selectedCredentialType = type;
|
||||||
|
if (type === TYPE_CREDENTIAL_DYNAMIC) {
|
||||||
|
this.showDynamicCredentials = true;
|
||||||
|
} else {
|
||||||
|
this.showDynamicCredentials = false;
|
||||||
|
}
|
||||||
|
this.args.changeCredentialType(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,4 +9,5 @@
|
|||||||
submit=@submit
|
submit=@submit
|
||||||
cancel=@cancel
|
cancel=@cancel
|
||||||
changeType=@changeType
|
changeType=@changeType
|
||||||
|
changeCredentialType=@changeCredentialType
|
||||||
}}
|
}}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
class='full-width'
|
class='full-width'
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@onSubmit={{this.submit}}
|
@onSubmit={{this.submit}}
|
||||||
|
@disabled={{this.isSaving}}
|
||||||
as |form|
|
as |form|
|
||||||
>
|
>
|
||||||
<div class='create-resources-form'>
|
<div class='create-resources-form'>
|
||||||
|
|||||||
@@ -9,12 +9,26 @@ import { tracked } from '@glimmer/tracking';
|
|||||||
|
|
||||||
export default class FormOnboardingComponent extends Component {
|
export default class FormOnboardingComponent extends Component {
|
||||||
// =properties
|
// =properties
|
||||||
|
|
||||||
@tracked targetAddress;
|
@tracked targetAddress;
|
||||||
@tracked targetAddressInvalid = false;
|
@tracked targetAddressInvalid = false;
|
||||||
@tracked targetPort;
|
@tracked targetPort;
|
||||||
@tracked targetPortInvalid = false;
|
@tracked targetPortInvalid = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if any of the resources that are being created are still saving.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isSaving() {
|
||||||
|
const { org, project, target, role } = this.args.model;
|
||||||
|
return org.isSaving || project.isSaving || target.isSaving || role.isSaving;
|
||||||
|
}
|
||||||
|
|
||||||
// =actions
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes in target address and port to submit function for further proccessing.
|
||||||
|
*/
|
||||||
@action
|
@action
|
||||||
submit() {
|
submit() {
|
||||||
// Check targetAddress and targetPort are valid before submit
|
// Check targetAddress and targetPort are valid before submit
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{{#if @model.totalItemsCount}}
|
{{#if @model.totalItemsCount}}
|
||||||
<Rose::Form
|
<Rose::Form
|
||||||
class='full-width role-manage-custom-scopes-form'
|
class='full-width role-manage-custom-scopes-form'
|
||||||
@onSubmit={{fn @submit this.allGrantScopes}}
|
@onSubmit={{@submit}}
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@disabled={{@model.role.isSaving}}
|
@disabled={{@model.role.isSaving}}
|
||||||
as |form|
|
as |form|
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
/>
|
/>
|
||||||
</Hds::SegmentedGroup>
|
</Hds::SegmentedGroup>
|
||||||
{{#if @model.orgScopes}}
|
{{#if @model.orgScopes}}
|
||||||
{{#if this.selectedItems.length}}
|
<div class='table-selected-items-text'>
|
||||||
<div class='table-selected-items-text'>
|
{{#if @model.role.grantScopeOrgIDs.length}}
|
||||||
<Hds::Text::Body @tag='p' @color='faint'>
|
<Hds::Text::Body @tag='p' @color='faint'>
|
||||||
{{t
|
{{t
|
||||||
'resources.role.scope.titles.selected'
|
'resources.role.scope.titles.selected'
|
||||||
items=this.selectedItems.length
|
items=@model.role.grantScopeOrgIDs.length
|
||||||
total=@model.totalItemsCount
|
total=@model.totalItemsCount
|
||||||
}}
|
}}
|
||||||
</Hds::Text::Body>
|
</Hds::Text::Body>
|
||||||
</div>
|
{{/if}}
|
||||||
{{/if}}
|
</div>
|
||||||
<Hds::Table
|
<Hds::Table
|
||||||
@columns={{array
|
@columns={{array
|
||||||
(hash label=(t 'resources.role.scope.title'))
|
(hash label=(t 'resources.role.scope.title'))
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<:body as |B|>
|
<:body as |B|>
|
||||||
<B.Tr
|
<B.Tr
|
||||||
@selectionKey={{B.data.id}}
|
@selectionKey={{B.data.id}}
|
||||||
@isSelected={{includes B.data.id this.selectedItems}}
|
@isSelected={{includes B.data.id @model.role.grantScopeOrgIDs}}
|
||||||
@selectionAriaLabelSuffix='row {{B.data.id}}'
|
@selectionAriaLabelSuffix='row {{B.data.id}}'
|
||||||
data-test-org-scopes-table-row={{B.data.id}}
|
data-test-org-scopes-table-row={{B.data.id}}
|
||||||
>
|
>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
@iconPosition='trailing'
|
@iconPosition='trailing'
|
||||||
@text={{t
|
@text={{t
|
||||||
'resources.role.scope.actions.view-projects'
|
'resources.role.scope.actions.view-projects'
|
||||||
selected=projectTotals.selected
|
selected=projectTotals.selected.length
|
||||||
total=projectTotals.total
|
total=projectTotals.total
|
||||||
}}
|
}}
|
||||||
@route='scopes.scope.roles.role.manage-scopes.manage-org-projects'
|
@route='scopes.scope.roles.role.manage-scopes.manage-org-projects'
|
||||||
@@ -127,4 +127,63 @@
|
|||||||
/>
|
/>
|
||||||
</A.Footer>
|
</A.Footer>
|
||||||
</Hds::ApplicationState>
|
</Hds::ApplicationState>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.selectedOrg}}
|
||||||
|
<Hds::Modal
|
||||||
|
@onClose={{this.toggleRemoveOrgModal}}
|
||||||
|
data-test-manage-scopes-remove-org-modal
|
||||||
|
as |M|
|
||||||
|
>
|
||||||
|
<M.Header>{{t 'resources.role.scope.remove-org.title'}}</M.Header>
|
||||||
|
<M.Body>
|
||||||
|
<Hds::Text::Body @tag='p' @color='primary'>
|
||||||
|
{{t
|
||||||
|
'resources.role.scope.remove-org.description'
|
||||||
|
orgDisplayName=this.orgDisplayName
|
||||||
|
}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
</M.Body>
|
||||||
|
<M.Footer as |F|>
|
||||||
|
<Hds::ButtonSet>
|
||||||
|
<Hds::Button
|
||||||
|
@text={{t 'resources.role.scope.actions.remove-org-and-projects'}}
|
||||||
|
{{on 'click' (fn this.removeProjects false)}}
|
||||||
|
/>
|
||||||
|
<Hds::Button
|
||||||
|
@color='secondary'
|
||||||
|
@text={{t 'resources.role.scope.actions.remove-org-only'}}
|
||||||
|
{{on 'click' F.close}}
|
||||||
|
/>
|
||||||
|
</Hds::ButtonSet>
|
||||||
|
</M.Footer>
|
||||||
|
</Hds::Modal>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.selectedOrgs}}
|
||||||
|
<Hds::Modal
|
||||||
|
@onClose={{this.toggleRemoveAllModal}}
|
||||||
|
data-test-manage-scopes-remove-all-orgs-modal
|
||||||
|
as |M|
|
||||||
|
>
|
||||||
|
<M.Header>{{t 'resources.role.scope.remove-all-orgs.title'}}</M.Header>
|
||||||
|
<M.Body>
|
||||||
|
<Hds::Text::Body @tag='p' @color='primary'>
|
||||||
|
{{t 'resources.role.scope.remove-all-orgs.description'}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
</M.Body>
|
||||||
|
<M.Footer as |F|>
|
||||||
|
<Hds::ButtonSet>
|
||||||
|
<Hds::Button
|
||||||
|
@text={{t 'resources.role.scope.actions.remove-orgs-and-projects'}}
|
||||||
|
{{on 'click' (fn this.removeProjects true)}}
|
||||||
|
/>
|
||||||
|
<Hds::Button
|
||||||
|
@color='secondary'
|
||||||
|
@text={{t 'resources.role.scope.actions.remove-orgs-only'}}
|
||||||
|
{{on 'click' F.close}}
|
||||||
|
/>
|
||||||
|
</Hds::ButtonSet>
|
||||||
|
</M.Footer>
|
||||||
|
</Hds::Modal>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -10,14 +10,18 @@ import { tracked } from '@glimmer/tracking';
|
|||||||
export default class FormRoleManageCustomScopesIndexComponent extends Component {
|
export default class FormRoleManageCustomScopesIndexComponent extends Component {
|
||||||
// =attributes
|
// =attributes
|
||||||
|
|
||||||
@tracked selectedItems = [...this.args.model.role.grantScopeOrgIDs];
|
@tracked selectedOrgs = [];
|
||||||
|
@tracked selectedOrg = '';
|
||||||
|
|
||||||
get allGrantScopes() {
|
/**
|
||||||
return [
|
* Returns the display name of the selectedOrg.
|
||||||
...this.args.model.role.grantScopeKeywords,
|
* @type {string}
|
||||||
...this.args.model.role.grantScopeProjectIDs,
|
*/
|
||||||
...this.selectedItems,
|
get orgDisplayName() {
|
||||||
];
|
const org = this.args.model.orgScopes.find(
|
||||||
|
({ id }) => id === this.selectedOrg,
|
||||||
|
);
|
||||||
|
return org.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =actions
|
// =actions
|
||||||
@@ -25,19 +29,84 @@ export default class FormRoleManageCustomScopesIndexComponent extends Component
|
|||||||
/**
|
/**
|
||||||
* Handles the selection changes for the paginated table.
|
* Handles the selection changes for the paginated table.
|
||||||
* @param {object} selectableRowsStates
|
* @param {object} selectableRowsStates
|
||||||
|
* @param {object} selectionKey
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
selectionChange({ selectableRowsStates }) {
|
selectionChange({ selectableRowsStates, selectionKey }) {
|
||||||
selectableRowsStates.forEach((row) => {
|
const { role, projectTotals } = this.args.model;
|
||||||
const { isSelected, selectionKey: key } = row;
|
|
||||||
const includesId = this.selectedItems.includes(key);
|
const addOrRemoveValues = (add, remove, orgId) => {
|
||||||
if (isSelected) {
|
let selectedOrg;
|
||||||
if (!includesId) this.selectedItems = [...this.selectedItems, key];
|
const includesId = role.grant_scope_ids.includes(orgId);
|
||||||
} else if (includesId) {
|
const selected = projectTotals[orgId]?.selected;
|
||||||
this.selectedItems = this.selectedItems.filter(
|
if (add && !includesId) {
|
||||||
(item) => item !== key,
|
role.grant_scope_ids = [...role.grant_scope_ids, orgId];
|
||||||
);
|
} else if (remove && includesId) {
|
||||||
|
if (selected?.length) {
|
||||||
|
selectedOrg = orgId;
|
||||||
|
}
|
||||||
|
role.grant_scope_ids = role.grant_scope_ids.filter(
|
||||||
|
(item) => item !== orgId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return selectedOrg;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectionKey === 'all') {
|
||||||
|
const selectedOrgs = [];
|
||||||
|
selectableRowsStates.forEach(({ selectionKey, isSelected }) => {
|
||||||
|
const selectedOrg = addOrRemoveValues(
|
||||||
|
isSelected,
|
||||||
|
!isSelected,
|
||||||
|
selectionKey,
|
||||||
|
);
|
||||||
|
if (selectedOrg) {
|
||||||
|
selectedOrgs.push(selectedOrg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.selectedOrgs = selectedOrgs;
|
||||||
|
} else {
|
||||||
|
this.selectedOrg = addOrRemoveValues(true, true, selectionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes projects from an org that was deselected and toggles the correct modal off.
|
||||||
|
* @param {boolean} toggleRemoveAllModal
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
removeProjects(toggleRemoveAllModal) {
|
||||||
|
const selectedOrgs = toggleRemoveAllModal
|
||||||
|
? this.selectedOrgs
|
||||||
|
: [this.selectedOrg];
|
||||||
|
const { role, projectTotals } = this.args.model;
|
||||||
|
selectedOrgs.forEach((orgId) => {
|
||||||
|
const { selected, total } = projectTotals[orgId];
|
||||||
|
role.grant_scope_ids = role.grant_scope_ids.filter(
|
||||||
|
(item) => !selected.includes(item),
|
||||||
|
);
|
||||||
|
projectTotals[orgId] = { selected: [], total };
|
||||||
});
|
});
|
||||||
|
if (toggleRemoveAllModal) {
|
||||||
|
this.toggleRemoveAllModal();
|
||||||
|
} else {
|
||||||
|
this.toggleRemoveOrgModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the modal to remove an org and projects off.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
toggleRemoveOrgModal() {
|
||||||
|
this.selectedOrg = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the modal to remove orgs and projects off.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
toggleRemoveAllModal() {
|
||||||
|
this.selectedOrgs = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
{{#if @model.totalItemsCount}}
|
{{#if @model.totalItemsCount}}
|
||||||
<Rose::Form
|
<Rose::Form
|
||||||
class='full-width role-manage-custom-scopes-form'
|
class='full-width role-manage-custom-scopes-form'
|
||||||
@onSubmit={{fn @submit this.allGrantScopes}}
|
@onSubmit={{@submit}}
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@disabled={{@model.role.isSaving}}
|
@disabled={{@model.role.isSaving}}
|
||||||
as |form|
|
as |form|
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
/>
|
/>
|
||||||
</Hds::SegmentedGroup>
|
</Hds::SegmentedGroup>
|
||||||
{{#if @model.projectScopes}}
|
{{#if @model.projectScopes}}
|
||||||
{{#if this.selectedItems.length}}
|
<div class='table-selected-items-text'>
|
||||||
<div class='table-selected-items-text'>
|
{{#if this.selectedProjectsCount}}
|
||||||
<Hds::Text::Body @tag='p' @color='faint'>
|
<Hds::Text::Body @tag='p' @color='faint'>
|
||||||
{{t
|
{{t
|
||||||
'resources.role.scope.titles.selected'
|
'resources.role.scope.titles.selected'
|
||||||
items=this.selectedItems.length
|
items=this.selectedProjectsCount
|
||||||
total=@model.totalItemsCount
|
total=@model.totalItemsCount
|
||||||
}}
|
}}
|
||||||
</Hds::Text::Body>
|
</Hds::Text::Body>
|
||||||
</div>
|
{{/if}}
|
||||||
{{/if}}
|
</div>
|
||||||
<Hds::Table
|
<Hds::Table
|
||||||
@columns={{array
|
@columns={{array
|
||||||
(hash label=(t 'resources.project.title'))
|
(hash label=(t 'resources.project.title'))
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<:body as |B|>
|
<:body as |B|>
|
||||||
<B.Tr
|
<B.Tr
|
||||||
@selectionKey={{B.data.id}}
|
@selectionKey={{B.data.id}}
|
||||||
@isSelected={{includes B.data.id this.selectedItems}}
|
@isSelected={{includes B.data.id @model.role.grant_scope_ids}}
|
||||||
@selectionAriaLabelSuffix='row {{B.data.id}}'
|
@selectionAriaLabelSuffix='row {{B.data.id}}'
|
||||||
data-test-project-scopes-table-row={{B.data.id}}
|
data-test-project-scopes-table-row={{B.data.id}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,20 +5,13 @@
|
|||||||
|
|
||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
|
||||||
|
|
||||||
export default class FormRoleManageOrgProjectsIndexComponent extends Component {
|
export default class FormRoleManageOrgProjectsIndexComponent extends Component {
|
||||||
// =attributes
|
// =attributes
|
||||||
|
|
||||||
@tracked selectedItems = [...this.args.model.selectedProjectIDs];
|
get selectedProjectsCount() {
|
||||||
|
const { role, remainingProjectIDs } = this.args.model;
|
||||||
get allGrantScopes() {
|
return role.grantScopeProjectIDs.length - remainingProjectIDs.length;
|
||||||
return [
|
|
||||||
...this.args.model.role.grantScopeKeywords,
|
|
||||||
...this.args.model.role.grantScopeOrgIDs,
|
|
||||||
...this.args.model.remainingProjectIDs,
|
|
||||||
...this.selectedItems,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =actions
|
// =actions
|
||||||
@@ -29,16 +22,16 @@ export default class FormRoleManageOrgProjectsIndexComponent extends Component {
|
|||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
selectionChange({ selectableRowsStates }) {
|
selectionChange({ selectableRowsStates }) {
|
||||||
|
const { role } = this.args.model;
|
||||||
selectableRowsStates.forEach((row) => {
|
selectableRowsStates.forEach((row) => {
|
||||||
const { isSelected, selectionKey: key } = row;
|
const { isSelected, selectionKey: key } = row;
|
||||||
const includesId = this.selectedItems.includes(key);
|
const includesId = role.grant_scope_ids.includes(key);
|
||||||
if (isSelected) {
|
if (isSelected && !includesId) {
|
||||||
if (!includesId) this.selectedItems = [...this.selectedItems, key];
|
role.grant_scope_ids = [...role.grant_scope_ids, key];
|
||||||
} else {
|
} else if (!isSelected && includesId) {
|
||||||
if (includesId)
|
role.grant_scope_ids = role.grant_scope_ids.filter(
|
||||||
this.selectedItems = this.selectedItems.filter(
|
(item) => item !== key,
|
||||||
(item) => item !== key,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<Rose::Form
|
<Rose::Form
|
||||||
class='full-width role-manage-scopes-form'
|
class='full-width role-manage-scopes-form'
|
||||||
@onSubmit={{fn @submit this.selectedItems}}
|
@onSubmit={{@submit}}
|
||||||
@cancel={{@cancel}}
|
@cancel={{@cancel}}
|
||||||
@disabled={{@model.isSaving}}
|
@disabled={{@model.isSaving}}
|
||||||
as |form|
|
as |form|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<Hds::Form::Toggle::Field
|
<Hds::Form::Toggle::Field
|
||||||
@value={{this.keywords.keyThis}}
|
@value={{this.keywords.keyThis}}
|
||||||
name={{this.keywords.keyThis}}
|
name={{this.keywords.keyThis}}
|
||||||
checked={{includes this.keywords.keyThis this.selectedItems}}
|
checked={{includes this.keywords.keyThis @model.grant_scope_ids}}
|
||||||
disabled={{form.disabled}}
|
disabled={{form.disabled}}
|
||||||
{{on 'change' this.toggleField}}
|
{{on 'change' this.toggleField}}
|
||||||
as |F|
|
as |F|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<Hds::Form::Toggle::Field
|
<Hds::Form::Toggle::Field
|
||||||
@value={{this.keywords.keyChildren}}
|
@value={{this.keywords.keyChildren}}
|
||||||
name={{this.keywords.keyChildren}}
|
name={{this.keywords.keyChildren}}
|
||||||
checked={{includes this.keywords.keyChildren this.selectedItems}}
|
checked={{includes this.keywords.keyChildren @model.grant_scope_ids}}
|
||||||
disabled={{form.disabled}}
|
disabled={{form.disabled}}
|
||||||
{{on 'change' this.toggleField}}
|
{{on 'change' this.toggleField}}
|
||||||
as |F|
|
as |F|
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<Hds::Form::Toggle::Field
|
<Hds::Form::Toggle::Field
|
||||||
@value={{this.keywords.keyDescendants}}
|
@value={{this.keywords.keyDescendants}}
|
||||||
name={{this.keywords.keyDescendants}}
|
name={{this.keywords.keyDescendants}}
|
||||||
checked={{includes this.keywords.keyDescendants this.selectedItems}}
|
checked={{includes this.keywords.keyDescendants @model.grant_scope_ids}}
|
||||||
disabled={{form.disabled}}
|
disabled={{form.disabled}}
|
||||||
{{on 'change' this.toggleField}}
|
{{on 'change' this.toggleField}}
|
||||||
as |F|
|
as |F|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
|
||||||
import {
|
import {
|
||||||
GRANT_SCOPE_THIS,
|
GRANT_SCOPE_THIS,
|
||||||
GRANT_SCOPE_CHILDREN,
|
GRANT_SCOPE_CHILDREN,
|
||||||
@@ -21,8 +20,6 @@ export default class FormRoleManageScopesIndexComponent extends Component {
|
|||||||
keyDescendants: GRANT_SCOPE_DESCENDANTS,
|
keyDescendants: GRANT_SCOPE_DESCENDANTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@tracked selectedItems = [...this.args.model.grant_scope_ids];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if role is global level and either
|
* Returns true if role is global level and either
|
||||||
* "children" or "descendants" is toggled on.
|
* "children" or "descendants" is toggled on.
|
||||||
@@ -31,8 +28,8 @@ export default class FormRoleManageScopesIndexComponent extends Component {
|
|||||||
get showAlert() {
|
get showAlert() {
|
||||||
return (
|
return (
|
||||||
this.args.model.scope.isGlobal &&
|
this.args.model.scope.isGlobal &&
|
||||||
(this.selectedItems.includes(GRANT_SCOPE_CHILDREN) ||
|
(this.args.model.grant_scope_ids.includes(GRANT_SCOPE_CHILDREN) ||
|
||||||
this.selectedItems.includes(GRANT_SCOPE_DESCENDANTS))
|
this.args.model.grant_scope_ids.includes(GRANT_SCOPE_DESCENDANTS))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,10 +43,15 @@ export default class FormRoleManageScopesIndexComponent extends Component {
|
|||||||
toggleField(event) {
|
toggleField(event) {
|
||||||
const { checked, value } = event.target;
|
const { checked, value } = event.target;
|
||||||
const removeValue = (value) => {
|
const removeValue = (value) => {
|
||||||
this.selectedItems = this.selectedItems.filter((item) => item !== value);
|
this.args.model.grant_scope_ids = this.args.model.grant_scope_ids.filter(
|
||||||
|
(item) => item !== value,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.selectedItems = [...this.selectedItems, value];
|
this.args.model.grant_scope_ids = [
|
||||||
|
...this.args.model.grant_scope_ids,
|
||||||
|
value,
|
||||||
|
];
|
||||||
if (value === GRANT_SCOPE_CHILDREN) {
|
if (value === GRANT_SCOPE_CHILDREN) {
|
||||||
removeValue(GRANT_SCOPE_DESCENDANTS);
|
removeValue(GRANT_SCOPE_DESCENDANTS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,7 +424,7 @@
|
|||||||
{{t 'resources.storage-bucket.form.role_tags.label'}}
|
{{t 'resources.storage-bucket.form.role_tags.label'}}
|
||||||
</F.Legend>
|
</F.Legend>
|
||||||
<F.HelperText>
|
<F.HelperText>
|
||||||
{{t 'resources.storage-bucket.form.role_tags.label'}}
|
{{t 'resources.storage-bucket.form.role_tags.help'}}
|
||||||
</F.HelperText>
|
</F.HelperText>
|
||||||
|
|
||||||
{{#if @model.errors.role_tags}}
|
{{#if @model.errors.role_tags}}
|
||||||
@@ -436,11 +436,18 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</:fieldset>
|
</:fieldset>
|
||||||
<:field as |F|>
|
<:field as |F|>
|
||||||
<F.TextInput
|
<F.KeyValue
|
||||||
@name='role_tags'
|
@name='role_tags'
|
||||||
@options={{@model.role_tags}}
|
@options={{@model.role_tags}}
|
||||||
@model={{@model}}
|
@model={{@model}}
|
||||||
/>
|
>
|
||||||
|
<:key as |K|>
|
||||||
|
<K.text />
|
||||||
|
</:key>
|
||||||
|
<:value as |V|>
|
||||||
|
<V.text />
|
||||||
|
</:value>
|
||||||
|
</F.KeyValue>
|
||||||
</:field>
|
</:field>
|
||||||
</Form::Field::ListWrapper>
|
</Form::Field::ListWrapper>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -3,35 +3,28 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-manage-host-catalogs-dropdown as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
|
||||||
as |dropdown|
|
|
||||||
>
|
|
||||||
{{#if (can 'create model' @model collection='hosts')}}
|
{{#if (can 'create model' @model collection='hosts')}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
@route='scopes.scope.host-catalogs.host-catalog.hosts.new'
|
@route='scopes.scope.host-catalogs.host-catalog.hosts.new'
|
||||||
@model={{@model}}
|
@model={{@model}}
|
||||||
>
|
@text={{t 'resources.host.actions.create'}}
|
||||||
{{t 'resources.host.actions.create'}}
|
/>
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'create model' @model collection='host-sets')}}
|
{{#if (can 'create model' @model collection='host-sets')}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
@route='scopes.scope.host-catalogs.host-catalog.host-sets.new'
|
@route='scopes.scope.host-catalogs.host-catalog.host-sets.new'
|
||||||
@model={{@model}}
|
@model={{@model}}
|
||||||
>
|
@text={{t 'resources.host-set.actions.create'}}
|
||||||
{{t 'resources.host-set.actions.create'}}
|
/>
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<dropdown.separator />
|
<dd.Separator />
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@style='danger'
|
@text={{t 'resources.host-catalog.actions.delete'}}
|
||||||
@disabled={{@model.canSave}}
|
@color='critical'
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.host-catalog.actions.delete'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Dropdown>
|
</Hds::Dropdown>
|
||||||
+15
-21
@@ -3,31 +3,25 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-manage-dropdown-host-sets as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
|
||||||
as |dropdown|
|
|
||||||
>
|
|
||||||
{{#if (can 'addHosts hostSet' @model)}}
|
{{#if (can 'addHosts hostSet' @model)}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
|
@text={{t 'resources.host-set.actions.create-and-add-host'}}
|
||||||
@route='scopes.scope.host-catalogs.host-catalog.host-sets.host-set.create-and-add-host'
|
@route='scopes.scope.host-catalogs.host-catalog.host-sets.host-set.create-and-add-host'
|
||||||
>
|
/>
|
||||||
{{t 'resources.host-set.actions.create-and-add-host'}}
|
<dd.Interactive
|
||||||
</dropdown.link>
|
@text={{t 'resources.host-set.actions.add-hosts'}}
|
||||||
<dropdown.link
|
|
||||||
@route='scopes.scope.host-catalogs.host-catalog.host-sets.host-set.add-hosts'
|
@route='scopes.scope.host-catalogs.host-catalog.host-sets.host-set.add-hosts'
|
||||||
>
|
/>
|
||||||
{{t 'resources.host-set.actions.add-hosts'}}
|
<dd.Separator />
|
||||||
</dropdown.link>
|
|
||||||
<dropdown.separator />
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@style='danger'
|
@text={{t 'resources.host-set.actions.delete'}}
|
||||||
@disabled={{@model.canSave}}
|
@color='critical'
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.host-set.actions.delete'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Dropdown>
|
</Hds::Dropdown>
|
||||||
@@ -4,17 +4,12 @@
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<Rose::Dropdown
|
<Hds::Dropdown data-test-manage-hosts-dropdown as |dd|>
|
||||||
@text={{t 'actions.manage'}}
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
@dropdownRight={{true}}
|
<dd.Interactive
|
||||||
as |dropdown|
|
@text={{t 'resources.host.actions.delete'}}
|
||||||
>
|
@color='critical'
|
||||||
<dropdown.button
|
|
||||||
@style='danger'
|
|
||||||
@disabled={{@model.canSave}}
|
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.host.actions.delete'}}
|
</Hds::Dropdown>
|
||||||
</dropdown.button>
|
|
||||||
</Rose::Dropdown>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -2,44 +2,39 @@
|
|||||||
Copyright (c) HashiCorp, Inc.
|
Copyright (c) HashiCorp, Inc.
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
<Hds::Dropdown data-test-manage-targets-dropdown as |dd|>
|
||||||
|
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
|
||||||
|
|
||||||
<Rose::Dropdown
|
|
||||||
@text={{t 'actions.manage'}}
|
|
||||||
@dropdownRight={{true}}
|
|
||||||
as |dropdown|
|
|
||||||
>
|
|
||||||
{{#if (can 'addHostSources target' @model)}}
|
{{#if (can 'addHostSources target' @model)}}
|
||||||
<dropdown.link @route='scopes.scope.targets.target.add-host-sources'>
|
<dd.Interactive
|
||||||
{{t 'resources.target.actions.add-host-sources'}}
|
@route='scopes.scope.targets.target.add-host-sources'
|
||||||
</dropdown.link>
|
@text={{t 'resources.target.actions.add-host-sources'}}
|
||||||
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (can 'addBrokeredCredentialSources target' @model)}}
|
{{#if (can 'addBrokeredCredentialSources target' @model)}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
@route='scopes.scope.targets.target.add-brokered-credential-sources'
|
@route='scopes.scope.targets.target.add-brokered-credential-sources'
|
||||||
|
@text={{t 'resources.target.actions.add-brokered-credential-sources'}}
|
||||||
data-test-add-brokered-cred-sources-action
|
data-test-add-brokered-cred-sources-action
|
||||||
>
|
/>
|
||||||
{{t 'resources.target.actions.add-brokered-credential-sources'}}
|
|
||||||
</dropdown.link>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (can 'addInjectedApplicationCredentialSources target' @model)}}
|
{{#if (can 'addInjectedApplicationCredentialSources target' @model)}}
|
||||||
<dropdown.link
|
<dd.Interactive
|
||||||
@route='scopes.scope.targets.target.add-injected-application-credential-sources'
|
@route='scopes.scope.targets.target.add-injected-application-credential-sources'
|
||||||
data-test-add-injected-cred-sources-action
|
@text={{t
|
||||||
>
|
|
||||||
{{t
|
|
||||||
'resources.target.actions.add-injected-application-credential-sources'
|
'resources.target.actions.add-injected-application-credential-sources'
|
||||||
}}
|
}}
|
||||||
</dropdown.link>
|
data-test-add-injected-cred-sources-action
|
||||||
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (can 'delete model' @model)}}
|
{{#if (can 'delete model' @model)}}
|
||||||
<dropdown.separator />
|
<dd.Separator />
|
||||||
<dropdown.button
|
<dd.Interactive
|
||||||
@style='danger'
|
@text={{t 'resources.target.actions.delete'}}
|
||||||
@disabled={{@model.canSave}}
|
@color='critical'
|
||||||
{{on 'click' (fn @delete @model)}}
|
{{on 'click' (fn @delete @model)}}
|
||||||
>
|
/>
|
||||||
{{t 'resources.target.actions.delete'}}
|
|
||||||
</dropdown.button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Dropdown>
|
|
||||||
|
</Hds::Dropdown>
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { loading } from 'ember-loading';
|
||||||
|
import { notifyError } from 'core/decorators/notify';
|
||||||
|
|
||||||
|
export default class OnboardingIndexController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service intl;
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates resources then redirects to success page.
|
||||||
|
* @param {object} model
|
||||||
|
* @param {string} targetAddress
|
||||||
|
* @param {string} targetPort
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@notifyError(function () {
|
||||||
|
return this.intl.t('errors.onboarding-failed.description');
|
||||||
|
})
|
||||||
|
async createResources(model, targetAddress, targetPort) {
|
||||||
|
const { org, project, target, role } = model;
|
||||||
|
|
||||||
|
await this.createOrg(org);
|
||||||
|
await this.createProject(org, project);
|
||||||
|
await this.createTarget(project, target, targetAddress, targetPort);
|
||||||
|
await this.createGrants(org, project, target, role);
|
||||||
|
|
||||||
|
// Redirect to success
|
||||||
|
this.router.transitionTo('onboarding.success');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Org, if fails, redirects to Orgs empty list
|
||||||
|
* @param {ScopeModel} org
|
||||||
|
*/
|
||||||
|
async createOrg(org) {
|
||||||
|
try {
|
||||||
|
await org.save();
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error ', error);
|
||||||
|
// Redirects to Orgs screen
|
||||||
|
this.router.transitionTo('scopes.scope', 'global');
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Project, if fails redirects to Projects from Org empty list.
|
||||||
|
* @param {ScopeModel} org
|
||||||
|
* @param {ScopeModel} project
|
||||||
|
*/
|
||||||
|
async createProject(org, project) {
|
||||||
|
project.scopeID = org.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await project.save();
|
||||||
|
} catch (error) {
|
||||||
|
// Redirects to Project screen
|
||||||
|
this.router.transitionTo('scopes.scope.scopes', project.scopeID);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Target, if fails redirects to Targets empty list.
|
||||||
|
* @param {ScopeModel} project
|
||||||
|
* @param {TargetModel} target
|
||||||
|
* @param {string} targetAddress
|
||||||
|
* @param {string} targetPort
|
||||||
|
*/
|
||||||
|
async createTarget(project, target, targetAddress, targetPort) {
|
||||||
|
target.scopeID = project.id;
|
||||||
|
target.address = targetAddress;
|
||||||
|
target.default_port = targetPort;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await target.save();
|
||||||
|
} catch (error) {
|
||||||
|
// Redirect to targets global
|
||||||
|
this.router.replaceWith('scopes.scope.targets', project.scopeID);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns default grants and scope to a role.
|
||||||
|
* @param {ScopeModel} org
|
||||||
|
* @param {ScopeModel} project
|
||||||
|
* @param {TargetModel} target
|
||||||
|
* @param {RoleModel} role
|
||||||
|
*/
|
||||||
|
async createGrants(org, project, target, role) {
|
||||||
|
role.scopeID = org.id;
|
||||||
|
role.grant_scope_id = project.id;
|
||||||
|
try {
|
||||||
|
await role.save();
|
||||||
|
await role.saveGrantStrings([
|
||||||
|
`type=target;actions=list`,
|
||||||
|
`ids=${target.id};actions=authorize-session`,
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
// Redirect to targets that belong to scope
|
||||||
|
this.router.replaceWith('scopes.scope.targets', project.scopeID);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects user to index route for further processing.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
doLater() {
|
||||||
|
this.router.transitionTo('index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
|
export default class OnboardingSuccessController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect user to target created during onboarding.
|
||||||
|
* @param {object} model
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
showTargetList(model) {
|
||||||
|
const {
|
||||||
|
project: { id: scopeID },
|
||||||
|
target: { id: targetID },
|
||||||
|
} = model;
|
||||||
|
this.router.transitionTo('scopes.scope.targets.target', scopeID, targetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,26 @@
|
|||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { loading } from 'ember-loading';
|
import { loading } from 'ember-loading';
|
||||||
|
import { debounce } from 'core/decorators/debounce';
|
||||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||||
import { confirm } from 'core/decorators/confirm';
|
import { confirm } from 'core/decorators/confirm';
|
||||||
|
|
||||||
export default class ScopesScopeAliasesIndexController extends Controller {
|
export default class ScopesScopeAliasesIndexController extends Controller {
|
||||||
// =services
|
// =services
|
||||||
|
|
||||||
@service can;
|
@service can;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
|
// =attributes
|
||||||
|
|
||||||
|
queryParams = ['search', 'page', 'pageSize'];
|
||||||
|
|
||||||
|
@tracked search;
|
||||||
|
@tracked page = 1;
|
||||||
|
@tracked pageSize = 10;
|
||||||
|
|
||||||
// =actions
|
// =actions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,10 +33,10 @@ export default class ScopesScopeAliasesIndexController extends Controller {
|
|||||||
* @param {AliasModel} alias
|
* @param {AliasModel} alias
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async cancel(alias) {
|
cancel(alias) {
|
||||||
const { isNew } = alias;
|
const { isNew } = alias;
|
||||||
alias.rollbackAttributes();
|
alias.rollbackAttributes();
|
||||||
if (isNew) await this.router.transitionTo('scopes.scope.aliases');
|
if (isNew) this.router.transitionTo('scopes.scope.aliases');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +60,7 @@ export default class ScopesScopeAliasesIndexController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove destaination_id from alias
|
* Remove destination_id from alias
|
||||||
* @param {AliasModel} alias
|
* @param {AliasModel} alias
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
@@ -77,4 +88,16 @@ export default class ScopesScopeAliasesIndexController extends Controller {
|
|||||||
await this.router.replaceWith('scopes.scope.aliases');
|
await this.router.replaceWith('scopes.scope.aliases');
|
||||||
await this.router.refresh();
|
await this.router.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles input on each keystroke and the search queryParam
|
||||||
|
* @param {object} event
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@debounce(250)
|
||||||
|
handleSearchInput(event) {
|
||||||
|
const { value } = event.target;
|
||||||
|
this.search = value;
|
||||||
|
this.page = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { loading } from 'ember-loading';
|
||||||
|
import { notifyError } from 'core/decorators/notify';
|
||||||
|
import {
|
||||||
|
TYPE_AUTH_METHOD_PASSWORD,
|
||||||
|
TYPE_AUTH_METHOD_OIDC,
|
||||||
|
TYPE_AUTH_METHOD_LDAP,
|
||||||
|
} from 'api/models/auth-method';
|
||||||
|
|
||||||
|
export default class ScopesScopeAuthenticateMethodIndexController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service session;
|
||||||
|
@service windowManager;
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates authentication to an authenticator of type matching the current
|
||||||
|
* auth method. If authentication fails, notifies with an alert.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@notifyError(() => 'errors.authentication-failed.title', {
|
||||||
|
sticky: false,
|
||||||
|
catch: true,
|
||||||
|
})
|
||||||
|
async authenticate(authMethod, creds) {
|
||||||
|
const scope = authMethod.scope;
|
||||||
|
const authenticatorName = `authenticator:${authMethod.type}`;
|
||||||
|
const requestCookies = false;
|
||||||
|
switch (authMethod.type) {
|
||||||
|
case TYPE_AUTH_METHOD_PASSWORD:
|
||||||
|
case TYPE_AUTH_METHOD_LDAP:
|
||||||
|
await this.session.authenticate(
|
||||||
|
authenticatorName,
|
||||||
|
creds,
|
||||||
|
requestCookies,
|
||||||
|
{ scope, authMethod },
|
||||||
|
);
|
||||||
|
this.router.transitionTo('index');
|
||||||
|
break;
|
||||||
|
case TYPE_AUTH_METHOD_OIDC:
|
||||||
|
await this.startOIDCAuthentication(authenticatorName, {
|
||||||
|
scope,
|
||||||
|
authMethod,
|
||||||
|
});
|
||||||
|
this.router.transitionTo('scopes.scope.authenticate.method.oidc');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kicks off the OIDC auth flow by opening a new window to the provider.
|
||||||
|
* @param {string} authenticatorName
|
||||||
|
* @param {object} options
|
||||||
|
*/
|
||||||
|
async startOIDCAuthentication(authenticatorName, options) {
|
||||||
|
const oidc = getOwner(this).lookup(authenticatorName);
|
||||||
|
// TODO: delegate this call from the session service so that we don't have
|
||||||
|
// to look up the authenticator directly
|
||||||
|
const json = await oidc.startAuthentication(options);
|
||||||
|
await this.windowManager.open(json.attributes.auth_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Controller, { inject as controller } from '@ember/controller';
|
||||||
|
|
||||||
|
export default class ScopesScopeEditController extends Controller {
|
||||||
|
@controller('scopes/scope/index') scopes;
|
||||||
|
}
|
||||||
@@ -99,4 +99,14 @@ export default class ScopesScopeHostCatalogsIndexController extends Controller {
|
|||||||
if (type === 'plugin') type = 'aws';
|
if (type === 'plugin') type = 'aws';
|
||||||
this.router.replaceWith({ queryParams: { type } });
|
this.router.replaceWith({ queryParams: { type } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates credential type
|
||||||
|
* @param {model} hostcatalog
|
||||||
|
* @param {string} credentialType
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
changeCredentialType(hostCatalog, credentialType) {
|
||||||
|
hostCatalog.credentialType = credentialType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 ScopesScopeIndexController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
//= actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback changes on scope.
|
||||||
|
* @param {ScopeModel} scope
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
cancel(scope) {
|
||||||
|
const { isNew } = scope;
|
||||||
|
scope.rollbackAttributes();
|
||||||
|
if (isNew) this.router.transitionTo('scopes.scope');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle save scope.
|
||||||
|
* @param {ScopeModel} scope
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@notifyError(({ message }) => message)
|
||||||
|
@notifySuccess(({ isNew }) =>
|
||||||
|
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
||||||
|
)
|
||||||
|
async save(scope) {
|
||||||
|
const { isNew } = scope;
|
||||||
|
await scope.save();
|
||||||
|
await this.router.transitionTo('scopes.scope.edit', scope);
|
||||||
|
if (isNew) this.router.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the scope and redirects to index.
|
||||||
|
* @param {ScopeModel} scope
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@confirm('questions.delete-confirm')
|
||||||
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
|
@notifySuccess('notifications.delete-success')
|
||||||
|
async delete(scope) {
|
||||||
|
const { scopeID } = scope;
|
||||||
|
await scope.destroyRecord();
|
||||||
|
this.router.replaceWith('scopes.scope.scopes', scopeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the scope and redirects to index.
|
||||||
|
* @param {ScopeModel} scope
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@confirm('resources.policy.questions.detach')
|
||||||
|
@notifyError(({ message }) => message)
|
||||||
|
@notifySuccess('resources.policy.messages.detach')
|
||||||
|
async detachStoragePolicy(scope) {
|
||||||
|
const { storage_policy_id } = scope;
|
||||||
|
scope.storage_policy_id = '';
|
||||||
|
await scope.detachStoragePolicy(storage_policy_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,22 +23,23 @@ export default class ScopesScopeRolesRoleManageScopesIndexController extends Con
|
|||||||
/**
|
/**
|
||||||
* Save grant scope IDs to current role via the API.
|
* Save grant scope IDs to current role via the API.
|
||||||
* @param {RoleModel} role
|
* @param {RoleModel} role
|
||||||
* @param {[string]} grantScopeIDs
|
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
@loading
|
@loading
|
||||||
@notifyError(({ message }) => message, { catch: true })
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
@notifySuccess('resources.role.scope.messages.manage-scopes.success')
|
@notifySuccess('resources.role.scope.messages.manage-scopes.success')
|
||||||
async setGrantScopes(role, grantScopeIDs) {
|
async setGrantScopes(role) {
|
||||||
await role.setGrantScopes(grantScopeIDs);
|
await role.setGrantScopes(role.grant_scope_ids);
|
||||||
this.router.replaceWith('scopes.scope.roles.role.scopes');
|
this.router.replaceWith('scopes.scope.roles.role.scopes');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to role scopes as if nothing ever happened.
|
* Redirect to role scopes as if nothing ever happened.
|
||||||
|
* @param {RoleModel} role
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
cancel() {
|
cancel(role) {
|
||||||
|
role.rollbackAttributes();
|
||||||
this.router.replaceWith('scopes.scope.roles.role.scopes');
|
this.router.replaceWith('scopes.scope.roles.role.scopes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-6
@@ -43,23 +43,24 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesControlle
|
|||||||
/**
|
/**
|
||||||
* Save grant scope IDs to current role via the API.
|
* Save grant scope IDs to current role via the API.
|
||||||
* @param {RoleModel} role
|
* @param {RoleModel} role
|
||||||
* @param {[string]} grantScopeIDs
|
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
@loading
|
@loading
|
||||||
@notifyError(({ message }) => message, { catch: true })
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
@notifySuccess('resources.role.scope.messages.manage-custom-scopes.success')
|
@notifySuccess('resources.role.scope.messages.manage-custom-scopes.success')
|
||||||
async setGrantScopes(role, grantScopeIDs) {
|
async setGrantScopes(role) {
|
||||||
await role.setGrantScopes(grantScopeIDs);
|
await role.setGrantScopes(role.grant_scope_ids);
|
||||||
this.manageScopes.showCheckIcon = true;
|
this.manageScopes.showCheckIcon = true;
|
||||||
await this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
|
this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to manage scopes as if nothing ever happened.
|
* Redirect to manage scopes as if nothing ever happened.
|
||||||
|
* @param {RoleModel} role
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async cancel() {
|
cancel(role) {
|
||||||
await this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
|
role.rollbackAttributes();
|
||||||
|
this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-3
@@ -43,14 +43,13 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController
|
|||||||
/**
|
/**
|
||||||
* Save grant scope IDs to current role via the API.
|
* Save grant scope IDs to current role via the API.
|
||||||
* @param {RoleModel} role
|
* @param {RoleModel} role
|
||||||
* @param {[string]} grantScopeIDs
|
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
@loading
|
@loading
|
||||||
@notifyError(({ message }) => message, { catch: true })
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
@notifySuccess('resources.role.scope.messages.manage-org-projects.success')
|
@notifySuccess('resources.role.scope.messages.manage-org-projects.success')
|
||||||
async setGrantScopes(role, grantScopeIDs) {
|
async setGrantScopes(role) {
|
||||||
await role.setGrantScopes(grantScopeIDs);
|
await role.setGrantScopes(role.grant_scope_ids);
|
||||||
this.manageScopes.showCheckIcon = true;
|
this.manageScopes.showCheckIcon = true;
|
||||||
if (role.scope.isGlobal) {
|
if (role.scope.isGlobal) {
|
||||||
this.router.replaceWith(
|
this.router.replaceWith(
|
||||||
@@ -63,9 +62,11 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to previous as if nothing ever happened.
|
* Redirect to previous as if nothing ever happened.
|
||||||
|
* @param {RoleModel} role
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
cancel(role) {
|
cancel(role) {
|
||||||
|
role.rollbackAttributes();
|
||||||
if (role.scope.isGlobal) {
|
if (role.scope.isGlobal) {
|
||||||
this.router.replaceWith(
|
this.router.replaceWith(
|
||||||
'scopes.scope.roles.role.manage-scopes.manage-custom-scopes',
|
'scopes.scope.roles.role.manage-scopes.manage-custom-scopes',
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Controller, { inject as controller } from '@ember/controller';
|
||||||
|
|
||||||
|
export default class ScopesScopeScopesNewController extends Controller {
|
||||||
|
@controller('scopes/scope/index') scopes;
|
||||||
|
}
|
||||||
@@ -4,21 +4,192 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { debounce } from 'core/decorators/debounce';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default class ScopesScopeSessionRecordingsIndexController extends Controller {
|
export default class ScopesScopeSessionRecordingsIndexController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service store;
|
||||||
|
@service intl;
|
||||||
|
|
||||||
|
// =attributes
|
||||||
|
|
||||||
|
queryParams = [
|
||||||
|
'search',
|
||||||
|
'time',
|
||||||
|
{ users: { type: 'array' } },
|
||||||
|
{ scopes: { type: 'array' } },
|
||||||
|
{ targets: { type: 'array' } },
|
||||||
|
'page',
|
||||||
|
'pageSize',
|
||||||
|
];
|
||||||
|
|
||||||
|
now = new Date();
|
||||||
|
|
||||||
|
@tracked search = '';
|
||||||
|
@tracked time = null;
|
||||||
|
@tracked users = [];
|
||||||
|
@tracked scopes = [];
|
||||||
|
@tracked targets = [];
|
||||||
|
@tracked page = 1;
|
||||||
|
@tracked pageSize = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if any session recordings exist
|
* Returns object of filters to be used for displaying selected filters
|
||||||
* @type {boolean}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
get hasSessionRecordings() {
|
get filters() {
|
||||||
return this.model?.sessionRecordings?.length;
|
return {
|
||||||
|
allFilters: {
|
||||||
|
time: this.timeOptions,
|
||||||
|
users: this.filterOptions('user'),
|
||||||
|
scopes: this.projectScopes,
|
||||||
|
targets: this.filterOptions('target'),
|
||||||
|
},
|
||||||
|
selectedFilters: {
|
||||||
|
time: [this.time],
|
||||||
|
users: this.users,
|
||||||
|
scopes: this.scopes,
|
||||||
|
targets: this.targets,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if any storage buckets exist
|
* Returns array of time options for time filter
|
||||||
* @type {boolean}
|
* @returns {[object]}
|
||||||
*/
|
*/
|
||||||
get hasSessionRecordingsConfigured() {
|
get timeOptions() {
|
||||||
return this.model?.storageBuckets?.length;
|
const last24Hours = new Date(this.now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
const last3Days = new Date(this.now);
|
||||||
|
last3Days.setDate(this.now.getDate() - 3);
|
||||||
|
const last7Days = new Date(this.now);
|
||||||
|
last7Days.setDate(this.now.getDate() - 7);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: last24Hours.toISOString(),
|
||||||
|
name: this.intl.t(
|
||||||
|
'resources.session-recording.filters.time.last-twenty-four-hours',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: last3Days.toISOString(),
|
||||||
|
name: this.intl.t(
|
||||||
|
'resources.session-recording.filters.time.last-three-days',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: last7Days.toISOString(),
|
||||||
|
name: this.intl.t(
|
||||||
|
'resources.session-recording.filters.time.last-seven-days',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unique project scopes from targets
|
||||||
|
* linked to the session recordings
|
||||||
|
* @returns {[object]}
|
||||||
|
*/
|
||||||
|
get projectScopes() {
|
||||||
|
const uniqueMap = new Map();
|
||||||
|
this.model.allSessionRecordings.forEach(
|
||||||
|
({
|
||||||
|
create_time_values: {
|
||||||
|
target: {
|
||||||
|
scope: { id, name, parent_scope_id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (!uniqueMap.has(id)) {
|
||||||
|
const projectName = name || id;
|
||||||
|
uniqueMap.set(id, { id, name: projectName, parent_scope_id });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return Array.from(uniqueMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up org by ID and returns the org name
|
||||||
|
* @param {string} orgID
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
orgName(orgID) {
|
||||||
|
const org = this.store.peekRecord('scope', orgID);
|
||||||
|
return org.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all filter options for key for session recordings
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {[object]}
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
filterOptions(key) {
|
||||||
|
const uniqueMap = new Map();
|
||||||
|
this.model.allSessionRecordings.forEach(
|
||||||
|
({
|
||||||
|
create_time_values: {
|
||||||
|
[key]: { id, name },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (!uniqueMap.has(id)) {
|
||||||
|
uniqueMap.set(id, { id, name });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return Array.from(uniqueMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles input on each keystroke and the search queryParam
|
||||||
|
* @param {object} event
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@debounce(250)
|
||||||
|
handleSearchInput(event) {
|
||||||
|
const { value } = event.target;
|
||||||
|
this.search = value;
|
||||||
|
this.page = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the selected items for the given paramKey and sets the page to 1
|
||||||
|
* @param {string} paramKey
|
||||||
|
* @param {[string]} selectedItems
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
applyFilter(paramKey, selectedItems) {
|
||||||
|
this[paramKey] = [...selectedItems];
|
||||||
|
this.page = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time filter, sets the page to 1, and closes the filter dropdown
|
||||||
|
* @param {object} timeId
|
||||||
|
* @param {func} onClose
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
changeTimeFilter(timeId, onClose) {
|
||||||
|
this.time = timeId;
|
||||||
|
this.page = 1;
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the all data for the current page
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
refresh() {
|
||||||
|
this.send('refreshAll');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+51
-2
@@ -4,15 +4,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Controller from '@ember/controller';
|
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';
|
||||||
import {
|
import {
|
||||||
STATE_SESSION_RECORDING_STARTED,
|
STATE_SESSION_RECORDING_STARTED,
|
||||||
STATE_SESSION_RECORDING_UNKNOWN,
|
STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
} from 'api/models/session-recording';
|
} from 'api/models/session-recording';
|
||||||
|
|
||||||
export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConnectionIndexController extends Controller {
|
export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConnectionIndexController extends Controller {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
// =attributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the session recording has started
|
* Returns true if the session recording has started
|
||||||
* but has not started any connections
|
* but has not started any connections.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isSessionInprogressWithNoConnections() {
|
get isSessionInprogressWithNoConnections() {
|
||||||
@@ -24,7 +35,7 @@ export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConne
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the session recording state is unknown
|
* Returns true if the session recording state is unknown
|
||||||
* and has no connections
|
* and has no connections.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isSessionUnknownWithNoConnections() {
|
get isSessionUnknownWithNoConnections() {
|
||||||
@@ -33,4 +44,42 @@ export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConne
|
|||||||
this.model?.sessionRecording?.state === STATE_SESSION_RECORDING_UNKNOWN
|
this.model?.sessionRecording?.state === STATE_SESSION_RECORDING_UNKNOWN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reapplies storage policy dates to session recording.
|
||||||
|
* @param {SessionRecordingModel}
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
|
@notifySuccess(({ end_time }) =>
|
||||||
|
!end_time
|
||||||
|
? 'resources.policy.messages.later'
|
||||||
|
: 'resources.policy.messages.reapply',
|
||||||
|
)
|
||||||
|
async reapplyStoragePolicy(sessionRecording) {
|
||||||
|
try {
|
||||||
|
await sessionRecording.reapplyStoragePolicy();
|
||||||
|
} catch (e) {
|
||||||
|
this.router.replaceWith('scopes.scope.session-recordings');
|
||||||
|
this.router.refresh();
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the session recording.
|
||||||
|
* @param {SessionRecording} recording
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
@loading
|
||||||
|
@confirm('resources.session-recording.questions.delete')
|
||||||
|
@notifyError(({ message }) => message, { catch: true })
|
||||||
|
@notifySuccess('notifications.delete-success')
|
||||||
|
async delete(sessionRecording) {
|
||||||
|
await sessionRecording.destroyRecord();
|
||||||
|
this.router.replaceWith('scopes.scope.session-recordings');
|
||||||
|
this.router.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fetch from 'fetch';
|
|
||||||
|
|
||||||
async function autoInitializeFeatureEdition(owner) {
|
async function autoInitializeFeatureEdition(owner) {
|
||||||
const service = owner.lookup('service:feature-edition');
|
const service = owner.lookup('service:feature-edition');
|
||||||
const adapter = owner.lookup('adapter:application');
|
const adapter = owner.lookup('adapter:application');
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export default class ApplicationRoute extends Route {
|
|||||||
this.router.on('routeWillChange', async (transition) => {
|
this.router.on('routeWillChange', async (transition) => {
|
||||||
const fromName = transition?.from?.name;
|
const fromName = transition?.from?.name;
|
||||||
const toName = transition?.to?.name;
|
const toName = transition?.to?.name;
|
||||||
const maybeModel = transition?.from?.attributes;
|
const maybeModel =
|
||||||
|
transition?.data?.model ?? transition?.from?.attributes;
|
||||||
if (fromName !== toName && maybeModel?.hasDirtyAttributes) {
|
if (fromName !== toName && maybeModel?.hasDirtyAttributes) {
|
||||||
transition.abort();
|
transition.abort();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
|
||||||
import { loading } from 'ember-loading';
|
|
||||||
import { notifyError } from 'core/decorators/notify';
|
|
||||||
import { TYPE_TARGET_TCP } from 'api/models/target';
|
import { TYPE_TARGET_TCP } from 'api/models/target';
|
||||||
|
|
||||||
export default class OnboardingRoute extends Route {
|
export default class OnboardingRoute extends Route {
|
||||||
@@ -16,7 +13,6 @@ export default class OnboardingRoute extends Route {
|
|||||||
@service store;
|
@service store;
|
||||||
@service session;
|
@service session;
|
||||||
@service router;
|
@service router;
|
||||||
@service intl;
|
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
@@ -34,7 +30,7 @@ export default class OnboardingRoute extends Route {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* List all scopes (orgs) under global.
|
* List all scopes (orgs) under global.
|
||||||
* Creare org, project, target and roles
|
* Create org, project, target and roles
|
||||||
*/
|
*/
|
||||||
async model() {
|
async model() {
|
||||||
return {
|
return {
|
||||||
@@ -59,95 +55,4 @@ export default class OnboardingRoute extends Route {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// =actions
|
|
||||||
@action
|
|
||||||
@loading
|
|
||||||
@notifyError(function () {
|
|
||||||
return this.intl.t('errors.onboarding-failed.description');
|
|
||||||
})
|
|
||||||
async createResources(targetAddress, targetPort) {
|
|
||||||
const { org, project, target, role } = this.currentModel;
|
|
||||||
|
|
||||||
await this.createOrg(org);
|
|
||||||
await this.createProject(org, project);
|
|
||||||
await this.createTarget(project, target, targetAddress, targetPort);
|
|
||||||
await this.createGrants(org, project, target, role);
|
|
||||||
|
|
||||||
// Redirect to success
|
|
||||||
this.router.transitionTo('onboarding.success');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates Org, if fails, redirects to Orgs empty list
|
|
||||||
* @param {Object} org
|
|
||||||
*/
|
|
||||||
async createOrg(org) {
|
|
||||||
try {
|
|
||||||
await org.save();
|
|
||||||
} catch (error) {
|
|
||||||
// Redirects to Orgs screen
|
|
||||||
this.router.transitionTo('scopes.scope', 'global');
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates Project, if fails redirects to Projects from Org empty list.
|
|
||||||
* @param {Object} org
|
|
||||||
* @param {Object} project
|
|
||||||
*/
|
|
||||||
async createProject(org, project) {
|
|
||||||
project.scopeID = org.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await project.save();
|
|
||||||
} catch (error) {
|
|
||||||
// Redirects to Project screen
|
|
||||||
this.router.transitionTo('scopes.scope.scopes', project.scopeID);
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Target, if fails redirects to Targets empyt list.
|
|
||||||
* @param {Object} project
|
|
||||||
* @param {Object} target
|
|
||||||
* @param {Object} targetAddress
|
|
||||||
* @param {Object} targetPort
|
|
||||||
*/
|
|
||||||
async createTarget(project, target, targetAddress, targetPort) {
|
|
||||||
target.scopeID = project.id;
|
|
||||||
target.address = targetAddress;
|
|
||||||
target.default_port = targetPort;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await target.save();
|
|
||||||
} catch (error) {
|
|
||||||
// Redirect to targets global
|
|
||||||
this.router.replaceWith('scopes.scope.targets', project.scopeID);
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createGrants(org, project, target, role) {
|
|
||||||
role.scopeID = org.id;
|
|
||||||
role.grant_scope_ids = [project.id];
|
|
||||||
try {
|
|
||||||
await role.save();
|
|
||||||
await role.saveGrantStrings([
|
|
||||||
`type=target;actions=list`,
|
|
||||||
`ids=${target.id};actions=authorize-session`,
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
// Redirect to targets that belong to scope
|
|
||||||
this.router.replaceWith('scopes.scope.targets', project.scopeID);
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
async doLater() {
|
|
||||||
this.router.transitionTo('index');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,15 +5,14 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
|
||||||
|
|
||||||
export default class OnboardingSuccessRoute extends Route {
|
export default class OnboardingSuccessRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
@service router;
|
|
||||||
@service session;
|
|
||||||
@service('browser/window') window;
|
@service('browser/window') window;
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
const model = this.modelFor('onboarding');
|
const model = this.modelFor('onboarding');
|
||||||
return {
|
return {
|
||||||
@@ -21,13 +20,4 @@ export default class OnboardingSuccessRoute extends Route {
|
|||||||
clusterUrl: this.window.origin,
|
clusterUrl: this.window.origin,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
showTargetList(scope) {
|
|
||||||
const {
|
|
||||||
project: { id: scopeID },
|
|
||||||
target: { id: targetID },
|
|
||||||
} = scope;
|
|
||||||
this.router.transitionTo('scopes.scope.targets.target', scopeID, targetID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,6 @@
|
|||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { inject as service } from '@ember/service';
|
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 ScopesRoute extends Route {
|
export default class ScopesRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
@@ -41,19 +37,4 @@ export default class ScopesRoute extends Route {
|
|||||||
})
|
})
|
||||||
.catch(() => A([]));
|
.catch(() => A([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the scope and redirects to index.
|
|
||||||
* @param {Model} scope
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
@loading
|
|
||||||
@confirm('resources.policy.questions.detach')
|
|
||||||
@notifyError(({ message }) => message)
|
|
||||||
@notifySuccess('resources.policy.messages.detach')
|
|
||||||
async detachStoragePolicy(scope) {
|
|
||||||
const { storage_policy_id } = scope;
|
|
||||||
scope.storage_policy_id = '';
|
|
||||||
await scope.detachStoragePolicy(storage_policy_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,21 +6,13 @@
|
|||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { action } from '@ember/object';
|
|
||||||
import { next } from '@ember/runloop';
|
|
||||||
import { loading } from 'ember-loading';
|
|
||||||
import { confirm } from 'core/decorators/confirm';
|
|
||||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
|
||||||
import { TYPE_SCOPE_GLOBAL, TYPE_SCOPE_ORG } from 'api/models/scope';
|
import { TYPE_SCOPE_GLOBAL, TYPE_SCOPE_ORG } from 'api/models/scope';
|
||||||
|
|
||||||
export default class ScopesScopeRoute extends Route {
|
export default class ScopesScopeRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
|
|
||||||
@service store;
|
@service store;
|
||||||
@service intl;
|
|
||||||
@service session;
|
|
||||||
@service scope;
|
@service scope;
|
||||||
@service router;
|
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
@@ -105,52 +97,4 @@ export default class ScopesScopeRoute extends Route {
|
|||||||
/* eslint-disable-next-line ember/no-controller-access-in-routes */
|
/* eslint-disable-next-line ember/no-controller-access-in-routes */
|
||||||
this.controller.setProperties({ scopes });
|
this.controller.setProperties({ scopes });
|
||||||
}
|
}
|
||||||
|
|
||||||
// =actions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rollback changes on scope.
|
|
||||||
* @param {Model} scope
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
cancel(scope) {
|
|
||||||
const { isNew } = scope;
|
|
||||||
next(() => {
|
|
||||||
scope.rollbackAttributes();
|
|
||||||
if (isNew) this.router.transitionTo('scopes.scope');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle save scope.
|
|
||||||
* @param {Model} scope
|
|
||||||
* @param {Event} e
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
@loading
|
|
||||||
@notifyError(({ message }) => message)
|
|
||||||
@notifySuccess(({ isNew }) =>
|
|
||||||
isNew ? 'notifications.create-success' : 'notifications.save-success',
|
|
||||||
)
|
|
||||||
async save(scope) {
|
|
||||||
const { isNew } = scope;
|
|
||||||
await scope.save();
|
|
||||||
await this.router.transitionTo('scopes.scope.edit', scope);
|
|
||||||
if (isNew) this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the scope and redirects to index.
|
|
||||||
* @param {Model} scope
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
@loading
|
|
||||||
@confirm('questions.delete-confirm')
|
|
||||||
@notifyError(({ message }) => message, { catch: true })
|
|
||||||
@notifySuccess('notifications.delete-success')
|
|
||||||
async delete(scope) {
|
|
||||||
const { scopeID } = scope;
|
|
||||||
await scope.destroyRecord();
|
|
||||||
this.router.replaceWith('scopes.scope.scopes', scopeID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,11 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { all, hash } from 'rsvp';
|
|
||||||
|
|
||||||
export default class ScopesScopeAliasesRoute extends Route {
|
export default class ScopesScopeAliasesRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
|
|
||||||
@service store;
|
|
||||||
@service session;
|
@service session;
|
||||||
@service can;
|
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
@@ -23,34 +20,4 @@ export default class ScopesScopeAliasesRoute extends Route {
|
|||||||
beforeModel() {
|
beforeModel() {
|
||||||
if (!this.session.isAuthenticated) this.router.transitionTo('index');
|
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',
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
const aliases = await this.store.query('alias', { scope_id });
|
|
||||||
// since we don't receive target info from aliases list API,
|
|
||||||
// we query the store to fetch target information based on the destination id
|
|
||||||
const aliasesWithResourceNames = await all(
|
|
||||||
aliases.map((alias) =>
|
|
||||||
hash({
|
|
||||||
alias,
|
|
||||||
target: alias.destination_id
|
|
||||||
? this.store.findRecord('target', alias.destination_id)
|
|
||||||
: null,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return aliasesWithResourceNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,100 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { hash } from 'rsvp';
|
||||||
|
|
||||||
export default class ScopesScopeAliasesIndexRoute extends Route {
|
export default class ScopesScopeAliasesIndexRoute extends Route {
|
||||||
|
// =services
|
||||||
|
|
||||||
|
@service store;
|
||||||
|
@service can;
|
||||||
|
|
||||||
|
// =attributes
|
||||||
|
|
||||||
|
queryParams = {
|
||||||
|
search: {
|
||||||
|
refreshModel: true,
|
||||||
|
replace: true,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
refreshModel: true,
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
refreshModel: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads queried aliases and the number of aliases under current scope.
|
||||||
|
* @returns {Promise<{totalItems: number, aliases: [object], aliasesExist: boolean}>}
|
||||||
|
*/
|
||||||
|
async model({ search, page, pageSize }) {
|
||||||
|
const scope = this.modelFor('scopes.scope');
|
||||||
|
const { id: scope_id } = scope;
|
||||||
|
|
||||||
|
let aliases;
|
||||||
|
let doAliasesExist = false;
|
||||||
|
let totalItems = 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.can.can('list model', scope, {
|
||||||
|
collection: 'aliases',
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
aliases = await this.store.query('alias', {
|
||||||
|
scope_id,
|
||||||
|
query: { search },
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
});
|
||||||
|
totalItems = aliases.meta?.totalItems;
|
||||||
|
// since we don't receive target info from aliases list API,
|
||||||
|
// we query the store to fetch target information based on the destination id
|
||||||
|
aliases = await Promise.all(
|
||||||
|
aliases.map((alias) =>
|
||||||
|
hash({
|
||||||
|
alias,
|
||||||
|
target: alias.destination_id
|
||||||
|
? this.store.findRecord('target', alias.destination_id, {
|
||||||
|
backgroundReload: false,
|
||||||
|
})
|
||||||
|
: null,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
doAliasesExist = await this.getDoAliasesExist(scope_id, totalItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { aliases, doAliasesExist, totalItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets doAliasesExist to true if there are any aliases.
|
||||||
|
* @param {string} scope_id
|
||||||
|
* @param {number} totalItems
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async getDoAliasesExist(scope_id, totalItems) {
|
||||||
|
if (totalItems > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const options = { pushToStore: false, peekIndexedDB: true };
|
||||||
|
const aliases = await this.store.query(
|
||||||
|
'alias',
|
||||||
|
{
|
||||||
|
scope_id,
|
||||||
|
query: { filters: { scope_id: [{ equals: scope_id }] } },
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
return aliases.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
setupController(controller) {
|
setupController(controller) {
|
||||||
const scope = this.modelFor('scopes.scope');
|
const scope = this.modelFor('scopes.scope');
|
||||||
super.setupController(...arguments);
|
super.setupController(...arguments);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default class ScopesScopeAuthMethodsIndexRoute extends Route {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads queried auth-methods and the number of auth-methods under current scope.
|
* Loads queried auth-methods and the number of auth-methods under current scope.
|
||||||
* @returns {Promise<{totalItems: number, authMethods: [AuthMethodModel], authMethodsExist: boolean }> }
|
* @returns {Promise<{totalItems: number, authMethods: [AuthMethodModel], doAuthMethodsExist: boolean }> }
|
||||||
*/
|
*/
|
||||||
async model({ search, types, primary, page, pageSize }) {
|
async model({ search, types, primary, page, pageSize }) {
|
||||||
const scope = this.modelFor('scopes.scope');
|
const scope = this.modelFor('scopes.scope');
|
||||||
@@ -62,7 +62,7 @@ export default class ScopesScopeAuthMethodsIndexRoute extends Route {
|
|||||||
|
|
||||||
let authMethods;
|
let authMethods;
|
||||||
let totalItems = 0;
|
let totalItems = 0;
|
||||||
let authMethodsExist = false;
|
let doAuthMethodsExist = false;
|
||||||
if (this.can.can('list model', scope, { collection: 'auth-methods' })) {
|
if (this.can.can('list model', scope, { collection: 'auth-methods' })) {
|
||||||
// TODO: Remove storeToken option as this is a temporary fix for auth-methods.
|
// TODO: Remove storeToken option as this is a temporary fix for auth-methods.
|
||||||
const options = { storeToken: false };
|
const options = { storeToken: false };
|
||||||
@@ -77,19 +77,22 @@ export default class ScopesScopeAuthMethodsIndexRoute extends Route {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
totalItems = authMethods.meta?.totalItems;
|
totalItems = authMethods.meta?.totalItems;
|
||||||
authMethodsExist = await this.getAuthMethodsExist(scope_id, totalItems);
|
doAuthMethodsExist = await this.getDoAuthMethodsExist(
|
||||||
|
scope_id,
|
||||||
|
totalItems,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { authMethods, authMethodsExist, totalItems };
|
return { authMethods, doAuthMethodsExist, totalItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets authMethodsExist to true if there exists any auth methods.
|
* Sets doAuthMethodsExist to true if there exists any auth methods.
|
||||||
* @param {string} scope_id
|
* @param {string} scope_id
|
||||||
* @param {number} totalItems
|
* @param {number} totalItems
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async getAuthMethodsExist(scope_id, totalItems) {
|
async getDoAuthMethodsExist(scope_id, totalItems) {
|
||||||
if (totalItems > 0) {
|
if (totalItems > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,11 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { getOwner } from '@ember/application';
|
|
||||||
import { action } from '@ember/object';
|
|
||||||
import { loading } from 'ember-loading';
|
|
||||||
import { notifyError } from 'core/decorators/notify';
|
|
||||||
import {
|
|
||||||
TYPE_AUTH_METHOD_PASSWORD,
|
|
||||||
TYPE_AUTH_METHOD_OIDC,
|
|
||||||
TYPE_AUTH_METHOD_LDAP,
|
|
||||||
} from 'api/models/auth-method';
|
|
||||||
|
|
||||||
export default class ScopesScopeAuthenticateMethodRoute extends Route {
|
export default class ScopesScopeAuthenticateMethodRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
|
|
||||||
@service store;
|
@service store;
|
||||||
@service session;
|
|
||||||
@service intl;
|
|
||||||
@service windowManager;
|
|
||||||
@service router;
|
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
@@ -35,55 +22,4 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
|
|||||||
const adapterOptions = { scopeID: this.modelFor('scopes.scope').id };
|
const adapterOptions = { scopeID: this.modelFor('scopes.scope').id };
|
||||||
return this.store.peekRecord('auth-method', id, adapterOptions);
|
return this.store.peekRecord('auth-method', id, adapterOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Kicks off the OIDC auth flow by opening a new window to the provider.
|
|
||||||
* @param {string} authenticatorName
|
|
||||||
* @param {object} options
|
|
||||||
*/
|
|
||||||
async startOIDCAuthentication(authenticatorName, options) {
|
|
||||||
const oidc = getOwner(this).lookup(authenticatorName);
|
|
||||||
// TODO: delegate this call from the session service so that we don't have
|
|
||||||
// to look up the authenticator directly
|
|
||||||
const json = await oidc.startAuthentication(options);
|
|
||||||
await this.windowManager.open(json.attributes.auth_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =actions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delegates authentication to an authenticator of type matching the current
|
|
||||||
* auth method. If authentication fails, notifies with an alert.
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
@loading
|
|
||||||
@notifyError(() => 'errors.authentication-failed.title', {
|
|
||||||
sticky: false,
|
|
||||||
catch: true,
|
|
||||||
})
|
|
||||||
async authenticate(creds) {
|
|
||||||
const scope = this.modelFor('scopes.scope');
|
|
||||||
const authMethod = this.modelFor('scopes.scope.authenticate.method');
|
|
||||||
const authenticatorName = `authenticator:${authMethod.type}`;
|
|
||||||
const requestCookies = false;
|
|
||||||
switch (authMethod.type) {
|
|
||||||
case TYPE_AUTH_METHOD_PASSWORD:
|
|
||||||
case TYPE_AUTH_METHOD_LDAP:
|
|
||||||
await this.session.authenticate(
|
|
||||||
authenticatorName,
|
|
||||||
creds,
|
|
||||||
requestCookies,
|
|
||||||
{ scope, authMethod },
|
|
||||||
);
|
|
||||||
this.router.transitionTo('index');
|
|
||||||
break;
|
|
||||||
case TYPE_AUTH_METHOD_OIDC:
|
|
||||||
await this.startOIDCAuthentication(authenticatorName, {
|
|
||||||
scope,
|
|
||||||
authMethod,
|
|
||||||
});
|
|
||||||
this.router.transitionTo('scopes.scope.authenticate.method.oidc');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ export default class ScopesScopeCredentialStoresIndexRoute extends Route {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads queried credential-stores and the number of credential-stores under current scope.
|
* Loads queried credential-stores and the number of credential-stores under current scope.
|
||||||
* @returns {Promise<{totalItems: number, credentialStores: [CredentialStoreModel], credentialStoresExist: boolean }> }
|
* @returns {Promise<{totalItems: number, credentialStores: [CredentialStoreModel], doCredentialStoresExist: boolean }> }
|
||||||
*/
|
*/
|
||||||
async model({ search, types, page, pageSize }) {
|
async model({ search, types, page, pageSize }) {
|
||||||
const scope = this.modelFor('scopes.scope');
|
const scope = this.modelFor('scopes.scope');
|
||||||
@@ -51,7 +51,7 @@ export default class ScopesScopeCredentialStoresIndexRoute extends Route {
|
|||||||
|
|
||||||
let credentialStores;
|
let credentialStores;
|
||||||
let totalItems = 0;
|
let totalItems = 0;
|
||||||
let credentialStoresExist = false;
|
let doCredentialStoresExist = false;
|
||||||
if (
|
if (
|
||||||
this.can.can('list model', scope, {
|
this.can.can('list model', scope, {
|
||||||
collection: 'credential-stores',
|
collection: 'credential-stores',
|
||||||
@@ -64,22 +64,21 @@ export default class ScopesScopeCredentialStoresIndexRoute extends Route {
|
|||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
totalItems = credentialStores.meta?.totalItems;
|
totalItems = credentialStores.meta?.totalItems;
|
||||||
credentialStoresExist = await this.getCredentialStoresExist(
|
doCredentialStoresExist = await this.getDoCredentialStoresExist(
|
||||||
scope_id,
|
scope_id,
|
||||||
totalItems,
|
totalItems,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return { credentialStores, doCredentialStoresExist, totalItems };
|
||||||
return { credentialStores, credentialStoresExist, totalItems };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets credentialStoresExist to true if there exists any credential-stores.
|
* Sets doCredentialStoresExist to true if there exists any credential-stores.
|
||||||
* @param {string} scope_id
|
* @param {string} scope_id
|
||||||
* @param {number} totalItems
|
* @param {number} totalItems
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async getCredentialStoresExist(scope_id, totalItems) {
|
async getDoCredentialStoresExist(scope_id, totalItems) {
|
||||||
if (totalItems > 0) {
|
if (totalItems > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 457 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user