Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e31adb9db | ||
|
|
f0feb9e563 | ||
|
|
44624e3820 | ||
|
|
225c94b853 | ||
|
|
bd1b8542eb | ||
|
|
c92e93ab1c | ||
|
|
1358727832 | ||
|
|
9bc84a2c59 | ||
|
|
7f00a83b19 | ||
|
|
19426cfd51 | ||
|
|
30c05eb834 | ||
|
|
f1d334b848 | ||
|
|
961f497b6b | ||
|
|
89f33f3714 | ||
|
|
4bad52f43a | ||
|
|
fd6cd3ddf7 | ||
|
|
adf7f8603b | ||
|
|
2a2e4350ee | ||
|
|
f563a4ef45 | ||
|
|
37b6ef6d3f | ||
|
|
96ff0ddf27 | ||
|
|
746862a897 | ||
|
|
5d6e65f206 | ||
|
|
6f9a464302 | ||
|
|
abf7866292 | ||
|
|
10c98c66e7 | ||
|
|
c1f7fe2ddd | ||
|
|
7f4586ed9e | ||
|
|
0b6049c8b0 | ||
|
|
2d347171a9 | ||
|
|
a085d4d4f7 | ||
|
|
7e2f3e3df3 | ||
|
|
3899daea7c | ||
|
|
3d1ee732b4 | ||
|
|
899d7beee0 | ||
|
|
4c7f3fad75 | ||
|
|
b8eb2c3c9b | ||
|
|
f5d612c8b5 | ||
|
|
4020f2d4e2 | ||
|
|
5794cfc7b2 | ||
|
|
1284a16978 | ||
|
|
f255a470cd | ||
|
|
f738e71657 | ||
|
|
beeee851ac | ||
|
|
eb365f0d39 | ||
|
|
57e614e8db | ||
|
|
738fa1f15f | ||
|
|
4c959ab297 | ||
|
|
e685295d04 | ||
|
|
204bd10e04 | ||
|
|
5a302b0bcc | ||
|
|
daa23f66c4 | ||
|
|
b06c396086 | ||
|
|
5555603a5c | ||
|
|
7c3b6d6d9e | ||
|
|
ce87d7728c | ||
|
|
e4da851172 | ||
|
|
e3433ec1f7 | ||
|
|
96f30d1b31 | ||
|
|
f4a48467e3 | ||
|
|
bada405511 | ||
|
|
96d7c055b0 | ||
|
|
a747614a74 | ||
|
|
84856407ee | ||
|
|
9bfd624713 | ||
|
|
b2a6e4d47f | ||
|
|
308240be89 | ||
|
|
c12edc7f73 | ||
|
|
44bd5ee1c6 | ||
|
|
8f3cccce32 | ||
|
|
1ef7d9b5a7 | ||
|
|
c445f95aa4 | ||
|
|
96db738f4b | ||
|
|
6181df4c69 | ||
|
|
376787fda1 | ||
|
|
80ffa856bd | ||
|
|
eb9fe573b8 | ||
|
|
e222d5b909 | ||
|
|
1c774aa783 | ||
|
|
04b51da60f | ||
|
|
28ddec6898 | ||
|
|
d987f844de | ||
|
|
b0d26fe1e6 | ||
|
|
e96131a933 | ||
|
|
3d58745bf1 | ||
|
|
9560148253 | ||
|
|
ec00efb656 | ||
|
|
ba34307918 | ||
|
|
28d8afae2e | ||
|
|
2e507be454 | ||
|
|
dbf3617224 | ||
|
|
200a7944e6 | ||
|
|
8dafc2b9a0 | ||
|
|
b7559c1f4e | ||
|
|
dd8e1864db | ||
|
|
d85a84980f | ||
|
|
4295966b7f | ||
|
|
a87c079ade | ||
|
|
b3604e3e40 | ||
|
|
6be8130329 | ||
|
|
ca3972c04f | ||
|
|
4d98729cde | ||
|
|
185af6f00e | ||
|
|
c5353d6e5e | ||
|
|
20a05dc96a | ||
|
|
30028b5ba0 | ||
|
|
3bafb708ea | ||
|
|
24d86a230a | ||
|
|
620f822504 | ||
|
|
93a57f26ee | ||
|
|
40ec4493ea | ||
|
|
de83d59bde | ||
|
|
a937c5e8de | ||
|
|
b820bb19d7 | ||
|
|
36db2d3f1d | ||
|
|
5786b86521 | ||
|
|
c6a5f93b44 | ||
|
|
8814e2eacb | ||
|
|
9b1e4a8291 | ||
|
|
01c2468f86 | ||
|
|
af705718ff | ||
|
|
e22ef12529 | ||
|
|
62510d9243 | ||
|
|
0fd25a6bb4 | ||
|
|
a268a7b75e | ||
|
|
3dded0a7bc | ||
|
|
ab3140be0a | ||
|
|
0fcd58c080 | ||
|
|
52f0d9f9e8 | ||
|
|
6585fa8e41 | ||
|
|
bef9b3b276 | ||
|
|
af411b854a | ||
|
|
a697dba5b9 | ||
|
|
1203081ec7 | ||
|
|
2db63ae2f8 | ||
|
|
9dd1617b57 | ||
|
|
834058ff45 | ||
|
|
0f41a0a576 | ||
|
|
1025cbfb87 | ||
|
|
ca3c1c7623 | ||
|
|
69d127a89e | ||
|
|
b4d9cd5bb7 | ||
|
|
4b1327db24 | ||
|
|
2f2ceed24e | ||
|
|
de0f20809d | ||
|
|
b1f5857cac | ||
|
|
ddbd1ca163 | ||
|
|
777e40fff8 | ||
|
|
68460331f5 | ||
|
|
32e9fe8b6a | ||
|
|
b3a2460b3f | ||
|
|
3260bf1b2a | ||
|
|
e69c6456d1 | ||
|
|
f8b3792e62 | ||
|
|
01c34c0a79 | ||
|
|
825751239f | ||
|
|
2465739060 | ||
|
|
e6834d2033 | ||
|
|
924100df4c | ||
|
|
1ac7d1e08b | ||
|
|
7becccd628 | ||
|
|
dff5cac4fe | ||
|
|
ecd0c1de20 | ||
|
|
878cb17b62 | ||
|
|
706981c231 | ||
|
|
d212cf7c40 | ||
|
|
19b6e5154f | ||
|
|
89263918ea | ||
|
|
e8fc6d163b | ||
|
|
34a670f8d7 | ||
|
|
6a46e2dd57 | ||
|
|
5e0fa7c744 | ||
|
|
474ce17a13 | ||
|
|
f00eed8633 | ||
|
|
42bcefa068 | ||
|
|
29bde998ed | ||
|
|
e29a83ec25 | ||
|
|
ddfcbde7d4 | ||
|
|
42e31d3ece | ||
|
|
cd8480bc67 | ||
|
|
06dc05be08 | ||
|
|
d3fda81af3 | ||
|
|
f58d345b63 | ||
|
|
f35382a4e0 | ||
|
|
8087430dd2 | ||
|
|
2a5f1f3668 | ||
|
|
a889cb30e0 | ||
|
|
e411c0ab7b | ||
|
|
a136371c1f | ||
|
|
83fa213854 | ||
|
|
750cc1ecae | ||
|
|
e25a854d24 | ||
|
|
3b32195271 | ||
|
|
f9411794c4 | ||
|
|
c5fdbfd62d | ||
|
|
f12ae90aba | ||
|
|
20c4bbcee2 | ||
|
|
f3232f34c2 | ||
|
|
8fedb23399 | ||
|
|
4ca519ce62 | ||
|
|
6d4768272a | ||
|
|
e8cc22879f | ||
|
|
b287683a61 | ||
|
|
ea43ee99d6 | ||
|
|
48db68e7ac | ||
|
|
7807b7a280 | ||
|
|
ad0e223742 | ||
|
|
c9a74f2800 | ||
|
|
570ed61edb | ||
|
|
835b302c19 | ||
|
|
a0fad9874a | ||
|
|
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 |
No files matched your search
@@ -15,5 +15,7 @@ project {
|
|||||||
"**/coverage/**",
|
"**/coverage/**",
|
||||||
"ui/desktop/electron-app/out/**",
|
"ui/desktop/electron-app/out/**",
|
||||||
"ui/desktop/electron-app/ember-dist/**",
|
"ui/desktop/electron-app/ember-dist/**",
|
||||||
|
"**/pnpm-lock.yaml",
|
||||||
|
"**/pnpm-workspace.yaml",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -11,21 +11,57 @@ assignees: ''
|
|||||||
|
|
||||||
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
||||||
|
|
||||||
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose by contacting us at security@hashicorp.com. Our PGP key is available at [our security page](https://www.hashicorp.com/security/).
|
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose this by contacting us at security@hashicorp.com. Our PGP key is available on [our security page](https://www.hashicorp.com/security/).
|
||||||
|
|
||||||
|
Logging is available on Boundary Desktop Client version >= 2.1.0
|
||||||
|
|
||||||
|
Logs are found at:
|
||||||
|
|
||||||
|
Linux: ~/.config/Boundary/logs/desktop-client.log
|
||||||
|
MacOS: ~/Library/Logs/Boundary/desktop-client.log
|
||||||
|
Windows: $env:USERPROFILE\AppData\Roaming\Boundary\logs\desktop-client.log
|
||||||
|
|
||||||
|
If requests are taking longer than expected, please change the logging level to DEBUG. This will log the cache daemon request times as well.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
**Describe the bug**
|
## :bug: Bug Report
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
### **Description**
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Run `boundary authenticate ...`
|
|
||||||
2. Run `boundary connect ...`
|
|
||||||
3. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
_A clear and concise description of the bug._
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Additional context**
|
### **Steps to Reproduce**
|
||||||
Add any other context about the problem here.
|
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '...'
|
||||||
|
3. Scroll down to '...'
|
||||||
|
4. See the error
|
||||||
|
|
||||||
|
## **Expected behavior**
|
||||||
|
|
||||||
|
_A clear description of what you expected to happen._
|
||||||
|
|
||||||
|
## **Actual behavior**
|
||||||
|
|
||||||
|
_A clear description of what actually happened._
|
||||||
|
|
||||||
|
## **Screenshots (if applicable)**
|
||||||
|
|
||||||
|
_Add screenshots or screen recordings to help explain the issue._
|
||||||
|
|
||||||
|
## **Environment**
|
||||||
|
|
||||||
|
- **OS and Version:** (e.g., Windows 10 (22H2), macOS (14.7.2))
|
||||||
|
- **Architecture:** (e.g., arm64, x86_64)
|
||||||
|
- **Browser/Version:** (if applicable)
|
||||||
|
- **Desktop Client Version:** (if applicable)
|
||||||
|
- **Controller/worker versions:** (if applicable)
|
||||||
|
|
||||||
|
## **Logs & Console Output (if applicable)**
|
||||||
|
|
||||||
|
_Paste any relevant error logs or browser console output here._
|
||||||
|
|
||||||
|
## **Additional Context**
|
||||||
|
|
||||||
|
_Any other details that might be relevant._
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
@@ -18,3 +18,11 @@
|
|||||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
|
|
||||||
|
## PCI review checklist
|
||||||
|
|
||||||
|
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
|
||||||
|
- [ ] I have documented a clear reason for, and description of, the change I am making.
|
||||||
|
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
|
||||||
|
- [ ] If applicable, I've documented the impact of any changes to security controls.
|
||||||
|
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
|
||||||
@@ -27,7 +27,6 @@ echo "Trying to merge the latest oss commit - ${latest_oss_commit} from oss/${BR
|
|||||||
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
|
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
|
||||||
# Here we can try to auto resolve conflicts
|
# Here we can try to auto resolve conflicts
|
||||||
# This logic will vary from project to project. The below is just an example.
|
# This logic will vary from project to project. The below is just an example.
|
||||||
# We'll try to resolve package.json and yarn.lock conflicts by taking the OSS changes
|
|
||||||
conflicts=$(git diff --name-only --diff-filter=U)
|
conflicts=$(git diff --name-only --diff-filter=U)
|
||||||
printf "Found conflicts:\n${conflicts}\n"
|
printf "Found conflicts:\n${conflicts}\n"
|
||||||
# Resolve package.json conflicts by taking oss
|
# Resolve package.json conflicts by taking oss
|
||||||
@@ -36,11 +35,12 @@ if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit $
|
|||||||
git checkout --theirs package.json
|
git checkout --theirs package.json
|
||||||
git add package.json
|
git add package.json
|
||||||
fi
|
fi
|
||||||
# Resolve yarn.lock conflicts by taking oss
|
# Resolve pnpm-lock.yaml conflicts by re-running pnpm install:
|
||||||
if echo "$conflicts" | grep -q yarn.lock; then
|
# https://pnpm.io/git#merge-conflicts
|
||||||
echo "Checking out oss yarn.lock..."
|
if echo "$conflicts" | grep -q pnpm-lock.yaml; then
|
||||||
git checkout --theirs yarn.lock
|
echo "Re-running pnpm install to fix conflicts by regenerating pnpm-lock.yaml..."
|
||||||
git add yarn.lock
|
pnpm install
|
||||||
|
git add pnpm-lock.yaml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If we can commit all the changes without conflict, we have resolved everything automatically
|
# If we can commit all the changes without conflict, we have resolved everything automatically
|
||||||
|
|||||||
@@ -9,91 +9,86 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
dependencies:
|
dependencies:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
build-admin-ui-oss:
|
build-admin-ui-oss:
|
||||||
name: Build Admin UI OSS
|
name: Build Admin UI OSS
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:oss
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:oss
|
||||||
- name: Upload artifact admin-ui
|
- name: Upload artifact admin-ui
|
||||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui-oss
|
name: admin-ui-oss
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
build-admin-ui-enterprise:
|
build-admin-ui-enterprise:
|
||||||
name: Build Admin UI Enterprise
|
name: Build Admin UI Enterprise
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:enterprise
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:enterprise
|
||||||
- name: Upload artifact enterprise admin UI
|
- name: Upload artifact enterprise admin UI
|
||||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: admin-ui-ent
|
name: admin-ui-ent
|
||||||
path: ./ui/admin/dist/
|
path: ./ui/admin/dist/
|
||||||
build-admin-ui-hcp:
|
build-admin-ui-hcp:
|
||||||
name: Build Admin UI HCP
|
name: Build Admin UI HCP
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules"
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run build:ui:admin:hcp
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run build:ui:admin:hcp
|
||||||
- name: Upload artifact HCP admin UI
|
- name: Upload artifact HCP admin UI
|
||||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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 }}" \
|
|
||||||
@@ -17,7 +17,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -27,18 +27,18 @@ jobs:
|
|||||||
language: ['javascript']
|
language: ['javascript']
|
||||||
# Learn more...
|
# Learn more...
|
||||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||||
|
|
||||||
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@5618c9fc1e675841ca52c1c6b1304f5255a905a0 # codeql-bundle-v2.19.0
|
uses: github/codeql-action/init@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||||
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.
|
||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
@@ -54,4 +54,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@5618c9fc1e675841ca52c1c6b1304f5255a905a0 # codeql-bundle-v2.19.0
|
uses: github/codeql-action/analyze@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||||
@@ -2,14 +2,14 @@ name: Add Copyright Headers
|
|||||||
on: pull_request
|
on: pull_request
|
||||||
jobs:
|
jobs:
|
||||||
copywrite:
|
copywrite:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
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: Add missing Copyright Headers
|
- name: Add missing Copyright Headers
|
||||||
run: copywrite headers
|
run: copywrite headers
|
||||||
- uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
|
- uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1
|
||||||
with:
|
with:
|
||||||
commit_message: Add missing copyright headers
|
commit_message: Add missing copyright headers
|
||||||
@@ -11,7 +11,7 @@ name: Jira Sync
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
name: Jira sync
|
name: Jira sync
|
||||||
steps:
|
steps:
|
||||||
- name: Check if community user
|
- name: Check if community user
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -4,71 +4,71 @@ jobs:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn install
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
compliance:
|
compliance:
|
||||||
name: License Compliance
|
name: License Compliance
|
||||||
needs: [dependencies]
|
needs: [dependencies]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn run compliance:licenses
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm run compliance:licenses
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
needs: [dependencies, compliance]
|
needs: [dependencies, compliance]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
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: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
run_install: false
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
restore-keys: |
|
|
||||||
node-modules-
|
|
||||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn lint
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
- uses: browser-actions/setup-chrome@c785b87e244131f27c9f19c1a33e2ead956ab7ce # v1.7.3
|
||||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
|
||||||
id: node-modules-cache
|
|
||||||
with:
|
with:
|
||||||
path: '**/node_modules'
|
install-dependencies: true
|
||||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: yarn test
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- run: pnpm install --offline
|
||||||
|
- run: pnpm test
|
||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
trigger-oss-merge:
|
trigger-oss-merge:
|
||||||
# Run this only on merge events in OSS repo
|
# Run this only on merge events in OSS repo
|
||||||
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
|
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Merge
|
- name: Trigger Merge
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ on:
|
|||||||
types: [ oss-merge ]
|
types: [ oss-merge ]
|
||||||
jobs:
|
jobs:
|
||||||
oss-merge:
|
oss-merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
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: |
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: Test E2E Desktop Client
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, opened, synchronize, reopened]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
if: ${{contains(github.event.pull_request.labels.*.name, 'desktop')}}
|
||||||
|
name: Run Desktop E2E Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
ENOS_VAR_boundary_edition: "ent"
|
||||||
|
ENOS_VAR_e2e_debug_no_run: true
|
||||||
|
ENOS_VAR_boundary_license_path: "./license.hclic"
|
||||||
|
SETUP_CLI: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: |
|
||||||
|
pnpm-lock.yaml
|
||||||
|
ui/desktop/electron-app/pnpm-lock.yaml
|
||||||
|
|
||||||
|
- name: Set up Terraform
|
||||||
|
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
|
||||||
|
|
||||||
|
- name: Set up Enos
|
||||||
|
uses: hashicorp/action-setup-enos@b9fa53484a1e8fdcc7b02a118bcf01d65b9414c9
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install Vault CLI
|
||||||
|
run: |
|
||||||
|
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/vault.zip
|
||||||
|
unzip /tmp/vault.zip -d /usr/local/bin
|
||||||
|
|
||||||
|
- name: Checkout boundary-enterprise
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: hashicorp/boundary-enterprise
|
||||||
|
# ref: ${{ github.event.inputs.boundary-enterprise-branch }}
|
||||||
|
path: support/src/boundary-enterprise
|
||||||
|
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: GH fix for localhost resolution
|
||||||
|
run: |
|
||||||
|
cat /etc/hosts && echo "-----------"
|
||||||
|
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
|
||||||
|
cat /etc/hosts
|
||||||
|
ssh -V
|
||||||
|
|
||||||
|
echo "127.0.0.1 localhost boundary" | sudo tee -a /etc/hosts
|
||||||
|
echo "127.0.0.1 localhost worker" | sudo tee -a /etc/hosts
|
||||||
|
echo "127.0.0.1 localhost vault" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
# TODO: Build image ourselves in case we don't want to use latest?
|
||||||
|
- name: Get Boundary docker image
|
||||||
|
run: |
|
||||||
|
docker pull hashicorp/boundary-enterprise:latest
|
||||||
|
docker save hashicorp/boundary-enterprise:latest | gzip > /tmp/boundary-enterprise.zip
|
||||||
|
|
||||||
|
# TODO: Set up license in a better way? Use directly rather than writing to file and pull from secrets manager
|
||||||
|
- name: Set up test infra
|
||||||
|
id: infra
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.BOUNDARY_ENT_LICENSE }}" > ./support/src/boundary-enterprise/enos/license.hclic
|
||||||
|
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
|
||||||
|
mkdir -p ./enos/terraform-plugin-cache
|
||||||
|
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||||
|
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||||
|
export ENOS_VAR_boundary_docker_image_name=hashicorp/boundary-enterprise:latest && \
|
||||||
|
export ENOS_VAR_boundary_docker_image_file=/tmp/boundary-enterprise.zip && \
|
||||||
|
enos scenario launch --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
cd e2e-tests
|
||||||
|
pnpm playwright install --with-deps
|
||||||
|
|
||||||
|
# Runner is headless so we need to enable a display to run the client
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
source <(bash ./support/src/boundary-enterprise/enos/scripts/test_e2e_env.sh)
|
||||||
|
cd e2e-tests
|
||||||
|
export DISPLAY=:99
|
||||||
|
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||||
|
pnpm run desktop
|
||||||
|
|
||||||
|
- name: Upload Playwright report
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: e2e-tests/playwright-report
|
||||||
|
|
||||||
|
- name: Clean up test infra
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||||
|
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||||
|
enos scenario destroy --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
|
||||||
@@ -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:
|
||||||
@@ -7,16 +7,16 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger-gha-release:
|
trigger-gha-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||||
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/}"
|
||||||
@@ -19,7 +19,6 @@ coverage-*
|
|||||||
libpeerconnection.log
|
libpeerconnection.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
testem.log
|
testem.log
|
||||||
yarn-error.log
|
|
||||||
.watchman-cookie*
|
.watchman-cookie*
|
||||||
|
|
||||||
### VisualStudioCode ###
|
### VisualStudioCode ###
|
||||||
|
|||||||
+2
-5
@@ -1,5 +1,2 @@
|
|||||||
#!/usr/bin/env sh
|
pnpm compliance:licenses
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
pnpm lint-staged
|
||||||
|
|
||||||
yarn compliance:licenses
|
|
||||||
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
|
||||||
@@ -24,4 +21,3 @@ else
|
|||||||
# This is to allow interaction with the terminal for commitizen
|
# This is to allow interaction with the terminal for commitizen
|
||||||
exec < /dev/tty && npx git-cz --hook || true
|
exec < /dev/tty && npx git-cz --hook || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Binary file not shown.
@@ -25,7 +25,7 @@ subproject, e.g. running or testing.
|
|||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Optional Tooling](#optional-tooling)
|
- [Optional Tooling](#optional-tooling)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Yarn Commands](#yarn-commands)
|
- [Pnpm Commands](#pnpm-commands)
|
||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
- [Branching](#branching)
|
- [Branching](#branching)
|
||||||
- [Building ToC](#building-toc)
|
- [Building ToC](#building-toc)
|
||||||
@@ -33,8 +33,8 @@ subproject, e.g. running or testing.
|
|||||||
- [Building with a Container](#building-with-a-container)
|
- [Building with a Container](#building-with-a-container)
|
||||||
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
|
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
|
||||||
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
|
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
|
||||||
- [For admin](#for-admin)
|
- [For Admin](#for-admin)
|
||||||
- [For desktop](#for-desktop)
|
- [For Desktop](#for-desktop)
|
||||||
- [Committing](#committing)
|
- [Committing](#committing)
|
||||||
- [License Checking](#license-checking)
|
- [License Checking](#license-checking)
|
||||||
|
|
||||||
@@ -67,8 +67,8 @@ You will need the following things properly installed on your computer.
|
|||||||
|
|
||||||
* [Git][git]
|
* [Git][git]
|
||||||
* [Node.js][node]
|
* [Node.js][node]
|
||||||
* Supported versions: `v18` and `v20`.
|
* Supported versions: `v20` and `v22`.
|
||||||
* [Yarn][yarn] installed globally
|
* [Pnpm][pnpm] installed globally
|
||||||
* [Google Chrome][chrome]
|
* [Google Chrome][chrome]
|
||||||
* [Firefox][firefox]
|
* [Firefox][firefox]
|
||||||
|
|
||||||
@@ -90,18 +90,18 @@ that best suit you and your personal style.
|
|||||||
|
|
||||||
* `git clone <repository-url>` this repository
|
* `git clone <repository-url>` this repository
|
||||||
* `cd boundary-ui`
|
* `cd boundary-ui`
|
||||||
* `yarn install`
|
* `pnpm install`
|
||||||
|
|
||||||
## Yarn Commands
|
## Pnpm Commands
|
||||||
|
|
||||||
List of available project commands. `yarn run <command-name>`
|
List of available project commands. `pnpm run <command-name>`
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------------------|---|
|
|---------------------|---|
|
||||||
| build | Builds Admin UI for production. |
|
| 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 `pnpm audit` with moderate and above criticality.|
|
||||||
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
|
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
|
||||||
| doc:toc | Automatically generates a table of contents for the README file. |
|
| doc:toc | Automatically generates a table of contents for the README file. |
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ We follow [GitHub Flow][github-flow].
|
|||||||
### Building ToC
|
### Building ToC
|
||||||
|
|
||||||
To autogenerate a ToC (table of contents) for this README,
|
To autogenerate a ToC (table of contents) for this README,
|
||||||
run `yarn doc:toc`. Please update the ToC whenever editing the structure
|
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
|
||||||
of README.
|
of README.
|
||||||
|
|
||||||
### Building Admin UI for Production
|
### Building Admin UI for Production
|
||||||
@@ -126,8 +126,8 @@ necessary for your target environment, as described in the [Environment Variable
|
|||||||
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:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
pnpm install
|
||||||
yarn build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
Assets are saved to `ui/admin/dist/`.
|
Assets are saved to `ui/admin/dist/`.
|
||||||
@@ -151,20 +151,20 @@ necessary for your target environment, as described in the [Environment Variable
|
|||||||
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:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
pnpm install
|
||||||
yarn build:ui:desktop
|
pnpm build:ui:desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
In windows, UI is generated using docker to provide a stable UI across platforms.
|
In windows, UI is generated using docker to provide a stable UI across platforms.
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
yarn install
|
pnpm install
|
||||||
docker-compose -f docker-compose-embedding.yml run build-desktop-production
|
docker-compose -f docker-compose-embedding.yml run build-desktop-production
|
||||||
yarn build:ui:desktop:app
|
pnpm build:ui:desktop:app
|
||||||
```
|
```
|
||||||
|
|
||||||
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
|
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
|
||||||
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn build:ui:desktop:app`.
|
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `pnpm build:ui:desktop:app`.
|
||||||
|
|
||||||
Assets are saved to `ui/desktop/electron-app/out/make/`.
|
Assets are saved to `ui/desktop/electron-app/out/make/`.
|
||||||
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
|
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
|
||||||
@@ -180,7 +180,7 @@ This assumes you already have up and running a Boundary dev instance in your loc
|
|||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/admin`.
|
This assumes you are within `boundary-ui/ui/admin`.
|
||||||
|
|
||||||
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 yarn start`.
|
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 pnpm start`.
|
||||||
|
|
||||||
Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ Once you open the UI you will see the login screen, authenticate using generated
|
|||||||
|
|
||||||
This assumes you are within `boundary-ui/ui/desktop`.
|
This assumes you are within `boundary-ui/ui/desktop`.
|
||||||
|
|
||||||
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start:desktop`.
|
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false pnpm start:desktop`.
|
||||||
|
|
||||||
In login screen, authenticate using generated admin login name and password.
|
In login screen, authenticate using generated admin login name and password.
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ Enter the cluster URL of your Boundary dev instance, by default is: `http://loca
|
|||||||
### Committing
|
### Committing
|
||||||
|
|
||||||
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
|
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
|
||||||
commit is completed. Simply do a `yarn install` to make sure the hooks are installed.
|
commit is completed. Simply do a `pnpm install` to make sure the hooks are installed.
|
||||||
|
|
||||||
Use a normal `git commit` to go through the checks, if you need to skip these checks,
|
Use a normal `git commit` to go through the checks, if you need to skip these checks,
|
||||||
use `git commit --no-verify` to bypass them. However, a consistent commit message will
|
use `git commit --no-verify` to bypass them. However, a consistent commit message will
|
||||||
@@ -218,10 +218,10 @@ will not be accepted.
|
|||||||
|
|
||||||
[git]: https://git-scm.com/
|
[git]: https://git-scm.com/
|
||||||
[node]: https://nodejs.org/
|
[node]: https://nodejs.org/
|
||||||
[yarn]: https://classic.yarnpkg.com/lang/en/
|
[pnpm]: https://pnpm.io
|
||||||
[chrome]: https://google.com/chrome/
|
[chrome]: https://google.com/chrome/
|
||||||
[firefox]: https://firefox.com/
|
[firefox]: https://firefox.com/
|
||||||
[yarn-workspaces]: https://classic.yarnpkg.com/en/docs/workspaces/
|
[pnpm-workspaces]: https://pnpm.io/workspaces
|
||||||
[homebrew]: https://brew.sh
|
[homebrew]: https://brew.sh
|
||||||
[warp]: https://www.warp.dev
|
[warp]: https://www.warp.dev
|
||||||
[vscode]: https://code.visualstudio.com/
|
[vscode]: https://code.visualstudio.com/
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{diff,md}]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.hbs]
|
[*.hbs]
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.{diff,md}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
/**
|
|
||||||
Ember CLI sends analytics information by default. The data is completely
|
|
||||||
anonymous, but there are times when you might want to disable this behavior.
|
|
||||||
|
|
||||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
|
||||||
*/
|
|
||||||
"disableAnalytics": false,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
|
||||||
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
# unconventional js
|
# unconventional js
|
||||||
/blueprints/*/files/
|
/blueprints/*/files/
|
||||||
/vendor/
|
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
|
/declarations/
|
||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/coverage/
|
/coverage/
|
||||||
!.*
|
!.*
|
||||||
.*/
|
.*/
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -56,7 +56,7 @@ module.exports = {
|
|||||||
extends: ['plugin:n/recommended'],
|
extends: ['plugin:n/recommended'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test files:
|
// test files
|
||||||
files: ['tests/**/*-test.{js,ts}'],
|
files: ['tests/**/*-test.{js,ts}'],
|
||||||
extends: ['plugin:qunit/recommended'],
|
extends: ['plugin:qunit/recommended'],
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist/
|
||||||
|
/declarations/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.env*
|
||||||
|
/.pnp*
|
||||||
|
/.eslintcache
|
||||||
|
/coverage/
|
||||||
|
/testem.log
|
||||||
|
|
||||||
|
# ember-try
|
||||||
|
/.node_modules.ember-try/
|
||||||
|
/package.json.ember-try
|
||||||
|
|
||||||
|
# broccoli-debug
|
||||||
|
/DEBUG/
|
||||||
+2
-12
@@ -2,11 +2,7 @@
|
|||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
/tmp/
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/.bowerrc
|
|
||||||
/.editorconfig
|
/.editorconfig
|
||||||
/.ember-cli
|
/.ember-cli
|
||||||
/.env*
|
/.env*
|
||||||
@@ -19,21 +15,15 @@
|
|||||||
/.prettierignore
|
/.prettierignore
|
||||||
/.prettierrc.js
|
/.prettierrc.js
|
||||||
/.template-lintrc.js
|
/.template-lintrc.js
|
||||||
/.travis.yml
|
|
||||||
/.watchmanconfig
|
/.watchmanconfig
|
||||||
/bower.json
|
|
||||||
/CONTRIBUTING.md
|
/CONTRIBUTING.md
|
||||||
/ember-cli-build.js
|
/ember-cli-build.js
|
||||||
/testem.js
|
/testem.js
|
||||||
/tests/
|
/tests/
|
||||||
/yarn-error.log
|
/tsconfig.declarations.json
|
||||||
/yarn.lock
|
/tsconfig.json
|
||||||
.gitkeep
|
.gitkeep
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
/package.json.ember-try
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
# unconventional js
|
# unconventional js
|
||||||
/blueprints/*/files/
|
/blueprints/*/files/
|
||||||
/vendor/
|
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist/
|
/dist/
|
||||||
/tmp/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/bower_components/
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/coverage/
|
/coverage/
|
||||||
!.*
|
!.*
|
||||||
.eslintcache
|
.*/
|
||||||
.lint-todo/
|
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
/bower.json.ember-try
|
|
||||||
/npm-shrinkwrap.json.ember-try
|
|
||||||
/package.json.ember-try
|
|
||||||
/package-lock.json.ember-try
|
|
||||||
/yarn.lock.ember-try
|
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"ignore_dirs": ["tmp", "dist"]
|
"ignore_dirs": ["dist"]
|
||||||
}
|
}
|
||||||
+15
-15
@@ -8,7 +8,7 @@ This addon contains the API data access layer for Boundary.
|
|||||||
|
|
||||||
- [Add API to an App](#add-api-to-an-app)
|
- [Add API to an App](#add-api-to-an-app)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Yarn Commands](#yarn-commands)
|
- [Pnpm Commands](#pnpm-commands)
|
||||||
- [Linting](#linting)
|
- [Linting](#linting)
|
||||||
- [Formatting](#formatting)
|
- [Formatting](#formatting)
|
||||||
- [Running tests](#running-tests)
|
- [Running tests](#running-tests)
|
||||||
@@ -21,7 +21,7 @@ This addon contains the API data access layer for Boundary.
|
|||||||
## Add API to an App
|
## Add API to an App
|
||||||
|
|
||||||
Add this addon to an Ember application's `devDependencies` as:
|
Add this addon to an Ember application's `devDependencies` as:
|
||||||
`"api": "*"`, for applications included in this monorepo.
|
`"api": "workspace:*"`, for applications included in this monorepo.
|
||||||
|
|
||||||
Since this addon also includes Mirage mocks, be sure to install
|
Since this addon also includes Mirage mocks, be sure to install
|
||||||
`ember-cli-mirage` and add the following config to your UI project:
|
`ember-cli-mirage` and add the following config to your UI project:
|
||||||
@@ -38,9 +38,9 @@ See monorepo README for installation instructions.
|
|||||||
|
|
||||||
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
|
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
|
||||||
|
|
||||||
## Yarn Commands
|
## Pnpm Commands
|
||||||
|
|
||||||
List of available project commands. `yarn run <command-name>`
|
List of available project commands. `pnpm run <command-name>`
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------- | ----------- |
|
| ------- | ----------- |
|
||||||
@@ -64,28 +64,28 @@ Additional commands in the monorepo package may affect this projects.
|
|||||||
|
|
||||||
## Linting
|
## Linting
|
||||||
|
|
||||||
* `yarn lint:fix`
|
* `pnpm lint:fix`
|
||||||
* `yarn lint:hbs`
|
* `pnpm lint:hbs`
|
||||||
* `yarn lint:hbs:fix`
|
* `pnpm lint:hbs:fix`
|
||||||
* `yarn lint:js`
|
* `pnpm lint:js`
|
||||||
* `yarn lint:js:fix`
|
* `pnpm lint:js:fix`
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
||||||
Before submitting your work, be sure to run auto-formatters
|
Before submitting your work, be sure to run auto-formatters
|
||||||
(see commands above). This helps to ensure consistency among authors.
|
(see commands above). This helps to ensure consistency among authors.
|
||||||
|
|
||||||
* `yarn format`
|
* `pnpm format`
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
* `yarn test` – Runs the test suite on the current Ember version
|
* `pnpm test` – Runs the test suite on the current Ember version
|
||||||
* `yarn test --server` – Runs the test suite in "watch mode"
|
* `pnpm test --server` – Runs the test suite in "watch mode"
|
||||||
* `yarn test:all` – Runs the test suite against multiple Ember versions
|
* `pnpm test:all` – Runs the test suite against multiple Ember versions
|
||||||
|
|
||||||
## Running the dummy application
|
## Running the dummy application
|
||||||
|
|
||||||
* `yarn start`
|
* `pnpm start`
|
||||||
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
|
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
@@ -95,5 +95,5 @@ See monorepo README for more contribution instructions.
|
|||||||
### Building ToC
|
### Building ToC
|
||||||
|
|
||||||
To autogenerate a ToC (table of contents) for this README,
|
To autogenerate a ToC (table of contents) for this README,
|
||||||
run `yarn doc:toc`. Please update the ToC whenever editing the structure
|
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
|
||||||
of README.
|
of README.
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
* @param hooks
|
* @param hooks
|
||||||
*/
|
*/
|
||||||
export function setupIndexedDb(hooks) {
|
export function setupIndexedDb(hooks) {
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(async function () {
|
||||||
const indexedDb = this.owner.lookup('service:indexed-db');
|
const indexedDb = this.owner.lookup('service:indexed-db');
|
||||||
indexedDb.setup(`test-indexed-db-${Date.now()}`);
|
await indexedDb.setup(`test-indexed-db-${Date.now()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks.afterEach(async function () {
|
hooks.afterEach(async function () {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides abilities for channel recordings.
|
* Provides abilities for channel recordings.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
/**
|
/**
|
||||||
* Provides abilities for credential store.
|
* Provides abilities for credential store.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ModelAbility from './model';
|
import ModelAbility from './model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
class InvalidRolePrincipalTypeError extends Error {
|
class InvalidRolePrincipalTypeError extends Error {
|
||||||
name = 'InvalidRolePrincipalTypeError';
|
name = 'InvalidRolePrincipalTypeError';
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
* @return {Promise} promise
|
* @return {Promise} promise
|
||||||
*/
|
*/
|
||||||
async query(store, schema, query) {
|
async query(store, schema, query) {
|
||||||
const queryObject = structuredClone(query);
|
const { batchLimit, ...queryObject } = query;
|
||||||
let result;
|
let result;
|
||||||
let data = [];
|
let data = [];
|
||||||
|
|
||||||
@@ -126,8 +126,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
result = await super.query(store, schema, query);
|
result = await super.query(store, schema, query);
|
||||||
return prenormalizeArrayResponse(result);
|
return prenormalizeArrayResponse(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this loop as long as the response_type is delta,
|
// Run this loop as long as the response_type is delta,
|
||||||
// which indicates that there are more items in the list
|
// which indicates that there are more items in the list
|
||||||
|
// or the batch size has not been reached
|
||||||
do {
|
do {
|
||||||
result = await super.query(store, schema, queryObject);
|
result = await super.query(store, schema, queryObject);
|
||||||
//add the result items to a data array
|
//add the result items to a data array
|
||||||
@@ -136,7 +138,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
//pass in the list token for subsequent calls to fetch the remaining list items
|
//pass in the list token for subsequent calls to fetch the remaining list items
|
||||||
queryObject.list_token = result.list_token;
|
queryObject.list_token = result.list_token;
|
||||||
}
|
}
|
||||||
} while (result && result.response_type === 'delta');
|
} while (
|
||||||
|
result?.response_type === 'delta' &&
|
||||||
|
(!batchLimit || data.length < batchLimit)
|
||||||
|
);
|
||||||
|
|
||||||
result.items = data;
|
result.items = data;
|
||||||
return prenormalizeArrayResponse(result);
|
return prenormalizeArrayResponse(result);
|
||||||
@@ -163,7 +168,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides default ajax method by rewritting PUT requests as PATCH.
|
* Overrides default ajax method by rewriting PUT requests as PATCH.
|
||||||
* PATCH is the request method used by our API for updates.
|
* PATCH is the request method used by our API for updates.
|
||||||
*
|
*
|
||||||
* @override
|
* @override
|
||||||
@@ -291,9 +296,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
|||||||
const errors = fieldErrors.map((error) => ({
|
const errors = fieldErrors.map((error) => ({
|
||||||
detail: error.description,
|
detail: error.description,
|
||||||
// Going forward, nested attributes will be hoisted. Thus "attributes."
|
// Going forward, nested attributes will be hoisted. Thus "attributes."
|
||||||
// must be stripped from the error name key. Until all models are
|
// must be stripped from the error name key.
|
||||||
// transitioned away from model fragments, however, both this and the
|
|
||||||
// prior error key mappings are necessary.
|
|
||||||
source: {
|
source: {
|
||||||
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
|
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,23 +55,37 @@ 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 specific
|
// AWS and GCP specific
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: ['aws', 'gcp'],
|
||||||
|
},
|
||||||
description:
|
description:
|
||||||
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
|
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
|
||||||
})
|
})
|
||||||
worker_filter;
|
worker_filter;
|
||||||
|
|
||||||
|
// AWS specific
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
@@ -79,12 +93,22 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
|
|
||||||
// AWS static credentials
|
// 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: '',
|
||||||
})
|
})
|
||||||
@@ -92,24 +116,44 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
|
|
||||||
// AWS dynamic credentials
|
// AWS dynamic credentials
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role ARN to use.',
|
description: 'The role ARN to use.',
|
||||||
})
|
})
|
||||||
role_arn;
|
role_arn;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role external ID to use.',
|
description: 'The role external ID to use.',
|
||||||
})
|
})
|
||||||
role_external_id;
|
role_external_id;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role session to use.',
|
description: 'The role session to use.',
|
||||||
})
|
})
|
||||||
role_session_name;
|
role_session_name;
|
||||||
|
|
||||||
@attr('object-as-array', {
|
@attr('object-as-array', {
|
||||||
|
for: {
|
||||||
|
type: 'plugin',
|
||||||
|
name: 'aws',
|
||||||
|
credentialType: 'dynamic-credential',
|
||||||
|
},
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role tags to use.',
|
description: 'The role tags to use.',
|
||||||
})
|
})
|
||||||
@@ -118,32 +162,117 @@ export default class GeneratedHostCatalogModel extends BaseModel {
|
|||||||
// 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,21 @@ 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,
|
||||||
|
trimWhitespace: 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 +105,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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
|
|||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The AWS region to use.',
|
description: 'The AWS region to use.',
|
||||||
|
trimWhitespace: true,
|
||||||
})
|
})
|
||||||
region;
|
region;
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
|
|||||||
@attr('string', {
|
@attr('string', {
|
||||||
isNestedAttribute: true,
|
isNestedAttribute: true,
|
||||||
description: 'The role ARN to use.',
|
description: 'The role ARN to use.',
|
||||||
|
trimWhitespace: true,
|
||||||
})
|
})
|
||||||
role_arn;
|
role_arn;
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global __electronLog */
|
import { service } from '@ember/service';
|
||||||
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';
|
import { underscore } from '@ember/string';
|
||||||
|
|
||||||
|
const { __electronLog } = globalThis;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* just whitelist the ones we need to start with. Keys are the resource
|
* just whitelist the ones we need to start with. Keys are the resource
|
||||||
@@ -42,7 +43,10 @@ const fetchControllerData = async (context, next) => {
|
|||||||
// If we get an error or cache daemon is unavailable, fall back to calling the API
|
// If we get an error or cache daemon is unavailable, fall back to calling the API
|
||||||
context.request.data.query = remainingQuery;
|
context.request.data.query = remainingQuery;
|
||||||
const results = await next(context.request);
|
const results = await next(context.request);
|
||||||
const models = results.content.toArray();
|
|
||||||
|
// As a result of https://rfcs.emberjs.com/id/0846-ember-data-deprecate-proxies/ we cannot use proxy array methods like .toArray
|
||||||
|
// use slice() instead as indicated in the RFC.
|
||||||
|
const models = results.content.slice();
|
||||||
|
|
||||||
const paginatedResult = paginateResults(models, page, pageSize);
|
const paginatedResult = paginateResults(models, page, pageSize);
|
||||||
paginatedResult.meta = { totalItems: models.length };
|
paginatedResult.meta = { totalItems: models.length };
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { modelIndexes } from 'api/services/indexed-db';
|
import { modelIndexes } from 'api/services/indexed-db';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { getOwner, setOwner } from '@ember/application';
|
import { getOwner, setOwner } from '@ember/application';
|
||||||
import { queryIndexedDb } from '../utils/indexed-db-query';
|
import { queryIndexedDb } from '../utils/indexed-db-query';
|
||||||
import { paginateResults } from '../utils/paginate-results';
|
import { paginateResults } from '../utils/paginate-results';
|
||||||
|
import { sortResults } from '../utils/sort-results';
|
||||||
import { hashCode } from '../utils/hash-code';
|
import { hashCode } from '../utils/hash-code';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,11 +18,15 @@ import { hashCode } from '../utils/hash-code';
|
|||||||
export default class IndexedDbHandler {
|
export default class IndexedDbHandler {
|
||||||
@service indexedDb;
|
@service indexedDb;
|
||||||
|
|
||||||
|
batchLimit = 10_000;
|
||||||
|
|
||||||
constructor(context) {
|
constructor(context) {
|
||||||
setOwner(this, getOwner(context));
|
setOwner(this, getOwner(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
async request(context, next) {
|
async request(context, next) {
|
||||||
|
const { batchLimit } = this;
|
||||||
|
|
||||||
switch (context.request.op) {
|
switch (context.request.op) {
|
||||||
case 'query': {
|
case 'query': {
|
||||||
const { store, data } = context.request;
|
const { store, data } = context.request;
|
||||||
@@ -52,111 +57,89 @@ export default class IndexedDbHandler {
|
|||||||
|
|
||||||
if (!peekIndexedDB) {
|
if (!peekIndexedDB) {
|
||||||
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
||||||
|
let payload;
|
||||||
let listToken;
|
let listToken;
|
||||||
|
let writeToIndexedDbPromise;
|
||||||
|
|
||||||
if (storeToken) {
|
if (storeToken) {
|
||||||
listToken = await indexedDb.token.get(tokenKey);
|
const tokenObj = await indexedDb.token.get(tokenKey);
|
||||||
|
listToken = tokenObj?.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload;
|
do {
|
||||||
try {
|
try {
|
||||||
payload = await adapter.query(store, schema, {
|
|
||||||
...remainingQuery,
|
|
||||||
list_token: listToken?.token,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// If we get an invalid list token, we'll delete the token and
|
|
||||||
// clear the DB and try again without a token.
|
|
||||||
if (
|
|
||||||
e?.errors[0].status === 400 &&
|
|
||||||
e?.errors[0].code === 'invalid list token'
|
|
||||||
) {
|
|
||||||
await indexedDb[type].clear();
|
|
||||||
// Delete all tokens of the same resource type so we don't keep clearing the DB.
|
|
||||||
// Even if other tokens may still be valid, we might have cleared data that they
|
|
||||||
// depended on so we should clear all tokens of the same type when clearing the DB.
|
|
||||||
const tokenKeys = await indexedDb.token
|
|
||||||
.where(':id')
|
|
||||||
.startsWith(type)
|
|
||||||
.primaryKeys();
|
|
||||||
|
|
||||||
await indexedDb.token.bulkDelete(tokenKeys);
|
|
||||||
|
|
||||||
payload = await adapter.query(store, schema, {
|
payload = await adapter.query(store, schema, {
|
||||||
...remainingQuery,
|
...remainingQuery,
|
||||||
|
list_token: listToken,
|
||||||
|
batchLimit: batchLimit,
|
||||||
|
});
|
||||||
|
|
||||||
|
// await the previous writeToIndexedDbPromise before writing to indexedDb again
|
||||||
|
if (writeToIndexedDbPromise) {
|
||||||
|
await writeToIndexedDbPromise;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
payload = await this.retryQueryFailure({
|
||||||
|
err,
|
||||||
|
indexedDb,
|
||||||
|
type,
|
||||||
|
adapter,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
remainingQuery,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
listToken = payload.list_token;
|
||||||
|
|
||||||
// Store the token we just got back from the payload if it exists
|
// Don't await immediately and let the next query run while we write to indexedDb
|
||||||
if (payload.list_token && storeToken) {
|
writeToIndexedDbPromise = this.writeToIndexedDb({
|
||||||
await indexedDb.token.put({
|
payload,
|
||||||
id: tokenKey,
|
storeToken,
|
||||||
token: payload.list_token,
|
indexedDb,
|
||||||
});
|
tokenKey,
|
||||||
}
|
type,
|
||||||
|
|
||||||
// Remove any records from the DB if the API indicates they've been deleted
|
|
||||||
if (payload.removed_ids?.length > 0) {
|
|
||||||
await indexedDb[type].bulkDelete(payload.removed_ids);
|
|
||||||
}
|
|
||||||
// This is a temporary fix of clearing the DB (specifically for auth-methods)
|
|
||||||
// since we are not storing the token we do not get back a list of removed_ids
|
|
||||||
// from the API call and we do not want deleted items from showing in list view.
|
|
||||||
if (!storeToken) {
|
|
||||||
await indexedDb[type].clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedPayload = serializer.normalizeResponse(
|
|
||||||
store,
|
|
||||||
schema,
|
|
||||||
payload,
|
|
||||||
null,
|
|
||||||
'query',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: payloadData } = normalizedPayload;
|
|
||||||
|
|
||||||
// Store the new data we just got back from the API refresh
|
|
||||||
const items = payloadData.map((datum) =>
|
|
||||||
this.indexedDb.normalizeData({
|
|
||||||
data: datum,
|
|
||||||
cleanData: true,
|
|
||||||
schema,
|
|
||||||
serializer,
|
serializer,
|
||||||
}),
|
store,
|
||||||
);
|
schema,
|
||||||
|
});
|
||||||
|
} while (payload.response_type === 'delta');
|
||||||
|
|
||||||
await indexedDb[type].bulkPut(items);
|
await writeToIndexedDbPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Results returned in JSON API reponse, but in a format optimized for indexedDb
|
||||||
const indexedDbResults = await queryIndexedDb(
|
const indexedDbResults = await queryIndexedDb(
|
||||||
indexedDb,
|
indexedDb,
|
||||||
type,
|
type,
|
||||||
queryObj,
|
queryObj,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dbRecords = paginateResults(indexedDbResults, page, pageSize).map(
|
// Normalize the results and clean up indexedDb format, ready for ember data consumption
|
||||||
(item) =>
|
const normalizedResults = indexedDbResults.map((item) => {
|
||||||
this.indexedDb.normalizeData({
|
return this.indexedDb.normalizeData({
|
||||||
data: item,
|
data: item,
|
||||||
cleanData: false,
|
cleanData: false,
|
||||||
schema,
|
schema,
|
||||||
serializer,
|
serializer,
|
||||||
}),
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
// Return the raw data if we don't push to the store.
|
// Sort the results
|
||||||
let records = dbRecords.map((record) => ({
|
const sortedResults = sortResults(normalizedResults, {
|
||||||
...record.attributes,
|
querySort: queryObj?.sort,
|
||||||
id: record.id,
|
schema,
|
||||||
}));
|
});
|
||||||
|
|
||||||
if (pushToStore) {
|
// Paginate the results
|
||||||
records = store.push({ data: dbRecords });
|
const paginatedResults = paginateResults(sortedResults, page, pageSize);
|
||||||
}
|
|
||||||
|
// If we are not pushing to the store, use the raw data with id property
|
||||||
|
const records = pushToStore
|
||||||
|
? store.push({ data: paginatedResults })
|
||||||
|
: paginatedResults.map((record) => ({
|
||||||
|
...record.attributes,
|
||||||
|
id: record.id,
|
||||||
|
}));
|
||||||
|
|
||||||
// Set a meta property on the array to store the total items of the results.
|
// Set a meta property on the array to store the total items of the results.
|
||||||
// This isn't conventional but is better than returning an ArrayProxy
|
// This isn't conventional but is better than returning an ArrayProxy
|
||||||
@@ -169,4 +152,91 @@ export default class IndexedDbHandler {
|
|||||||
return next(context.request);
|
return next(context.request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async retryQueryFailure({
|
||||||
|
err,
|
||||||
|
indexedDb,
|
||||||
|
type,
|
||||||
|
adapter,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
remainingQuery,
|
||||||
|
}) {
|
||||||
|
// If we get an invalid list token, we'll delete the token and
|
||||||
|
// clear the DB and try again without a token.
|
||||||
|
if (
|
||||||
|
err?.errors[0].status === 400 &&
|
||||||
|
err?.errors[0].code === 'invalid list token'
|
||||||
|
) {
|
||||||
|
await indexedDb[type].clear();
|
||||||
|
// Delete all tokens of the same resource type so we don't keep clearing the DB.
|
||||||
|
// Even if other tokens may still be valid, we might have cleared data that they
|
||||||
|
// depended on so we should clear all tokens of the same type when clearing the DB.
|
||||||
|
const tokenKeys = await indexedDb.token
|
||||||
|
.where(':id')
|
||||||
|
.startsWith(type)
|
||||||
|
.primaryKeys();
|
||||||
|
|
||||||
|
await indexedDb.token.bulkDelete(tokenKeys);
|
||||||
|
|
||||||
|
return await adapter.query(store, schema, {
|
||||||
|
...remainingQuery,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeToIndexedDb({
|
||||||
|
payload,
|
||||||
|
storeToken,
|
||||||
|
indexedDb,
|
||||||
|
tokenKey,
|
||||||
|
type,
|
||||||
|
serializer,
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
}) {
|
||||||
|
// Store the token we just got back from the payload if it exists
|
||||||
|
if (payload.list_token && storeToken) {
|
||||||
|
await indexedDb.token.put({
|
||||||
|
id: tokenKey,
|
||||||
|
token: payload.list_token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any records from the DB if the API indicates they've been deleted
|
||||||
|
if (payload.removed_ids?.length > 0) {
|
||||||
|
await indexedDb[type].bulkDelete(payload.removed_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a temporary fix of clearing the DB (specifically for auth-methods)
|
||||||
|
// since we are not storing the token we do not get back a list of removed_ids
|
||||||
|
// from the API call and we do not want deleted items from showing in list view.
|
||||||
|
if (!storeToken) {
|
||||||
|
await indexedDb[type].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPayload = serializer.normalizeResponse(
|
||||||
|
store,
|
||||||
|
schema,
|
||||||
|
payload,
|
||||||
|
null,
|
||||||
|
'query',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: payloadData } = normalizedPayload;
|
||||||
|
|
||||||
|
// Store the new data we just got back from the API refresh
|
||||||
|
const items = payloadData.map((datum) =>
|
||||||
|
this.indexedDb.normalizeData({
|
||||||
|
data: datum,
|
||||||
|
cleanData: true,
|
||||||
|
schema,
|
||||||
|
serializer,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await indexedDb[type].bulkPut(items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import Mixin from '@ember/object/mixin';
|
import Mixin from '@ember/object/mixin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides URL-building methods in an adpater for greater flexibility.
|
* Overrides URL-building methods in an adapter for greater flexibility.
|
||||||
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
|
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
|
||||||
* `snapshot`, which aren't normally passed. And adds support for a
|
* `snapshot`, which aren't normally passed. And adds support for a
|
||||||
* `urlSuffix` method, the return value of which is appended to the end of
|
* `urlSuffix` method, the return value of which is appended to the end of
|
||||||
@@ -22,7 +22,7 @@ export default Mixin.create({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This override behaves identically to the default method by default.
|
* This override behaves identically to the default method by default.
|
||||||
* It exists only to document the additional paramters to the method.
|
* It exists only to document the additional parameters to the method.
|
||||||
* @override
|
* @override
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @param {string} parentURL
|
* @param {string} parentURL
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export default class AccountModel extends GeneratedAccountModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get accountName() {
|
get accountName() {
|
||||||
const { email, full_name, login_name } = this;
|
const { email, full_name, login_name, subject } = this;
|
||||||
return email || full_name || login_name;
|
return email || full_name || login_name || subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =methods
|
// =methods
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import Model from '@ember-data/model';
|
import Model from '@ember-data/model';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base model class provides common functionality for all models.
|
* Base model class provides common functionality for all models.
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import GeneratedGroupModel from '../generated/models/group';
|
|||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export default class GroupModel extends GeneratedGroupModel {
|
export default class GroupModel extends GeneratedGroupModel {
|
||||||
// =services
|
// =services
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
|
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export const TYPE_HOST_CATALOG_STATIC = 'static';
|
export const TYPE_HOST_CATALOG_STATIC = 'static';
|
||||||
export const TYPE_HOST_CATALOG_DYNAMIC = 'plugin';
|
export const TYPE_HOST_CATALOG_DYNAMIC = 'plugin';
|
||||||
@@ -14,9 +15,11 @@ 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_STATIC = 'static-credential';
|
||||||
@@ -27,10 +30,19 @@ export const TYPES_CREDENTIALS = Object.freeze([
|
|||||||
TYPE_CREDENTIAL_DYNAMIC,
|
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;
|
@tracked _credentialTypeValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the host catalog is static.
|
* True if the host catalog is static.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -52,14 +64,14 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get credentialType() {
|
get credentialType() {
|
||||||
if (!this.#credentialType) {
|
if (!this._credentialTypeValue) {
|
||||||
if (this.role_arn) {
|
if (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
|
return TYPE_CREDENTIAL_DYNAMIC;
|
||||||
} else {
|
} else {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_STATIC;
|
return TYPE_CREDENTIAL_STATIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.#credentialType;
|
return this._credentialTypeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +79,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
set credentialType(type) {
|
set credentialType(type) {
|
||||||
this.#credentialType = type;
|
this._credentialTypeValue = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +97,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +105,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedRoleModel from '../generated/models/role';
|
import GeneratedRoleModel from '../generated/models/role';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const GRANT_SCOPE_THIS = 'this';
|
export const GRANT_SCOPE_THIS = 'this';
|
||||||
export const GRANT_SCOPE_CHILDREN = 'children';
|
export const GRANT_SCOPE_CHILDREN = 'children';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedSessionRecordingModel from '../generated/models/session-recording';
|
import GeneratedSessionRecordingModel from '../generated/models/session-recording';
|
||||||
import { hasMany } from '@ember-data/model';
|
import { hasMany } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const TYPE_SESSION_RECORDING_SSH = 'ssh';
|
export const TYPE_SESSION_RECORDING_SSH = 'ssh';
|
||||||
export const TYPES_SESSION_RECORDING = Object.freeze([
|
export const TYPES_SESSION_RECORDING = Object.freeze([
|
||||||
@@ -71,7 +71,15 @@ export default class SessionRecordingModel extends GeneratedSessionRecordingMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reapply policy dates via the `reapply-staorage-policy` method.
|
* Returns true if retention of session-recording is forever.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get retainForever() {
|
||||||
|
return this.retain_until?.toISOString() === '9999-12-31T23:23:23.999Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reapply policy dates via the `reapply-storage-policy` method.
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @param {object} options.adapterOptions
|
* @param {object} options.adapterOptions
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import GeneratedSessionModel from '../generated/models/session';
|
|||||||
import { equal } from '@ember/object/computed';
|
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 { 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import GeneratedStorageBucketModel from '../generated/models/storage-bucket';
|
import GeneratedStorageBucketModel from '../generated/models/storage-bucket';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
|
export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
|
||||||
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
|
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
|
||||||
@@ -26,7 +27,8 @@ export const TYPES_CREDENTIALS = Object.freeze([
|
|||||||
export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
||||||
// attributes
|
// attributes
|
||||||
|
|
||||||
#credentialType;
|
@tracked _credentialTypeValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the storage bucket is a plugin.
|
* True if the storage bucket is a plugin.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -40,14 +42,14 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get credentialType() {
|
get credentialType() {
|
||||||
if (!this.#credentialType) {
|
if (!this._credentialTypeValue) {
|
||||||
if (this.role_arn) {
|
if (this.role_arn) {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
|
return TYPE_CREDENTIAL_DYNAMIC;
|
||||||
} else {
|
} else {
|
||||||
this.#credentialType = TYPE_CREDENTIAL_STATIC;
|
return TYPE_CREDENTIAL_STATIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.#credentialType;
|
return this._credentialTypeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,8 +57,9 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
set credentialType(type) {
|
set credentialType(type) {
|
||||||
this.#credentialType = type;
|
this._credentialTypeValue = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the storage bucket is an unknown type.
|
* True if the storage bucket is an unknown type.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import GeneratedTargetModel from '../generated/models/target';
|
import GeneratedTargetModel from '../generated/models/target';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export const TYPE_TARGET_TCP = 'tcp';
|
export const TYPE_TARGET_TCP = 'tcp';
|
||||||
export const TYPE_TARGET_SSH = 'ssh';
|
export const TYPE_TARGET_SSH = 'ssh';
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -59,6 +60,18 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
// Do not serialize `disabled` fields.
|
// Do not serialize `disabled` fields.
|
||||||
// TODO: disabled is temporarily disabled
|
// TODO: disabled is temporarily disabled
|
||||||
if (key === 'disabled') delete json[key];
|
if (key === 'disabled') delete json[key];
|
||||||
|
|
||||||
|
// Before we transform our json
|
||||||
|
// Let's trim the values in the json obj
|
||||||
|
// Currently, json values will only ever be a string or an array
|
||||||
|
if (options.trimWhitespace && json[key]) {
|
||||||
|
if (typeOf(json[key]) === 'array') {
|
||||||
|
json[key] = json[key].map((str) => str.trim());
|
||||||
|
} else if (typeOf(json[key] === 'string')) {
|
||||||
|
json[key] = json[key].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Push nested attributes down into the attributes key
|
// Push nested attributes down into the attributes key
|
||||||
if (options.isNestedAttribute && json[key] !== undefined) {
|
if (options.isNestedAttribute && json[key] !== undefined) {
|
||||||
if (!json.attributes) json.attributes = {};
|
if (!json.attributes) json.attributes = {};
|
||||||
@@ -74,18 +87,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;
|
||||||
@@ -211,12 +244,10 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
normalizeMissingArrays(store, primaryModelClass, payload) {
|
normalizeMissingArrays(store, primaryModelClass, payload) {
|
||||||
const attrDefs = store
|
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
|
||||||
.getSchemaDefinitionService()
|
|
||||||
.attributesDefinitionFor({ type: primaryModelClass.modelName });
|
|
||||||
if (attrDefs) {
|
if (attrDefs) {
|
||||||
Object.keys(attrDefs).forEach((key) => {
|
attrDefs.keys().forEach((key) => {
|
||||||
if (!payload[key] && attrDefs[key]?.options?.emptyArrayIfMissing) {
|
if (!payload[key] && attrDefs.get(key)?.options?.emptyArrayIfMissing) {
|
||||||
payload[key] = [];
|
payload[key] = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -235,12 +266,10 @@ export default class ApplicationSerializer extends RESTSerializer {
|
|||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
normalizeMissingObjects(store, primaryModelClass, payload) {
|
normalizeMissingObjects(store, primaryModelClass, payload) {
|
||||||
const attrDefs = store
|
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
|
||||||
.getSchemaDefinitionService()
|
|
||||||
.attributesDefinitionFor({ type: primaryModelClass.modelName });
|
|
||||||
if (attrDefs) {
|
if (attrDefs) {
|
||||||
Object.keys(attrDefs).forEach((key) => {
|
attrDefs.keys().forEach((key) => {
|
||||||
if (!payload[key] && attrDefs[key]?.options?.emptyObjectIfMissing) {
|
if (!payload[key] && attrDefs.get(key)?.options?.emptyObjectIfMissing) {
|
||||||
payload[key] = {};
|
payload[key] = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
|||||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
|
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
|
||||||
return this.serializeVaultGeneric(...arguments);
|
return this.serializeVaultGeneric(...arguments);
|
||||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
|
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
|
||||||
|
return this.serializeVaultSshCertificate(...arguments);
|
||||||
default:
|
default:
|
||||||
return super.serialize(...arguments);
|
return super.serialize(...arguments);
|
||||||
}
|
}
|
||||||
@@ -61,4 +62,16 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
|||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializeVaultSshCertificate() {
|
||||||
|
const serialized = super.serialize(...arguments);
|
||||||
|
if (serialized.attributes) {
|
||||||
|
const { key_type } = serialized.attributes;
|
||||||
|
if (key_type !== 'rsa' && key_type !== 'ecdsa') {
|
||||||
|
// If the key type is not RSA or ECDSA, set key_bits to null
|
||||||
|
serialized.attributes.key_bits = null;
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,101 +4,45 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ApplicationSerializer from './application';
|
import ApplicationSerializer from './application';
|
||||||
import {
|
import { TYPE_CREDENTIAL_DYNAMIC } from '../models/host-catalog';
|
||||||
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
|
||||||
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
|
||||||
TYPE_CREDENTIAL_DYNAMIC,
|
|
||||||
} from '../models/host-catalog';
|
|
||||||
|
|
||||||
// These fields are Azure specific
|
|
||||||
const azureFields = [
|
|
||||||
'disable_credential_rotation',
|
|
||||||
'tenant_id',
|
|
||||||
'client_id',
|
|
||||||
'subscription_id',
|
|
||||||
'secret_id',
|
|
||||||
'secret_value',
|
|
||||||
];
|
|
||||||
|
|
||||||
// These fields are AWS specific, AWS provider now has two credentialTypes: static and dynamic [not to be confused with static and dynamic host-catalogs]
|
|
||||||
const AWSfieldsWithCredentialType = {
|
|
||||||
'static-credential': [
|
|
||||||
'access_key_id',
|
|
||||||
'secret_access_key',
|
|
||||||
'region',
|
|
||||||
'disable_credential_rotation',
|
|
||||||
'worker_filter',
|
|
||||||
],
|
|
||||||
'dynamic-credential': [
|
|
||||||
'role_arn',
|
|
||||||
'role_external_id',
|
|
||||||
'role_session_name',
|
|
||||||
'role_tags',
|
|
||||||
'region',
|
|
||||||
'disable_credential_rotation',
|
|
||||||
'worker_filter',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class HostCatalogSerializer extends ApplicationSerializer {
|
export default class HostCatalogSerializer extends ApplicationSerializer {
|
||||||
serialize(snapshot) {
|
serialize(snapshot) {
|
||||||
const { compositeType, credentialType } = snapshot.record;
|
const { credentialType } = snapshot.record;
|
||||||
const serialized = super.serialize(...arguments);
|
const serialized = super.serialize(...arguments);
|
||||||
|
|
||||||
// This is a hack to remove the worker_filter field from the serialized data if the compositeType is not AWS
|
|
||||||
// Ideally we should refactor the model to include the compositeType using `for` attribute
|
|
||||||
if (compositeType !== TYPE_HOST_CATALOG_PLUGIN_AWS) {
|
|
||||||
delete serialized.worker_filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, disable credential rotation for dynamic credentials
|
// By default, disable credential rotation for dynamic credentials
|
||||||
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
|
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
|
||||||
serialized.attributes.disable_credential_rotation = true;
|
serialized.attributes.disable_credential_rotation = true;
|
||||||
}
|
}
|
||||||
|
return serialized;
|
||||||
switch (compositeType) {
|
|
||||||
case 'static':
|
|
||||||
return this.serializeStatic(...arguments);
|
|
||||||
default:
|
|
||||||
return serialized;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeAttribute(snapshot, json, key, attribute) {
|
serializeAttribute(snapshot, json, key, attribute) {
|
||||||
const value = super.serializeAttribute(...arguments);
|
const value = super.serializeAttribute(...arguments);
|
||||||
const { compositeType, credentialType, isPlugin } = snapshot.record;
|
const { compositeType, credentialType } = snapshot.record;
|
||||||
const { options } = attribute;
|
const { options } = attribute;
|
||||||
|
const isSamePluginName = options?.for?.name?.includes(compositeType);
|
||||||
|
|
||||||
const fields =
|
// Remove attributes that are not applicable to the current credential type for the same plugin
|
||||||
compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE
|
// For example, 'aws' dynamic plugin has two credential types: 'static' and 'dynamic'
|
||||||
? azureFields
|
// 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)
|
||||||
: AWSfieldsWithCredentialType[credentialType];
|
// Empty secrets should be removed from the JSON
|
||||||
|
if (
|
||||||
// Delete any nested attribute fields that don't belong to the record type
|
isSamePluginName &&
|
||||||
if (isPlugin && options.isNestedAttribute && json.attributes) {
|
options.for.credentialType &&
|
||||||
if (!fields.includes(key)) {
|
options.for.credentialType !== credentialType
|
||||||
delete json.attributes[key];
|
) {
|
||||||
}
|
if (options.isNestedAttribute) {
|
||||||
}
|
json.attributes[key] = null;
|
||||||
|
} else if (options.isNestedSecret && json.secrets?.[key]) {
|
||||||
// Delete any secret fields that don't belong to the record type
|
|
||||||
if (isPlugin && options.isNestedSecret && json.secrets) {
|
|
||||||
if (!fields.includes(key)) {
|
|
||||||
delete json.secrets[key];
|
delete json.secrets[key];
|
||||||
if (json['secrets'] && Object.keys(json.secrets).length === 0) {
|
if (Object.keys(json.secrets).length === 0) {
|
||||||
delete json.secrets;
|
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;
|
|
||||||
delete serialized.worker_filter;
|
|
||||||
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;
|
||||||
@@ -107,4 +95,18 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
|||||||
|
|
||||||
return super.normalize(typeClass, normalizedHash, ...rest);
|
return super.normalize(typeClass, normalizedHash, ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts errors from the payload and transforms specific keys if needed.
|
||||||
|
* @override
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
extractErrors() {
|
||||||
|
const errors = super.extractErrors(...arguments);
|
||||||
|
if (errors?.filter) {
|
||||||
|
errors.filter_string = errors.filter;
|
||||||
|
delete errors.filter;
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -53,4 +53,18 @@ export default class ManagedGroupSerializer extends ApplicationSerializer {
|
|||||||
|
|
||||||
return super.normalize(typeClass, normalizedHash, ...rest);
|
return super.normalize(typeClass, normalizedHash, ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts errors from the payload and transforms specific keys if needed.
|
||||||
|
* @override
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
extractErrors() {
|
||||||
|
const errors = super.extractErrors(...arguments);
|
||||||
|
if (errors?.filter) {
|
||||||
|
errors.filter_string = errors.filter;
|
||||||
|
delete errors.filter;
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,7 @@ export default class StorageBucketSerializer extends ApplicationSerializer {
|
|||||||
const value = super.serializeAttribute(...arguments);
|
const value = super.serializeAttribute(...arguments);
|
||||||
const { credentialType } = snapshot.record;
|
const { credentialType } = snapshot.record;
|
||||||
const { options } = attribute;
|
const { options } = attribute;
|
||||||
|
|
||||||
// This deletes any fields that don't belong to the record's credential type
|
// This deletes any fields that don't belong to the record's credential type
|
||||||
if (options.isNestedAttribute && json.attributes) {
|
if (options.isNestedAttribute && json.attributes) {
|
||||||
// The key must be included in the fieldsByType list above
|
// The key must be included in the fieldsByType list above
|
||||||
|
|||||||
@@ -97,23 +97,42 @@ const unconvertFields = (result, object, schema, serializer) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to encapsulate the IndexedDB implementation. To use this service, call
|
* Service to encapsulate the IndexedDB implementation. To use this service, call
|
||||||
* `setup` from the application root and
|
* `setup` from the application root. This will create the database and the necessary indexes.
|
||||||
|
*
|
||||||
|
* Make sure to increment the version number of the database whenever the indexes change.
|
||||||
|
* This will delete the old database and create a new one with the new indexes.
|
||||||
*/
|
*/
|
||||||
export default class IndexedDbService extends Service {
|
export default class IndexedDbService extends Service {
|
||||||
// =attributes
|
// =attributes
|
||||||
#db;
|
#db;
|
||||||
|
#version = 2;
|
||||||
|
|
||||||
get db() {
|
get db() {
|
||||||
return this.#db;
|
return this.#db;
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(dbName) {
|
async setup(dbName) {
|
||||||
// Don't run setup again if we already have one or if we didn't get a name
|
// Don't run setup again if we already have one or if we didn't get a name
|
||||||
if (this.#db || !dbName) {
|
if (this.#db || !dbName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const doesDbExist = await Dexie.exists(dbName);
|
||||||
|
|
||||||
|
// If the database already exists, open the database and
|
||||||
|
// check if the version is a lower version than the current one
|
||||||
|
if (doesDbExist) {
|
||||||
|
const dbVerifier = new Dexie(dbName);
|
||||||
|
await dbVerifier.open();
|
||||||
|
if (dbVerifier.verno < this.#version) {
|
||||||
|
await dbVerifier.delete();
|
||||||
|
} else {
|
||||||
|
dbVerifier.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.#db = new Dexie(dbName);
|
this.#db = new Dexie(dbName);
|
||||||
this.#db.version(2).stores(modelIndexes);
|
this.#db.version(this.#version).stores(modelIndexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Service from '@ember/service';
|
import Service from '@ember/service';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a POJO representing a query and converts it to a boolean expression
|
* Takes a POJO representing a query and converts it to a boolean expression
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class MemoryStorage {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* import Route from '@ember/routing/route';
|
* import Route from '@ember/routing/route';
|
||||||
* import { inject as service } from '@ember/service';
|
* import { service } from '@ember/service';
|
||||||
* export default class ApplicationRoute extends Route {
|
* export default class ApplicationRoute extends Route {
|
||||||
* @service storage;
|
* @service storage;
|
||||||
* afterModel() {
|
* afterModel() {
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Store, { CacheHandler } from '@ember-data/store';
|
// Importing 'Store' from 'ember-data/store' to get default configuration automatically.
|
||||||
|
// TODO: Remove eslint-disable after upgrading to eslint-plugin-ember >= v12.2.1
|
||||||
|
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
|
||||||
|
import Store from 'ember-data/store';
|
||||||
|
import { CacheHandler } from '@ember-data/store';
|
||||||
import RequestManager from '@ember-data/request';
|
import RequestManager from '@ember-data/request';
|
||||||
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
||||||
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class AccountValueMapArrayTransform {
|
||||||
|
|
||||||
export default class AccountValueMapArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const accountValues = serialized || [];
|
const accountValues = serialized || [];
|
||||||
return accountValues.map((accountValueString) => ({
|
return accountValues.map((accountValueString) => ({
|
||||||
@@ -18,4 +16,8 @@ export default class AccountValueMapArrayTransform extends Transform {
|
|||||||
const accountValues = deserialized || [];
|
const accountValues = deserialized || [];
|
||||||
return accountValues.map(({ key, value }) => `${key}=${value}`);
|
return accountValues.map(({ key, value }) => `${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
|
||||||
import { TrackedArray } from 'tracked-built-ins';
|
import { TrackedArray } from 'tracked-built-ins';
|
||||||
|
|
||||||
export default class ArrayTransform extends Transform {
|
export default class ArrayTransform {
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const arr = new TrackedArray(serialized || []);
|
const arr = new TrackedArray(serialized || []);
|
||||||
return arr;
|
return arr;
|
||||||
@@ -16,4 +15,8 @@ export default class ArrayTransform extends Transform {
|
|||||||
const arr = deserialized || [];
|
const arr = deserialized || [];
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class DurationTransform {
|
||||||
|
|
||||||
export default class DurationTransform extends Transform {
|
|
||||||
/**
|
/**
|
||||||
* Normalize a duration string in the format "3.0001s" to milliseconds.
|
* Normalize a duration string in the format "3.0001s" to milliseconds.
|
||||||
* The backend returns a precision of nanoseconds but we round to whole
|
* The backend returns a precision of nanoseconds but we round to whole
|
||||||
@@ -37,4 +35,8 @@ export default class DurationTransform extends Transform {
|
|||||||
maximumFractionDigits: 9,
|
maximumFractionDigits: 9,
|
||||||
})}s`;
|
})}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class HostSourceIdArrayTransform {
|
||||||
|
|
||||||
export default class HostSourceIdArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const stringValues = serialized || [];
|
const stringValues = serialized || [];
|
||||||
return stringValues.map(({ id: host_source_id, ...obj }) => ({
|
return stringValues.map(({ id: host_source_id, ...obj }) => ({
|
||||||
@@ -18,4 +16,8 @@ export default class HostSourceIdArrayTransform extends Transform {
|
|||||||
const strings = deserialized || [];
|
const strings = deserialized || [];
|
||||||
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
|
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class ObjectAsArrayTransform {
|
||||||
|
|
||||||
export default class ObjectAsArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const obj = serialized || {};
|
const obj = serialized || {};
|
||||||
return Object.entries(obj).map(([key, value]) => ({ key, value }));
|
return Object.entries(obj).map(([key, value]) => ({ key, value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(deserialized) {
|
serialize(deserialized) {
|
||||||
const array = deserialized || [];
|
const array = deserialized || [];
|
||||||
|
|
||||||
@@ -24,4 +23,8 @@ export default class ObjectAsArrayTransform extends Transform {
|
|||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
|
||||||
import { TrackedObject } from 'tracked-built-ins';
|
import { TrackedObject } from 'tracked-built-ins';
|
||||||
|
|
||||||
export default class ObjectTransform extends Transform {
|
export default class ObjectTransform {
|
||||||
/**
|
/**
|
||||||
* @param serialized
|
* @param serialized
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
@@ -26,4 +25,8 @@ export default class ObjectTransform extends Transform {
|
|||||||
}
|
}
|
||||||
return deserialized;
|
return deserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Transform from '@ember-data/serializer/transform';
|
export default class StringArrayTransform {
|
||||||
|
|
||||||
export default class StringArrayTransform extends Transform {
|
|
||||||
deserialize(serialized) {
|
deserialize(serialized) {
|
||||||
const stringValues = serialized || [];
|
const stringValues = serialized || [];
|
||||||
return stringValues.map((value) => ({ value }));
|
return stringValues.map((value) => ({ value }));
|
||||||
@@ -15,4 +13,8 @@ export default class StringArrayTransform extends Transform {
|
|||||||
const strings = deserialized || [];
|
const strings = deserialized || [];
|
||||||
return strings.map(({ value }) => value);
|
return strings.map(({ value }) => value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
return new this();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -70,14 +70,9 @@ export async function queryIndexedDb(indexedDb, resource, query) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderBy has to be used on where clauses while if it's a
|
// filterCollection can be Dexie Table or a Dexie Collection.
|
||||||
// collection already we have to use sortBy. We'll also sort by
|
// This is an important detail you need to keep in mind when working on it.
|
||||||
// descending order based on created time to match the API as any
|
return filterCollection.toArray();
|
||||||
// "or" clause can change the order
|
|
||||||
if (filterCollection.orderBy) {
|
|
||||||
return filterCollection.orderBy(getKey('created_time')).reverse().toArray();
|
|
||||||
}
|
|
||||||
return filterCollection.reverse().sortBy(getKey('created_time'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildInitialWhereClause = ({ filterArrayOrObject, table, key }) => {
|
const buildInitialWhereClause = ({ filterArrayOrObject, table, key }) => {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// created_time is not an attribute defined on all models, but is present in all the resources
|
||||||
|
// coming from API
|
||||||
|
const SORT_DEFAULT_ATTRIBUTE = 'created_time';
|
||||||
|
const SORT_DIRECTION_ASCENDING = 'asc';
|
||||||
|
const SORT_DIRECTION_DESCENDING = 'desc';
|
||||||
|
|
||||||
|
const sortFunctions = {
|
||||||
|
string: (a, b) => String(a).localeCompare(String(b)),
|
||||||
|
date: (a, b) => new Date(a).getTime() - new Date(b).getTime(),
|
||||||
|
number: (a, b) => Number(a) - Number(b),
|
||||||
|
boolean: (a, b) => Number(a) - Number(b),
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSortableValue(schema, record, attribute) {
|
||||||
|
if (attribute === 'id') {
|
||||||
|
return record.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all models have a created_time attribute but not all models have it defined
|
||||||
|
if (attribute === 'created_time') {
|
||||||
|
return record.attributes.created_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.attributes.has(attribute)) {
|
||||||
|
return record.attributes[attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = Array.from(schema.attributes.keys());
|
||||||
|
throw new Error(
|
||||||
|
`The attribute "${attribute}" does not map to the model definition of type "${record.type}". Supported sortable attributes are 'id', 'created_time', or ${attributes.map((attr) => `'${attr}'`).join(', ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sortResults = (results, { querySort, schema }) => {
|
||||||
|
querySort = querySort ?? {};
|
||||||
|
const sortAttribute = querySort.attribute || SORT_DEFAULT_ATTRIBUTE;
|
||||||
|
|
||||||
|
// Default sort direction is ascending unless we are sorting by `created_time` (default sort attribute)
|
||||||
|
const defaultSortDirection =
|
||||||
|
sortAttribute === SORT_DEFAULT_ATTRIBUTE && !querySort.sortFunction
|
||||||
|
? SORT_DIRECTION_DESCENDING
|
||||||
|
: SORT_DIRECTION_ASCENDING;
|
||||||
|
const sortDirection = querySort.direction || defaultSortDirection;
|
||||||
|
|
||||||
|
if (
|
||||||
|
![SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING].includes(
|
||||||
|
sortDirection,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error('Invalid sort direction');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute custom sort function if one is provided.
|
||||||
|
if (querySort.sortFunction) {
|
||||||
|
return results.toSorted((a, b) => {
|
||||||
|
const sortResult = querySort.sortFunction(a, b);
|
||||||
|
|
||||||
|
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||||
|
? sortResult
|
||||||
|
: -1 * sortResult;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortAttributeDataType = schema.attributes.get(sortAttribute)?.type;
|
||||||
|
|
||||||
|
const sortFunction =
|
||||||
|
sortFunctions[sortAttributeDataType] ?? sortFunctions.string;
|
||||||
|
|
||||||
|
return results.toSorted((a, b) => {
|
||||||
|
// Extract the values to sort and sort them.
|
||||||
|
const sortValueA = getSortableValue(schema, a, sortAttribute);
|
||||||
|
const sortValueB = getSortableValue(schema, b, sortAttribute);
|
||||||
|
const sortResult = sortFunction(sortValueA, sortValueB);
|
||||||
|
|
||||||
|
return sortDirection === SORT_DIRECTION_ASCENDING
|
||||||
|
? sortResult
|
||||||
|
: -1 * sortResult;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ember-data no longer automatically installs BooleanTransform for apps.
|
||||||
|
export { BooleanTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default, initialize } from 'core/initializers/deprecations';
|
export { DateTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default } from 'rose/components/rose/button';
|
export { NumberTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ember-data no longer automatically installs StringTransform for apps.
|
||||||
|
export { StringTransform as default } from '@ember-data/serializer/transform';
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
|
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
|
||||||
const { maybeEmbroider } = require('@embroider/test-setup');
|
|
||||||
|
|
||||||
module.exports = function (defaults) {
|
module.exports = function (defaults) {
|
||||||
const app = new EmberAddon(defaults, {
|
const app = new EmberAddon(defaults, {
|
||||||
@@ -19,6 +18,8 @@ module.exports = function (defaults) {
|
|||||||
This build file does *not* influence how the addon or the app using it
|
This build file does *not* influence how the addon or the app using it
|
||||||
behave. You most likely want to be modifying `./index.js` or app's build file
|
behave. You most likely want to be modifying `./index.js` or app's build file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const { maybeEmbroider } = require('@embroider/test-setup');
|
||||||
return maybeEmbroider(app, {
|
return maybeEmbroider(app, {
|
||||||
skipBabel: [
|
skipBabel: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export default factory.extend({
|
|||||||
}
|
}
|
||||||
case TYPE_AUTH_METHOD_OIDC:
|
case TYPE_AUTH_METHOD_OIDC:
|
||||||
return {
|
return {
|
||||||
issuer: faker.internet.ip(),
|
issuer:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
subject: 'sub',
|
subject: 'sub',
|
||||||
email: faker.internet.email(),
|
email: faker.internet.email(),
|
||||||
full_name: faker.person.fullName(),
|
full_name: faker.person.fullName(),
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export default factory.extend({
|
|||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case 'vault':
|
case 'vault':
|
||||||
return {
|
return {
|
||||||
address: faker.internet.ip(),
|
address:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
namespace: faker.word.words(1),
|
namespace: faker.word.words(1),
|
||||||
ca_cert: 'ca-cert-123',
|
ca_cert: 'ca-cert-123',
|
||||||
tls_server_name: faker.internet.domainName(),
|
tls_server_name: faker.internet.domainName(),
|
||||||
|
|||||||
@@ -8,19 +8,29 @@ 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,
|
||||||
let pluginTypeCounter = 1;
|
TYPE_HOST_CATALOG_PLUGIN_AWS,
|
||||||
|
TYPE_HOST_CATALOG_PLUGIN_AZURE,
|
||||||
|
'foobar',
|
||||||
|
];
|
||||||
|
|
||||||
export default factory.extend({
|
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 +40,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'],
|
||||||
@@ -39,16 +49,20 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
worker_filter: function () {
|
worker_filter: function () {
|
||||||
if (this.type === 'plugin' && this.plugin?.name === 'aws') {
|
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()}"`;
|
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[i % pluginTypes.length],
|
||||||
description: faker.word.words(),
|
description: faker.word.words(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -56,35 +70,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_'),
|
||||||
@@ -27,10 +43,12 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
preferred_endpoints() {
|
preferred_endpoints() {
|
||||||
if (this.type === 'plugin') {
|
if (this.type === 'plugin') {
|
||||||
const enpointsAmount = faker.number.int({ min: 1, max: 5 });
|
const endpointsAmount = faker.number.int({ min: 1, max: 5 });
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let i = 0; i < enpointsAmount; ++i) {
|
for (let i = 0; i < endpointsAmount; ++i) {
|
||||||
result.push(faker.internet.ip());
|
result.push(
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -42,22 +60,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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -32,18 +36,20 @@ export default factory.extend({
|
|||||||
const addressesAmount = Math.floor(Math.random() * 6) + 1;
|
const addressesAmount = Math.floor(Math.random() * 6) + 1;
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let i = 0; i < addressesAmount; ++i) {
|
for (let i = 0; i < addressesAmount; ++i) {
|
||||||
result.push(faker.internet.ip());
|
result.push(
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
// 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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,31 @@ export default factory.extend({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
withManyHosts: trait({
|
||||||
|
afterCreate(target, server) {
|
||||||
|
const { scope } = target;
|
||||||
|
const hostCatalog = server.create('host-catalog', { scope });
|
||||||
|
const hosts = server.createList('host', 15, {
|
||||||
|
scope,
|
||||||
|
hostCatalog,
|
||||||
|
type: hostCatalog.type,
|
||||||
|
});
|
||||||
|
const hostSets = [
|
||||||
|
server.create('host-set', {
|
||||||
|
scope,
|
||||||
|
hostCatalog,
|
||||||
|
hosts,
|
||||||
|
type: hostCatalog.type,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
|
target.update({ hostSets });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
withTwoHosts: trait({
|
withTwoHosts: trait({
|
||||||
afterCreate(target, server) {
|
afterCreate(target, server) {
|
||||||
const { scope } = target;
|
const { scope } = target;
|
||||||
@@ -120,6 +145,10 @@ export default factory.extend({
|
|||||||
type: hostCatalog.type,
|
type: hostCatalog.type,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
target.update({ hostSets });
|
target.update({ hostSets });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -139,6 +168,10 @@ export default factory.extend({
|
|||||||
type: hostCatalog.type,
|
type: hostCatalog.type,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||||
|
hosts.forEach((host) => {
|
||||||
|
host.update({ hostSetIds });
|
||||||
|
});
|
||||||
target.update({ hostSets });
|
target.update({ hostSets });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default Factory.extend({
|
|||||||
updated_time: () => faker.date.recent(),
|
updated_time: () => faker.date.recent(),
|
||||||
version: () => faker.number.int(),
|
version: () => faker.number.int(),
|
||||||
attributes: () => {
|
attributes: () => {
|
||||||
return { address: faker.internet.ipv6() };
|
return {
|
||||||
|
address:
|
||||||
|
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -3,31 +3,8 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// import config from '../../config/environment';
|
|
||||||
// import { v1 } from 'ember-uuid';
|
|
||||||
import { Response } from 'miragejs';
|
import { Response } from 'miragejs';
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Simulates a cookie-based response to authentication requests. Any user/pass
|
|
||||||
// * values are allowed *except* when `username` equals `error`, in which case a
|
|
||||||
// * simulated error response is returned.
|
|
||||||
// * @param {object} schema
|
|
||||||
// * @param {object} request
|
|
||||||
// * @return {Response}
|
|
||||||
// */
|
|
||||||
// export default function authenticateHandler(schema, request) {
|
|
||||||
// const payload = JSON.parse(request.requestBody);
|
|
||||||
// if (payload.attributes.login_name === 'error') {
|
|
||||||
// return new Response(400);
|
|
||||||
// } else {
|
|
||||||
// const cookieName = config.auth.passwordCookieName;
|
|
||||||
// const cookieValue = v1();
|
|
||||||
// const cookiePath = config.rootURL;
|
|
||||||
// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`;
|
|
||||||
// return new Response(200);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Quick and dirty counter system to simulate the polling behavior
|
// Quick and dirty counter system to simulate the polling behavior
|
||||||
// of the API. If the OIDC flow is pending, the route returns 100.
|
// of the API. If the OIDC flow is pending, the route returns 100.
|
||||||
// If complete it returns 200 with the token JSON.
|
// If complete it returns 200 with the token JSON.
|
||||||
|
|||||||
@@ -110,7 +110,18 @@ export default function (server) {
|
|||||||
|
|
||||||
// Other resources
|
// Other resources
|
||||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||||
server.createList('host-catalog', 8, { scope }, 'withChildren');
|
server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
2,
|
||||||
|
{ scope, type: 'static' },
|
||||||
|
'withChildren',
|
||||||
|
);
|
||||||
|
server.createList(
|
||||||
|
'host-catalog',
|
||||||
|
8,
|
||||||
|
{ scope, type: 'plugin' },
|
||||||
|
'withChildren',
|
||||||
|
);
|
||||||
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
||||||
server.createList('target', 2, { scope }, 'withAssociations');
|
server.createList('target', 2, { scope }, 'withAssociations');
|
||||||
server.create('target', { scope, address: '0.0.0.0' });
|
server.create('target', { scope, address: '0.0.0.0' });
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -167,6 +199,9 @@ export default function initializeMockIPC(server, config) {
|
|||||||
* Check for OS chrome state
|
* Check for OS chrome state
|
||||||
*/
|
*/
|
||||||
showWindowActions() {
|
showWindowActions() {
|
||||||
|
if (navigator.userAgent.includes('Mac')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-32
@@ -10,8 +10,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:development": "ember build",
|
"build:development": "ember build",
|
||||||
"build": "ember build --environment=production",
|
"build": "ember build --environment=production",
|
||||||
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
|
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"",
|
||||||
"lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
|
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\"",
|
||||||
|
"lint:hbs": "ember-template-lint .",
|
||||||
|
"lint:hbs:fix": "ember-template-lint . --fix",
|
||||||
"lint:js": "eslint .",
|
"lint:js": "eslint .",
|
||||||
"lint:js:fix": "eslint . --fix",
|
"lint:js:fix": "eslint . --fix",
|
||||||
"format": "concurrently \"npm:format:*\" --names \"format:\"",
|
"format": "concurrently \"npm:format:*\" --names \"format:\"",
|
||||||
@@ -19,64 +21,69 @@
|
|||||||
"start": "ember serve",
|
"start": "ember serve",
|
||||||
"test": "ember exam --test-port 0 --split=4 --parallel=1 --random",
|
"test": "ember exam --test-port 0 --split=4 --parallel=1 --random",
|
||||||
"test:ember-compatibility": "ember try:each",
|
"test:ember-compatibility": "ember try:each",
|
||||||
"precommit": "yarn lint && yarn format && yarn test",
|
"precommit": "pnpm lint && pnpm format && pnpm test",
|
||||||
"doc:toc": "doctoc README.md"
|
"doc:toc": "doctoc README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.26.10",
|
||||||
|
"@ember/string": "^4.0.0",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"ember-auto-import": "^2.6.3",
|
"ember-auto-import": "^2.10.0",
|
||||||
"ember-can": "^4.2.0",
|
"ember-can": "^4.2.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.2.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-cli-mirage": "^3.0.3",
|
"ember-cli-mirage": "^3.0.3",
|
||||||
|
"ember-data": "~5.3.12",
|
||||||
"miragejs": "^0.1.48",
|
"miragejs": "^0.1.48",
|
||||||
"ember-data": "~4.12.0",
|
|
||||||
"tracked-built-ins": "^3.1.1"
|
"tracked-built-ins": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/eslint-parser": "^7.25.1",
|
||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/test-helpers": "^5.2.1",
|
||||||
"@ember/string": "^3.0.1",
|
"@ember/test-waiters": "^4.1.0",
|
||||||
"@ember/test-helpers": "^3.3.1",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@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-clean-css": "^3.0.0",
|
||||||
"ember-cli-dependency-checker": "^3.3.2",
|
"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": "^8.2.3",
|
||||||
"ember-qunit": "^8.1.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-resolver": "^10.0.0",
|
"ember-resolver": "^12.0.1",
|
||||||
"ember-source": "~4.12.0",
|
"ember-source": "~5.12.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"ember-template-lint": "^5.7.2",
|
"ember-template-lint": "^6.0.0",
|
||||||
"eslint": "^8.54.0",
|
"ember-try": "^3.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-plugin-ember": "^11.11.1",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-n": "^16.3.1",
|
"eslint-plugin-ember": "^12.2.1",
|
||||||
"eslint-plugin-qunit": "^8.0.1",
|
"eslint-plugin-n": "^16.6.2",
|
||||||
|
"eslint-plugin-qunit": "^8.1.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.3.3",
|
||||||
"qunit": "^2.19.4",
|
"qunit": "^2.22.0",
|
||||||
"qunit-dom": "^2.0.0",
|
"qunit-dom": "^3.2.1",
|
||||||
"webpack": "^5.94.0"
|
"sinon": "^19.0.2",
|
||||||
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ember-source": ">4.0.0"
|
"ember-source": ">= 4.0.0",
|
||||||
|
"sinon": "^19.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.* || 20.*"
|
"node": "20.* || 22.*"
|
||||||
},
|
},
|
||||||
"ember": {
|
"ember": {
|
||||||
"edition": "octane"
|
"edition": "octane"
|
||||||
@@ -85,8 +92,11 @@
|
|||||||
"configPath": "tests/dummy/config"
|
"configPath": "tests/dummy/config"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": "eslint --fix",
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
"*.hbs": "ember-template-lint --fix",
|
"*.hbs": "ember-template-lint --fix",
|
||||||
"*.{js,scss,yaml}": "prettier --write"
|
"*.{scss,yaml}": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,12 @@ module.exports = {
|
|||||||
// --no-sandbox is needed when running Chrome inside a container
|
// --no-sandbox is needed when running Chrome inside a container
|
||||||
process.env.CI ? '--no-sandbox' : null,
|
process.env.CI ? '--no-sandbox' : null,
|
||||||
'--headless',
|
'--headless',
|
||||||
|
// Disable backgrounding renders for occluded windows
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
// Disable renderer process backgrounding
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
// Disable task throttling of timer tasks from background pages
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
'--disable-dev-shm-usage',
|
'--disable-dev-shm-usage',
|
||||||
'--disable-gpu',
|
'--disable-gpu',
|
||||||
//'--disable-software-rasterizer',
|
//'--disable-software-rasterizer',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { inject as service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
export default class ApplicationRoute extends Route {
|
export default class ApplicationRoute extends Route {
|
||||||
// =services
|
// =services
|
||||||
|
|||||||
@@ -2,4 +2,3 @@
|
|||||||
* Copyright (c) HashiCorp, Inc.
|
* Copyright (c) HashiCorp, Inc.
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
{{page-title "Dummy"}}
|
{{page-title "Dummy"}}
|
||||||
|
|
||||||
<h2 id="title">Welcome to Ember</h2>
|
<Hds::Text::Display @tag="h2" id="title">Welcome to Ember</Hds::Text::Display>
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "ember-cli",
|
"name": "ember-cli",
|
||||||
"version": "4.12.1",
|
"version": "5.12.0",
|
||||||
"blueprints": [
|
"blueprints": [
|
||||||
{
|
{
|
||||||
"name": "addon",
|
"name": "addon",
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"codemodsSource": "ember-addon-codemods-manifest@1",
|
"codemodsSource": "ember-addon-codemods-manifest@1",
|
||||||
"isBaseBlueprint": true,
|
"isBaseBlueprint": true,
|
||||||
"options": [
|
"options": [
|
||||||
"--no-welcome"
|
"--no-welcome",
|
||||||
|
"--pnpm"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,28 +10,21 @@ const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
|
|||||||
|
|
||||||
module.exports = async function () {
|
module.exports = async function () {
|
||||||
return {
|
return {
|
||||||
|
usePnpm: true,
|
||||||
scenarios: [
|
scenarios: [
|
||||||
{
|
{
|
||||||
name: 'ember-lts-4.4',
|
name: 'ember-lts-4.12',
|
||||||
npm: {
|
npm: {
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'ember-source': '~4.4.0',
|
'ember-source': '~4.12.0',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ember-lts-4.8',
|
name: 'ember-lts-5.4',
|
||||||
npm: {
|
npm: {
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'ember-source': '~4.8.0',
|
'ember-source': '~5.4.0',
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ember-lts-4.4',
|
|
||||||
npm: {
|
|
||||||
devDependencies: {
|
|
||||||
'ember-source': '~4.4.0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
setupTest as upstreamSetupTest,
|
setupTest as upstreamSetupTest,
|
||||||
} from 'ember-qunit';
|
} from 'ember-qunit';
|
||||||
|
|
||||||
// This file exists to provide wrappers around ember-qunit's / ember-mocha's
|
// This file exists to provide wrappers around ember-qunit's
|
||||||
// test setup functions. This way, you can easily extend the setup that is
|
// test setup functions. This way, you can easily extend the setup that is
|
||||||
// needed per test type.
|
// needed per test type.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,487 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
|
||||||
|
import RequestManager from '@ember-data/request';
|
||||||
|
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import { assert } from '@ember/debug';
|
||||||
|
import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
|
||||||
|
import TargetModel from 'api/models/target';
|
||||||
|
|
||||||
|
function createPaginatedResponseHandler(mirageRecords, { pageSize }) {
|
||||||
|
assert('pageSize is required', pageSize);
|
||||||
|
const pagesCount = mirageRecords.length / pageSize;
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (let i = 0; i < pagesCount; i++) {
|
||||||
|
const items = mirageRecords.slice(i * pageSize, (i + 1) * pageSize);
|
||||||
|
const isLastPage = i === pagesCount - 1;
|
||||||
|
const list_token = `list-token-${faker.string.nanoid({ min: 5, max: 5 })}`;
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
items,
|
||||||
|
list_token,
|
||||||
|
response_type: !isLastPage ? 'delta' : 'complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextResult(listToken) {
|
||||||
|
const index = results.findIndex(
|
||||||
|
({ list_token }) => list_token === listToken,
|
||||||
|
);
|
||||||
|
const nextResult = index === -1 ? null : results[index + 1];
|
||||||
|
return nextResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirage handler compatible with paginated responses
|
||||||
|
return function (_schema, request) {
|
||||||
|
const listToken = request.queryParams.list_token;
|
||||||
|
if (!listToken) {
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = nextResult(listToken);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return new Response(
|
||||||
|
404,
|
||||||
|
{},
|
||||||
|
`No results found for list token "${listToken}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module('Unit | Handler | indexed-db-handler', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
setupIndexedDb(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
let manager,
|
||||||
|
indexeddbHandler,
|
||||||
|
store,
|
||||||
|
applicationAdapter,
|
||||||
|
indexedbHandlerSpy,
|
||||||
|
applicationAdapterSpy;
|
||||||
|
hooks.beforeEach(async function setupIndexHandler() {
|
||||||
|
store = this.owner.lookup('service:store');
|
||||||
|
manager = new RequestManager();
|
||||||
|
indexeddbHandler = new IndexedDbHandler(store);
|
||||||
|
applicationAdapter = store.adapterFor('application');
|
||||||
|
|
||||||
|
// spy on all handler and application adapter methods
|
||||||
|
indexedbHandlerSpy = sinon.spy(indexeddbHandler);
|
||||||
|
applicationAdapterSpy = sinon.spy(applicationAdapter);
|
||||||
|
|
||||||
|
store.requestManager = manager;
|
||||||
|
manager.use([indexeddbHandler]);
|
||||||
|
});
|
||||||
|
|
||||||
|
module('fetching and batch loading', function (hooks) {
|
||||||
|
// `testBatchLimit` and `serverResultPageLimit` are intentionally difference
|
||||||
|
// so that multiple calls to the server are required to fetch a single batch,
|
||||||
|
// and these multiples can be asserted in the tests individually
|
||||||
|
const testBatchLimit = 10;
|
||||||
|
const serverResultPageLimit = 5;
|
||||||
|
let aliases, aliasResponseHandlerSpy;
|
||||||
|
|
||||||
|
hooks.beforeEach(function setupMirageData() {
|
||||||
|
indexeddbHandler.batchLimit = testBatchLimit;
|
||||||
|
|
||||||
|
aliases = this.server.createList('alias', 50);
|
||||||
|
|
||||||
|
const aliasResponseHandler = createPaginatedResponseHandler(
|
||||||
|
this.server.schema.aliases.all().models,
|
||||||
|
{ pageSize: serverResultPageLimit },
|
||||||
|
);
|
||||||
|
|
||||||
|
aliasResponseHandlerSpy = sinon.spy(aliasResponseHandler);
|
||||||
|
this.server.get('aliases', aliasResponseHandlerSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it returns all data for a resource from the server', async function (assert) {
|
||||||
|
const results = await store.query('alias', {});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
aliases.length,
|
||||||
|
results.length,
|
||||||
|
'all records are returned',
|
||||||
|
);
|
||||||
|
|
||||||
|
const aliasesSortedByCreatedTime = [...aliases].sort(
|
||||||
|
(a, b) => new Date(b.created_time) - new Date(a.created_time),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
aliasesSortedByCreatedTime.map(({ name }) => name),
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
'`name` property matches between the returned results and the mirage records sorted by created_time',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports batching requests and writes to indexedDB', async function (assert) {
|
||||||
|
await store.query('alias', {});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
indexedbHandlerSpy.writeToIndexedDb.callCount,
|
||||||
|
aliases.length / testBatchLimit,
|
||||||
|
'handler #writeToIndexedDb is called once for each batch',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
applicationAdapterSpy.query.callCount,
|
||||||
|
aliases.length / testBatchLimit,
|
||||||
|
'application adapter #query is called once for each batch',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
aliasResponseHandlerSpy.callCount,
|
||||||
|
aliases.length / serverResultPageLimit,
|
||||||
|
'server handler should be called once for each page',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it loads from indexeddb only and does not fetch from api, when peekIndexedDB option is used', async function (assert) {
|
||||||
|
const callCounts = {
|
||||||
|
get writeToIndexedDb() {
|
||||||
|
return indexedbHandlerSpy.writeToIndexedDb.callCount;
|
||||||
|
},
|
||||||
|
get aliasResponseHandler() {
|
||||||
|
return aliasResponseHandlerSpy.callCount;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// before first query, no data has been fetched or written to indexeddb
|
||||||
|
// the call counts should therefore be 0
|
||||||
|
assert.strictEqual(callCounts.aliasResponseHandler, 0);
|
||||||
|
assert.strictEqual(callCounts.writeToIndexedDb, 0);
|
||||||
|
|
||||||
|
const results = await store.query('alias', {});
|
||||||
|
assert.strictEqual(results.length, 50);
|
||||||
|
|
||||||
|
// call counts expected after first query
|
||||||
|
const expectedWriteToIndexedDbCallCount = 5;
|
||||||
|
const expectedResponseHandlerCallCount = 10;
|
||||||
|
|
||||||
|
// first `store.query()` calls api and writes batches to indexededdb
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.aliasResponseHandler,
|
||||||
|
expectedResponseHandlerCallCount,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.writeToIndexedDb,
|
||||||
|
expectedWriteToIndexedDbCallCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.query('alias', {}, { peekIndexedDB: true });
|
||||||
|
assert.strictEqual(results.length, 50);
|
||||||
|
|
||||||
|
// second run does not increase call counts to api or writes to indexeddb
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.aliasResponseHandler,
|
||||||
|
expectedResponseHandlerCallCount,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
callCounts.writeToIndexedDb,
|
||||||
|
expectedWriteToIndexedDbCallCount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('sorting', function () {
|
||||||
|
test('it sorts by descending `created_time` by default', async function (assert) {
|
||||||
|
const target2020 = this.server.create('target', {
|
||||||
|
name: 'Target 2020',
|
||||||
|
created_time: '2020-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2021 = this.server.create('target', {
|
||||||
|
name: 'Target 2021',
|
||||||
|
created_time: '2021-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2022 = this.server.create('target', {
|
||||||
|
name: 'Target 2022',
|
||||||
|
created_time: '2022-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const target2023 = this.server.create('target', {
|
||||||
|
name: 'Target 2023',
|
||||||
|
created_time: '2023-01-01T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledTargets = faker.helpers.shuffle([
|
||||||
|
target2020,
|
||||||
|
target2021,
|
||||||
|
target2022,
|
||||||
|
target2023,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(shuffledTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {});
|
||||||
|
assert.deepEqual(
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
['Target 2023', 'Target 2022', 'Target 2021', 'Target 2020'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by ascending (default) `name` using string sorting', async function (assert) {
|
||||||
|
const target001 = this.server.create('target', { name: 'Target 001' });
|
||||||
|
const target002 = this.server.create('target', { name: 'Target 002' });
|
||||||
|
const target003 = this.server.create('target', { name: 'Target 003' });
|
||||||
|
const target004 = this.server.create('target', { name: 'Target 004' });
|
||||||
|
const target005 = this.server.create('target', { name: 'Target 005' });
|
||||||
|
|
||||||
|
const shuffledTargets = faker.helpers.shuffle([
|
||||||
|
target001,
|
||||||
|
target002,
|
||||||
|
target003,
|
||||||
|
target004,
|
||||||
|
target005,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(shuffledTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { sort: { attribute: 'name' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
results.map(({ name }) => name),
|
||||||
|
['Target 001', 'Target 002', 'Target 003', 'Target 004', 'Target 005'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by descending, then ascending `name` using string sorting', async function (assert) {
|
||||||
|
const alias001 = this.server.create('alias', { name: 'Alias 001' });
|
||||||
|
const alias002 = this.server.create('alias', { name: 'Alias 002' });
|
||||||
|
const alias003 = this.server.create('alias', { name: 'Alias 003' });
|
||||||
|
const alias004 = this.server.create('alias', { name: 'Alias 004' });
|
||||||
|
|
||||||
|
const shuffledAliases = faker.helpers.shuffle([
|
||||||
|
alias001,
|
||||||
|
alias002,
|
||||||
|
alias003,
|
||||||
|
alias004,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'aliases',
|
||||||
|
createPaginatedResponseHandler(shuffledAliases, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsDesc = await store.query('alias', {
|
||||||
|
query: { sort: { attribute: 'name', direction: 'desc' } },
|
||||||
|
});
|
||||||
|
const resultsAsc = await store.query('alias', {
|
||||||
|
query: { sort: { attribute: 'name', direction: 'asc' } },
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsDesc.map(({ name }) => name),
|
||||||
|
['Alias 004', 'Alias 003', 'Alias 002', 'Alias 001'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsAsc.map(({ name }) => name),
|
||||||
|
['Alias 001', 'Alias 002', 'Alias 003', 'Alias 004'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it sorts by descending, then ascending `name` using custom string sorting', async function (assert) {
|
||||||
|
const alias1 = this.server.create('alias', { name: 'alias1' });
|
||||||
|
const alias2 = this.server.create('alias', { name: 'alias2' });
|
||||||
|
const alias3 = this.server.create('alias', { name: 'alias3' });
|
||||||
|
const alias4 = this.server.create('alias', { name: 'alias4' });
|
||||||
|
|
||||||
|
const sortName = (recordA, recordB) => {
|
||||||
|
const nameMap = {
|
||||||
|
alias3: 'Alpha',
|
||||||
|
alias4: 'Beta',
|
||||||
|
alias1: 'Delta',
|
||||||
|
alias2: 'Epsilon',
|
||||||
|
};
|
||||||
|
return String(nameMap[recordA.attributes.name]).localeCompare(
|
||||||
|
String(nameMap[recordB.attributes.name]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shuffledAliases = faker.helpers.shuffle([
|
||||||
|
alias1,
|
||||||
|
alias2,
|
||||||
|
alias3,
|
||||||
|
alias4,
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'aliases',
|
||||||
|
createPaginatedResponseHandler(shuffledAliases, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsDesc = await store.query('alias', {
|
||||||
|
query: { sort: { sortFunction: sortName, direction: 'desc' } },
|
||||||
|
});
|
||||||
|
const resultsAsc = await store.query('alias', {
|
||||||
|
query: { sort: { sortFunction: sortName, direction: 'asc' } },
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsDesc.map(({ name }) => name),
|
||||||
|
['alias2', 'alias1', 'alias4', 'alias3'],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
resultsAsc.map(({ name }) => name),
|
||||||
|
['alias3', 'alias4', 'alias1', 'alias2'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('option: pushToStore', function (hooks) {
|
||||||
|
let mirageTargets;
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
mirageTargets = this.server.createList('target', 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it pushes fetched results to the store by default', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {});
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => result instanceof TargetModel),
|
||||||
|
'results are ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it pushes fethed results to the store when pushToStore is true', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {}, { pushToStore: true });
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => result instanceof TargetModel),
|
||||||
|
'results are ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it does not push fetched results to the store when pushToStore is false', async function (assert) {
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(mirageTargets, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(mirageTargets.length, 5);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
const results = await store.query('target', {}, { pushToStore: false });
|
||||||
|
assert.strictEqual(results.length, 5);
|
||||||
|
assert.ok(
|
||||||
|
results.every((result) => !(result instanceof TargetModel)),
|
||||||
|
'results are not ember data models',
|
||||||
|
);
|
||||||
|
assert.strictEqual(store.peekAll('target').length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it supports searching', async function (assert) {
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'Magical Target',
|
||||||
|
description: 'A target',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'Target 2',
|
||||||
|
description: 'Target with magic',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.createList('target', 4);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(this.server.schema.targets.all().models, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { search: 'magic' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
results
|
||||||
|
// search results aren't returning in a consistent order so
|
||||||
|
// the sorting is used to be able to assert
|
||||||
|
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||||
|
.map(({ name, description }) => ({ name, description })),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
description: 'A target',
|
||||||
|
name: 'Magical Target',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Target with magic',
|
||||||
|
name: 'Target 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it supports filtering', async function (assert) {
|
||||||
|
this.server.create('target', {
|
||||||
|
name: 'specific-target',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.createList('target', 5);
|
||||||
|
|
||||||
|
this.server.get(
|
||||||
|
'targets',
|
||||||
|
createPaginatedResponseHandler(this.server.schema.targets.all().models, {
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await store.query('target', {
|
||||||
|
query: { filters: { name: [{ equals: 'specific-target' }] } },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(results[0].name, 'specific-target');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Loaded 100 of 1374 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user