Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb895024e0 | ||
|
|
2e94ebdb64 | ||
|
|
ae1e6d2748 | ||
|
|
c72d4f35a0 | ||
|
|
9742d824c4 | ||
|
|
639aa47e19 | ||
|
|
5fa0d11aa9 | ||
|
|
fed32aa57b | ||
|
|
1e2134bd7e | ||
|
|
064375d457 | ||
|
|
deff38de56 | ||
|
|
15950d3a09 | ||
|
|
c4539b8e30 | ||
|
|
904864b14e | ||
|
|
390d179917 | ||
|
|
3b6a421af2 | ||
|
|
cc8d4fe952 | ||
|
|
dfa62dd04b | ||
|
|
520020f2c6 | ||
|
|
31f1ed8dd4 | ||
|
|
5abd4f1f4f | ||
|
|
862ce645cf | ||
|
|
9eccbb91db | ||
|
|
343e1d2193 | ||
|
|
9a11925216 | ||
|
|
f7f1041daa | ||
|
|
1cbf3b227e | ||
|
|
faf4ecfac6 | ||
|
|
ca4aad0a2b | ||
|
|
ed569306bb | ||
|
|
65abd7a4e3 | ||
|
|
422cff4547 | ||
|
|
7695135302 | ||
|
|
5be4868e9a | ||
|
|
fce97fd57a | ||
|
|
af8655ec45 | ||
|
|
daed714b98 | ||
|
|
584e62cff3 | ||
|
|
6d2f1dcbbf | ||
|
|
5e7b74a7ed | ||
|
|
6de20b03ef | ||
|
|
2869020e65 | ||
|
|
40c8ceeca6 | ||
|
|
0623d4ad47 | ||
|
|
0b9c235dd2 | ||
|
|
e16b4e68c8 | ||
|
|
078e36f9b8 | ||
|
|
8874d0be86 | ||
|
|
b59afd3455 | ||
|
|
85a2684532 | ||
|
|
f9a7864b07 | ||
|
|
371301b4ba | ||
|
|
9c10d79661 | ||
|
|
018d879a0a | ||
|
|
b64f1876dc | ||
|
|
055607b3dd | ||
|
|
8a7fa26cb5 | ||
|
|
9cac16fe6a | ||
|
|
61297dba02 | ||
|
|
2f8a53e3ea | ||
|
|
96b9a20844 | ||
|
|
443f230251 | ||
|
|
86ee79e6bf | ||
|
|
b24e5a2c22 | ||
|
|
8c6b1aa69a | ||
|
|
36c2aee271 | ||
|
|
b049635d28 | ||
|
|
42dd029611 | ||
|
|
e02c50aef8 | ||
|
|
d9cf112694 | ||
|
|
a4456f50cd | ||
|
|
a358061f22 | ||
|
|
f155e8ad44 | ||
|
|
c930bb235b | ||
|
|
543cf4697f | ||
|
|
66e9e971a1 | ||
|
|
a961b5e4be | ||
|
|
f8796b362e | ||
|
|
415f638028 | ||
|
|
4523615829 | ||
|
|
61214af01a | ||
|
|
2f0bf5dc17 | ||
|
|
6e71712092 | ||
|
|
7e56e1cccf | ||
|
|
1e3f533d83 | ||
|
|
57568f9a8c | ||
|
|
7c1814118d | ||
|
|
ca50a963fd | ||
|
|
6e188c6996 | ||
|
|
0095c5c666 | ||
|
|
bdd7f303c4 | ||
|
|
f0383522d8 | ||
|
|
a32cc13171 | ||
|
|
dbda8c5674 | ||
|
|
186873d9d4 | ||
|
|
37482c0b36 | ||
|
|
ccdc5a03ac | ||
|
|
2ae24c4627 | ||
|
|
b9e9fd21ee | ||
|
|
6eb83b2a25 | ||
|
|
70aceb1699 | ||
|
|
5e208c4115 | ||
|
|
390f051e43 | ||
|
|
ab0f1fd549 | ||
|
|
788df7b16d | ||
|
|
01a2d8e9a6 | ||
|
|
e842eb001d | ||
|
|
f195e5dc9d | ||
|
|
7f7f0f1859 | ||
|
|
b494a703eb | ||
|
|
0f52b86364 | ||
|
|
1b31188d5e | ||
|
|
30e7268297 | ||
|
|
6956b30fd6 | ||
|
|
98d19ef591 | ||
|
|
f0ffcea424 | ||
|
|
6454fb7061 | ||
|
|
c0d7af7899 | ||
|
|
2424395415 | ||
|
|
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
@@ -11,15 +11,15 @@ jobs:
|
|||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
path: "**/node_modules"
|
||||||
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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@@ -28,8 +28,8 @@ jobs:
|
|||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
path: "**/node_modules"
|
||||||
@@ -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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-oss
|
name: admin-ui-oss
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -56,8 +56,8 @@ jobs:
|
|||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
path: "**/node_modules"
|
||||||
@@ -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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-ent
|
name: admin-ui-ent
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -79,8 +79,8 @@ jobs:
|
|||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
path: "**/node_modules"
|
||||||
@@ -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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: admin-ui-hcp
|
name: admin-ui-hcp
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
name: "Trigger Build Artifacts for Testing"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
description: Please use tag-like format n.n.n-someword (1.111.11-beta)
|
|
||||||
required: true
|
|
||||||
product_version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
description: The version number belonging to the oss binary to be used
|
|
||||||
commit:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
description: The git SHA of the oss artifacts to be used
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
trigger-gha-build-artifacts-for-testing:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Release description
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |
|
|
||||||
echo "Triggering Build Artifacts for Testing using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}"
|
|
||||||
- name: Trigger Build Artifacts for Testing in boundary-ui-releases
|
|
||||||
run: |
|
|
||||||
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
|
||||||
gh workflow run build-artifacts-for-testing.yml \
|
|
||||||
--repo hashicorp/boundary-ui-releases \
|
|
||||||
--ref main \
|
|
||||||
-f TAG="${{ inputs.branch }}" \
|
|
||||||
-f PRODUCT_VERSION="${{ inputs.product_version }}" \
|
|
||||||
-f COMMIT="${{ inputs.commit }}" \
|
|
||||||
@@ -30,11 +30,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
# 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@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.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@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- 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
|
||||||
@@ -6,15 +6,15 @@ jobs:
|
|||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
path: '**/node_modules'
|
||||||
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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@@ -24,15 +24,15 @@ jobs:
|
|||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
path: '**/node_modules'
|
||||||
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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run compliance:licenses
|
- run: yarn run compliance:licenses
|
||||||
@@ -43,15 +43,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
path: '**/node_modules'
|
||||||
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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
@@ -62,13 +62,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
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@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
@@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
- run: |
|
- run: |
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
name: "Trigger Test Boundary UI release"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
description: Please use tag-like format n.n.n-someword (1.111.11-beta)
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
trigger-gha-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Release description
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |
|
|
||||||
echo "Triggering Test release using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}"
|
|
||||||
- name: Trigger Release workflow in boundary-ui-releases
|
|
||||||
run: |
|
|
||||||
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
|
||||||
gh workflow run release.yml \
|
|
||||||
--repo hashicorp/boundary-ui-releases \
|
|
||||||
--ref main \
|
|
||||||
-f TAG="${{ github.event.inputs.branch }}"
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: "Trigger Boundary UI release"
|
name: "Trigger Boundary Desktop release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -9,14 +9,14 @@ jobs:
|
|||||||
trigger-gha-release:
|
trigger-gha-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Release description
|
- name: Release description
|
||||||
run: |
|
run: |
|
||||||
echo "Triggering release with a TAG ${GITHUB_REF#refs/tags/}"
|
echo "Triggering release with a TAG ${GITHUB_REF#refs/tags/}"
|
||||||
- name: Trigger Release workflow in boundary-ui-releases
|
- name: Trigger Release workflow in boundary-desktop-releases
|
||||||
run: |
|
run: |
|
||||||
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
||||||
gh workflow run release.yml \
|
gh workflow run release.yml \
|
||||||
--repo hashicorp/boundary-ui-releases \
|
--repo hashicorp/boundary-desktop-releases \
|
||||||
--ref main \
|
--ref main \
|
||||||
-f TAG="${GITHUB_REF#refs/tags/}"
|
-f TAG="${GITHUB_REF#refs/tags/}"
|
||||||
@@ -1,5 +1,2 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn compliance:licenses
|
yarn compliance:licenses
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
COMMIT_MSG_FILE=$1
|
COMMIT_MSG_FILE=$1
|
||||||
COMMIT_SOURCE=$2
|
COMMIT_SOURCE=$2
|
||||||
SHA1=$3
|
SHA1=$3
|
||||||
|
|||||||
@@ -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));
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,63 +55,224 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
})
|
})
|
||||||
plugin;
|
plugin;
|
||||||
|
|
||||||
// AWS & Azure
|
// AWS, Azure & GCP
|
||||||
|
|
||||||
@attr('boolean', {
|
@attr('boolean', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'azure', 'gcp'],
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
disable_credential_rotation;
|
disable_credential_rotation;
|
||||||
|
|
||||||
|
// AWS and GCP specific
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'gcp'],
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
|
||||||
|
})
|
||||||
|
worker_filter;
|
||||||
|
|
||||||
// AWS specific
|
// AWS specific
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
region;
|
region;
|
||||||
|
|
||||||
|
// AWS static credentials
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'static-credential',
|
||||||
|
},
|
||||||
isNestedSecret: true,
|
isNestedSecret: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
access_key_id;
|
access_key_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'static-credential',
|
||||||
|
},
|
||||||
isNestedSecret: true,
|
isNestedSecret: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
secret_access_key;
|
secret_access_key;
|
||||||
|
|
||||||
|
// AWS dynamic credentials
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role ARN to use.',
|
||||||
|
})
|
||||||
|
role_arn;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role external ID to use.',
|
||||||
|
})
|
||||||
|
role_external_id;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role session to use.',
|
||||||
|
})
|
||||||
|
role_session_name;
|
||||||
|
|
||||||
|
@attr('object-as-array', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The role tags to use.',
|
||||||
|
})
|
||||||
|
role_tags;
|
||||||
|
|
||||||
// Azure specific
|
// Azure specific
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
tenant_id;
|
tenant_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
client_id;
|
client_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
subscription_id;
|
subscription_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
isNestedSecret: true,
|
isNestedSecret: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
secret_id;
|
secret_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
isNestedSecret: true,
|
isNestedSecret: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
secret_value;
|
secret_value;
|
||||||
|
|
||||||
|
// GCP specific
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The project ID associated with the service account.',
|
||||||
|
})
|
||||||
|
project_id;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description: 'The deployment area within a region.',
|
||||||
|
})
|
||||||
|
zone;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description:
|
||||||
|
'The email address associated with the service account. The email address used to uniquely identify the service account. It is required for authentication and authorization.',
|
||||||
|
})
|
||||||
|
client_email;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
isNestedAttribute: true,
|
||||||
|
description:
|
||||||
|
'The unique identifier for the service account that will be impersonated.',
|
||||||
|
})
|
||||||
|
target_service_account_id;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'The ID of the private key used to sign the JWT and authentication.',
|
||||||
|
isNestedSecret: true,
|
||||||
|
})
|
||||||
|
private_key_id;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'gcp',
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'The private key used to sign the JWT and obtain a OAuth 2.0 access token.',
|
||||||
|
isNestedSecret: true,
|
||||||
|
})
|
||||||
|
private_key;
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,10 @@ export default class GeneratedHostSetModel extends BaseModel {
|
|||||||
plugin;
|
plugin;
|
||||||
|
|
||||||
@attr('number', {
|
@attr('number', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'azure', 'gcp'],
|
||||||
|
},
|
||||||
description:
|
description:
|
||||||
"The number of seconds between the time boundary syncs the hosts in this set using this host set's plugin. If not provided a system determined default is used",
|
"The number of seconds between the time boundary syncs the hosts in this set using this host set's plugin. If not provided a system determined default is used",
|
||||||
})
|
})
|
||||||
@@ -79,12 +83,20 @@ export default class GeneratedHostSetModel extends BaseModel {
|
|||||||
host_ids;
|
host_ids;
|
||||||
|
|
||||||
@attr('string-array', {
|
@attr('string-array', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'azure', 'gcp'],
|
||||||
|
},
|
||||||
emptyArrayIfMissing: true,
|
emptyArrayIfMissing: true,
|
||||||
})
|
})
|
||||||
preferred_endpoints;
|
preferred_endpoints;
|
||||||
|
|
||||||
// AWS specific
|
// AWS & GCP specific
|
||||||
@attr('string-array', {
|
@attr('string-array', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'gcp'],
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
emptyArrayIfMissing: true,
|
emptyArrayIfMissing: true,
|
||||||
})
|
})
|
||||||
@@ -92,6 +104,10 @@ export default class GeneratedHostSetModel extends BaseModel {
|
|||||||
|
|
||||||
// Azure specific, comes in from API as "filter". This is to avoid collisions with filter
|
// Azure specific, comes in from API as "filter". This is to avoid collisions with filter
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'azure',
|
||||||
|
},
|
||||||
description: '',
|
description: '',
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -8,7 +8,16 @@ import GeneratedCredentialModel from '../generated/models/credential';
|
|||||||
/**
|
/**
|
||||||
* Supported Credential types.
|
* Supported Credential types.
|
||||||
*/
|
*/
|
||||||
export const types = ['username_password', 'ssh_private_key', 'json'];
|
|
||||||
|
export const TYPE_CREDENTIAL_USERNAME_PASSWORD = 'username_password';
|
||||||
|
export const TYPE_CREDENTIAL_SSH_PRIVATE_KEY = 'ssh_private_key';
|
||||||
|
export const TYPE_CREDENTIAL_JSON = 'json';
|
||||||
|
|
||||||
|
export const TYPES_CREDENTIAL = Object.freeze([
|
||||||
|
TYPE_CREDENTIAL_USERNAME_PASSWORD,
|
||||||
|
TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
|
||||||
|
TYPE_CREDENTIAL_JSON,
|
||||||
|
]);
|
||||||
|
|
||||||
export default class CredentialModel extends GeneratedCredentialModel {
|
export default class CredentialModel extends GeneratedCredentialModel {
|
||||||
// =attributes
|
// =attributes
|
||||||
@@ -28,7 +37,7 @@ export default class CredentialModel extends GeneratedCredentialModel {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isJSON() {
|
get isJSON() {
|
||||||
return this.type === 'json';
|
return this.type === TYPE_CREDENTIAL_JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +45,6 @@ export default class CredentialModel extends GeneratedCredentialModel {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isUnknown() {
|
get isUnknown() {
|
||||||
return !types.includes(this.type);
|
return !TYPES_CREDENTIAL.includes(this.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,14 +14,33 @@ export const TYPES_HOST_CATALOG = Object.freeze([
|
|||||||
|
|
||||||
export const TYPE_HOST_CATALOG_PLUGIN_AWS = 'aws';
|
export const TYPE_HOST_CATALOG_PLUGIN_AWS = 'aws';
|
||||||
export const TYPE_HOST_CATALOG_PLUGIN_AZURE = 'azure';
|
export const TYPE_HOST_CATALOG_PLUGIN_AZURE = 'azure';
|
||||||
|
export const TYPE_HOST_CATALOG_PLUGIN_GCP = 'gcp';
|
||||||
export const TYPES_HOST_CATALOG_PLUGIN = [
|
export const TYPES_HOST_CATALOG_PLUGIN = [
|
||||||
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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 const DYNAMIC_CREDENTIAL_FIELDS = [
|
||||||
|
'role_arn',
|
||||||
|
'role_external_id',
|
||||||
|
'role_session_name',
|
||||||
|
'role_tags',
|
||||||
|
];
|
||||||
|
export const STATIC_CREDENTIAL_FIELDS = ['access_key_id, secret_access_key'];
|
||||||
|
|
||||||
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 +57,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 (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) {
|
||||||
|
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}
|
||||||
@@ -53,7 +95,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isAWS() {
|
get isAWS() {
|
||||||
return this.compositeType === 'aws';
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +103,15 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isAzure() {
|
get isAzure() {
|
||||||
return this.compositeType === 'azure';
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if host catalog plugin type is GCP.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isGCP() {
|
||||||
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import GeneratedHostSetModel from '../generated/models/host-set';
|
import GeneratedHostSetModel from '../generated/models/host-set';
|
||||||
import { TYPES_HOST_CATALOG_PLUGIN } from './host-catalog';
|
import {
|
||||||
|
TYPES_HOST_CATALOG_PLUGIN,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
} from './host-catalog';
|
||||||
|
|
||||||
export default class HostSetModel extends GeneratedHostSetModel {
|
export default class HostSetModel extends GeneratedHostSetModel {
|
||||||
// =attributes
|
// =attributes
|
||||||
@@ -40,14 +45,22 @@ export default class HostSetModel extends GeneratedHostSetModel {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isAWS() {
|
get isAWS() {
|
||||||
return this.compositeType === 'aws';
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return if a host-set plugin is Azure or not.
|
* Return if a host-set plugin is Azure or not.
|
||||||
*/
|
*/
|
||||||
get isAzure() {
|
get isAzure() {
|
||||||
return this.compositeType === 'azure';
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if host catalog plugin type is GCP.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isGCP() {
|
||||||
|
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { equal } from '@ember/object/computed';
|
|||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
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 { flattenObject } from '../utils/flatten-nested-object';
|
||||||
|
import { TYPES_CREDENTIAL_LIBRARY } from 'api/models/credential-library';
|
||||||
|
import { TYPE_CREDENTIAL_JSON } from 'api/models/credential';
|
||||||
|
|
||||||
export const STATUS_SESSION_ACTIVE = 'active';
|
export const STATUS_SESSION_ACTIVE = 'active';
|
||||||
export const STATUS_SESSION_PENDING = 'pending';
|
export const STATUS_SESSION_PENDING = 'pending';
|
||||||
@@ -23,7 +26,7 @@ export const statusTypes = [
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class SessionCredential {
|
export class SessionCredential {
|
||||||
// =classes
|
// =classes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,12 +37,14 @@ class SessionCredential {
|
|||||||
name;
|
name;
|
||||||
description;
|
description;
|
||||||
type;
|
type;
|
||||||
|
credentialtype;
|
||||||
|
|
||||||
constructor(id, name, description, type) {
|
constructor(id, name, description, type, credentialtype) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.credentialType = credentialtype;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,8 +80,10 @@ class SessionCredential {
|
|||||||
get secrets() {
|
get secrets() {
|
||||||
if (this.#payloadSecret?.decoded) {
|
if (this.#payloadSecret?.decoded) {
|
||||||
const secretJSON = this.#payloadSecret.decoded;
|
const secretJSON = this.#payloadSecret.decoded;
|
||||||
return Object.keys(secretJSON).map(
|
return this.extractSecrets(
|
||||||
(key) => new SessionCredential.SecretItem(key, secretJSON[key]),
|
secretJSON,
|
||||||
|
this.source.type,
|
||||||
|
this.source.credentialType,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// decode from base64
|
// decode from base64
|
||||||
@@ -85,11 +92,41 @@ class SessionCredential {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts secrets from the payload secret JSON object.
|
||||||
|
* We only need to flatten the object if the type is vault or if its a json type credential.
|
||||||
|
* @param {object} secretJSON - The payload secret JSON object.
|
||||||
|
* @param {string} type - The credential source type.
|
||||||
|
* @param {string} credentialType - The credential type.
|
||||||
|
* @returns {SessionCredential.SecretItem[]} - The array of secret items.
|
||||||
|
*/
|
||||||
|
extractSecrets(secretJSON, type, credentialType) {
|
||||||
|
let source;
|
||||||
|
if (credentialType === TYPE_CREDENTIAL_JSON) {
|
||||||
|
source = flattenObject(secretJSON);
|
||||||
|
} else if (TYPES_CREDENTIAL_LIBRARY.includes(type) && secretJSON?.data) {
|
||||||
|
source = flattenObject(secretJSON.data);
|
||||||
|
} else {
|
||||||
|
source = secretJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(source).map(
|
||||||
|
([key, value]) => new SessionCredential.SecretItem(key, value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
constructor(cred) {
|
constructor(cred) {
|
||||||
const { id, name, description, type } = cred.credential_source;
|
const { id, name, description, type, credential_type } =
|
||||||
this.source = new SessionCredential.Source(id, name, description, type);
|
cred.credential_source;
|
||||||
|
this.source = new SessionCredential.Source(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
type,
|
||||||
|
credential_type,
|
||||||
|
);
|
||||||
this.#payloadSecret = cred.secret;
|
this.#payloadSecret = cred.secret;
|
||||||
this.rawCredential = cred;
|
this.rawCredential = cred;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import RESTSerializer from '@ember-data/serializer/rest';
|
import RESTSerializer from '@ember-data/serializer/rest';
|
||||||
import { underscore } from '@ember/string';
|
import { underscore } from '@ember/string';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
import { typeOf } from '@ember/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages serialization/normalization of data to/from the API.
|
* Manages serialization/normalization of data to/from the API.
|
||||||
@@ -42,7 +43,7 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
*/
|
*/
|
||||||
serializeAttribute(snapshot, json, key, attribute) {
|
serializeAttribute(snapshot, json, key, attribute) {
|
||||||
const { type, options } = attribute;
|
const { type, options } = attribute;
|
||||||
const { type: recordType } = snapshot.record;
|
const { type: recordType, compositeType } = snapshot.record;
|
||||||
let value = super.serializeAttribute(...arguments);
|
let value = super.serializeAttribute(...arguments);
|
||||||
// Remove secret attributes that are null or empty
|
// Remove secret attributes that are null or empty
|
||||||
if (options.isSecret) {
|
if (options.isSecret) {
|
||||||
@@ -74,18 +75,38 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
delete json[key];
|
delete json[key];
|
||||||
}
|
}
|
||||||
// If an attribute has a `for` option, it must match the
|
// If an attribute has a `for` option, it must match the
|
||||||
// record's `type`, else the attribute is excluded
|
// record's `type`, otherwise the attribute is excluded
|
||||||
// from serialization.
|
// from serialization.
|
||||||
//note: This doesn't handle secrets yet, we can add support for them if needed.
|
|
||||||
if (
|
if (options?.for) {
|
||||||
options?.for &&
|
// Check the typeof the `for` option to determine if it is a plugin or not
|
||||||
options.for !== recordType &&
|
const isPlugin = options.for?.type === 'plugin';
|
||||||
!options.for.includes(recordType)
|
// Helper function to handle deleting attribute and secret keys
|
||||||
) {
|
const deleteKey = (json) => {
|
||||||
if (options.isNestedAttribute) {
|
if (options.isNestedAttribute) {
|
||||||
delete json?.attributes?.[key];
|
delete json?.attributes?.[key];
|
||||||
} else {
|
} else if (options.isNestedSecret && json?.secrets?.[key]) {
|
||||||
delete json[key];
|
delete json?.secrets?.[key];
|
||||||
|
if (Object.keys(json.secrets).length === 0) {
|
||||||
|
delete json.secrets;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete json[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For plugins, the `for` option is an object with a `name` key
|
||||||
|
// For non-plugins, the `for` option can be string or an array
|
||||||
|
if (
|
||||||
|
(isPlugin && !options.for?.name.includes(compositeType)) ||
|
||||||
|
(!isPlugin &&
|
||||||
|
!options.for?.includes(recordType) &&
|
||||||
|
typeOf(options.for) === 'array') ||
|
||||||
|
(!isPlugin &&
|
||||||
|
options.for !== recordType &&
|
||||||
|
typeOf(options.for) === 'string')
|
||||||
|
) {
|
||||||
|
deleteKey(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -4,61 +4,45 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ApplicationSerializer from './application';
|
import ApplicationSerializer from './application';
|
||||||
|
import { TYPE_CREDENTIAL_DYNAMIC } from '../models/host-catalog';
|
||||||
const fieldsByType = {
|
|
||||||
aws: [
|
|
||||||
'disable_credential_rotation',
|
|
||||||
'region',
|
|
||||||
'access_key_id',
|
|
||||||
'secret_access_key',
|
|
||||||
],
|
|
||||||
azure: [
|
|
||||||
'disable_credential_rotation',
|
|
||||||
'tenant_id',
|
|
||||||
'client_id',
|
|
||||||
'subscription_id',
|
|
||||||
'secret_id',
|
|
||||||
'secret_value',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class HostCatalogSerializer extends ApplicationSerializer {
|
export default class HostCatalogSerializer extends ApplicationSerializer {
|
||||||
// =methods
|
|
||||||
serialize(snapshot) {
|
serialize(snapshot) {
|
||||||
|
const { credentialType } = snapshot.record;
|
||||||
const serialized = super.serialize(...arguments);
|
const serialized = super.serialize(...arguments);
|
||||||
switch (snapshot.record.compositeType) {
|
// By default, disable credential rotation for dynamic credentials
|
||||||
case 'static':
|
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
|
||||||
return this.serializeStatic(...arguments);
|
serialized.attributes.disable_credential_rotation = true;
|
||||||
default:
|
|
||||||
return serialized;
|
|
||||||
}
|
}
|
||||||
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 } = snapshot.record;
|
||||||
const { options } = attribute;
|
const { options } = attribute;
|
||||||
// Nested conditionals for readability
|
const isSamePluginName = options?.for?.name?.includes(compositeType);
|
||||||
// This deletes any fields that don't belong to the record type
|
|
||||||
if (isPlugin && options.isNestedAttribute && json.attributes) {
|
|
||||||
// The key must be included in the fieldsByType list above
|
|
||||||
if (!fieldsByType[compositeType].includes(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
|
// Remove attributes that are not applicable to the current credential type for the same plugin
|
||||||
|
// For example, 'aws' dynamic plugin has two credential types: 'static' and 'dynamic'
|
||||||
|
// API expects the fields to be 'null' for the fields that are not from the current credential type (that's why we are not using the application serializer for this, where we delete attrs)
|
||||||
|
// Empty secrets should be removed from the JSON
|
||||||
|
if (
|
||||||
|
isSamePluginName &&
|
||||||
|
options.for.credentialType &&
|
||||||
|
options.for.credentialType !== credentialType
|
||||||
|
) {
|
||||||
|
if (options.isNestedAttribute) {
|
||||||
|
json.attributes[key] = null;
|
||||||
|
} else if (options.isNestedSecret && json.secrets[key]) {
|
||||||
|
delete json.secrets[key];
|
||||||
|
if (Object.keys(json.secrets).length === 0) {
|
||||||
|
delete json.secrets;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete json[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeStatic() {
|
|
||||||
const serialized = super.serialize(...arguments);
|
|
||||||
// Delete unnecessary fields for static host-catalog
|
|
||||||
delete serialized.attributes;
|
|
||||||
delete serialized.secrets;
|
|
||||||
return serialized;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,6 @@
|
|||||||
|
|
||||||
import ApplicationSerializer from './application';
|
import ApplicationSerializer from './application';
|
||||||
|
|
||||||
const fieldByType = {
|
|
||||||
aws: ['preferred_endpoints', 'filters', 'sync_interval_seconds'],
|
|
||||||
azure: ['preferred_endpoints', 'filter_string', 'sync_interval_seconds'],
|
|
||||||
};
|
|
||||||
export default class HostSetSerializer extends ApplicationSerializer {
|
export default class HostSetSerializer extends ApplicationSerializer {
|
||||||
// =properties
|
// =properties
|
||||||
|
|
||||||
@@ -63,16 +59,8 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
|||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeAttribute(snapshot, json, key, attribute) {
|
serializeAttribute(snapshot, json, key) {
|
||||||
const value = super.serializeAttribute(...arguments);
|
const value = super.serializeAttribute(...arguments);
|
||||||
const { isPlugin, compositeType } = snapshot.record;
|
|
||||||
const { options } = attribute;
|
|
||||||
|
|
||||||
if (isPlugin && options.isNestedAttribute) {
|
|
||||||
if (!fieldByType[compositeType].includes(key)) {
|
|
||||||
delete json.attributes[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'filter_string') {
|
if (key === 'filter_string') {
|
||||||
const { filter_string } = json.attributes;
|
const { filter_string } = json.attributes;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
import { typeOf } from '@ember/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens a nested object into a single-level object.
|
||||||
|
* @param obj
|
||||||
|
* @param result
|
||||||
|
* @param parentKey
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const flattenObject = (obj, result = {}, parentKey = '') => {
|
||||||
|
for (const key in obj) {
|
||||||
|
// Check if the value is an object and it is not null
|
||||||
|
if (typeOf(obj[key]) === 'object' && obj[key] !== null) {
|
||||||
|
// Recursively flatten the object
|
||||||
|
flattenObject(obj[key], result, `${parentKey}${key}.`);
|
||||||
|
} else if (obj[key] !== null && obj[key] !== '') {
|
||||||
|
// Only add to result if the value is not null or an empty string
|
||||||
|
result[`${parentKey}${key}`] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { default, initialize } from 'api/initializers/deprecations';
|
|
||||||
@@ -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 } }) => {
|
||||||
|
|||||||
@@ -8,11 +8,23 @@ import { trait } from 'miragejs';
|
|||||||
import permissions from '../helpers/permissions';
|
import permissions from '../helpers/permissions';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import generateId from '../helpers/id';
|
import generateId from '../helpers/id';
|
||||||
|
import {
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_STATIC,
|
||||||
|
TYPE_HOST_CATALOG_DYNAMIC,
|
||||||
|
TYPES_HOST_CATALOG,
|
||||||
|
} from 'api/models/host-catalog';
|
||||||
|
|
||||||
const types = ['static', 'plugin'];
|
|
||||||
// Represents known plugin types, except "foobar" which models the possibility
|
// Represents known plugin types, except "foobar" which models the possibility
|
||||||
// of receiving an _unknown_ type, which the UI must handle gracefully.
|
// of receiving an _unknown_ type, which the UI must handle gracefully.
|
||||||
const pluginTypes = ['aws', 'azure', 'foobar'];
|
const pluginTypes = [
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
'foobar',
|
||||||
|
];
|
||||||
|
|
||||||
let pluginTypeCounter = 1;
|
let pluginTypeCounter = 1;
|
||||||
|
|
||||||
@@ -20,7 +32,7 @@ export default factory.extend({
|
|||||||
id: () => generateId('hc_'),
|
id: () => generateId('hc_'),
|
||||||
|
|
||||||
// Cycle through available types
|
// Cycle through available types
|
||||||
type: (i) => types[i % types.length],
|
type: (i) => TYPES_HOST_CATALOG[i % TYPES_HOST_CATALOG.length],
|
||||||
|
|
||||||
authorized_actions: () =>
|
authorized_actions: () =>
|
||||||
permissions.authorizedActionsFor('host-catalog') || [
|
permissions.authorizedActionsFor('host-catalog') || [
|
||||||
@@ -30,7 +42,7 @@ export default factory.extend({
|
|||||||
'delete',
|
'delete',
|
||||||
],
|
],
|
||||||
authorized_collection_actions: function () {
|
authorized_collection_actions: function () {
|
||||||
const isStatic = this.type === 'static';
|
const isStatic = this.type === TYPE_HOST_CATALOG_STATIC;
|
||||||
return {
|
return {
|
||||||
// only static catalogs allow host create at this time
|
// only static catalogs allow host create at this time
|
||||||
hosts: isStatic ? ['create', 'list'] : ['list'],
|
hosts: isStatic ? ['create', 'list'] : ['list'],
|
||||||
@@ -38,8 +50,18 @@ export default factory.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
worker_filter: function () {
|
||||||
|
if (
|
||||||
|
this.type === TYPE_HOST_CATALOG_DYNAMIC &&
|
||||||
|
(this.plugin?.name === TYPE_HOST_CATALOG_PLUGIN_AWS ||
|
||||||
|
this.plugin?.name === TYPE_HOST_CATALOG_PLUGIN_GCP)
|
||||||
|
) {
|
||||||
|
return `"${faker.word.noun()}" in "${faker.system.directoryPath()}"`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
plugin: function (i) {
|
plugin: function (i) {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
||||||
return {
|
return {
|
||||||
id: `plugin-id-${i}`,
|
id: `plugin-id-${i}`,
|
||||||
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
|
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
|
||||||
@@ -50,35 +72,50 @@ export default factory.extend({
|
|||||||
|
|
||||||
attributes() {
|
attributes() {
|
||||||
switch (this.plugin?.name) {
|
switch (this.plugin?.name) {
|
||||||
case 'aws':
|
case TYPE_HOST_CATALOG_PLUGIN_AWS:
|
||||||
return {
|
return {
|
||||||
disable_credential_rotation: faker.datatype.boolean(),
|
disable_credential_rotation: faker.datatype.boolean(),
|
||||||
region: `us-${faker.location.cardinalDirection()}-${faker.number.int(
|
region: `us-${faker.location.cardinalDirection()}-${faker.number.int(
|
||||||
9,
|
9,
|
||||||
)}`,
|
)}`,
|
||||||
};
|
};
|
||||||
case 'azure':
|
case TYPE_HOST_CATALOG_PLUGIN_AZURE:
|
||||||
return {
|
return {
|
||||||
disable_credential_rotation: faker.datatype.boolean(),
|
disable_credential_rotation: faker.datatype.boolean(),
|
||||||
tenant_id: faker.string.uuid(),
|
tenant_id: faker.string.uuid(),
|
||||||
client_id: faker.string.uuid(),
|
client_id: faker.string.uuid(),
|
||||||
subscription_id: faker.string.uuid(),
|
subscription_id: faker.string.uuid(),
|
||||||
};
|
};
|
||||||
|
case TYPE_HOST_CATALOG_PLUGIN_GCP:
|
||||||
|
return {
|
||||||
|
disable_credential_rotation: faker.datatype.boolean(),
|
||||||
|
project_id: faker.string.uuid(),
|
||||||
|
client_email: faker.internet.email(),
|
||||||
|
target_service_account_id: faker.string.uuid(),
|
||||||
|
zone: `us-${faker.location.cardinalDirection()}${faker.number.int(
|
||||||
|
9,
|
||||||
|
)}-${faker.string.fromCharacters('abc')}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
secrets() {
|
secrets() {
|
||||||
switch (this.plugin?.name) {
|
switch (this.plugin?.name) {
|
||||||
case 'aws':
|
case TYPE_HOST_CATALOG_PLUGIN_AWS:
|
||||||
return {
|
return {
|
||||||
access_key_id: faker.string.nanoid(),
|
access_key_id: faker.string.nanoid(),
|
||||||
secret_access_key: faker.string.nanoid(),
|
secret_access_key: faker.string.nanoid(),
|
||||||
};
|
};
|
||||||
case 'azure':
|
case TYPE_HOST_CATALOG_PLUGIN_AZURE:
|
||||||
return {
|
return {
|
||||||
secret_id: faker.string.nanoid(),
|
secret_id: faker.string.nanoid(),
|
||||||
secret_value: faker.string.nanoid(),
|
secret_value: faker.string.nanoid(),
|
||||||
};
|
};
|
||||||
|
case TYPE_HOST_CATALOG_PLUGIN_GCP:
|
||||||
|
return {
|
||||||
|
private_key: faker.string.nanoid(),
|
||||||
|
private_key_id: faker.string.nanoid(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
withChildren: trait({
|
withChildren: trait({
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ import factory from '../generated/factories/host-set';
|
|||||||
import permissions from '../helpers/permissions';
|
import permissions from '../helpers/permissions';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import generateId from '../helpers/id';
|
import generateId from '../helpers/id';
|
||||||
|
import {
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
} from 'api/models/host-catalog';
|
||||||
|
|
||||||
|
function generateFilters(prefix) {
|
||||||
|
const filtersAmount = faker.number.int({ min: 1, max: 5 });
|
||||||
|
let filters = [];
|
||||||
|
for (let i = 0; i < filtersAmount; ++i) {
|
||||||
|
filters.push(`${prefix}${faker.word.words(1)}=${faker.word.words(1)}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
filters,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default factory.extend({
|
export default factory.extend({
|
||||||
id: () => generateId('hs_'),
|
id: () => generateId('hs_'),
|
||||||
@@ -42,22 +58,15 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
attributes() {
|
attributes() {
|
||||||
// AWS specific
|
switch (this.plugin?.name) {
|
||||||
if (this.plugin?.name === 'aws') {
|
case TYPE_HOST_CATALOG_PLUGIN_AWS:
|
||||||
const filtersAmount = faker.number.int({ min: 1, max: 5 });
|
return generateFilters('tag:');
|
||||||
let filters = [];
|
case TYPE_HOST_CATALOG_PLUGIN_AZURE:
|
||||||
for (let i = 0; i < filtersAmount; ++i) {
|
return {
|
||||||
filters.push(`${faker.word.words(1)}=${faker.word.words(1)}`);
|
filter: `${faker.database.column()}=${faker.database.collation()}`,
|
||||||
}
|
};
|
||||||
return {
|
case TYPE_HOST_CATALOG_PLUGIN_GCP:
|
||||||
filters,
|
return generateFilters('labels.');
|
||||||
};
|
|
||||||
}
|
|
||||||
// Azure specific
|
|
||||||
if (this.plugin?.name === 'azure') {
|
|
||||||
return {
|
|
||||||
filter: `${faker.database.column()}=${faker.database.collation()}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -7,24 +7,28 @@ import factory from '../generated/factories/host';
|
|||||||
import permissions from '../helpers/permissions';
|
import permissions from '../helpers/permissions';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import generateId from '../helpers/id';
|
import generateId from '../helpers/id';
|
||||||
|
import {
|
||||||
|
TYPE_HOST_CATALOG_STATIC,
|
||||||
|
TYPE_HOST_CATALOG_DYNAMIC,
|
||||||
|
} from 'api/models/host-catalog';
|
||||||
|
|
||||||
export default factory.extend({
|
export default factory.extend({
|
||||||
id: () => generateId('h_'),
|
id: () => generateId('h_'),
|
||||||
name() {
|
name() {
|
||||||
if (this.type === 'static') return faker.word.words();
|
if (this.type === TYPE_HOST_CATALOG_STATIC) return faker.word.words();
|
||||||
},
|
},
|
||||||
type() {
|
type() {
|
||||||
return this.hostCatalog?.type || factory.attrs.type;
|
return this.hostCatalog?.type || factory.attrs.type;
|
||||||
},
|
},
|
||||||
authorized_actions: function () {
|
authorized_actions: function () {
|
||||||
const isStatic = this.type === 'static';
|
const isStatic = this.type === TYPE_HOST_CATALOG_STATIC;
|
||||||
const defaults = ['no-op', 'read'];
|
const defaults = ['no-op', 'read'];
|
||||||
// Only static allows update/delete at this time.
|
// Only static allows update/delete at this time.
|
||||||
if (isStatic) defaults.push('update', 'delete');
|
if (isStatic) defaults.push('update', 'delete');
|
||||||
return permissions.authorizedActionsFor('host') || defaults;
|
return permissions.authorizedActionsFor('host') || defaults;
|
||||||
},
|
},
|
||||||
plugin() {
|
plugin() {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
||||||
return this.hostCatalog.plugin;
|
return this.hostCatalog.plugin;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -38,12 +42,12 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
// Return external fields only for plugins
|
// Return external fields only for plugins
|
||||||
external_id() {
|
external_id() {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
||||||
return faker.string.uuid();
|
return faker.string.uuid();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
external_name(i) {
|
external_name(i) {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
|
||||||
return `${this.plugin.name} provided name ${i}`;
|
return `${this.plugin.name} provided name ${i}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default factory.extend({
|
|||||||
authorized_actions() {
|
authorized_actions() {
|
||||||
const authorizedActions = ['no-op', 'read', 'update'];
|
const authorizedActions = ['no-op', 'read', 'update'];
|
||||||
|
|
||||||
// Storage policies can only be attatched to global and org scopes.
|
// Storage policies can only be attached to global and org scopes.
|
||||||
if (this.type === 'global' || this.type === 'org') {
|
if (this.type === 'global' || this.type === 'org') {
|
||||||
authorizedActions.push('attach-storage-policy', 'detach-storage-policy');
|
authorizedActions.push('attach-storage-policy', 'detach-storage-policy');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// import config from '../../config/environment';
|
// import config from '../../config/environment';
|
||||||
// import { v1 } from 'ember-uuid';
|
|
||||||
import { Response } from 'miragejs';
|
import { Response } from 'miragejs';
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
|
|||||||
@@ -83,6 +83,37 @@ export default function initializeMockIPC(server, config) {
|
|||||||
raw: btoa('just-a-random-string'),
|
raw: btoa('just-a-random-string'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
credential_source: {
|
||||||
|
id: 'clvlt_4cvscMTl0N',
|
||||||
|
name: 'Credential Library 0',
|
||||||
|
description: 'Source Description',
|
||||||
|
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||||
|
type: 'vault-generic',
|
||||||
|
},
|
||||||
|
secret: {
|
||||||
|
raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ',
|
||||||
|
decoded: {
|
||||||
|
data: {
|
||||||
|
backslash: 'password\\withslash\\',
|
||||||
|
backslash1: 'password\\fwithslash\\f',
|
||||||
|
password: '123',
|
||||||
|
username: 'test',
|
||||||
|
email: {
|
||||||
|
address: 'test.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
created_time: '2024-04-12T18:38:36.226715555Z',
|
||||||
|
custom_metadata: null,
|
||||||
|
deletion_time: '',
|
||||||
|
destroyed: false,
|
||||||
|
version: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
credential_source: {
|
credential_source: {
|
||||||
id: 'credjson_7cKBbBEkC3',
|
id: 'credjson_7cKBbBEkC3',
|
||||||
@@ -98,6 +129,7 @@ export default function initializeMockIPC(server, config) {
|
|||||||
nested_secret: {
|
nested_secret: {
|
||||||
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
||||||
complex_nest: {
|
complex_nest: {
|
||||||
|
blackslash: 'password\\withslash\\',
|
||||||
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -190,6 +222,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() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+14
-17
@@ -24,37 +24,38 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.8.1",
|
||||||
"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": "^4.0.4",
|
||||||
"@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": "^9.1.0",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^4.12.1",
|
"ember-cli": "^5.12.0",
|
||||||
"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,16 +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.22.0",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^3.2.1",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"ember-source": ">4.0.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -81,6 +81,19 @@ module('Unit | Model | host catalog', function (hooks) {
|
|||||||
assert.false(modelRandom.isAzure);
|
assert.false(modelRandom.isAzure);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it has isGCP property and return the expected values', async function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const modelGCP = store.createRecord('host-catalog', {
|
||||||
|
type: 'plugin',
|
||||||
|
plugin: { name: 'gcp' },
|
||||||
|
});
|
||||||
|
const modelRandom = store.createRecord('host-catalog', {
|
||||||
|
plugin: { name: 'random' },
|
||||||
|
});
|
||||||
|
assert.true(modelGCP.isGCP);
|
||||||
|
assert.false(modelRandom.isGCP);
|
||||||
|
});
|
||||||
|
|
||||||
test('get compositeType returns expected values', async function (assert) {
|
test('get compositeType returns expected values', async function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
const modelA = store.createRecord('host-catalog', {
|
const modelA = store.createRecord('host-catalog', {
|
||||||
@@ -94,8 +107,13 @@ module('Unit | Model | host catalog', function (hooks) {
|
|||||||
type: 'plugin',
|
type: 'plugin',
|
||||||
plugin: { name: 'no-such-type' },
|
plugin: { name: 'no-such-type' },
|
||||||
});
|
});
|
||||||
|
const modelD = store.createRecord('host-catalog', {
|
||||||
|
type: 'plugin',
|
||||||
|
plugin: { name: 'gcp' },
|
||||||
|
});
|
||||||
assert.strictEqual(modelA.compositeType, 'static');
|
assert.strictEqual(modelA.compositeType, 'static');
|
||||||
assert.strictEqual(modelB.compositeType, 'aws');
|
assert.strictEqual(modelB.compositeType, 'aws');
|
||||||
|
assert.strictEqual(modelD.compositeType, 'gcp');
|
||||||
assert.strictEqual(modelC.compositeType, 'unknown');
|
assert.strictEqual(modelC.compositeType, 'unknown');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,8 +125,12 @@ module('Unit | Model | host catalog', function (hooks) {
|
|||||||
const modelStatic = store.createRecord('host-catalog', {
|
const modelStatic = store.createRecord('host-catalog', {
|
||||||
compositeType: 'static',
|
compositeType: 'static',
|
||||||
});
|
});
|
||||||
|
const modelGCP = store.createRecord('host-catalog', {
|
||||||
|
compositeType: 'gcp',
|
||||||
|
});
|
||||||
assert.strictEqual(modelPlugin.type, 'plugin');
|
assert.strictEqual(modelPlugin.type, 'plugin');
|
||||||
assert.strictEqual(modelPlugin.plugin.name, 'aws');
|
assert.strictEqual(modelPlugin.plugin.name, 'aws');
|
||||||
assert.strictEqual(modelStatic.type, 'static');
|
assert.strictEqual(modelStatic.type, 'static');
|
||||||
|
assert.strictEqual(modelGCP.plugin.name, 'gcp');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import { SessionCredential } from 'api/models/session';
|
||||||
|
import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library';
|
||||||
|
|
||||||
module('Unit | Model | session', function (hooks) {
|
module('Unit | Model | session', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
@@ -16,6 +18,46 @@ module('Unit | Model | session', function (hooks) {
|
|||||||
assert.notOk(model.isAvailable);
|
assert.notOk(model.isAvailable);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('`extractSecrets` method should return an array of secret items', function (assert) {
|
||||||
|
const sessionCredential = new SessionCredential({
|
||||||
|
credential_source: {
|
||||||
|
id: '123',
|
||||||
|
name: 'Test Source',
|
||||||
|
description: 'Test Description',
|
||||||
|
type: 'Test Type',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const secrets = {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedSecretes = sessionCredential.extractSecrets(
|
||||||
|
secrets,
|
||||||
|
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||||
|
);
|
||||||
|
assert.deepEqual(formattedSecretes, [
|
||||||
|
new SessionCredential.SecretItem('username', 'user'),
|
||||||
|
new SessionCredential.SecretItem('password', 'pass'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// check for nested data, empty values should be filtered out
|
||||||
|
const nestedSecrets = {
|
||||||
|
data: { username: 'user', password: '', email: 'test.com' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedNestedSecrets = sessionCredential.extractSecrets(
|
||||||
|
nestedSecrets,
|
||||||
|
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(formattedNestedSecrets, [
|
||||||
|
new SessionCredential.SecretItem('username', 'user'),
|
||||||
|
new SessionCredential.SecretItem('email', 'test.com'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('it allows cancellation of an active session', function (assert) {
|
test('it allows cancellation of an active session', function (assert) {
|
||||||
let store = this.owner.lookup('service:store');
|
let store = this.owner.lookup('service:store');
|
||||||
let model = store.createRecord('session', {
|
let model = store.createRecord('session', {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
|
import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
|
||||||
|
import { TYPE_HOST_CATALOG_PLUGIN_GCP } from 'api/models/host-catalog';
|
||||||
|
|
||||||
module('Unit | Serializer | application', function (hooks) {
|
module('Unit | Serializer | application', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
@@ -133,7 +134,7 @@ module('Unit | Serializer | application', function (hooks) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it serializes attributes with `for` option containing array or string correctly', function (assert) {
|
test('it serializes attributes with `for` option containing array or string correctly for non plugins', function (assert) {
|
||||||
let store = this.owner.lookup('service:store');
|
let store = this.owner.lookup('service:store');
|
||||||
let record = store.createRecord('auth-method', {
|
let record = store.createRecord('auth-method', {
|
||||||
type: TYPE_AUTH_METHOD_OIDC, //has both string and array `for` option
|
type: TYPE_AUTH_METHOD_OIDC, //has both string and array `for` option
|
||||||
@@ -184,6 +185,54 @@ module('Unit | Serializer | application', function (hooks) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it serializes attributes with `for` option containing array or string correctly for plugin types', function (assert) {
|
||||||
|
let store = this.owner.lookup('service:store');
|
||||||
|
let record = store.createRecord('host-catalog', {
|
||||||
|
compositeType: TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
name: 'GCP plugin',
|
||||||
|
description: 'this is a GCP plugin host-catalog',
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
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' },
|
||||||
|
],
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
|
client_email: 'email',
|
||||||
|
target_service_account_id: 'service-account',
|
||||||
|
private_key_id: 'key-id',
|
||||||
|
private_key: 'key',
|
||||||
|
});
|
||||||
|
|
||||||
|
let serializedRecord = record.serialize();
|
||||||
|
const expectedResult = {
|
||||||
|
name: 'GCP plugin',
|
||||||
|
description: 'this is a GCP plugin host-catalog',
|
||||||
|
type: 'plugin',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
attributes: {
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
|
target_service_account_id: 'service-account',
|
||||||
|
client_email: 'email',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
private_key_id: 'key-id',
|
||||||
|
private_key: 'key',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(serializedRecord, expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
test('it normalizes array records from an `items` root key', function (assert) {
|
test('it normalizes array records from an `items` root key', function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
const serializer = store.serializerFor('user');
|
const serializer = store.serializerFor('user');
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
|
|
||||||
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,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_GCP,
|
||||||
|
} from 'api/models/host-catalog';
|
||||||
|
|
||||||
module('Unit | Serializer | host catalog', function (hooks) {
|
module('Unit | Serializer | host catalog', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
@@ -27,17 +33,153 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
name: 'static',
|
name: 'static',
|
||||||
description: 'this is a static host-catalog',
|
description: 'this is a static host-catalog',
|
||||||
type: 'static',
|
type: 'static',
|
||||||
|
attributes: {},
|
||||||
};
|
};
|
||||||
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 gcp 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_GCP,
|
||||||
|
name: 'GCP',
|
||||||
|
description: 'this is a GCP plugin host-catalog',
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
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' },
|
||||||
|
],
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
|
client_email: 'email',
|
||||||
|
target_service_account_id: 'service-account',
|
||||||
|
private_key_id: 'key-id',
|
||||||
|
private_key: 'key',
|
||||||
|
});
|
||||||
|
const expectedResult = {
|
||||||
|
name: 'GCP',
|
||||||
|
description: 'this is a GCP plugin host-catalog',
|
||||||
|
type: 'plugin',
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
|
attributes: {
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
|
target_service_account_id: 'service-account',
|
||||||
|
client_email: 'email',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
private_key_id: 'key-id',
|
||||||
|
private_key: 'key',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it serializes a new aws static 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,
|
||||||
|
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',
|
||||||
|
region: 'west',
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
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_arn: null,
|
||||||
|
role_external_id: null,
|
||||||
|
role_session_name: null,
|
||||||
|
role_tags: null,
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
access_key_id: 'foobars',
|
||||||
|
secret_access_key: 'testing',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it serializes a new aws dynamic credential type 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 static AWS fields and should be excluded
|
||||||
|
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 credential type plugin as expected, ignoring azure and GCP 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',
|
||||||
@@ -48,14 +190,21 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
subscription_id: 'a1b2c3',
|
subscription_id: 'a1b2c3',
|
||||||
secret_id: 'a1b2c3',
|
secret_id: 'a1b2c3',
|
||||||
secret_value: 'a1b2c3',
|
secret_value: 'a1b2c3',
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
});
|
});
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
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',
|
||||||
|
role_arn: null,
|
||||||
|
role_external_id: null,
|
||||||
|
role_session_name: null,
|
||||||
|
role_tags: null,
|
||||||
},
|
},
|
||||||
secrets: {
|
secrets: {
|
||||||
access_key_id: 'foobars',
|
access_key_id: 'foobars',
|
||||||
@@ -65,7 +214,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
assert.deepEqual(record.serialize(), expectedResult);
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it serializes a new azure record as expected, ignoring aws fields', async function (assert) {
|
test('it serializes a new azure record as expected, ignoring aws and GCP fields', 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: 'azure',
|
compositeType: 'azure',
|
||||||
@@ -75,12 +224,15 @@ 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',
|
||||||
subscription_id: 'a1b2c3',
|
subscription_id: 'a1b2c3',
|
||||||
secret_id: 'a1b2c3',
|
secret_id: 'a1b2c3',
|
||||||
secret_value: 'a1b2c3',
|
secret_value: 'a1b2c3',
|
||||||
|
project_id: 'project',
|
||||||
|
zone: 'zone',
|
||||||
});
|
});
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
name: 'Azure',
|
name: 'Azure',
|
||||||
@@ -106,6 +258,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 +266,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
plugin: {
|
plugin: {
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
},
|
},
|
||||||
|
worker_filter: 'workerfilter',
|
||||||
region: 'andorra',
|
region: 'andorra',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,9 +276,14 @@ 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',
|
||||||
|
role_arn: null,
|
||||||
|
role_external_id: null,
|
||||||
|
role_session_name: null,
|
||||||
|
role_tags: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.deepEqual(record.serialize(), expectedResult);
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
@@ -164,7 +323,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
assert.deepEqual(record.serialize(), expectedResult);
|
assert.deepEqual(record.serialize(), expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it normalizes an static host catalog as expected', function (assert) {
|
test('it normalizes a static host catalog as expected', function (assert) {
|
||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
const serializer = store.serializerFor('host-catalog');
|
const serializer = store.serializerFor('host-catalog');
|
||||||
const hostCatalog = store.createRecord('host-catalog').constructor;
|
const hostCatalog = store.createRecord('host-catalog').constructor;
|
||||||
@@ -193,6 +352,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
secret_access_key: null,
|
secret_access_key: null,
|
||||||
secret_id: null,
|
secret_id: null,
|
||||||
secret_value: null,
|
secret_value: null,
|
||||||
|
private_key_id: null,
|
||||||
|
private_key: null,
|
||||||
},
|
},
|
||||||
relationships: {},
|
relationships: {},
|
||||||
},
|
},
|
||||||
@@ -215,6 +376,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 +400,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',
|
||||||
@@ -249,6 +412,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
secret_access_key: 'zq{2:IVc8@W^',
|
secret_access_key: 'zq{2:IVc8@W^',
|
||||||
secret_id: null,
|
secret_id: null,
|
||||||
secret_value: null,
|
secret_value: null,
|
||||||
|
private_key_id: null,
|
||||||
|
private_key: null,
|
||||||
},
|
},
|
||||||
relationships: {},
|
relationships: {},
|
||||||
},
|
},
|
||||||
@@ -309,6 +474,70 @@ module('Unit | Serializer | host catalog', function (hooks) {
|
|||||||
subscription_id: 'subscription',
|
subscription_id: 'subscription',
|
||||||
access_key_id: null,
|
access_key_id: null,
|
||||||
secret_access_key: null,
|
secret_access_key: null,
|
||||||
|
private_key_id: null,
|
||||||
|
private_key: null,
|
||||||
|
},
|
||||||
|
relationships: {},
|
||||||
|
},
|
||||||
|
included: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it normalizes a GCP plugin type host catalog as expected', function (assert) {
|
||||||
|
const store = this.owner.lookup('service:store');
|
||||||
|
const serializer = store.serializerFor('host-catalog');
|
||||||
|
const hostCatalog = store.createRecord('host-catalog').constructor;
|
||||||
|
const payload = {
|
||||||
|
id: '1',
|
||||||
|
name: 'Host catalog test',
|
||||||
|
description: 'Test description',
|
||||||
|
authorized_actions: ['no-op', 'read', 'update', 'delete'],
|
||||||
|
type: 'plugin',
|
||||||
|
plugin: {
|
||||||
|
id: 'plugin-id-7',
|
||||||
|
name: 'gcp',
|
||||||
|
description: 'gcp host catalog',
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
project_id: 'project_id',
|
||||||
|
zone: 'zone',
|
||||||
|
target_service_account_id: 'target_service_account_id',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
private_key_id: '0xF3B0a6f8',
|
||||||
|
private_key: 'zq{2:IVc8@W^',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const normalized = serializer.normalizeSingleResponse(
|
||||||
|
store,
|
||||||
|
hostCatalog,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
assert.deepEqual(normalized, {
|
||||||
|
data: {
|
||||||
|
id: '1',
|
||||||
|
type: 'host-catalog',
|
||||||
|
attributes: {
|
||||||
|
name: 'Host catalog test',
|
||||||
|
type: 'plugin',
|
||||||
|
description: 'Test description',
|
||||||
|
authorized_actions: ['no-op', 'read', 'update', 'delete'],
|
||||||
|
plugin: {
|
||||||
|
id: 'plugin-id-7',
|
||||||
|
name: 'gcp',
|
||||||
|
description: 'gcp host catalog',
|
||||||
|
},
|
||||||
|
disable_credential_rotation: true,
|
||||||
|
private_key_id: '0xF3B0a6f8',
|
||||||
|
private_key: 'zq{2:IVc8@W^',
|
||||||
|
project_id: 'project_id',
|
||||||
|
zone: 'zone',
|
||||||
|
target_service_account_id: 'target_service_account_id',
|
||||||
|
access_key_id: null,
|
||||||
|
secret_access_key: null,
|
||||||
|
secret_id: null,
|
||||||
|
secret_value: null,
|
||||||
},
|
},
|
||||||
relationships: {},
|
relationships: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class SessionService extends Service {}
|
|||||||
module('Unit | Service | indexed-db', function (hooks) {
|
module('Unit | Service | indexed-db', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
// TODO: Replace this with your real tests.
|
|
||||||
test('it exists', function (assert) {
|
test('it exists', function (assert) {
|
||||||
this.owner.register('service:session', SessionService);
|
this.owner.register('service:session', SessionService);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { setupTest } from 'dummy/tests/helpers';
|
|||||||
module('Unit | Service | store', function (hooks) {
|
module('Unit | Service | store', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
// TODO: Replace this with your real tests.
|
|
||||||
test('it exists', function (assert) {
|
test('it exists', function (assert) {
|
||||||
let service = this.owner.lookup('service:store');
|
let service = this.owner.lookup('service:store');
|
||||||
assert.ok(service);
|
assert.ok(service);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { flattenObject } from 'api/utils/flatten-nested-object';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
|
||||||
|
module('Unit | Utility | flatten-nested-object', function () {
|
||||||
|
test('it flattens a nested object to single level object', function (assert) {
|
||||||
|
const input = {
|
||||||
|
a: 1,
|
||||||
|
b: { c: 2, d: 3, e: { f: { g: { h: 10 } } } },
|
||||||
|
};
|
||||||
|
const expected = {
|
||||||
|
a: 1,
|
||||||
|
'b.c': 2,
|
||||||
|
'b.d': 3,
|
||||||
|
'b.e.f.g.h': 10,
|
||||||
|
};
|
||||||
|
assert.deepEqual(flattenObject(input), expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removes keys with null/empty values', function (assert) {
|
||||||
|
const inputOne = {
|
||||||
|
a: null,
|
||||||
|
b: { c: 2 },
|
||||||
|
d: {
|
||||||
|
e: { f: { g: { h: '' } } },
|
||||||
|
},
|
||||||
|
i: 0,
|
||||||
|
j: false,
|
||||||
|
};
|
||||||
|
const inputTwo = {
|
||||||
|
d: { e: null },
|
||||||
|
};
|
||||||
|
const expectedResultOne = {
|
||||||
|
'b.c': 2,
|
||||||
|
i: 0,
|
||||||
|
j: false,
|
||||||
|
};
|
||||||
|
const expectedResultTwo = {};
|
||||||
|
|
||||||
|
assert.deepEqual(flattenObject(inputOne), expectedResultOne);
|
||||||
|
assert.deepEqual(flattenObject(inputTwo), expectedResultTwo);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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,21 @@ 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, {
|
|
||||||
method: 'get',
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
||||||
});
|
const response = await waitForPromise(
|
||||||
|
fetch(tokenValidationURL, {
|
||||||
|
method: 'get',
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Note: Always consume response body in order to avoid memory leaks
|
||||||
|
// visit https://undici.nodejs.org/#/?id=garbage-collection for more info.
|
||||||
|
// We do not use the undici package but the link informs us that garbage
|
||||||
|
// collection is undefined when response body is not consumed.
|
||||||
|
await response.json();
|
||||||
|
|
||||||
// 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 +114,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 +124,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,10 +6,10 @@
|
|||||||
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 multi-step OIDC flow.
|
||||||
*
|
*
|
||||||
* 1. Start authentication flow: this step is actually a combination of two
|
* 1. Start authentication flow: this step is actually a combination of two
|
||||||
* sub steps:
|
* sub steps:
|
||||||
@@ -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,14 +96,20 @@ 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 }));
|
||||||
|
|
||||||
|
// Note: Always consume response body in order to avoid memory leaks
|
||||||
|
// visit https://undici.nodejs.org/#/?id=garbage-collection for more info.
|
||||||
|
// We do not use the undici package but the link informs us that garbage
|
||||||
|
// collection is undefined when response body is not consumed.
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
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;
|
||||||
} else if (response.status < 400) {
|
} else if (response.status < 400) {
|
||||||
// Response was successful, meaning a token was obtained.
|
// Response was successful, meaning a token was obtained.
|
||||||
// Authenticate with the session service using the response JSON.
|
// Authenticate with the session service using the response JSON.
|
||||||
const json = await response.json();
|
|
||||||
await this.session.authenticate('authenticator:oidc', json);
|
await this.session.authenticate('authenticator:oidc', json);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
+14
-13
@@ -21,35 +21,36 @@
|
|||||||
"doc:toc": "doctoc README.md"
|
"doc:toc": "doctoc README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ember-cli-babel": "^7.26.11",
|
"ember-auto-import": "2.8.1",
|
||||||
|
"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": "^6.1.0"
|
||||||
"ember-simple-auth": "^4.2.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@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": "^4.0.4",
|
||||||
|
"@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",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"concurrently": "^8.0.1",
|
"concurrently": "^9.1.0",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-cli": "^5.12.0",
|
||||||
"ember-cli": "^4.12.2",
|
"ember-cli-dependency-checker": "^3.3.2",
|
||||||
"ember-cli-dependency-checker": "^3.3.1",
|
|
||||||
"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-cookies": "^0.5.2",
|
"ember-cookies": "^1.2.0",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"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",
|
||||||
@@ -62,9 +63,9 @@
|
|||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"pretender": "^3.4.3",
|
"pretender": "^3.4.3",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"qunit": "^2.19.4",
|
"qunit": "^2.22.0",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^3.2.1",
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ module('Unit | Authenticator | base', function (hooks) {
|
|||||||
const authenticator = this.owner.lookup('authenticator:base');
|
const authenticator = this.owner.lookup('authenticator:base');
|
||||||
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||||
assert.ok(true, 'token validation was requested');
|
assert.ok(true, 'token validation was requested');
|
||||||
return [200];
|
return [200, {}, '{}'];
|
||||||
});
|
});
|
||||||
await authenticator.restore(mockData);
|
await authenticator.restore(mockData);
|
||||||
});
|
});
|
||||||
@@ -64,7 +64,7 @@ module('Unit | Authenticator | base', function (hooks) {
|
|||||||
const authenticator = this.owner.lookup('authenticator:base');
|
const authenticator = this.owner.lookup('authenticator:base');
|
||||||
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||||
assert.ok(true, 'token validation was requested');
|
assert.ok(true, 'token validation was requested');
|
||||||
return [401];
|
return [401, {}, '{}'];
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await authenticator.restore(mockData);
|
await authenticator.restore(mockData);
|
||||||
@@ -80,7 +80,7 @@ module('Unit | Authenticator | base', function (hooks) {
|
|||||||
const authenticator = this.owner.lookup('authenticator:base');
|
const authenticator = this.owner.lookup('authenticator:base');
|
||||||
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||||
assert.ok(true, 'token validation was requested');
|
assert.ok(true, 'token validation was requested');
|
||||||
return [404];
|
return [404, {}, '{}'];
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await authenticator.restore(mockData);
|
await authenticator.restore(mockData);
|
||||||
|
|||||||
@@ -3,16 +3,21 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<Rose::Message
|
<Hds::ApplicationState data-test-error-application-state as |A|>
|
||||||
@title={{t (concat 'errors.' this.messageCode '.title')}}
|
<A.Header
|
||||||
@subtitle={{concat (t 'errors.generic.title') ' ' @status}}
|
@title={{t (concat 'errors.' this.messageCode '.title')}}
|
||||||
@icon={{this.icon}}
|
@icon={{this.icon}}
|
||||||
as |message|
|
@errorCode={{@status}}
|
||||||
>
|
/>
|
||||||
<message.description>
|
<A.Body @text={{t (concat 'errors.' this.messageCode '.description')}} />
|
||||||
{{t (concat 'errors.' this.messageCode '.description')}}
|
{{#if @docLink}}
|
||||||
</message.description>
|
<A.Footer as |F|>
|
||||||
{{#if this.helpRoute}}
|
<F.LinkStandalone
|
||||||
<message.link @route={{this.helpRoute}}>{{t 'actions.help'}}</message.link>
|
@icon='help'
|
||||||
|
@text={{t 'actions.help'}}
|
||||||
|
@href={{@docLink}}
|
||||||
|
@iconPosition='trailing'
|
||||||
|
/>
|
||||||
|
</A.Footer>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</Rose::Message>
|
</Hds::ApplicationState>
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MessageComponent from 'rose/components/rose/message';
|
import Component from '@glimmer/component';
|
||||||
import { computed } from '@ember/object';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helpful error booleans are attached based on the error status code:
|
* Helpful error booleans are attached based on the error status code:
|
||||||
@@ -22,40 +21,29 @@ import { computed } from '@ember/object';
|
|||||||
|
|
||||||
const statuses = ['401', '403', '404', '500'];
|
const statuses = ['401', '403', '404', '500'];
|
||||||
|
|
||||||
export default class ErrorMessageComponent extends MessageComponent {
|
export default class ErrorMessageComponent extends Component {
|
||||||
// =methods
|
// =methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an icon for error status.
|
* Returns an icon for error status.
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
@computed('args.status')
|
|
||||||
get icon() {
|
get icon() {
|
||||||
switch (this.args.status) {
|
switch (this.args.status) {
|
||||||
case '404':
|
case '404':
|
||||||
return 'flight-icons/svg/help-16';
|
return 'help';
|
||||||
case '401':
|
case '401':
|
||||||
case '403':
|
case '403':
|
||||||
default:
|
default:
|
||||||
return 'flight-icons/svg/alert-circle-16';
|
return 'alert-circle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Enable when help documentation can be linked in.
|
|
||||||
/**
|
|
||||||
* Returns an route string for help text in message.
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
// get helpRoute() {
|
|
||||||
// return 'index'
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns 'unknown' status code when provided error status
|
* Returns 'unknown' status code when provided error status
|
||||||
* isn't part of predefined statuses.
|
* isn't part of predefined statuses.
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
@computed('args.status')
|
|
||||||
get messageCode() {
|
get messageCode() {
|
||||||
let messageCode = this.args.status;
|
let messageCode = this.args.status;
|
||||||
if (!statuses.includes(messageCode)) messageCode = 'unknown';
|
if (!statuses.includes(messageCode)) messageCode = 'unknown';
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Helper from '@ember/component/helper';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import { join } from '@ember/runloop';
|
|
||||||
import { runInDebug, assert } from '@ember/debug';
|
|
||||||
import { A } from '@ember/array';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper is a modern update on ember-route-action-helper, which is
|
|
||||||
* now unmaintained.
|
|
||||||
*
|
|
||||||
* See https://github.com/DockYard/ember-route-action-helper
|
|
||||||
*/
|
|
||||||
export default class RouteActionHelper extends Helper {
|
|
||||||
// =services
|
|
||||||
|
|
||||||
@service router;
|
|
||||||
|
|
||||||
// =attributes
|
|
||||||
|
|
||||||
get routes() {
|
|
||||||
const { currentInfos, mapBy } = this.currentInfos;
|
|
||||||
return A(currentInfos).mapBy(mapBy).reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentInfos() {
|
|
||||||
// eslint-disable-next-line ember/no-private-routing-service
|
|
||||||
const routerLib = this.router._router._routerMicrolib;
|
|
||||||
return {
|
|
||||||
currentInfos:
|
|
||||||
routerLib.currentRouteInfos || routerLib.currentHandlerInfos,
|
|
||||||
mapBy: (routerLib.currentRouteInfos && 'route') || 'handler',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// =methods
|
|
||||||
|
|
||||||
compute([actionName, ...outerArgs]) {
|
|
||||||
const { action, handler } = this.getRouteWithAction(actionName);
|
|
||||||
|
|
||||||
runInDebug(() => {
|
|
||||||
assert(
|
|
||||||
`
|
|
||||||
[boundary-core-helper-route-action]
|
|
||||||
Unable to find action ${actionName}
|
|
||||||
`,
|
|
||||||
handler,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (...innerArgs) =>
|
|
||||||
join(handler, action, ...outerArgs.concat(innerArgs));
|
|
||||||
}
|
|
||||||
|
|
||||||
getRouteWithAction(actionName) {
|
|
||||||
let action;
|
|
||||||
const handler = A(this.routes).find((route) => {
|
|
||||||
const actions = route.actions || route._actions;
|
|
||||||
action = actions[actionName];
|
|
||||||
return typeof action === 'function';
|
|
||||||
});
|
|
||||||
|
|
||||||
return { action, handler };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,22 @@
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility classes
|
||||||
|
.container-inputs__margin-bottom {
|
||||||
|
input:not(:last-child) {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 } from 'core/helpers/route-action';
|
export { default } from 'core/utils/seconds-to-duration';
|
||||||
+14
-17
@@ -31,19 +31,20 @@
|
|||||||
"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.8.1",
|
||||||
"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.7",
|
||||||
"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",
|
"lodash": "^4.17.21",
|
||||||
"sass": "^1.69.5"
|
"sass": "^1.69.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -51,23 +52,23 @@
|
|||||||
"@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": "^4.0.4",
|
||||||
"@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",
|
||||||
"@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": "^9.1.0",
|
||||||
"doctoc": "^2.2.0",
|
"doctoc": "^2.2.0",
|
||||||
"ember-cli": "^4.12.2",
|
"ember-cli": "^5.12.0",
|
||||||
"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",
|
||||||
@@ -81,15 +82,11 @@
|
|||||||
"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.22.0",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^3.2.1",
|
||||||
"rose": "*",
|
"rose": "*",
|
||||||
"sinon": "^17.0.1",
|
"sinon": "^19.0.2",
|
||||||
"webpack": "^5.78.0"
|
"webpack": "^5.94.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"ember-source": "^4.0.0",
|
|
||||||
"lodash": "^4.17.21"
|
|
||||||
},
|
},
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -5,69 +5,73 @@
|
|||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, find } from '@ember/test-helpers';
|
import { render } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { setupIntl } from 'ember-intl/test-support';
|
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');
|
||||||
|
|
||||||
|
const ERROR_MSG_TITLE =
|
||||||
|
'[data-test-error-application-state] .hds-application-state__title';
|
||||||
|
const ERROR_MSG_CODE =
|
||||||
|
'[data-test-error-application-state] .hds-application-state__error-code';
|
||||||
|
const ERROR_MSG_BODY =
|
||||||
|
'[data-test-error-application-state] .hds-application-state__body-text';
|
||||||
|
const ERROR_MSG_LINK =
|
||||||
|
'[data-test-error-application-state] .hds-link-standalone';
|
||||||
|
const ERROR_MSG_LINK_TEXT =
|
||||||
|
'[data-test-error-application-state] .hds-link-standalone__text';
|
||||||
|
|
||||||
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' />`);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-title').textContent.trim(),
|
assert.dom(ERROR_MSG_TITLE).hasText('You are not signed in');
|
||||||
'You are not signed in',
|
assert.dom(ERROR_MSG_CODE).hasText('ERROR 401');
|
||||||
);
|
assert
|
||||||
assert.strictEqual(
|
.dom(ERROR_MSG_BODY)
|
||||||
find('.rose-message-subtitle').textContent.trim(),
|
.hasText('You are not signed in. Please sign in and try again later.');
|
||||||
'Error 401',
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-description').textContent.trim(),
|
|
||||||
'You are not signed in. Please sign in and try again later.',
|
|
||||||
);
|
|
||||||
await render(hbs`<Error::Message @status='403' />`);
|
await render(hbs`<Error::Message @status='403' />`);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-title').textContent.trim(),
|
assert.dom(ERROR_MSG_TITLE).hasText('You are not authorized');
|
||||||
'You are not authorized',
|
assert.dom(ERROR_MSG_CODE).hasText('ERROR 403');
|
||||||
);
|
assert
|
||||||
assert.strictEqual(
|
.dom(ERROR_MSG_BODY)
|
||||||
find('.rose-message-subtitle').textContent.trim(),
|
.hasText(
|
||||||
'Error 403',
|
'You must be granted permissions to view this data. Ask your administrator if you think you should have access.',
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-description').textContent.trim(),
|
|
||||||
'You must be granted permissions to view this data. Ask your administrator if you think you should have access.',
|
|
||||||
);
|
|
||||||
await render(hbs`<Error::Message @status='404' />`);
|
await render(hbs`<Error::Message @status='404' />`);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-title').textContent.trim(),
|
assert.dom(ERROR_MSG_TITLE).hasText('Resource not found');
|
||||||
'Resource not found',
|
assert.dom(ERROR_MSG_CODE).hasText('ERROR 404');
|
||||||
);
|
assert
|
||||||
assert.strictEqual(
|
.dom(ERROR_MSG_BODY)
|
||||||
find('.rose-message-subtitle').textContent.trim(),
|
.hasText(
|
||||||
'Error 404',
|
'We could not find the requested resource. You can ask your administrator or try again later.',
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-description').textContent.trim(),
|
|
||||||
'We could not find the requested resource. You can ask your administrator or try again later.',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders default error for unknown error status', async function (assert) {
|
test('it renders default error for unknown error status', async function (assert) {
|
||||||
await render(hbs`<Error::Message @status='599' />`);
|
await render(hbs`<Error::Message @status='599' />`);
|
||||||
assert.strictEqual(
|
|
||||||
find('.rose-message-title').textContent.trim(),
|
assert.dom(ERROR_MSG_TITLE).hasText('Something went wrong');
|
||||||
'Something went wrong',
|
assert.dom(ERROR_MSG_CODE).hasText('ERROR 599');
|
||||||
);
|
assert
|
||||||
assert.strictEqual(
|
.dom(ERROR_MSG_BODY)
|
||||||
find('.rose-message-subtitle').textContent.trim(),
|
.hasText(
|
||||||
'Error 599',
|
"We're not sure what happened. Please contact your administrator or try again later.",
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
});
|
||||||
find('.rose-message-description').textContent.trim(),
|
|
||||||
"We're not sure what happened. Please contact your administrator or try again later.",
|
test('it renders help link with provided doc url', async function (assert) {
|
||||||
|
await render(
|
||||||
|
hbs`<Error::Message @status='599' @docLink='https://support' />`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.dom(ERROR_MSG_LINK_TEXT).hasText('Need help');
|
||||||
|
assert.dom(ERROR_MSG_LINK).hasAttribute('href', 'https://support');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -15,7 +15,7 @@ module('Integration | Component | session-status', function (hooks) {
|
|||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
this.model = store.createRecord('session', { status: 'active' });
|
this.model = store.createRecord('session', { status: 'active' });
|
||||||
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
||||||
assert.ok(find('.flight-icon-check'));
|
assert.ok(find('.hds-icon-check'));
|
||||||
assert.ok(find('.hds-badge--color-success'));
|
assert.ok(find('.hds-badge--color-success'));
|
||||||
assert.ok(find('.hds-badge--type-filled'));
|
assert.ok(find('.hds-badge--type-filled'));
|
||||||
});
|
});
|
||||||
@@ -24,7 +24,7 @@ module('Integration | Component | session-status', function (hooks) {
|
|||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
this.model = store.createRecord('session', { status: 'canceling' });
|
this.model = store.createRecord('session', { status: 'canceling' });
|
||||||
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
||||||
assert.ok(find('.flight-icon-alert-triangle'));
|
assert.ok(find('.hds-icon-alert-triangle'));
|
||||||
assert.ok(find('.hds-badge--color-warning'));
|
assert.ok(find('.hds-badge--color-warning'));
|
||||||
assert.ok(find('.hds-badge--type-filled'));
|
assert.ok(find('.hds-badge--type-filled'));
|
||||||
});
|
});
|
||||||
@@ -33,7 +33,7 @@ module('Integration | Component | session-status', function (hooks) {
|
|||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
this.model = store.createRecord('session', { status: 'terminated' });
|
this.model = store.createRecord('session', { status: 'terminated' });
|
||||||
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
||||||
assert.ok(find('.flight-icon-x'));
|
assert.ok(find('.hds-icon-x'));
|
||||||
assert.ok(find('.hds-badge--color-critical'));
|
assert.ok(find('.hds-badge--color-critical'));
|
||||||
assert.ok(find('.hds-badge--type-filled'));
|
assert.ok(find('.hds-badge--type-filled'));
|
||||||
});
|
});
|
||||||
@@ -42,7 +42,7 @@ module('Integration | Component | session-status', function (hooks) {
|
|||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
this.model = store.createRecord('session', { status: 'pending' });
|
this.model = store.createRecord('session', { status: 'pending' });
|
||||||
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
||||||
assert.ok(find('.flight-icon-delay'));
|
assert.ok(find('.hds-icon-delay'));
|
||||||
assert.ok(find('.hds-badge--color-neutral'));
|
assert.ok(find('.hds-badge--color-neutral'));
|
||||||
assert.ok(find('.hds-badge--type-filled'));
|
assert.ok(find('.hds-badge--type-filled'));
|
||||||
});
|
});
|
||||||
@@ -51,7 +51,7 @@ module('Integration | Component | session-status', function (hooks) {
|
|||||||
const store = this.owner.lookup('service:store');
|
const store = this.owner.lookup('service:store');
|
||||||
this.model = store.createRecord('session', { status: 'any string' });
|
this.model = store.createRecord('session', { status: 'any string' });
|
||||||
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
await render(hbs`<SessionStatus @model={{this.model}} />`);
|
||||||
assert.notOk(find('.flight-icon'));
|
assert.notOk(find('.hds-icon'));
|
||||||
assert.ok(find('.hds-badge--color-neutral'));
|
assert.ok(find('.hds-badge--color-neutral'));
|
||||||
assert.ok(find('.hds-badge--type-outlined'));
|
assert.ok(find('.hds-badge--type-outlined'));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ module('Unit | Initializer | deprecations', function (hooks) {
|
|||||||
run(this.application, 'destroy');
|
run(this.application, 'destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Replace this with your real tests.
|
|
||||||
test('it works', async function (assert) {
|
test('it works', async function (assert) {
|
||||||
await this.application.boot();
|
await this.application.boot();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { setupTest } from 'ember-qunit';
|
|||||||
module('Unit | Service | clock-tick', function (hooks) {
|
module('Unit | Service | clock-tick', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
// TODO: Replace this with your real tests.
|
|
||||||
test('it exists', function (assert) {
|
test('it exists', function (assert) {
|
||||||
let service = this.owner.lookup('service:clock-tick');
|
let service = this.owner.lookup('service:clock-tick');
|
||||||
assert.ok(service);
|
assert.ok(service);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,7 +8,6 @@ delete: Delete
|
|||||||
remove: Remove
|
remove: Remove
|
||||||
discard: Discard
|
discard: Discard
|
||||||
disconnect: Disconnect
|
disconnect: Disconnect
|
||||||
dismiss: Dismiss
|
|
||||||
save: Save
|
save: Save
|
||||||
submit: Submit
|
submit: Submit
|
||||||
manage: Manage
|
manage: Manage
|
||||||
@@ -16,12 +15,14 @@ confirm: Confirm
|
|||||||
done: Done
|
done: Done
|
||||||
ok: OK
|
ok: OK
|
||||||
deny: Deny
|
deny: Deny
|
||||||
help: Need help?
|
help: Need help
|
||||||
next: Next
|
next: Next
|
||||||
back: Back
|
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 +61,7 @@ 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
|
||||||
|
edit-worker-filter: Edit Worker Filter
|
||||||
|
add-worker-filter: Add Worker Filter
|
||||||
@@ -44,6 +44,8 @@ titles:
|
|||||||
not-set: Not set
|
not-set: Not set
|
||||||
year: '{numberOfYears, plural, =1 {# year} other {# years}}'
|
year: '{numberOfYears, plural, =1 {# year} other {# years}}'
|
||||||
days: '{days, plural, =1 {# day} other {# days}}'
|
days: '{days, plural, =1 {# day} other {# days}}'
|
||||||
|
workers: Workers
|
||||||
|
no-worker-filter: No worker filter added
|
||||||
descriptions:
|
descriptions:
|
||||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||||
cluster-url-initialization: To get started, please enter your cluster URL
|
cluster-url-initialization: To get started, please enter your cluster URL
|
||||||
@@ -105,3 +107,54 @@ 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
|
||||||
|
worker-filter-generator:
|
||||||
|
title: Filter generator
|
||||||
|
description: Choose what you want to format into a filter.
|
||||||
|
link: Learn more about formatting a worker filter.
|
||||||
|
toggle:
|
||||||
|
title: Show filter generator
|
||||||
|
description: The generator will automatically generate a pre-formatted filter for you. You can copy the result to the editor above.
|
||||||
|
input-values:
|
||||||
|
title: Input values
|
||||||
|
description: Next, input what you want to format.
|
||||||
|
formatted-result:
|
||||||
|
title: Formatted result
|
||||||
|
description: Copy and paste this into the editor above.
|
||||||
|
label: Generated result
|
||||||
|
tag:
|
||||||
|
label: Tag
|
||||||
|
helper: Format a worker tag
|
||||||
|
name:
|
||||||
|
label: Name
|
||||||
|
helper: Format a worker name
|
||||||
|
operator:
|
||||||
|
matches: matches (regex)
|
||||||
|
contains: contains
|
||||||
@@ -18,6 +18,8 @@ description:
|
|||||||
type:
|
type:
|
||||||
label: Type
|
label: Type
|
||||||
help: The type of the resource, to help differentiate schemas
|
help: The type of the resource, to help differentiate schemas
|
||||||
|
plugin_type:
|
||||||
|
label: plugin_type
|
||||||
login_name:
|
login_name:
|
||||||
label: Login Name
|
label: Login Name
|
||||||
help: The account login name
|
help: The account login name
|
||||||
@@ -134,6 +136,8 @@ key:
|
|||||||
label: Key
|
label: Key
|
||||||
value:
|
value:
|
||||||
label: Value
|
label: Value
|
||||||
|
operator:
|
||||||
|
label: Operator
|
||||||
callback_url:
|
callback_url:
|
||||||
label: Callback URL
|
label: Callback URL
|
||||||
help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration.
|
help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration.
|
||||||
@@ -148,10 +152,10 @@ subscription_id:
|
|||||||
help: The subscription ID for the subscription with read access.
|
help: The subscription ID for the subscription with read access.
|
||||||
access_key_id:
|
access_key_id:
|
||||||
label: Access Key ID
|
label: Access Key ID
|
||||||
help: The access key ID generated by AWS for the IAM user to use with this host catalog.
|
help: The access key ID generated by AWS for the IAM user to use with this AWS Host Catalog.
|
||||||
secret_access_key:
|
secret_access_key:
|
||||||
label: Secret Access Key
|
label: Secret Access Key
|
||||||
help: The secret access key generated by AWS for the IAM user to use with this host catalog.
|
help: The secret access key generated by AWS for the IAM user to use with this AWS Host Catalog.
|
||||||
secret:
|
secret:
|
||||||
label: Client Secret Value
|
label: Client Secret Value
|
||||||
help: The client secret value generated by Azure for Boundary to use for authentication.
|
help: The client secret value generated by Azure for Boundary to use for authentication.
|
||||||
@@ -179,3 +183,5 @@ created:
|
|||||||
other_fields:
|
other_fields:
|
||||||
label: Other Fields
|
label: Other Fields
|
||||||
help: This information is provided by the server and cannot be edited in Boundary
|
help: This information is provided by the server and cannot be edited in Boundary
|
||||||
|
worker_filter:
|
||||||
|
label: Worker Filter
|
||||||
@@ -211,20 +211,54 @@ host-catalog:
|
|||||||
delete: Delete Host Catalog
|
delete: Delete 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, AWS, or GCP
|
||||||
|
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
|
||||||
|
gcp: GCP
|
||||||
|
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
|
||||||
help: AWS region of the hosts to be added to the catalog.
|
help: AWS region of the hosts to be added to this AWS 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:
|
||||||
|
help: Insert a worker filter in order to route requests to the correct worker.
|
||||||
|
project_id:
|
||||||
|
label: Project ID
|
||||||
|
help: Project ID of the instances you want to add to this GCP Host Catalog.
|
||||||
|
zone:
|
||||||
|
label: Zone
|
||||||
|
help: Zone of the instances you want to add to this GCP Host Catalog.
|
||||||
|
client_email:
|
||||||
|
label: Client Email
|
||||||
|
help: The email address used to uniquely identify the service account.
|
||||||
|
target_service_account_id:
|
||||||
|
label: Target Service Account ID
|
||||||
|
help: The unique identifier for the service account that will be impersonated. This is only used when authenticating with service account impersonation.
|
||||||
|
private_key_id:
|
||||||
|
label: Private Key ID
|
||||||
|
help: The unique identifier of private key.
|
||||||
|
private_key:
|
||||||
|
label: Private Key
|
||||||
|
help: The private key used to obtain a OAuth 2.0 access token. Key must be PEM encoded.
|
||||||
host-set:
|
host-set:
|
||||||
title: Host Set
|
title: Host Set
|
||||||
title_plural: Host Sets
|
title_plural: Host Sets
|
||||||
@@ -241,9 +275,11 @@ host-set:
|
|||||||
filter:
|
filter:
|
||||||
label: Filter
|
label: Filter
|
||||||
aws:
|
aws:
|
||||||
help: Create a filter to select resources using values such as tag name or tag value. The AWS filter format is tag:<tag-name>=<tag-value>. For example:tag:application=production
|
help: 'Create a filter to select resources using values such as tag name or tag value. The AWS filter format is tag:<tag-name>=<tag-value>. For example: tag:application=production'
|
||||||
azure:
|
azure:
|
||||||
help: Create a filter to select resources using values such as tag name or tag value. The Azure filter format is:tagName eq ‘<tag-name>’ and tagValue eq ‘<tag-value>'. For example:tagName eq 'application' and tagValue eq 'production'
|
help: Create a filter to select resources using values such as tag name or tag value. The Azure filter format is:tagName eq ‘<tag-name>’ and tagValue eq ‘<tag-value>'. For example:tagName eq 'application' and tagValue eq 'production'
|
||||||
|
gcp:
|
||||||
|
help: 'Create a filter to select resources using values such as label name or label value. The GCP filter format is labels.<label-name>=<label-value>. For example: labels.application=production'
|
||||||
preferred_endpoints:
|
preferred_endpoints:
|
||||||
label: Preferred Endpoints
|
label: Preferred Endpoints
|
||||||
help: Preferred address at which the host should be accessed when multiple options are present, in cidr:<valid IPv4/6 CIDR> or dns:<globbed name> format.
|
help: Preferred address at which the host should be accessed when multiple options are present, in cidr:<valid IPv4/6 CIDR> or dns:<globbed name> format.
|
||||||
@@ -357,6 +393,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 +426,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 +481,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 +490,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 +501,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.
|
||||||
@@ -479,8 +521,7 @@ storage-bucket:
|
|||||||
label: Secret access key
|
label: Secret access key
|
||||||
help: The secret access key generated by your storage provider for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
|
help: The secret access key generated by your storage provider for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it.
|
||||||
worker_filter:
|
worker_filter:
|
||||||
label: Worker filter
|
help: Filters to the worker(s) that can handle requests for this storage bucket.
|
||||||
help: Filter for Boundary workers that have network access to your storage. Boolean expression.
|
|
||||||
disable_credential_rotation:
|
disable_credential_rotation:
|
||||||
label: Disable credential rotation
|
label: Disable credential rotation
|
||||||
help: Disabling credential rotation could allow entities outside of Boundary to know the client secret in use.
|
help: Disabling credential rotation could allow entities outside of Boundary to know the client secret in use.
|
||||||
@@ -615,6 +656,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 +672,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 +740,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:
|
||||||
@@ -750,11 +801,9 @@ target:
|
|||||||
title: Add Injected Application Credential Sources
|
title: Add Injected Application Credential Sources
|
||||||
description: Select injected Application credential sources to assign to this target.
|
description: Select injected Application credential sources to assign to this target.
|
||||||
workers:
|
workers:
|
||||||
title: Workers
|
|
||||||
description: You can customize how your workers route traffic to this target by setting up a filter that matches specific worker tags. If your target is in a private network, we recommend setting up an egress filter to specify a worker inside the network so that Boundary can access the host. Ingress and egress filters are allowed to select the same worker.
|
description: You can customize how your workers route traffic to this target by setting up a filter that matches specific worker tags. If your target is in a private network, we recommend setting up an egress filter to specify a worker inside the network so that Boundary can access the host. Ingress and egress filters are allowed to select the same worker.
|
||||||
messages:
|
messages:
|
||||||
none:
|
none:
|
||||||
title: No worker filter added
|
|
||||||
description: You haven't added an {type} worker filter yet.
|
description: You haven't added an {type} worker filter yet.
|
||||||
diagram:
|
diagram:
|
||||||
client: Client
|
client: Client
|
||||||
@@ -780,13 +829,10 @@ target:
|
|||||||
title: Edit Ingress Worker Filter
|
title: Edit Ingress Worker Filter
|
||||||
description: Specify workers Boundary should use to start the route to this target.
|
description: Specify workers Boundary should use to start the route to this target.
|
||||||
worker-filter:
|
worker-filter:
|
||||||
title: Worker filter
|
|
||||||
description: The worker filter format for this target.
|
description: The worker filter format for this target.
|
||||||
accordion-label:
|
accordion-label:
|
||||||
egress-workers: Egress workers
|
egress-workers: Egress workers
|
||||||
ingress-workers: Ingress workers
|
ingress-workers: Ingress workers
|
||||||
actions:
|
|
||||||
edit-worker-filter: Edit worker filter
|
|
||||||
credential-store:
|
credential-store:
|
||||||
title: Credential Store
|
title: Credential Store
|
||||||
title_plural: Credential Stores
|
title_plural: Credential Stores
|
||||||
@@ -796,6 +842,13 @@ credential-store:
|
|||||||
title: Welcome to Credential Stores
|
title: Welcome to Credential Stores
|
||||||
titles:
|
titles:
|
||||||
new: New Credential Store
|
new: New Credential Store
|
||||||
|
worker-filter:
|
||||||
|
description: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Boundary.
|
||||||
|
messages:
|
||||||
|
none:
|
||||||
|
description: You haven't added a worker filter yet.
|
||||||
|
edit:
|
||||||
|
description: Specify workers that have access to the credential store.
|
||||||
actions:
|
actions:
|
||||||
delete: Delete Credential Store
|
delete: Delete Credential Store
|
||||||
types:
|
types:
|
||||||
@@ -833,9 +886,6 @@ credential-store:
|
|||||||
help: A PEM-encoded private key matching the client certificate from Client Certificate.
|
help: A PEM-encoded private key matching the client certificate from Client Certificate.
|
||||||
client_certificate_key_hmac:
|
client_certificate_key_hmac:
|
||||||
label: Client Certificate Key HMAC
|
label: Client Certificate Key HMAC
|
||||||
worker_filter:
|
|
||||||
label: Worker Filter
|
|
||||||
help: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Boundary.
|
|
||||||
credential-library:
|
credential-library:
|
||||||
title: Credential Library
|
title: Credential Library
|
||||||
title_plural: Credential Libraries
|
title_plural: Credential Libraries
|
||||||
@@ -873,7 +923,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.
|
||||||
@@ -969,7 +1019,6 @@ managed-group:
|
|||||||
help: The list of groups that this Managed Group belongs to.
|
help: The list of groups that this Managed Group belongs to.
|
||||||
worker:
|
worker:
|
||||||
title: Worker
|
title: Worker
|
||||||
title_plural: Workers
|
|
||||||
titles:
|
titles:
|
||||||
new: New PKI Worker
|
new: New PKI Worker
|
||||||
description: Workers are the server components that perform the session handling.
|
description: Workers are the server components that perform the session handling.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,11 +10,7 @@ module.exports = {
|
|||||||
extends: ['recommended', 'ember-template-lint-plugin-prettier:recommended'],
|
extends: ['recommended', 'ember-template-lint-plugin-prettier:recommended'],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: [
|
files: ['addon/components/rose/anonymous/index.hbs'],
|
||||||
'addon/components/rose/anonymous/index.hbs',
|
|
||||||
'addon/components/rose/table/row/cell/index.hbs',
|
|
||||||
'addon/components/rose/anonymous/index.hbs',
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-yield-only': false,
|
'no-yield-only': false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
}}
|
}}
|
||||||
{{yield
|
{{yield
|
||||||
(hash
|
(hash
|
||||||
radioGroup=(component 'rose/form/radio/group' disabled=this.disabled)
|
|
||||||
checkbox=(component 'rose/form/checkbox' disabled=this.disabled)
|
checkbox=(component 'rose/form/checkbox' disabled=this.disabled)
|
||||||
checkboxGroup=(component
|
checkboxGroup=(component
|
||||||
'rose/form/checkbox/group' disabled=this.disabled
|
'rose/form/checkbox/group' disabled=this.disabled
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
{{yield
|
|
||||||
(hash
|
|
||||||
radio=(component
|
|
||||||
'rose/form/radio/radio'
|
|
||||||
name=@name
|
|
||||||
selectedValue=@selectedValue
|
|
||||||
disabled=@disabled
|
|
||||||
changed=@changed
|
|
||||||
inline=@inline
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<div class='{{this.className}} {{if @error " error"}} {{if @inline "inline"}}'>
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
...attributes
|
|
||||||
@radioClass='rose-form-radio-field'
|
|
||||||
@radioId={{this.id}}
|
|
||||||
type='radio'
|
|
||||||
disabled={{@disabled}}
|
|
||||||
@name={{@name}}
|
|
||||||
@value={{@value}}
|
|
||||||
@groupValue={{@selectedValue}}
|
|
||||||
@disabled={{@disabled}}
|
|
||||||
@changed={{@changed}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label class='rose-form-radio-label' for={{this.id}}>
|
|
||||||
|
|
||||||
{{#if @icon}}
|
|
||||||
<Rose::Icon @name={{@icon}} @size='medium' />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{@label}}
|
|
||||||
<span class='radio-card-overlay'></span>
|
|
||||||
{{#if @helperText}}
|
|
||||||
<Rose::Form::HelperText
|
|
||||||
@id='helper-text-{{this.id}}'
|
|
||||||
@error={{@error}}
|
|
||||||
@link={{@link}}
|
|
||||||
@linkText={{@linkText}}
|
|
||||||
>
|
|
||||||
{{@helperText}}
|
|
||||||
</Rose::Form::HelperText>
|
|
||||||
{{/if}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{{yield}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@glimmer/component';
|
|
||||||
import { generateComponentID } from '../../../../../utilities/component-auto-id';
|
|
||||||
|
|
||||||
export default class RoseFormRadioRadioComponent extends Component {
|
|
||||||
// =attributes
|
|
||||||
className = 'rose-form-radio';
|
|
||||||
id = generateComponentID();
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<LinkTo
|
|
||||||
class='rose-link-button
|
|
||||||
{{if @style (concat "rose-button-" @style)}}
|
|
||||||
{{if @iconOnly "has-icon-only"}}
|
|
||||||
{{if @iconLeft "has-icon-left"}}
|
|
||||||
{{if @iconRight "has-icon-right"}}'
|
|
||||||
@route={{@route}}
|
|
||||||
...attributes
|
|
||||||
>
|
|
||||||
<span class='rose-button-wrapper'>
|
|
||||||
{{#if @iconLeft}}
|
|
||||||
<Rose::Icon @name={{@iconLeft}} @size='medium' />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<span class='rose-button-body'>
|
|
||||||
{{yield}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{#if @iconOnly}}
|
|
||||||
<Rose::Icon @name={{@iconOnly}} @size='medium' />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if @iconRight}}
|
|
||||||
<Rose::Icon @name={{@iconRight}} @size='medium' />
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</LinkTo>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<article ...attributes class='rose-message'>
|
|
||||||
|
|
||||||
{{#if @title}}
|
|
||||||
<header class='rose-message-header'>
|
|
||||||
{{#if @icon}}
|
|
||||||
<Rose::Icon @name={{@icon}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<h2 class='rose-message-title'>{{@title}}</h2>
|
|
||||||
|
|
||||||
{{#if @subtitle}}
|
|
||||||
<h3 class='rose-message-subtitle'>{{@subtitle}}</h3>
|
|
||||||
{{/if}}
|
|
||||||
</header>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<section class='rose-message-body'>
|
|
||||||
{{yield
|
|
||||||
(hash
|
|
||||||
description=(component
|
|
||||||
'rose/anonymous' tagName='p' class='rose-message-description'
|
|
||||||
)
|
|
||||||
link=(component 'link-to')
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@glimmer/component';
|
|
||||||
|
|
||||||
export default class RoseMessageComponent extends Component {}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<li class='rose-metadata-list-item {{(concat "hds-foreground-" @color)}}'>
|
<li class='rose-metadata-list-item {{(concat "hds-foreground-" @color)}}'>
|
||||||
{{#if @icon}}
|
{{#if @icon}}
|
||||||
<FlightIcon @name={{@icon}} />
|
<Hds::Icon @name={{@icon}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</li>
|
</li>
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<section
|
|
||||||
...attributes
|
|
||||||
class='rose-notification is-{{@style}}{{if @dismiss " is-dismissible"}}'
|
|
||||||
role='alert'
|
|
||||||
aria-live={{if @dismiss 'assertive'}}
|
|
||||||
aria-atomic='true'
|
|
||||||
>
|
|
||||||
|
|
||||||
<header class='rose-notification-header'>
|
|
||||||
<Rose::Icon
|
|
||||||
class='rose-notification-icon'
|
|
||||||
@name={{this.icon}}
|
|
||||||
@size='medium'
|
|
||||||
/>
|
|
||||||
{{@heading}}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class='rose-notification-body'>
|
|
||||||
{{yield}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if @dismiss}}
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
class='rose-notification-dismiss'
|
|
||||||
title={{@dismissText}}
|
|
||||||
{{on 'click' @dismiss}}
|
|
||||||
>
|
|
||||||
<Rose::Icon @name='flight-icons/svg/x-16' @size='medium' />
|
|
||||||
<span class='rose-notification-dismiss-text'>
|
|
||||||
{{@dismissText}}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</section>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@glimmer/component';
|
|
||||||
import { computed } from '@ember/object';
|
|
||||||
|
|
||||||
export default class RoseNotificationComponent extends Component {
|
|
||||||
/**
|
|
||||||
* Returns an icon name based on the notification style.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@computed('style')
|
|
||||||
get icon() {
|
|
||||||
let icon = 'flight-icons/svg/info-16';
|
|
||||||
|
|
||||||
switch (this.style) {
|
|
||||||
case 'error':
|
|
||||||
icon = 'flight-icons/svg/x-square-16';
|
|
||||||
break;
|
|
||||||
case 'success':
|
|
||||||
icon = 'flight-icons/svg/check-circle-16';
|
|
||||||
break;
|
|
||||||
case 'warning':
|
|
||||||
icon = 'flight-icons/svg/alert-triangle-16';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<hr class='rose-separator' />
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<table
|
|
||||||
...attributes
|
|
||||||
class='rose-table {{if @style (concat "rose-table-" @style)}}'
|
|
||||||
>
|
|
||||||
{{#if @caption}}
|
|
||||||
<caption>{{@caption}}</caption>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{yield
|
|
||||||
(hash
|
|
||||||
header=(component
|
|
||||||
'rose/table/section' tagName='thead' classNames='rose-table-header'
|
|
||||||
)
|
|
||||||
body=(component
|
|
||||||
'rose/table/section' tagName='tbody' classNames='rose-table-body'
|
|
||||||
)
|
|
||||||
footer=(component
|
|
||||||
'rose/table/section' tagName='tfoot' classNames='rose-table-footer'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</table>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
{{yield}}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@ember/component';
|
|
||||||
import layout from './index';
|
|
||||||
import { computed } from '@ember/object';
|
|
||||||
|
|
||||||
export default Component.extend({
|
|
||||||
layout,
|
|
||||||
tagName: 'td',
|
|
||||||
classNameBindings: ['cellTypeStyle', 'cellShrink', 'cellAlignmentStyle'],
|
|
||||||
cellTypeStyle: computed(function () {
|
|
||||||
return this.tagName.match('th')
|
|
||||||
? `rose-table-header-cell`
|
|
||||||
: 'rose-table-cell';
|
|
||||||
}),
|
|
||||||
cellShrink: computed(function () {
|
|
||||||
if (this.shrink) {
|
|
||||||
return 'rose-table-cell-shrink';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
cellAlignmentStyle: computed(function () {
|
|
||||||
if (this.alignRight) {
|
|
||||||
return 'align-right';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.alignCenter) {
|
|
||||||
return 'align-center';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{{!
|
|
||||||
Copyright (c) HashiCorp, Inc.
|
|
||||||
SPDX-License-Identifier: BUSL-1.1
|
|
||||||
}}
|
|
||||||
|
|
||||||
<tr
|
|
||||||
...attributes
|
|
||||||
class='rose-table-row
|
|
||||||
{{if @hidden (concat "rose-table-row-visually-hidden")}}
|
|
||||||
{{if @style (concat "rose-table-row-" @style)}}'
|
|
||||||
>
|
|
||||||
{{yield
|
|
||||||
(hash
|
|
||||||
cell=(component 'rose/table/row/cell')
|
|
||||||
headerCell=(component 'rose/table/row/cell' tagName='th')
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</tr>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Component from '@ember/component';
|
|
||||||
import layout from './index';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A table row component that uses tr table element and
|
|
||||||
* configures a table cell element.
|
|
||||||
*/
|
|
||||||
export default Component.extend({
|
|
||||||
layout,
|
|
||||||
tagName: '',
|
|
||||||
});
|
|
||||||
Loaded 100 of 860 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user