Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06cc64e6af | ||
|
|
4d3c993b0f | ||
|
|
cb972cfc8a | ||
|
|
13f1c5cc23 | ||
|
|
f366f0a858 | ||
|
|
08d9801829 | ||
|
|
3f17435507 | ||
|
|
b63ddadf45 | ||
|
|
3721f63edf | ||
|
|
8f34d83c15 | ||
|
|
def6959ee7 | ||
|
|
e76cc2eeeb | ||
|
|
9a47a3ac5a | ||
|
|
2e1cba641c | ||
|
|
842a84296a | ||
|
|
b86ce41e68 | ||
|
|
5f9a66e421 | ||
|
|
24fdd926f1 | ||
|
|
db50bfc9df | ||
|
|
3f4beaada8 | ||
|
|
5a9cbdd913 | ||
|
|
314c1b36f4 | ||
|
|
f71fc3a0a4 | ||
|
|
5a74220f38 | ||
|
|
3f2fc63b5b | ||
|
|
c0793cd254 | ||
|
|
fb9a080d2e | ||
|
|
9e0467cbf0 | ||
|
|
c48fdb2017 | ||
|
|
60c5e25902 | ||
|
|
1488bdf95e | ||
|
|
9e6d647949 | ||
|
|
2e36d3c3e2 | ||
|
|
9046c01238 | ||
|
|
ba0c7d5683 | ||
|
|
16a054519f | ||
|
|
a1fbfdc1ad | ||
|
|
21122f1f29 | ||
|
|
a899ad0b74 | ||
|
|
c7483e5602 | ||
|
|
7fa2541ddb | ||
|
|
05553643a6 | ||
|
|
47cf7995d3 | ||
|
|
c06d18940d | ||
|
|
1122735b87 | ||
|
|
9fec047506 | ||
|
|
916b261cdf | ||
|
|
15da4ea69f | ||
|
|
f801f595f7 | ||
|
|
7af8e71a2d | ||
|
|
15d815c271 | ||
|
|
dfd484dbca | ||
|
|
50e4a2adbc | ||
|
|
7bc2ccb5ae | ||
|
|
3f11019ef0 | ||
|
|
1135f88cb7 | ||
|
|
221ab1f7bd | ||
|
|
4bf1a88764 | ||
|
|
b8b970ce4d | ||
|
|
25c6ed6100 | ||
|
|
550dee9ae0 | ||
|
|
776c46f129 | ||
|
|
6ae3067dd9 | ||
|
|
459029530b | ||
|
|
7dbd581a37 | ||
|
|
9bbd914a3a | ||
|
|
276713697a | ||
|
|
ba786d2172 | ||
|
|
ee4e19ace7 | ||
|
|
eefb9eba4f | ||
|
|
1cb3b708a2 | ||
|
|
8ad4e93ff7 | ||
|
|
9241b7dd0e | ||
|
|
0beec7b8fe | ||
|
|
9bcaa6dc68 | ||
|
|
3f178363d9 | ||
|
|
3810217ce7 | ||
|
|
b79eebf73a | ||
|
|
b3abb658ab | ||
|
|
245cfb395e | ||
|
|
adfa118ae5 | ||
|
|
cf0f4ab56a | ||
|
|
f40878034d | ||
|
|
01a986e08b | ||
|
|
e3406d9a95 | ||
|
|
d4adbe4317 | ||
|
|
01d4a329ab | ||
|
|
659067dd08 | ||
|
|
ade1601d8d | ||
|
|
4550cd0965 | ||
|
|
0bbc33d073 | ||
|
|
69a4eb53b9 | ||
|
|
d06111c803 | ||
|
|
b51c8203ee | ||
|
|
a94572214c | ||
|
|
751b5bef78 | ||
|
|
77f8235ebf | ||
|
|
9782f7e243 | ||
|
|
6328cc1fcf | ||
|
|
b986064447 | ||
|
|
6fbf243c31 | ||
|
|
66eca93f56 | ||
|
|
45d706dd40 | ||
|
|
99cf4c8d81 | ||
|
|
49923d968e | ||
|
|
b4a83a46d1 | ||
|
|
238ced63f7 | ||
|
|
602e4e62c3 | ||
|
|
f282dbb5b1 | ||
|
|
3f2f2ba756 | ||
|
|
02a89bd7ba | ||
|
|
a891f7a1a5 | ||
|
|
019b3e7390 | ||
|
|
bcf004b8a4 | ||
|
|
cb3a9e3bdc | ||
|
|
4127f6abd7 | ||
|
|
45a3cb8d34 | ||
|
|
e96171cab0 | ||
|
|
a7bf9a1071 | ||
|
|
3b3a00e1ee | ||
|
|
222f0c35b8 | ||
|
|
666491548d | ||
|
|
2558ee1353 | ||
|
|
c5edd4dd52 | ||
|
|
f102e7fca2 | ||
|
|
f593e6a8b1 | ||
|
|
f54d48c380 | ||
|
|
0129784322 | ||
|
|
7317ca2ff5 | ||
|
|
1ddabf4980 | ||
|
|
a574023c05 | ||
|
|
861562201b | ||
|
|
4cdcaefe15 | ||
|
|
e2487b7e98 | ||
|
|
f879bbb1ec | ||
|
|
933e272764 | ||
|
|
2953635aeb | ||
|
|
194561e1f8 | ||
|
|
4a3d8eee63 | ||
|
|
7d6f3df140 | ||
|
|
722acdc1fc | ||
|
|
3f8e39d0e1 | ||
|
|
9e7852f18d | ||
|
|
c548c381bf | ||
|
|
63be0f3aec | ||
|
|
8e4fac5ce2 | ||
|
|
2a9666285a | ||
|
|
bff94d6b4d | ||
|
|
5b8fc06e21 | ||
|
|
a20483d1af | ||
|
|
0bf5221389 | ||
|
|
297db470bd | ||
|
|
91a6023145 | ||
|
|
7b96dd1f81 | ||
|
|
aee55cb171 | ||
|
|
370b3ea1fb | ||
|
|
4f972a82d6 |
No files matched your search
+14
-7
@@ -2,22 +2,29 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
addons:
|
||||
- addons/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/**/*"
|
||||
|
||||
api-addon:
|
||||
- addons/api/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/api/**/*"
|
||||
|
||||
auth-addon:
|
||||
- addons/auth/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/auth/**/*"
|
||||
|
||||
styles-addon:
|
||||
- addons/rose/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/rose/**/*"
|
||||
|
||||
ui:
|
||||
- ui/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/**/*"
|
||||
|
||||
admin:
|
||||
- ui/admin/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/admin/**/*"
|
||||
|
||||
desktop:
|
||||
- ui/desktop/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/desktop/**/*"
|
||||
@@ -3,21 +3,23 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "release/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dependencies:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
path: "**/node_modules"
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn install
|
||||
@@ -26,16 +28,16 @@ jobs:
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
path: "**/node_modules"
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:oss
|
||||
@@ -54,16 +56,16 @@ jobs:
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
path: "**/node_modules"
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:enterprise
|
||||
@@ -77,16 +79,16 @@ jobs:
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
path: "**/node_modules"
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- name: Build
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run build:ui:admin:hcp
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: "Trigger Build Artifacts for Testing"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: Please use tag-like format n.n.n-someword (1.111.11-beta)
|
||||
required: true
|
||||
product_version:
|
||||
required: true
|
||||
type: string
|
||||
description: The version number belonging to the oss binary to be used
|
||||
commit:
|
||||
required: true
|
||||
type: string
|
||||
description: The git SHA of the oss artifacts to be used
|
||||
|
||||
jobs:
|
||||
trigger-gha-build-artifacts-for-testing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Release description
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "Triggering Build Artifacts for Testing using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}"
|
||||
- name: Trigger Build Artifacts for Testing in boundary-ui-releases
|
||||
run: |
|
||||
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
||||
gh workflow run build-artifacts-for-testing.yml \
|
||||
--repo hashicorp/boundary-ui-releases \
|
||||
--ref main \
|
||||
-f TAG="${{ inputs.branch }}" \
|
||||
-f PRODUCT_VERSION="${{ inputs.product_version }}" \
|
||||
-f COMMIT="${{ inputs.commit }}" \
|
||||
@@ -30,11 +30,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8e0b1c74b1d5a0077b04d064c76ee714d3da7637 # codeql-bundle-v2.14.6
|
||||
uses: github/codeql-action/init@4b6aa0b07da05d6e43d0e5f9c8596a6532ce1c85 # codeql-bundle-v2.15.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -54,4 +54,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8e0b1c74b1d5a0077b04d064c76ee714d3da7637 # codeql-bundle-v2.14.6
|
||||
uses: github/codeql-action/analyze@4b6aa0b07da05d6e43d0e5f9c8596a6532ce1c85 # codeql-bundle-v2.15.3
|
||||
@@ -6,6 +6,6 @@ jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn install
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn run compliance:licenses
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
node-modules-
|
||||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn lint
|
||||
@@ -62,13 +62,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn test
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
- run: |
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
trigger-gha-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Release description
|
||||
run: |
|
||||
echo "Triggering release with a TAG ${GITHUB_REF#refs/tags/}"
|
||||
|
||||
@@ -6,11 +6,22 @@ COMMIT_SOURCE=$2
|
||||
SHA1=$3
|
||||
# Gets the current branch name but if we're in a detached head state, return "HEAD"
|
||||
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
|
||||
# Regex to match the ICU-#### branch name pattern we use
|
||||
ISSUE_REGEX="^(ICU-[0-9]+)"
|
||||
|
||||
# Don't run commitizen for merge or squash commits, as well as when rebasing
|
||||
if [ "${COMMIT_SOURCE}" = merge ] || [ "${COMMIT_SOURCE}" = squash ] || [ "${COMMIT_SOURCE}" = "message" -a "${BRANCH_NAME}" = "HEAD" ];then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# This is to allow interaction with the terminal for commitizen
|
||||
exec < /dev/tty && npx git-cz --hook || true
|
||||
# If the branch name matches, set the JIRA link automatically
|
||||
if [[ $BRANCH_NAME =~ $ISSUE_REGEX ]]
|
||||
then
|
||||
issue="${BASH_REMATCH[1]}"
|
||||
url="https://hashicorp.atlassian.net/browse/${issue}"
|
||||
exec < /dev/tty && npx git-cz --issues="$url" --hook || true
|
||||
else
|
||||
# This is to allow interaction with the terminal for commitizen
|
||||
exec < /dev/tty && npx git-cz --hook || true
|
||||
fi
|
||||
|
||||
@@ -38,7 +38,6 @@ module.exports = {
|
||||
files: [
|
||||
'./.eslintrc.js',
|
||||
'./.prettierrc.js',
|
||||
'./.stylelintrc.js',
|
||||
'./.template-lintrc.js',
|
||||
'./ember-cli-build.js',
|
||||
'./index.js',
|
||||
@@ -60,6 +59,9 @@ module.exports = {
|
||||
// Test files:
|
||||
files: ['tests/**/*-test.{js,ts}'],
|
||||
extends: ['plugin:qunit/recommended'],
|
||||
rules: {
|
||||
'qunit/require-expect': [2, 'except-simple'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { typeOf } from '@ember/utils';
|
||||
|
||||
/**
|
||||
* This test helper can be used to help setup your sinon stubs in your tests.
|
||||
* Must be called after setupMirage.
|
||||
*
|
||||
* ```js
|
||||
* import setupStubs from 'api/test-support/handlers/client-daemon-search';
|
||||
*
|
||||
* module('Acceptance | my test', function(hooks) {
|
||||
* setupApplicationTest(hooks);
|
||||
* setupMirage(hooks);
|
||||
* setupStubs(hooks);
|
||||
*
|
||||
* // add your actual tests here
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This will setup a stub for the IPC service and include helper methods to
|
||||
* help setup what the client daemon search command returns.
|
||||
*
|
||||
* @module Test Helpers
|
||||
* @public
|
||||
*/
|
||||
export default function setupStubs(hooks) {
|
||||
let stubTypes = [];
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
const ipcService = this.owner.lookup('service:ipc');
|
||||
this.ipcStub = sinon.stub(ipcService, 'invoke');
|
||||
|
||||
/**
|
||||
* We aim to still use mirage data when stubbing out the client daemon
|
||||
* search. However, the handler expects the search data to be raw JSON from
|
||||
* the API which will get serialized. We add back the scope object for it to
|
||||
* get correctly normalized and other fields such as "attributes" from the
|
||||
* API will already be hoisted and will not be re-normalized from mirage data.
|
||||
*
|
||||
* This convenience method can take in multiple types,
|
||||
* e.g. this.stubClientDaemonSearch('targets', 'sessions').
|
||||
* This will have sinon stub out each call so the first call to search will
|
||||
* provide all the models from the first parameter you specify,
|
||||
* the second call will provide all the models from the second argument, etc.
|
||||
* @param types
|
||||
*/
|
||||
this.stubClientDaemonSearch = (...types) => {
|
||||
stubTypes = types;
|
||||
|
||||
types.forEach((type, i) => {
|
||||
let models;
|
||||
let resourceName;
|
||||
if (typeOf(type) === 'object') {
|
||||
models = type.func();
|
||||
resourceName = type.resource;
|
||||
} else {
|
||||
models = this.server.schema[type].all().models;
|
||||
resourceName = type;
|
||||
}
|
||||
|
||||
this.ipcStub
|
||||
.withArgs('searchClientDaemon')
|
||||
.onCall(i)
|
||||
.returns({
|
||||
[resourceName]: models.map((model) => {
|
||||
// Use internal serializer to serialize the model correctly
|
||||
// according to our mirage serializers
|
||||
const modelData =
|
||||
this.server.serializerOrRegistry.serialize(model);
|
||||
|
||||
// Serialize the data properly to standard JSON as that is what
|
||||
// we're expecting from the client daemon response
|
||||
return JSON.parse(JSON.stringify(modelData));
|
||||
}),
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
// Verify the number of mocks we set up match with how
|
||||
// many times the client daemon was called with
|
||||
sinon.assert.callCount(
|
||||
this.ipcStub.withArgs('searchClientDaemon'),
|
||||
stubTypes.length,
|
||||
);
|
||||
|
||||
stubTypes.forEach((type, i) => {
|
||||
// Assert that the stub was called with the resource we're trying to mock
|
||||
// so we don't mistakenly mock the wrong resource
|
||||
const ipcCall = this.ipcStub.withArgs('searchClientDaemon').getCall(i);
|
||||
let resource = type;
|
||||
if (typeOf(type) === 'object') {
|
||||
resource = type.resource;
|
||||
}
|
||||
|
||||
sinon.assert.match(ipcCall.args[1], { resource });
|
||||
});
|
||||
|
||||
sinon.restore();
|
||||
});
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export default class ChannelRecordingAbility extends ModelAbility {
|
||||
const sessionRecording = this.model.connection_recording.session_recording;
|
||||
const sessionRecordingAbility = this.abilities.abilityFor(
|
||||
'session-recording',
|
||||
sessionRecording
|
||||
sessionRecording,
|
||||
);
|
||||
|
||||
return this.model.isAsciicast && sessionRecordingAbility.canDownload;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import ModelAbility from './model';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
/**
|
||||
* Provides abilities for storage policies.
|
||||
*/
|
||||
export default class PolicyAbility extends ModelAbility {
|
||||
// =services
|
||||
@service features;
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* This ensures that storage policies may be read only if the
|
||||
* session-recording feature flag is enabled.
|
||||
*/
|
||||
get canRead() {
|
||||
return this.features.isEnabled('ssh-session-recording')
|
||||
? super.canRead
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that storage policies may be updated only if the
|
||||
* session-recording feature flag is enabled.
|
||||
*/
|
||||
get canUpdate() {
|
||||
return this.features.isEnabled('ssh-session-recording')
|
||||
? super.canUpdate
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that storage policies may be deleted only if the
|
||||
* session-recording feature flag is enabled.
|
||||
*/
|
||||
get canDelete() {
|
||||
return this.features.isEnabled('ssh-session-recording')
|
||||
? super.canDelete
|
||||
: false;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export default class RoleAbility extends ModelAbility {
|
||||
type === 'user' || type === 'group' || type === 'managed-group';
|
||||
if (!typeIsValid)
|
||||
throw new InvalidRolePrincipalTypeError(
|
||||
`Expected a role principal of type 'user', 'group', or 'managed-group'. Got '${type}'.`
|
||||
`Expected a role principal of type 'user', 'group', or 'managed-group'. Got '${type}'.`,
|
||||
);
|
||||
return this.can.can(`read ${type}`, this.model);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import ModelAbility from './model';
|
||||
|
||||
/**
|
||||
* Provides abilities for scopes.
|
||||
*/
|
||||
export default class ScopeAbility extends ModelAbility {
|
||||
// =permissions
|
||||
/**
|
||||
* Permission that checks whether a policy can be attached to a scope
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canAttachStoragePolicy() {
|
||||
return this.hasAuthorizedAction('attach-storage-policy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission that checks whether a policy can be detached to a scope
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canDetachStoragePolicy() {
|
||||
return this.hasAuthorizedAction('detach-storage-policy');
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,11 @@ export default class SessionRecordingAbility extends ModelAbility {
|
||||
get canDownload() {
|
||||
return this.hasAuthorizedAction('download');
|
||||
}
|
||||
|
||||
/* Permission that checks whether a policy can be reapplied to a session recording
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canReapplyStoragePolicy() {
|
||||
return this.hasAuthorizedAction('reapply-storage-policy');
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,25 @@ export default class SessionAbility extends ModelAbility {
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* Only "active" or "pending" sessions may be read.
|
||||
* Only available sessions may be read.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canRead() {
|
||||
return (this.model.isActive || this.model.isPending) && super.canRead;
|
||||
return (
|
||||
this.model.isAvailable &&
|
||||
(this.hasAuthorizedAction('read:self') || super.canRead)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only available sessions may be canceled.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canCancel() {
|
||||
return (
|
||||
this.model.isAvailable &&
|
||||
(this.hasAuthorizedAction('cancel:self') ||
|
||||
this.hasAuthorizedAction('cancel'))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ function prenormalizeArrayResponse(response) {
|
||||
}
|
||||
|
||||
export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
AdapterBuildURLMixin
|
||||
AdapterBuildURLMixin,
|
||||
) {
|
||||
// =attributes
|
||||
|
||||
@@ -108,22 +108,37 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
|
||||
/**
|
||||
* Intercepts "empty" responses and adds an empty `items` array.
|
||||
* @override
|
||||
* @method findAll
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
findAll() {
|
||||
return super.findAll(...arguments).then(prenormalizeArrayResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts "empty" responses and adds an empty `items` array.
|
||||
* This query method now supports pagination by checking the responseType and
|
||||
* passing in the list token if the responseType indicates more data
|
||||
* @override
|
||||
* @method query
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
query() {
|
||||
return super.query(...arguments).then(prenormalizeArrayResponse);
|
||||
async query(store, schema, query) {
|
||||
let result;
|
||||
let data = [];
|
||||
|
||||
// If the query has a page_size, we skip the pagination logic
|
||||
// and return the initial result. This is being used to determine
|
||||
// if the controller supports pagination
|
||||
if (query.page_size) {
|
||||
result = await super.query(store, schema, query);
|
||||
return prenormalizeArrayResponse(result);
|
||||
}
|
||||
// Run this loop as long as the response_type is delta,
|
||||
// which indicates that there are more items in the list
|
||||
do {
|
||||
result = await super.query(store, schema, query);
|
||||
//add the result items to a data array
|
||||
if (result && result.items) {
|
||||
data.push(...result.items);
|
||||
//pass in the list token for subsequent calls to fetch the remaining list items
|
||||
query.list_token = result.list_token;
|
||||
}
|
||||
} while (result && result.response_type === 'delta');
|
||||
|
||||
result.items = data;
|
||||
return prenormalizeArrayResponse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class SessionRecordingAdapter extends ApplicationAdapter {
|
||||
throw this.handleResponse(
|
||||
response.status,
|
||||
response.headers,
|
||||
await response.text()
|
||||
await response.text(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,13 @@ export default class GeneratedAuthMethodModel extends BaseModel {
|
||||
})
|
||||
account_claim_maps;
|
||||
|
||||
@attr('string-array', {
|
||||
for: 'oidc',
|
||||
emptyArrayIfMissing: true,
|
||||
isNestedAttribute: true,
|
||||
})
|
||||
prompts;
|
||||
|
||||
// =attributes(ldap)
|
||||
|
||||
@attr('boolean', {
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class GeneratedChannelRecordingModel extends BaseModel {
|
||||
'The total number of errors that occurred during the use of this Channel.',
|
||||
readOnly: true,
|
||||
})
|
||||
errors;
|
||||
errors_number;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time the Channel started.',
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class GeneratedConnectionRecordingModel extends BaseModel {
|
||||
'The total number of errors that occurred during the use of this Connection.',
|
||||
readOnly: true,
|
||||
})
|
||||
errors;
|
||||
errors_number;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time the Connection started.',
|
||||
|
||||
@@ -54,4 +54,10 @@ export default class GeneratedHostModel extends BaseModel {
|
||||
readOnly: true,
|
||||
})
|
||||
plugin;
|
||||
|
||||
@attr({
|
||||
emptyArrayIfMissing: true,
|
||||
readOnly: true,
|
||||
})
|
||||
host_set_ids;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import BaseModel from '../../models/base';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
/**
|
||||
* Storage Policy is a resource that manages the lifecycle of Session Recordings,
|
||||
* particularly to meet compliance frameworks.
|
||||
*/
|
||||
export default class GeneratedPolicyModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource, to help differentiate schemas',
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional name for identification purposes.',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional user-set description for identification purposes.',
|
||||
})
|
||||
description;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was created\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
created_time;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was last updated\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
updated_time;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
@attr('object', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Days is the number of days for which a session recording will be retained.',
|
||||
})
|
||||
retain_for;
|
||||
|
||||
@attr('object', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Days is the number of days for which a session recording will be automatically deleted.',
|
||||
})
|
||||
delete_after;
|
||||
}
|
||||
@@ -49,4 +49,9 @@ export default class GeneratedScopeModel extends BaseModel {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
|
||||
@attr('string', {
|
||||
description: 'The ID of the associated storage policy',
|
||||
})
|
||||
storage_policy_id;
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
||||
'The total number of errors that occurred during the use of the Session.',
|
||||
readOnly: true,
|
||||
})
|
||||
errors;
|
||||
errors_number;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time the Session started.',
|
||||
@@ -109,4 +109,17 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
||||
readOnly: true,
|
||||
})
|
||||
error_details;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time until a session recording is required to be stored.',
|
||||
readOnly: true,
|
||||
})
|
||||
retain_until;
|
||||
|
||||
@attr('date', {
|
||||
description:
|
||||
'The time a session recording is scheduled to be automatically deleted.',
|
||||
readOnly: true,
|
||||
})
|
||||
delete_after;
|
||||
}
|
||||
@@ -32,6 +32,13 @@ export default class GeneratedSessionModel extends BaseModel {
|
||||
})
|
||||
updated_time;
|
||||
|
||||
@attr('date', {
|
||||
description:
|
||||
'After this time the connection will be expired, e.g. forcefully terminated.\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
expiration_time;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import { getOwner, setOwner } from '@ember/application';
|
||||
import { pluralize } from 'ember-inflector';
|
||||
import { generateMQLExpression } from '../utils/mql-query';
|
||||
|
||||
/**
|
||||
* Not all types are yet supported by the client daemon so we'll
|
||||
* just whitelist the ones we need to start with. Keys are the resource
|
||||
* types and the values are the fields that are searchable by the client.
|
||||
* @type {{session: string[], target: string[]}}
|
||||
*/
|
||||
const supportedTypes = {
|
||||
target: ['id', 'name', 'description', 'address', 'scope_id'],
|
||||
session: ['id', 'type', 'status', 'endpoint', 'scope_id', 'target_id'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an array and the current page and pagesize to calculate the correct
|
||||
* number of results to return to the caller.
|
||||
*
|
||||
* @param array
|
||||
* @param page
|
||||
* @param pageSize
|
||||
* @returns {[*]}
|
||||
*/
|
||||
const paginateResults = (array, page, pageSize) => {
|
||||
const length = array?.length;
|
||||
if (!array || length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (!page || !pageSize) {
|
||||
return array;
|
||||
}
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const start = Math.min(length - 1, offset);
|
||||
const end = Math.min(length, offset + pageSize);
|
||||
|
||||
return array.slice(start, end);
|
||||
};
|
||||
|
||||
const fetchControllerData = async (context, next) => {
|
||||
const { data } = context.request;
|
||||
const { query: originalQuery } = data;
|
||||
// remove client daemon specific query params
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { query, page, pageSize, ...remainingQuery } = originalQuery;
|
||||
|
||||
// If we get an error or client daemon is unavailable, fall back to calling the API
|
||||
context.request.data.query = remainingQuery;
|
||||
const results = await next(context.request);
|
||||
const models = results.content.toArray();
|
||||
|
||||
const paginatedResult = paginateResults(models, page, pageSize);
|
||||
paginatedResult.meta = { totalItems: models.length };
|
||||
return paginatedResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler to sit in front of the API request layer
|
||||
* so we can request from the daemon first
|
||||
*/
|
||||
export default class ClientDaemonHandler {
|
||||
@service session;
|
||||
@service ipc;
|
||||
|
||||
constructor(context) {
|
||||
setOwner(this, getOwner(context));
|
||||
}
|
||||
|
||||
async request(context, next) {
|
||||
switch (context.request.op) {
|
||||
case 'query': {
|
||||
const { store, data } = context.request;
|
||||
const { type, query, options: { pushToStore = true } = {} } = data;
|
||||
const isClientDaemonRunning = await this.ipc.invoke(
|
||||
'isClientDaemonRunning',
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let { recursive, scope_id, page, pageSize, ...remainingQuery } = query;
|
||||
let searchQuery = '';
|
||||
|
||||
if (
|
||||
!Object.keys(supportedTypes).includes(type) ||
|
||||
!isClientDaemonRunning
|
||||
) {
|
||||
return fetchControllerData(context, next);
|
||||
}
|
||||
|
||||
if (remainingQuery.query) {
|
||||
const { search, filters } = remainingQuery.query;
|
||||
|
||||
searchQuery = generateMQLExpression({
|
||||
search: {
|
||||
text: search,
|
||||
fields: supportedTypes[type],
|
||||
},
|
||||
filters,
|
||||
});
|
||||
}
|
||||
const sessionData = this.session.data?.authenticated;
|
||||
const auth_token_id = sessionData?.id;
|
||||
const token = sessionData?.token;
|
||||
remainingQuery = {
|
||||
...remainingQuery,
|
||||
query: searchQuery,
|
||||
auth_token_id,
|
||||
token,
|
||||
resource: pluralize(type),
|
||||
};
|
||||
|
||||
let clientDaemonResults = {};
|
||||
try {
|
||||
clientDaemonResults = await this.ipc.invoke(
|
||||
'searchClientDaemon',
|
||||
remainingQuery,
|
||||
);
|
||||
} catch (e) {
|
||||
// If we got a 403, most likely the client daemon was restarted and our token is no longer valid
|
||||
// I'm not sure if we can get a 401 since we always send a token but we'll handle it in the same way
|
||||
if (e.statusCode === 403 || e.statusCode === 401) {
|
||||
try {
|
||||
await this.ipc.invoke('addTokenToClientDaemon', {
|
||||
tokenId: auth_token_id,
|
||||
token,
|
||||
});
|
||||
clientDaemonResults = await this.ipc.invoke(
|
||||
'searchClientDaemon',
|
||||
remainingQuery,
|
||||
);
|
||||
} catch {
|
||||
// If it fails again just fall back to fetching controller data
|
||||
}
|
||||
}
|
||||
|
||||
return fetchControllerData(context, next);
|
||||
}
|
||||
|
||||
// Currently returns with a singular top level field with resource name
|
||||
// e.g. { targets: [...] } or { sessions: [..] }
|
||||
// So this just unwraps to the array, or undefined
|
||||
const [results] = Object.values(clientDaemonResults);
|
||||
const payload = { items: paginateResults(results, page, pageSize) };
|
||||
|
||||
const schema = store.modelFor(type);
|
||||
const serializer = store.serializerFor(type);
|
||||
const normalizedPayload = serializer.normalizeResponse(
|
||||
store,
|
||||
schema,
|
||||
payload,
|
||||
null,
|
||||
'query',
|
||||
);
|
||||
|
||||
// Return the raw data if we don't push to the store.
|
||||
let records = normalizedPayload.data.map((record) => ({
|
||||
...record.attributes,
|
||||
id: record.id,
|
||||
}));
|
||||
if (pushToStore) {
|
||||
records = store.push(normalizedPayload);
|
||||
}
|
||||
|
||||
// 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
|
||||
// or EmberArray since the ember store query method asserts it has to be an array
|
||||
// so we can't just return an object.
|
||||
records.meta = { totalItems: results?.length ?? 0 };
|
||||
return records;
|
||||
}
|
||||
default:
|
||||
return next(context.request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default class AccountModel extends GeneratedAccountModel {
|
||||
changePassword(
|
||||
currentPassword,
|
||||
newPassword,
|
||||
options = { adapterOptions: {} }
|
||||
options = { adapterOptions: {} },
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'change-password',
|
||||
|
||||
@@ -37,6 +37,7 @@ export const options = {
|
||||
account_claim_maps: {
|
||||
to: ['sub', 'name', 'email'],
|
||||
},
|
||||
prompts: ['consent', 'select_account', 'login'],
|
||||
},
|
||||
ldap: {
|
||||
account_attribute_maps: {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import GeneratedPolicyModel from '../generated/models/policy';
|
||||
|
||||
export const TYPE_POLICY = 'storage';
|
||||
export default class PolicyModel extends GeneratedPolicyModel {}
|
||||
@@ -138,7 +138,7 @@ export default class RoleModel extends GeneratedRoleModel {
|
||||
const authMethods = this.resourceFilterStore.queryBy(
|
||||
'auth-method',
|
||||
{ type: [TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP] },
|
||||
{ scope_id: 'global', recursive: true }
|
||||
{ scope_id: 'global', recursive: true },
|
||||
);
|
||||
|
||||
// For each auth method, query all managed groups with IDs
|
||||
@@ -149,9 +149,9 @@ export default class RoleModel extends GeneratedRoleModel {
|
||||
methods.map(({ id: auth_method_id }) =>
|
||||
this.resourceFilterStore
|
||||
.queryBy('managed-group', { id: ids }, { auth_method_id })
|
||||
.then((models) => models.map((model) => model))
|
||||
)
|
||||
)
|
||||
.then((models) => models.map((model) => model)),
|
||||
),
|
||||
),
|
||||
)
|
||||
// The result is an array of arrays of model instances (grouped by
|
||||
// auth methods), so these must be flattened.
|
||||
|
||||
@@ -46,4 +46,48 @@ export default class ScopeModel extends GeneratedScopeModel {
|
||||
set isProject(value) {
|
||||
if (value) this.type = scopeTypes.project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach policy via the `attach-staorage-policy` method.
|
||||
* @param {[string]} policyId
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
attachStoragePolicy(policyId, options = { adapterOptions: {} }) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'attach-storage-policy',
|
||||
policyId,
|
||||
};
|
||||
// There is no "deep merge" in ES.
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches policy via the `detach-storage-policy` method.
|
||||
* @param {[string]} policyId
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
detachStoragePolicy(policyId, options = { adapterOptions: {} }) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'detach-storage-policy',
|
||||
policyId,
|
||||
};
|
||||
// There is no "deep merge" in ES.
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,23 @@ export default class SessionRecordingModel extends GeneratedSessionRecordingMode
|
||||
this.create_time_values?.user?.name || this.create_time_values?.user?.id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reapply policy dates via the `reapply-staorage-policy` method.
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
reapplyStoragePolicy(options = { adapterOptions: {} }) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'reapply-storage-policy',
|
||||
};
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import GeneratedSessionModel from '../generated/models/session';
|
||||
import { computed } from '@ember/object';
|
||||
import { equal } from '@ember/object/computed';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { A } from '@ember/array';
|
||||
@@ -77,7 +76,7 @@ class SessionCredential {
|
||||
if (this.#payloadSecret?.decoded) {
|
||||
const secretJSON = this.#payloadSecret.decoded;
|
||||
return Object.keys(secretJSON).map(
|
||||
(key) => new SessionCredential.SecretItem(key, secretJSON[key])
|
||||
(key) => new SessionCredential.SecretItem(key, secretJSON[key]),
|
||||
);
|
||||
} else {
|
||||
// decode from base64
|
||||
@@ -127,8 +126,7 @@ export default class SessionModel extends GeneratedSessionModel {
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@computed('isActive', 'isPending')
|
||||
get isCancelable() {
|
||||
get isAvailable() {
|
||||
return this.isActive || this.isPending;
|
||||
}
|
||||
|
||||
@@ -136,7 +134,6 @@ export default class SessionModel extends GeneratedSessionModel {
|
||||
* The full proxy address and port if address exists, otherwise null.
|
||||
* @type {?string}
|
||||
*/
|
||||
@computed('proxy_address', 'proxy_port')
|
||||
get proxy() {
|
||||
return this.proxy_address
|
||||
? `${this.proxy_address}:${this.proxy_port}`
|
||||
@@ -148,6 +145,9 @@ export default class SessionModel extends GeneratedSessionModel {
|
||||
* @type {TargetModel}
|
||||
*/
|
||||
get target() {
|
||||
if (!this.target_id) {
|
||||
return null;
|
||||
}
|
||||
return this.store.peekRecord('target', this.target_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,14 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
.filter((s) => s && s.target_id === this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Available sessions associated with this target (but only already loaded sessions).
|
||||
* @type {SessionModel[]}
|
||||
*/
|
||||
get availableSessions() {
|
||||
return this.sessions.filter((session) => session.isAvailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The project associated with this target (if already loaded).
|
||||
* @type {ScopeModel}
|
||||
@@ -122,7 +130,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
get isActive() {
|
||||
const pendingOrActiveSessions = this.sessions.filter(
|
||||
(s) => s.isActive || s.isPending
|
||||
(s) => s.isActive || s.isPending,
|
||||
);
|
||||
return Boolean(pendingOrActiveSessions.length);
|
||||
}
|
||||
@@ -195,7 +203,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
addBrokeredCredentialSources(
|
||||
brokeredCredentialSourceIDs,
|
||||
options = { adapterOptions: {} }
|
||||
options = { adapterOptions: {} },
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'add-credential-sources',
|
||||
@@ -221,7 +229,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
addInjectedApplicationCredentialSources(
|
||||
injectedApplicationCredentialSourceIDs,
|
||||
options = { adapterOptions: {} }
|
||||
options = { adapterOptions: {} },
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'add-credential-sources',
|
||||
@@ -247,7 +255,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
removeBrokeredCredentialSources(
|
||||
brokeredCredentialSourceIDs,
|
||||
options = { adapterOptions: {} }
|
||||
options = { adapterOptions: {} },
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'remove-credential-sources',
|
||||
@@ -272,7 +280,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
removeBrokeredCredentialSource(brokeredCredentialSourceID, options) {
|
||||
return this.removeBrokeredCredentialSources(
|
||||
[brokeredCredentialSourceID],
|
||||
options
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,7 +294,7 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
removeInjectedApplicationCredentialSources(
|
||||
injectedApplicationCredentialSourceIDs,
|
||||
options = { adapterOptions: {} }
|
||||
options = { adapterOptions: {} },
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'remove-credential-sources',
|
||||
@@ -310,11 +318,11 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
*/
|
||||
removeInjectedApplicationCredentialSource(
|
||||
injectedApplicationCredentialSourceID,
|
||||
options
|
||||
options,
|
||||
) {
|
||||
return this.removeInjectedApplicationCredentialSources(
|
||||
[injectedApplicationCredentialSourceID],
|
||||
options
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class WorkerModel extends GeneratedWorkerModel {
|
||||
|
||||
return Object.values(this.config_tags).reduce(
|
||||
(previousCount, currentTags) => previousCount + currentTags.length,
|
||||
0
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class WorkerModel extends GeneratedWorkerModel {
|
||||
}
|
||||
|
||||
return Object.entries(this.config_tags).flatMap(([key, value]) =>
|
||||
value.map((tag) => ({ key, value: tag }))
|
||||
value.map((tag) => ({ key, value: tag })),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class AccountSerializer extends ApplicationSerializer {
|
||||
serialized = this.serializeForChangePassword(
|
||||
snapshot,
|
||||
currentPassword,
|
||||
newPassword
|
||||
newPassword,
|
||||
);
|
||||
}
|
||||
return serialized;
|
||||
|
||||
@@ -145,18 +145,18 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
const transformedPayload = {};
|
||||
// Find the Ember-data-expected root key name.
|
||||
const payloadKey = this.payloadKeyFromModelName(
|
||||
primaryModelClass.modelName
|
||||
primaryModelClass.modelName,
|
||||
);
|
||||
// Copy the data rooted under `items` in the existing payload into the new
|
||||
// payload under the expected root key name.
|
||||
transformedPayload[payloadKey] = structuredClone(payload.items);
|
||||
transformedPayload[payloadKey] = structuredClone(payload?.items);
|
||||
// Return the result of normalizing the transformed payload.
|
||||
return super.normalizeArrayResponse(
|
||||
store,
|
||||
primaryModelClass,
|
||||
transformedPayload,
|
||||
id,
|
||||
requestType
|
||||
requestType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
const transformedPayload = {};
|
||||
// Find the Ember-data-expected root key name.
|
||||
const payloadKey = this.payloadKeyFromModelName(
|
||||
primaryModelClass.modelName
|
||||
primaryModelClass.modelName,
|
||||
);
|
||||
// Copy the unrooted payload under the expected root key name.
|
||||
transformedPayload[payloadKey] = payload;
|
||||
@@ -193,7 +193,7 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
primaryModelClass,
|
||||
transformedPayload,
|
||||
id,
|
||||
requestType
|
||||
requestType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
primaryModelClass,
|
||||
payload,
|
||||
id,
|
||||
requestType
|
||||
requestType,
|
||||
) {
|
||||
const newPayload = structuredClone(payload);
|
||||
|
||||
@@ -338,7 +338,7 @@ export default class ApplicationSerializer extends RESTSerializer {
|
||||
primaryModelClass,
|
||||
newPayload,
|
||||
id,
|
||||
requestType
|
||||
requestType,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export default class AuthMethodSerializer extends ApplicationSerializer {
|
||||
normalizeArrayResponse() {
|
||||
const normalized = super.normalizeArrayResponse(...arguments);
|
||||
normalized.data = normalized.data.sort((a) =>
|
||||
a.attributes.is_primary ? -1 : 1
|
||||
a.attributes.is_primary ? -1 : 1,
|
||||
);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from './application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class ChannelSerializer extends ApplicationSerializer.extend(
|
||||
EmbeddedRecordsMixin,
|
||||
) {
|
||||
normalize(typeClass, hash, ...rest) {
|
||||
const normalizedHash = structuredClone(hash);
|
||||
const err = normalizedHash.errors;
|
||||
delete normalizedHash.errors;
|
||||
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
|
||||
// we should not override the ember models errors field,
|
||||
// so we rename the errors field from the api
|
||||
normalized.data.attributes.errors_number = err;
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,21 @@ import ApplicationSerializer from './application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class ConnectionSerializer extends ApplicationSerializer.extend(
|
||||
EmbeddedRecordsMixin
|
||||
EmbeddedRecordsMixin,
|
||||
) {
|
||||
attrs = {
|
||||
channel_recordings: { embedded: 'always' },
|
||||
};
|
||||
|
||||
normalize(typeClass, hash, ...rest) {
|
||||
const normalizedHash = structuredClone(hash);
|
||||
const err = normalizedHash.errors;
|
||||
delete normalizedHash.errors;
|
||||
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
|
||||
// we should not override the ember models errors field,
|
||||
// so we rename the errors field from the api
|
||||
normalized.data.attributes.errors_number = err;
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export default class CredentialSerializer extends ApplicationSerializer {
|
||||
|
||||
if (serialized?.attributes?.json_object) {
|
||||
serialized.attributes.object = JSON.parse(
|
||||
serialized.attributes.json_object
|
||||
serialized.attributes.json_object,
|
||||
);
|
||||
delete serialized.attributes.json_object;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default class ScopeSerializer extends ApplicationSerializer {
|
||||
// =methods
|
||||
|
||||
serialize(snapshot) {
|
||||
let serialized = super.serialize(...arguments);
|
||||
const policyId = snapshot?.adapterOptions?.policyId;
|
||||
|
||||
if (policyId) {
|
||||
serialized = this.serializeWithStoragePolicy(snapshot, policyId);
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a payload containing only version and storage policy id,
|
||||
* rather than existing instances on the model.
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {[string]} hostSetIDs
|
||||
* @return {object}
|
||||
*/
|
||||
serializeWithStoragePolicy(snapshot, policyId) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
storage_policy_id: policyId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,31 @@
|
||||
import ApplicationSerializer from './application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
export default class RecordingSerializer extends ApplicationSerializer.extend(
|
||||
EmbeddedRecordsMixin
|
||||
EmbeddedRecordsMixin,
|
||||
) {
|
||||
attrs = {
|
||||
connection_recordings: { embedded: 'always' },
|
||||
};
|
||||
|
||||
serialize(snapshot) {
|
||||
let serialized = super.serialize(...arguments);
|
||||
// The post request for reapply-storage-policy doesn't expect a payload,
|
||||
// so we return nothing
|
||||
if (snapshot?.adapterOptions?.method === 'reapply-storage-policy') {
|
||||
return {};
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
normalize(typeClass, hash, ...rest) {
|
||||
const normalizedHash = structuredClone(hash);
|
||||
const err = normalizedHash.errors;
|
||||
delete normalizedHash.errors;
|
||||
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
|
||||
// we should not override the ember models errors field,
|
||||
// so we rename the errors field from the api
|
||||
normalized.data.attributes.errors_number = err;
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,12 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
if (brokeredCredentialSourceIDs)
|
||||
serialized = this.serializeWithBrokeredCredentialSources(
|
||||
snapshot,
|
||||
brokeredCredentialSourceIDs
|
||||
brokeredCredentialSourceIDs,
|
||||
);
|
||||
if (injectedApplicationCredentialSourceIDs)
|
||||
serialized = this.serializeWithInjectedApplicationCredentialSources(
|
||||
snapshot,
|
||||
injectedApplicationCredentialSourceIDs
|
||||
injectedApplicationCredentialSourceIDs,
|
||||
);
|
||||
|
||||
// Delete session recording related fields from non-SSH targets
|
||||
@@ -75,7 +75,7 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
*/
|
||||
serializeWithBrokeredCredentialSources(
|
||||
snapshot,
|
||||
brokered_credential_source_ids
|
||||
brokered_credential_source_ids,
|
||||
) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
@@ -92,7 +92,7 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
*/
|
||||
serializeWithInjectedApplicationCredentialSources(
|
||||
snapshot,
|
||||
injected_application_credential_source_ids
|
||||
injected_application_credential_source_ids,
|
||||
) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class WorkerSerializer extends ApplicationSerializer {
|
||||
if (workerGeneratedAuthToken)
|
||||
serialized = this.serializeWithGeneratedToken(
|
||||
snapshot,
|
||||
workerGeneratedAuthToken
|
||||
workerGeneratedAuthToken,
|
||||
);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ export class ResourceFilter {
|
||||
return this.in(value.contains, key);
|
||||
}
|
||||
return this.equals(key, value);
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
})
|
||||
.flat()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Store, { CacheHandler } from '@ember-data/store';
|
||||
import RequestManager from '@ember-data/request';
|
||||
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
||||
|
||||
export default class extends Store {
|
||||
requestManager = new RequestManager();
|
||||
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.requestManager.use([LegacyNetworkHandler]);
|
||||
this.requestManager.useCache(CacheHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Transform from '@ember-data/serializer/transform';
|
||||
import { TrackedObject } from 'tracked-built-ins';
|
||||
|
||||
export default class ObjectTransform extends Transform {
|
||||
/**
|
||||
* @param serialized
|
||||
* @returns {object}
|
||||
*/
|
||||
deserialize(serialized) {
|
||||
const obj = new TrackedObject(serialized || {});
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deserialized
|
||||
* @returns {object}
|
||||
*/
|
||||
serialize(deserialized) {
|
||||
if (!deserialized) {
|
||||
return {};
|
||||
}
|
||||
return deserialized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Takes a POJO representing a filter query and converts it to an
|
||||
* MQL expression string which is understood by the client daemon.
|
||||
*
|
||||
* @example
|
||||
* const filter = generateMQLFilterExpression({
|
||||
* name: [{ contains: 'search text' }],
|
||||
* scope: [{ equals: 'scope1'}, { equals: 'scope2' }],
|
||||
* });
|
||||
*
|
||||
* // (name % "search text") and (scope_id = "scope1" or scope_id = "scope2")
|
||||
*/
|
||||
export function generateMQLFilterExpression(filterObj) {
|
||||
if (!filterObj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const expressions = Object.entries(filterObj)
|
||||
.map(([key, filterObjValue]) => {
|
||||
const filterValueArray = Array.isArray(filterObjValue)
|
||||
? filterObjValue
|
||||
: filterObjValue.values;
|
||||
|
||||
let clauses;
|
||||
const filterValues = filterValueArray.map((filterObjValue) => {
|
||||
if (!filterObjValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [operation, value] = Object.entries(filterObjValue)[0];
|
||||
|
||||
switch (operation) {
|
||||
case 'contains':
|
||||
return contains(key, value);
|
||||
case 'gt':
|
||||
return gt(key, value);
|
||||
case 'gte':
|
||||
return gte(key, value);
|
||||
case 'lt':
|
||||
return lt(key, value);
|
||||
case 'lte':
|
||||
return lte(key, value);
|
||||
case 'notEquals':
|
||||
return notEquals(key, value);
|
||||
default:
|
||||
return equals(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Default to `or` if we don't have a logical operator
|
||||
const { logicalOperator } = filterObjValue;
|
||||
if (logicalOperator === 'and') {
|
||||
clauses = and(filterValues);
|
||||
} else {
|
||||
clauses = or(filterValues);
|
||||
}
|
||||
|
||||
return parenthetical(clauses);
|
||||
})
|
||||
.filter((item) => item);
|
||||
return and(expressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a POJO representing a search query and converts it to an
|
||||
* MQL expression string which is understood by the client daemon.
|
||||
*
|
||||
* @example
|
||||
* const search = generateMQLSearchExpression({
|
||||
* text: 'search',
|
||||
* fields: ['id', 'name', 'description']
|
||||
* });
|
||||
*
|
||||
* // (id % 'search' or name % 'search' or description % 'search')
|
||||
*/
|
||||
export function generateMQLSearchExpression(searchObj) {
|
||||
if (!searchObj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { text, fields } = searchObj;
|
||||
|
||||
return parenthetical(
|
||||
or(fields.map((field) => contains(field, text)).filter((item) => item)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the MQL expression when a user passes in both search and filters.
|
||||
*/
|
||||
export function generateMQLExpression(obj) {
|
||||
const { search, filters } = obj;
|
||||
|
||||
const expressions = [
|
||||
generateMQLSearchExpression(search),
|
||||
generateMQLFilterExpression(filters),
|
||||
].filter((item) => item);
|
||||
|
||||
return and(expressions);
|
||||
}
|
||||
|
||||
const isEmpty = (input) =>
|
||||
input === null || input === undefined || input === '';
|
||||
|
||||
// Escape any double quotes or backslashes and add quotes if it's a string
|
||||
const sanitize = (input) =>
|
||||
typeof input === 'string' ? `"${input?.replace(/(["\\])/g, '\\$1')}"` : input;
|
||||
|
||||
// Comparison Operators
|
||||
const equals = (key, value) =>
|
||||
!isEmpty(value) ? `${key} = ${sanitize(value)}` : null;
|
||||
|
||||
const notEquals = (key, value) =>
|
||||
!isEmpty(value) ? `${key} != ${sanitize(value)}` : null;
|
||||
|
||||
const contains = (key, value) =>
|
||||
!isEmpty(value) ? `${key} % ${sanitize(value)}` : null;
|
||||
|
||||
const gt = (key, value) =>
|
||||
!isEmpty(value) ? `${key} > ${sanitize(value)}` : null;
|
||||
|
||||
const gte = (key, value) =>
|
||||
!isEmpty(value) ? `${key} >= ${sanitize(value)}` : null;
|
||||
|
||||
const lt = (key, value) =>
|
||||
!isEmpty(value) ? `${key} < ${sanitize(value)}` : null;
|
||||
|
||||
const lte = (key, value) =>
|
||||
!isEmpty(value) ? `${key} <= ${sanitize(value)}` : null;
|
||||
|
||||
// Logical Operators
|
||||
const and = (clauses) => clauses.join(' and ');
|
||||
|
||||
const or = (clauses) => clauses.join(' or ');
|
||||
|
||||
const parenthetical = (expression) => (expression ? `(${expression})` : null);
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/abilities/policy';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/abilities/scope';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/handlers/client-daemon-handler';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/models/policy';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/serializers/channel-recording';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/serializers/scope';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/services/store';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/transforms/object';
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'api/utils/mql-query';
|
||||
+105
-37
@@ -73,8 +73,12 @@ function routes() {
|
||||
return scope.scope ? scope.scope.id === scope_id : false;
|
||||
});
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
const filteredResultSet = resultSet.filter(makeBooleanFilter(filter));
|
||||
// add a response_type to the response to simulate pagination
|
||||
// without having response_type, the unsupported controller modal
|
||||
// will show when running with mirage
|
||||
return { items: filteredResultSet.models, response_type: 'complete' };
|
||||
},
|
||||
);
|
||||
this.post('/scopes', function ({ scopes }) {
|
||||
// Parent scope comes through the payload via `scope_id`, but this needs
|
||||
@@ -88,7 +92,29 @@ function routes() {
|
||||
this.get('/scopes/:id');
|
||||
this.patch('/scopes/:id');
|
||||
this.del('/scopes/:id');
|
||||
this.post(
|
||||
'/scopes/:idMethod',
|
||||
function ({ scopes }, { params: { idMethod } }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const id = idMethod.split(':')[0];
|
||||
const method = idMethod.split(':')[1];
|
||||
const scope = scopes.find(id);
|
||||
|
||||
const updatedAttrs = {
|
||||
version: attrs.version,
|
||||
storagePolicyId: attrs.storagePolicyId,
|
||||
};
|
||||
|
||||
if (method === 'attach-storage-policy') {
|
||||
updatedAttrs.storagePolicyId = attrs.storagePolicyId;
|
||||
}
|
||||
|
||||
if (method === 'detach-storage-policy') {
|
||||
updatedAttrs.storagePolicyId = '';
|
||||
}
|
||||
return scope.update(updatedAttrs);
|
||||
},
|
||||
);
|
||||
// Auth & IAM resources
|
||||
|
||||
this.get(
|
||||
@@ -107,11 +133,11 @@ function routes() {
|
||||
});
|
||||
} else {
|
||||
resultSet = authMethods.where(
|
||||
(authMethod) => authMethod.scopeId === scope_id
|
||||
(authMethod) => authMethod.scopeId === scope_id,
|
||||
);
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/auth-methods', function ({ authMethods }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
@@ -130,7 +156,7 @@ function routes() {
|
||||
}
|
||||
|
||||
return authMethods.find(id).update(attrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.del('/auth-methods/:id', ({ authMethods }, { params: { id } }) => {
|
||||
const authMethod = authMethods.find(id);
|
||||
@@ -149,7 +175,7 @@ function routes() {
|
||||
'/accounts',
|
||||
(
|
||||
{ accounts },
|
||||
{ queryParams: { auth_method_id: authMethodId, filter } }
|
||||
{ queryParams: { auth_method_id: authMethodId, filter } },
|
||||
) => {
|
||||
let resultSet;
|
||||
if (authMethodId) {
|
||||
@@ -158,7 +184,7 @@ function routes() {
|
||||
resultSet = accounts.all();
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/accounts');
|
||||
this.get('/accounts/:id');
|
||||
@@ -184,7 +210,7 @@ function routes() {
|
||||
updatedAttrs.attributes.password = attrs.new_password;
|
||||
}
|
||||
return account.update(updatedAttrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Authenticate + auth methods custom routes
|
||||
@@ -211,7 +237,7 @@ function routes() {
|
||||
resultSet = users.where((user) => user.scopeId === scope_id);
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/users');
|
||||
this.get('/users/:id');
|
||||
@@ -262,7 +288,7 @@ function routes() {
|
||||
resultSet = groups.where((group) => group.scopeId === scope_id);
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/groups');
|
||||
this.get('/groups/:id');
|
||||
@@ -294,7 +320,7 @@ function routes() {
|
||||
});
|
||||
}
|
||||
return group.update(updatedAttrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// IAM: Roles
|
||||
@@ -309,7 +335,7 @@ function routes() {
|
||||
'/roles/:idMethod',
|
||||
function (
|
||||
{ roles, users, groups, managedGroups },
|
||||
{ params: { idMethod } }
|
||||
{ params: { idMethod } },
|
||||
) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const id = idMethod.split(':')[0];
|
||||
@@ -361,7 +387,7 @@ function routes() {
|
||||
updatedAttrs.managedGroupIds = updatedAttrs.managedGroupIds.filter(
|
||||
(id) => {
|
||||
return !attrs.principalIds.includes(id);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -373,7 +399,7 @@ function routes() {
|
||||
}
|
||||
|
||||
return role.update(updatedAttrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Other resources
|
||||
@@ -383,7 +409,7 @@ function routes() {
|
||||
'/host-catalogs',
|
||||
({ hostCatalogs }, { queryParams: { scope_id: scopeId } }) => {
|
||||
return hostCatalogs.where({ scopeId });
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post(
|
||||
'/host-catalogs',
|
||||
@@ -396,7 +422,7 @@ function routes() {
|
||||
};
|
||||
}
|
||||
return hostCatalogs.create(attrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.get('/host-catalogs/:id');
|
||||
this.patch('/host-catalogs/:id');
|
||||
@@ -408,7 +434,7 @@ function routes() {
|
||||
'/hosts',
|
||||
function ({ hosts }, { queryParams: { host_catalog_id: hostCatalogId } }) {
|
||||
return hosts.where({ hostCatalogId });
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/hosts', function ({ hostCatalogs, hosts }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
@@ -426,14 +452,22 @@ function routes() {
|
||||
'/host-sets',
|
||||
function (
|
||||
{ hostSets },
|
||||
{ queryParams: { host_catalog_id: hostCatalogId } }
|
||||
{ queryParams: { host_catalog_id: hostCatalogId } },
|
||||
) {
|
||||
return hostSets.where({ hostCatalogId });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.post('/host-sets', function ({ hostCatalogs, hostSets }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const hostCatalog = hostCatalogs.find(attrs.hostCatalogId);
|
||||
//this needs to be attached while creating a plugin type host set to mimic API
|
||||
if (hostCatalog.plugin) {
|
||||
attrs.type = 'plugin';
|
||||
attrs.plugin = {
|
||||
name: hostCatalog.plugin.name,
|
||||
};
|
||||
}
|
||||
attrs.scopeId = hostCatalog.scope.id;
|
||||
return hostSets.create(attrs);
|
||||
});
|
||||
@@ -469,7 +503,7 @@ function routes() {
|
||||
}
|
||||
|
||||
return hostSet.update(updatedAttrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// target
|
||||
@@ -492,7 +526,7 @@ function routes() {
|
||||
resultSet = targets.where((target) => target.scopeId === scope_id);
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/targets', function ({ targets }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
@@ -540,7 +574,7 @@ function routes() {
|
||||
resultSet = sessions.where((session) => session.scopeId === scope_id);
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
|
||||
const session = sessions.find(id);
|
||||
@@ -565,7 +599,7 @@ function routes() {
|
||||
updatedAttrs.status = 'canceling';
|
||||
}
|
||||
return session.update(updatedAttrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// auth-tokens
|
||||
@@ -581,7 +615,7 @@ function routes() {
|
||||
this.get(
|
||||
'/credential-stores',
|
||||
({ credentialStores }, { queryParams: { scope_id: scopeId } }) =>
|
||||
credentialStores.where({ scopeId })
|
||||
credentialStores.where({ scopeId }),
|
||||
);
|
||||
|
||||
this.get('/credential-stores/:id');
|
||||
@@ -595,8 +629,8 @@ function routes() {
|
||||
'/credential-libraries',
|
||||
(
|
||||
{ credentialLibraries },
|
||||
{ queryParams: { credential_store_id: credentialStoreId } }
|
||||
) => credentialLibraries.where({ credentialStoreId })
|
||||
{ queryParams: { credential_store_id: credentialStoreId } },
|
||||
) => credentialLibraries.where({ credentialStoreId }),
|
||||
);
|
||||
this.get('/credential-libraries/:id');
|
||||
this.post(
|
||||
@@ -606,7 +640,7 @@ function routes() {
|
||||
const credentialStore = credentialStores.find(attrs.credentialStoreId);
|
||||
attrs.scopeId = credentialStore.scope.id;
|
||||
return credentialLibraries.create(attrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.del('/credential-libraries/:id');
|
||||
this.patch('/credential-libraries/:id');
|
||||
@@ -617,8 +651,8 @@ function routes() {
|
||||
'/credentials',
|
||||
(
|
||||
{ credentials },
|
||||
{ queryParams: { credential_store_id: credentialStoreId } }
|
||||
) => credentials.where({ credentialStoreId })
|
||||
{ queryParams: { credential_store_id: credentialStoreId } },
|
||||
) => credentials.where({ credentialStoreId }),
|
||||
);
|
||||
this.get('/credentials/:id');
|
||||
this.post('/credentials', function ({ credentialStores, credentials }) {
|
||||
@@ -636,7 +670,7 @@ function routes() {
|
||||
'/managed-groups',
|
||||
(
|
||||
{ managedGroups },
|
||||
{ queryParams: { auth_method_id: authMethodId, filter } }
|
||||
{ queryParams: { auth_method_id: authMethodId, filter } },
|
||||
) => {
|
||||
let resultSet;
|
||||
if (authMethodId) {
|
||||
@@ -645,7 +679,7 @@ function routes() {
|
||||
resultSet = managedGroups.all();
|
||||
}
|
||||
return resultSet.filter(makeBooleanFilter(filter));
|
||||
}
|
||||
},
|
||||
);
|
||||
this.post('/managed-groups');
|
||||
this.get('/managed-groups/:id');
|
||||
@@ -657,7 +691,7 @@ function routes() {
|
||||
'/workers',
|
||||
({ workers }, { queryParams: { scope_id: scopeId } }) => {
|
||||
return workers.where({ scopeId });
|
||||
}
|
||||
},
|
||||
);
|
||||
this.get('/workers/:id');
|
||||
this.del('/workers/:id');
|
||||
@@ -681,7 +715,7 @@ function routes() {
|
||||
return storageBuckets.all();
|
||||
}
|
||||
return storageBuckets.where({ scopeId });
|
||||
}
|
||||
},
|
||||
);
|
||||
this.get('/storage-buckets/:id');
|
||||
this.del('/storage-buckets/:id');
|
||||
@@ -700,21 +734,37 @@ function routes() {
|
||||
delete attrs.secrets;
|
||||
|
||||
return storageBuckets.create(attrs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
//storage-policy
|
||||
this.get(
|
||||
'/policies',
|
||||
({ policies }, { queryParams: { scope_id: scopeId } }) => {
|
||||
return policies.where({ scopeId });
|
||||
},
|
||||
);
|
||||
this.get('/policies/:id');
|
||||
this.del('/policies/:id');
|
||||
this.patch('/policies/:id');
|
||||
this.post('/policies', function ({ policies }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
|
||||
return policies.create(attrs);
|
||||
});
|
||||
|
||||
// session recordings
|
||||
this.get(
|
||||
'/session-recordings',
|
||||
(
|
||||
{ sessionRecordings },
|
||||
{ queryParams: { scope_id: scopeId, recursive } }
|
||||
{ queryParams: { scope_id: scopeId, recursive } },
|
||||
) => {
|
||||
if (recursive && scopeId === 'global') {
|
||||
return sessionRecordings.all();
|
||||
}
|
||||
return sessionRecordings.where({ scopeId });
|
||||
}
|
||||
},
|
||||
);
|
||||
this.get(
|
||||
'/session-recordings/:idMethod',
|
||||
@@ -727,7 +777,25 @@ function routes() {
|
||||
} else {
|
||||
return sessionRecordings.find(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.post(
|
||||
'/session-recordings/:idMethod',
|
||||
function ({ sessionRecordings }, { params: { idMethod } }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const id = idMethod.split(':')[0];
|
||||
const method = idMethod.split(':')[1];
|
||||
const record = sessionRecordings.find(id);
|
||||
const updatedAttrs = {
|
||||
version: attrs.version,
|
||||
};
|
||||
if (method === 'reapply-storage-policy') {
|
||||
updatedAttrs.retain_until = faker.date.recent();
|
||||
updatedAttrs.delete_after = faker.date.recent();
|
||||
}
|
||||
return record.update(updatedAttrs);
|
||||
},
|
||||
);
|
||||
|
||||
/* Uncomment the following line and the Response import above
|
||||
|
||||
@@ -9,6 +9,7 @@ import permissions from '../helpers/permissions';
|
||||
import generateId from '../helpers/id';
|
||||
|
||||
import {
|
||||
TYPE_AUTH_METHOD_PASSWORD,
|
||||
TYPE_AUTH_METHOD_OIDC,
|
||||
TYPE_AUTH_METHOD_LDAP,
|
||||
TYPES_AUTH_METHOD,
|
||||
@@ -23,11 +24,17 @@ export default factory.extend({
|
||||
'delete',
|
||||
'authenticate',
|
||||
],
|
||||
authorized_collection_actions: () => {
|
||||
return {
|
||||
accounts: ['create', 'list'],
|
||||
'managed-groups': ['create', 'list'],
|
||||
};
|
||||
authorized_collection_actions() {
|
||||
switch (this.type) {
|
||||
case TYPE_AUTH_METHOD_PASSWORD:
|
||||
return { accounts: ['create', 'list'] };
|
||||
case TYPE_AUTH_METHOD_LDAP:
|
||||
case TYPE_AUTH_METHOD_OIDC:
|
||||
return {
|
||||
accounts: ['create', 'list'],
|
||||
'managed-groups': ['create', 'list'],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
id: () => generateId('am_'),
|
||||
|
||||
@@ -54,7 +54,7 @@ export default factory.extend({
|
||||
return {
|
||||
disable_credential_rotation: faker.datatype.boolean(),
|
||||
region: `us-${faker.location.cardinalDirection()}-${faker.number.int(
|
||||
9
|
||||
9,
|
||||
)}`,
|
||||
};
|
||||
case 'azure':
|
||||
@@ -85,7 +85,16 @@ export default factory.extend({
|
||||
afterCreate(hostCatalog, server) {
|
||||
const { scope, type } = hostCatalog;
|
||||
const hosts = server.createList('host', 10, { scope, hostCatalog, type });
|
||||
server.createList('host-set', 3, { scope, hostCatalog, hosts, type });
|
||||
const hostSets = server.createList('host-set', 3, {
|
||||
scope,
|
||||
hostCatalog,
|
||||
hosts,
|
||||
type,
|
||||
});
|
||||
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
|
||||
hosts.forEach((host) => {
|
||||
host.update({ hostSetIds });
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import factory from '../generated/factories/policy';
|
||||
import permissions from '../helpers/permissions';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import generateId from '../helpers/id';
|
||||
|
||||
export default factory.extend({
|
||||
id: () => generateId('pst_'),
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('policy') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
|
||||
attributes() {
|
||||
return {
|
||||
retain_for: {
|
||||
days: faker.number.int({ min: 10, max: 100 }),
|
||||
overridable: faker.datatype.boolean(),
|
||||
},
|
||||
|
||||
delete_after: {
|
||||
days: faker.number.int({ min: 101, max: 500 }),
|
||||
overridable: faker.datatype.boolean(),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -21,8 +21,8 @@ export default factory.extend({
|
||||
],
|
||||
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
|
||||
grant_strings: () => [
|
||||
'id=*;action=*',
|
||||
'id=*;type=host-catalog;actions=create,read',
|
||||
'ids=*;action=*',
|
||||
'ids=*;type=host-catalog;actions=create,read',
|
||||
],
|
||||
|
||||
id: () => generateId('r_'),
|
||||
|
||||
@@ -11,25 +11,26 @@ import generateId from '../helpers/id';
|
||||
export default factory.extend({
|
||||
type: 'global',
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('scope') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
authorized_actions() {
|
||||
const authorizedActions = ['no-op', 'read', 'update'];
|
||||
|
||||
// Storage policies can only be attatched to global and org scopes.
|
||||
if (this.type === 'global' || this.type === 'org') {
|
||||
authorizedActions.push('attach-storage-policy', 'detach-storage-policy');
|
||||
}
|
||||
|
||||
if (this.type === 'org' || this.type === 'project') {
|
||||
authorizedActions.push('delete');
|
||||
}
|
||||
|
||||
return permissions.authorizedActionsFor('scope') || authorizedActions;
|
||||
},
|
||||
|
||||
authorized_collection_actions() {
|
||||
const collectionActions = {
|
||||
scopes: ['create', 'list'],
|
||||
users: ['create', 'list'],
|
||||
sessions: ['list'],
|
||||
groups: ['create', 'list'],
|
||||
roles: ['create', 'list'],
|
||||
targets: ['create', 'list'],
|
||||
'credential-stores': ['create', 'list'],
|
||||
'auth-methods': ['create', 'list'],
|
||||
'host-catalogs': ['create', 'list'],
|
||||
};
|
||||
|
||||
// Worker permissions only available on the global scope
|
||||
@@ -41,6 +42,16 @@ export default factory.extend({
|
||||
if (this.type === 'global' || this.type === 'org') {
|
||||
collectionActions['storage-buckets'] = ['create', 'list'];
|
||||
collectionActions['session-recordings'] = ['list'];
|
||||
collectionActions.users = ['create', 'list'];
|
||||
collectionActions['auth-methods'] = ['create', 'list'];
|
||||
collectionActions['policies'] = ['create', 'list'];
|
||||
}
|
||||
|
||||
if (this.type === 'project') {
|
||||
collectionActions['credential-stores'] = ['create', 'list'];
|
||||
collectionActions['host-catalogs'] = ['create', 'list'];
|
||||
collectionActions.sessions = ['list'];
|
||||
collectionActions.targets = ['create', 'list'];
|
||||
}
|
||||
|
||||
return collectionActions;
|
||||
@@ -58,7 +69,7 @@ export default factory.extend({
|
||||
type: 'org',
|
||||
scope: { id: record.id, type: record.type },
|
||||
},
|
||||
'withChildren'
|
||||
'withChildren',
|
||||
);
|
||||
}
|
||||
if (record.type === 'org') {
|
||||
|
||||
@@ -43,11 +43,23 @@ export default factory.extend({
|
||||
: null;
|
||||
},
|
||||
|
||||
retain_until() {
|
||||
return this.state === STATE_SESSION_RECORDING_AVAILABLE
|
||||
? faker.date.recent()
|
||||
: null;
|
||||
},
|
||||
delete_after() {
|
||||
return this.state === STATE_SESSION_RECORDING_AVAILABLE
|
||||
? faker.date.recent()
|
||||
: null;
|
||||
},
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('session-recording') || [
|
||||
'no-op',
|
||||
'read',
|
||||
'download',
|
||||
'reapply-storage-policy',
|
||||
],
|
||||
|
||||
withNonExistingUserAndTarget: trait({
|
||||
@@ -57,7 +69,8 @@ export default factory.extend({
|
||||
const session = pickRandomElement(server.schema.sessions.all().models);
|
||||
session.id = 'deleted_session';
|
||||
const target = pickRandomElement(
|
||||
server.schema.targets.where((target) => target.storage_bucket_id).models
|
||||
server.schema.targets.where((target) => target.storage_bucket_id)
|
||||
.models,
|
||||
);
|
||||
target.id = 'deleted_target';
|
||||
|
||||
@@ -77,7 +90,8 @@ export default factory.extend({
|
||||
const user = pickRandomElement(server.schema.users.all().models);
|
||||
const session = pickRandomElement(server.schema.sessions.all().models);
|
||||
const target = pickRandomElement(
|
||||
server.schema.targets.where((target) => target.storage_bucket_id).models
|
||||
server.schema.targets.where((target) => target.storage_bucket_id)
|
||||
.models,
|
||||
);
|
||||
|
||||
sessionRecording.update({
|
||||
|
||||
@@ -7,6 +7,7 @@ import factory from '../generated/factories/session';
|
||||
import { trait } from 'miragejs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import generateId from '../helpers/id';
|
||||
import permissions from '../helpers/permissions';
|
||||
|
||||
const statusStrings = ['pending', 'active', 'canceling', 'terminated'];
|
||||
|
||||
@@ -15,7 +16,8 @@ export const pickRandomStatusString = () =>
|
||||
|
||||
export default factory.extend({
|
||||
id: () => generateId('ss_'),
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('session') || ['read', 'cancel'],
|
||||
status: pickRandomStatusString(),
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ export default factory.extend({
|
||||
case TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3:
|
||||
return {
|
||||
region: `us-${faker.location.cardinalDirection()}-${faker.number.int(
|
||||
9
|
||||
9,
|
||||
)}`,
|
||||
disable_credential_rotation: faker.datatype.boolean(),
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ export default factory.extend({
|
||||
|
||||
randomlySelectedCredentialLibraries = server.schema.credentialLibraries
|
||||
.where(
|
||||
(credentialLibrary) => credentialLibrary.scopeId === target.scope.id
|
||||
(credentialLibrary) => credentialLibrary.scopeId === target.scope.id,
|
||||
)
|
||||
.models.filter(() => randomBoolean())
|
||||
.map((cred) => cred.id);
|
||||
|
||||
@@ -42,6 +42,7 @@ export default Factory.extend({
|
||||
signing_algorithms: ['RS256', 'RS384'],
|
||||
allowed_audiences: ['www.alice.com', 'www.alice.com/admin'],
|
||||
idp_ca_certs: ['certificate-1234', 'certificate-5678'],
|
||||
prompts: ['consent'],
|
||||
};
|
||||
break;
|
||||
case TYPE_AUTH_METHOD_LDAP:
|
||||
@@ -51,7 +52,7 @@ export default Factory.extend({
|
||||
certificates: [faker.string.alphanumeric(50)],
|
||||
client_certificate: faker.string.alphanumeric(50),
|
||||
client_certificate_key: `---Begin Certificate --- ${faker.string.alphanumeric(
|
||||
170
|
||||
170,
|
||||
)} ---End Certificate ---`,
|
||||
client_certificate_key_hmac: faker.string.alphanumeric(50),
|
||||
enable_groups: true,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { Factory } from 'miragejs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
/**
|
||||
* GeneratedStoragePolicyModelFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
name: () => faker.word.words(),
|
||||
type: 'storage',
|
||||
description: () => faker.word.words(),
|
||||
created_time: () => faker.date.recent(),
|
||||
updated_time: () => faker.date.recent(),
|
||||
version: () => faker.number.int(),
|
||||
});
|
||||
@@ -13,5 +13,6 @@ export default Factory.extend({
|
||||
type: 'tcp',
|
||||
created_time: () => faker.date.recent(),
|
||||
updated_time: () => faker.date.recent(),
|
||||
expiration_time: () => faker.date.soon(),
|
||||
version: () => faker.number.int(),
|
||||
});
|
||||
@@ -22,6 +22,6 @@ export default Factory.extend({
|
||||
active_connection_count: () => faker.number.int(10),
|
||||
release_version: () =>
|
||||
`Boundary v${faker.number.int(11)}.${faker.number.int(
|
||||
50
|
||||
50,
|
||||
)}.${faker.number.int(12)}`,
|
||||
});
|
||||
@@ -39,7 +39,7 @@ export default function (prefix = '') {
|
||||
let result = '';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
result = result.concat(
|
||||
characters[Math.floor(random() * characters.length)]
|
||||
characters[Math.floor(random() * characters.length)],
|
||||
);
|
||||
}
|
||||
return prefix.concat(result);
|
||||
|
||||
@@ -35,13 +35,13 @@ class ToggledCapabilities {
|
||||
// Find the value of a pair with key `authorized_actions`
|
||||
// Safe-ish when the item is not present
|
||||
#rawAuthorizedActions = (this.#queryPairs.find(
|
||||
(item) => item[0] === 'authorized_actions'
|
||||
(item) => item[0] === 'authorized_actions',
|
||||
) || [])[1];
|
||||
|
||||
// Safe-ish when the value is not present
|
||||
#authorizedActions =
|
||||
JSON.parse(
|
||||
decodeURIComponent(this.#rawAuthorizedActions || '') || 'null'
|
||||
decodeURIComponent(this.#rawAuthorizedActions || '') || 'null',
|
||||
) || {};
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { belongsTo } from 'miragejs';
|
||||
import Model from './base';
|
||||
export default Model.extend({
|
||||
scope: belongsTo(),
|
||||
});
|
||||
@@ -56,7 +56,7 @@ const commandHandlers = {
|
||||
last_used_time: '',
|
||||
expiration_time: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -82,7 +82,7 @@ const commandHandlers = {
|
||||
auth_url: 'https://www.duckduckgo.com',
|
||||
token_id: 'token_1234',
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
token: (_, scopeAttrs) => {
|
||||
oidcAttemptCounter++;
|
||||
@@ -105,7 +105,7 @@ const commandHandlers = {
|
||||
last_used_time: '',
|
||||
expiration_time: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -115,7 +115,7 @@ const commandHandlers = {
|
||||
// Handles all custom methods on /auth-methods/:id route
|
||||
export function authHandler({ scopes, authMethods }, request) {
|
||||
const [, id, method] = request.params.id_method.match(
|
||||
/(?<id>.[^:]*):(?<method>(.*))/
|
||||
/(?<id>.[^:]*):(?<method>(.*))/,
|
||||
);
|
||||
const authMethod = authMethods.find(id);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedHostSourceIds) {
|
||||
const listOfHostSources = addToSourcesList(
|
||||
originHostSetIds,
|
||||
selectedHostSourceIds
|
||||
selectedHostSourceIds,
|
||||
);
|
||||
if (listOfHostSources?.length) {
|
||||
updatedAttrs.hostSetIds = listOfHostSources;
|
||||
@@ -52,7 +52,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedHostSourceIds) {
|
||||
const removedHostSources = removeFromSourcesList(
|
||||
originHostSetIds,
|
||||
selectedHostSourceIds
|
||||
selectedHostSourceIds,
|
||||
);
|
||||
updatedAttrs.hostSetIds = removedHostSources;
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedBrokeredCredentials) {
|
||||
const listOfBrokeredCredentialSources = addToSourcesList(
|
||||
originalBrokeredCredentials,
|
||||
selectedBrokeredCredentials
|
||||
selectedBrokeredCredentials,
|
||||
);
|
||||
if (listOfBrokeredCredentialSources?.length) {
|
||||
updatedAttrs.brokeredCredentialSourceIds =
|
||||
@@ -73,7 +73,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedInjectedCredentials) {
|
||||
const listOfInjectedCredentialSources = addToSourcesList(
|
||||
originalInjectedCredentials,
|
||||
selectedInjectedCredentials
|
||||
selectedInjectedCredentials,
|
||||
);
|
||||
if (listOfInjectedCredentialSources?.length) {
|
||||
updatedAttrs.injectedApplicationCredentialSourceIds =
|
||||
@@ -86,7 +86,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedBrokeredCredentials) {
|
||||
const removedCredentialSources = removeFromSourcesList(
|
||||
originalBrokeredCredentials,
|
||||
selectedBrokeredCredentials
|
||||
selectedBrokeredCredentials,
|
||||
);
|
||||
updatedAttrs.brokeredCredentialSourceIds = removedCredentialSources;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export function targetHandler({ targets }, { params: { idMethod } }) {
|
||||
if (selectedInjectedCredentials) {
|
||||
const removedCredentialSources = removeFromSourcesList(
|
||||
originalInjectedCredentials,
|
||||
selectedInjectedCredentials
|
||||
selectedInjectedCredentials,
|
||||
);
|
||||
updatedAttrs.injectedApplicationCredentialSourceIds =
|
||||
removedCredentialSources;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function (server) {
|
||||
type: 'org',
|
||||
scope: { id: globalScope.id, type: globalScope.type },
|
||||
},
|
||||
'withChildren'
|
||||
'withChildren',
|
||||
)[0];
|
||||
|
||||
// Auth
|
||||
@@ -29,13 +29,13 @@ export default function (server) {
|
||||
'auth-method',
|
||||
5,
|
||||
{ scope: globalScope },
|
||||
'withAccountsAndUsersAndManagedGroups'
|
||||
'withAccountsAndUsersAndManagedGroups',
|
||||
);
|
||||
const orgAuthMethods = server.createList(
|
||||
'auth-method',
|
||||
3,
|
||||
{ scope: orgScope },
|
||||
'withAccountsAndUsersAndManagedGroups'
|
||||
'withAccountsAndUsersAndManagedGroups',
|
||||
);
|
||||
// Authenticated user/account
|
||||
const user = server.create('user', {
|
||||
@@ -62,13 +62,13 @@ export default function (server) {
|
||||
'role',
|
||||
1,
|
||||
{ scope: globalScope },
|
||||
'withPrincipals'
|
||||
'withPrincipals',
|
||||
);
|
||||
//create managed groups for role/principals in globalScope
|
||||
globalScopeRoles.forEach((role) => {
|
||||
const { scope } = role;
|
||||
const oidcAuthMethod = globalAuthMethods.filter(
|
||||
(auth) => auth.type === TYPE_AUTH_METHOD_OIDC
|
||||
(auth) => auth.type === TYPE_AUTH_METHOD_OIDC,
|
||||
)[0];
|
||||
const { type } = oidcAuthMethod;
|
||||
const managedGroups = server.createList('managed-group', 2, {
|
||||
@@ -83,13 +83,13 @@ export default function (server) {
|
||||
'role',
|
||||
5,
|
||||
{ scope: orgScope },
|
||||
'withPrincipals'
|
||||
'withPrincipals',
|
||||
);
|
||||
//create managed groups for role/principals in orgScope
|
||||
orgScopeRoles.forEach((role) => {
|
||||
const { scope } = role;
|
||||
const oidcAuthMethod = orgAuthMethods.filter(
|
||||
(auth) => auth.type === TYPE_AUTH_METHOD_OIDC
|
||||
(auth) => auth.type === TYPE_AUTH_METHOD_OIDC,
|
||||
)[0];
|
||||
const { type } = oidcAuthMethod;
|
||||
const managedGroups = server.createList('managed-group', 2, {
|
||||
@@ -104,6 +104,10 @@ export default function (server) {
|
||||
server.createList('storage-bucket', 3, { scope: globalScope });
|
||||
server.createList('storage-bucket', 2, { scope: orgScope });
|
||||
|
||||
// Storage Policies
|
||||
server.createList('policy', 3, { scope: globalScope });
|
||||
server.createList('policy', 3, { scope: orgScope });
|
||||
|
||||
// Other resources
|
||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||
server.createList('host-catalog', 8, { scope }, 'withChildren');
|
||||
@@ -129,19 +133,19 @@ export default function (server) {
|
||||
3,
|
||||
{ scope: globalScope },
|
||||
'withConnectionAndChannels',
|
||||
'withExistingUserAndTarget'
|
||||
'withExistingUserAndTarget',
|
||||
);
|
||||
server.create(
|
||||
'session-recording',
|
||||
{ scope: globalScope },
|
||||
'withConnectionAndChannels',
|
||||
'withNonExistingUserAndTarget'
|
||||
'withNonExistingUserAndTarget',
|
||||
);
|
||||
server.createList(
|
||||
'session-recording',
|
||||
2,
|
||||
{ scope: orgScope },
|
||||
'withConnectionAndChannels',
|
||||
'withExistingUserAndTarget'
|
||||
'withExistingUserAndTarget',
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,7 @@ export default function initializeMockIPC(server, config) {
|
||||
userId: 'authenticateduser',
|
||||
type,
|
||||
created_time: new Date().toISOString(),
|
||||
expiration_time: faker.date.soon(),
|
||||
});
|
||||
const decoded = {
|
||||
key1: 'value 1',
|
||||
@@ -178,6 +179,14 @@ export default function initializeMockIPC(server, config) {
|
||||
closeWindow() {}
|
||||
toggleFullscreenWindow() {}
|
||||
stop() {}
|
||||
checkCommand() {
|
||||
return faker.system.filePath();
|
||||
}
|
||||
addTokenToClientDaemon() {}
|
||||
searchClientDaemon() {}
|
||||
isClientDaemonRunning() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.auth_method_id = model.authMethodId;
|
||||
delete json.attributes?.password;
|
||||
|
||||
@@ -38,7 +38,7 @@ export default RestSerializer.extend({
|
||||
|
||||
keyForRelationshipId(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_id`;
|
||||
},
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
// If the scope marks this auth method as primary, designate it
|
||||
// as such with a read-only boolean field (this is how the API behaves).
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
const { channelRecordings } = this.schema;
|
||||
if (model.channelRecordingIds) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.credential_store_id = model.credentialStoreId;
|
||||
return json;
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(/* model */) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
// in a real API, the following fields are set-only and returned in
|
||||
// associated HMAC fields
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.credential_store_id = model.credentialStoreId;
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ export default ApplicationSerializer.extend({
|
||||
|
||||
keyForRelationshipIds(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_ids`;
|
||||
},
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
if (model.memberIds.length) json.member_ids = model.memberIds;
|
||||
return json;
|
||||
|
||||
@@ -11,14 +11,14 @@ export default ApplicationSerializer.extend({
|
||||
|
||||
keyForRelationshipIds(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_ids`;
|
||||
},
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.host_catalog_id = model.hostCatalogId;
|
||||
if (model.hostIds?.length) {
|
||||
|
||||
@@ -7,4 +7,13 @@ import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'host',
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments,
|
||||
);
|
||||
json.host_catalog_id = model.hostCatalogId;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.auth_method_id = model.authMethodId;
|
||||
return json;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'policy',
|
||||
});
|
||||
@@ -11,7 +11,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
const serializedUsers = model.users.models.map((user) => ({
|
||||
scope_id: model.scope.id,
|
||||
@@ -28,7 +28,7 @@ export default ApplicationSerializer.extend({
|
||||
scope_id: model.scope.id,
|
||||
id: managedGroup.id,
|
||||
type: 'managed group',
|
||||
})
|
||||
}),
|
||||
);
|
||||
json.principals = [
|
||||
...serializedUsers,
|
||||
|
||||
@@ -12,7 +12,7 @@ export default ApplicationSerializer.extend({
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
const { connectionRecordings, scopes } = this.schema;
|
||||
|
||||
@@ -22,7 +22,7 @@ export default ApplicationSerializer.extend({
|
||||
const connection = connectionRecordings.find(id);
|
||||
return ConnectionRecordingSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
[connection]
|
||||
[connection],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ export default ApplicationSerializer.extend({
|
||||
|
||||
keyForRelationshipId(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_id`;
|
||||
},
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
json.user_id = model.userId;
|
||||
json.target_id = model.targetId;
|
||||
|
||||
@@ -11,14 +11,14 @@ export default ApplicationSerializer.extend({
|
||||
|
||||
keyForRelationshipIds(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_ids`;
|
||||
},
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
const { hostSets } = this.schema;
|
||||
if (model.hostSetIds?.length) {
|
||||
|
||||
@@ -11,14 +11,14 @@ export default ApplicationSerializer.extend({
|
||||
|
||||
keyForRelationshipIds(relationshipName) {
|
||||
return `${this._container.inflector.singularize(
|
||||
underscore(relationshipName)
|
||||
underscore(relationshipName),
|
||||
)}_ids`;
|
||||
},
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
arguments,
|
||||
);
|
||||
if (model.accountIds.length) json.account_ids = model.accountIds;
|
||||
return json;
|
||||
|
||||
+13
-11
@@ -14,7 +14,7 @@
|
||||
"lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
|
||||
"lint:js": "eslint .",
|
||||
"lint:js:fix": "eslint . --fix",
|
||||
"format": "yarn run format:js",
|
||||
"format": "concurrently \"npm:format:*\" --names \"format:\"",
|
||||
"format:js": "prettier --write '{addon,app,config,stories,tests,mirage}/**/*.js' *.js",
|
||||
"start": "ember serve",
|
||||
"test": "ember exam --test-port 0 --split=4 --parallel=1 --random",
|
||||
@@ -23,15 +23,18 @@
|
||||
"doc:toc": "doctoc README.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"ember-auto-import": "^2.6.3",
|
||||
"ember-can": "^4.2.0",
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
"ember-cli-htmlbars": "^6.2.0",
|
||||
"ember-cli-mirage": "^2.4.0",
|
||||
"ember-data": "~4.12.0",
|
||||
"ember-fetch": "^8.1.1"
|
||||
"ember-fetch": "^8.1.1",
|
||||
"ember-auto-import": "^2.6.3",
|
||||
"tracked-built-ins": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.5",
|
||||
"@babel/core": "^7.21.0",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.21.0",
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
@@ -43,7 +46,6 @@
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"doctoc": "^2.2.0",
|
||||
"ember-auto-import": "^2.6.3",
|
||||
"ember-cli": "^4.12.1",
|
||||
"ember-cli-dependency-checker": "^3.3.1",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
@@ -57,20 +59,20 @@
|
||||
"ember-source": "~4.12.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^5.7.2",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-ember": "^11.5.0",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-qunit": "^7.3.4",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-ember": "^11.11.1",
|
||||
"eslint-plugin-n": "^16.3.1",
|
||||
"eslint-plugin-qunit": "^8.0.1",
|
||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||
"loader.js": "^4.7.0",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier": "^3.0.0",
|
||||
"qunit": "^2.19.4",
|
||||
"qunit-dom": "^2.0.0",
|
||||
"webpack": "^5.78.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ember-source": "^4.0.0"
|
||||
"ember-source": ">4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.* || 18.*"
|
||||
|
||||
@@ -12,6 +12,6 @@ export default class ApplicationRoute extends Route {
|
||||
@service store;
|
||||
|
||||
model() {
|
||||
this.store.findAll('scope');
|
||||
this.store.query('scope', {});
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1057 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user