Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e63f6bd0f | ||
|
|
01e50cb8ce | ||
|
|
1a97b8b428 | ||
|
|
3f2be6fab0 | ||
|
|
89089dbb9a | ||
|
|
25d78b2fc5 | ||
|
|
4565676d4b | ||
|
|
ed9371fab7 | ||
|
|
68a01cfa3a | ||
|
|
b7129e38a8 | ||
|
|
26e27fd24b | ||
|
|
d0612c0a46 | ||
|
|
347107c109 | ||
|
|
0dac4ccf47 | ||
|
|
b8ea558f14 | ||
|
|
4fc061ec48 | ||
|
|
3313cd5316 | ||
|
|
af36d75252 | ||
|
|
72a69fbe8d | ||
|
|
e77ed3ae53 | ||
|
|
677a3c925b | ||
|
|
4cbe08276a | ||
|
|
20699d62dc | ||
|
|
048339982e | ||
|
|
2709c2e081 | ||
|
|
5e3addcc41 | ||
|
|
576fef9f8a | ||
|
|
b9517e0233 | ||
|
|
4d7ce40469 | ||
|
|
fa51973e6d | ||
|
|
f80d7ebeb3 | ||
|
|
9aea1a8fd8 | ||
|
|
35377e8466 | ||
|
|
0e580e3c9f | ||
|
|
e38d3afa58 | ||
|
|
0e223ded8f | ||
|
|
4e2b779d96 | ||
|
|
71ec7b465a | ||
|
|
8b870556a6 | ||
|
|
ecefa39b98 | ||
|
|
4a206c82dc | ||
|
|
2d0ed53202 | ||
|
|
5000d98e23 | ||
|
|
4afd7c2d0b | ||
|
|
207b1e8a7c | ||
|
|
8b90db1ff5 | ||
|
|
7fc10855d9 | ||
|
|
7c3be7c9a0 | ||
|
|
7cddf7cb91 | ||
|
|
878bd241f3 | ||
|
|
4aec0d3acc | ||
|
|
e43625f76c | ||
|
|
5b51edd17f | ||
|
|
6eb21d3e13 | ||
|
|
9b43819865 | ||
|
|
562b371db3 | ||
|
|
89d492042e | ||
|
|
f9197d5a99 | ||
|
|
ebd7f64fe3 | ||
|
|
7dadc53fe1 | ||
|
|
3d257afd49 | ||
|
|
95918cb997 | ||
|
|
077200d792 | ||
|
|
f34a1df298 | ||
|
|
7fa59bfd5d | ||
|
|
e0b80b1176 | ||
|
|
0479c3c8fc | ||
|
|
08b8b03fed | ||
|
|
1a8c895ea9 | ||
|
|
990146fa34 | ||
|
|
e5657a8d4c | ||
|
|
1d126dc074 | ||
|
|
dc224268f5 | ||
|
|
4c15d72321 | ||
|
|
add2a9d6c4 | ||
|
|
759c1f5555 | ||
|
|
37de7e3480 | ||
|
|
d469f305de | ||
|
|
fd1eba617b | ||
|
|
5048fd478a | ||
|
|
de907284ff | ||
|
|
6e9fe1c675 | ||
|
|
6b29a875f3 | ||
|
|
1fd638618b | ||
|
|
6704a251e0 | ||
|
|
46ed965360 | ||
|
|
283e128139 | ||
|
|
047db02a65 | ||
|
|
bd837a6383 | ||
|
|
2a6fac66fc | ||
|
|
4bdcd4ce69 | ||
|
|
9f76650dfb | ||
|
|
c33d1718a2 | ||
|
|
923dedc9bb | ||
|
|
2d9b9d9659 | ||
|
|
6d7e24fdc5 | ||
|
|
4f9086642b | ||
|
|
337963bff8 | ||
|
|
b87ed9f3db | ||
|
|
95b78299f9 | ||
|
|
cf0df900e0 | ||
|
|
214304dc8c | ||
|
|
ed8075d200 | ||
|
|
07089e97e2 | ||
|
|
14b3c8c6d0 | ||
|
|
6580350dcf | ||
|
|
51be681758 | ||
|
|
dab50e352d | ||
|
|
f3af41dea4 | ||
|
|
fb3f88a77d | ||
|
|
7345c79b20 | ||
|
|
9ed79be3ef | ||
|
|
7614f17efb |
No files matched your search
@@ -0,0 +1,91 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags-ignore: ["**"]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "41 5 * * *" # nightly: dependency audit + full test run
|
||||
|
||||
# One job on purpose: the shared runner pool is small, so everything runs
|
||||
# sequentially on a single runner instead of a matrix.
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: install pnpm
|
||||
run: |
|
||||
npm install -g pnpm@10.33.3
|
||||
pnpm --version
|
||||
|
||||
- name: install dependencies
|
||||
# Lifecycle scripts are skipped: the desktop postinstall compiles
|
||||
# electron-app native modules, which the admin build and ember tests
|
||||
# do not need.
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: install headless chrome
|
||||
# Debian/Ubuntu chromium from the distro archive (fast, mirrored),
|
||||
# exposed under the name testem's "Chrome" launcher looks for.
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v google-chrome >/dev/null 2>&1; then
|
||||
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y -qq --no-install-recommends chromium \
|
||||
|| $SUDO apt-get install -y -qq --no-install-recommends chromium-browser
|
||||
CHROME_BIN=$(command -v chromium || command -v chromium-browser)
|
||||
mkdir -p "$HOME/bin"
|
||||
ln -sf "$CHROME_BIN" "$HOME/bin/google-chrome"
|
||||
echo "$HOME/bin" >> "$GITHUB_PATH"
|
||||
fi
|
||||
PATH="$HOME/bin:$PATH" google-chrome --version
|
||||
|
||||
- name: build admin ui
|
||||
run: pnpm --filter admin build
|
||||
|
||||
- name: test addons
|
||||
run: |
|
||||
for a in core auth rose; do
|
||||
echo "::group::addons/$a"
|
||||
(cd addons/$a && npx ember test --test-port 0)
|
||||
echo "::endgroup::"
|
||||
done
|
||||
echo "::group::addons/api"
|
||||
(cd addons/api && npx ember exam --test-port 0 --split=4 --parallel=1)
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: test desktop ui
|
||||
run: cd ui/desktop && npx ember test --test-port 0
|
||||
|
||||
- name: test admin ui
|
||||
# The test build is made once and the partitions then run against the
|
||||
# static output, one browser at a time. Rebuilding in-process (and four
|
||||
# concurrent browsers) stalled on the small shared runners.
|
||||
run: |
|
||||
cd ui/admin
|
||||
npx ember build --environment=test --output-path="$RUNNER_TEMP/admin-test-dist"
|
||||
for p in 1 2 3 4; do
|
||||
echo "::group::admin partition $p/4"
|
||||
timeout 25m npx ember exam --test-port 0 --path "$RUNNER_TEMP/admin-test-dist" --split=4 --partition=$p
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: dependency audit
|
||||
# Fails on critical advisories only; remaining high/moderate advisories
|
||||
# are build/test tooling and are tracked in pnpm-overrides.md.
|
||||
run: |
|
||||
pnpm audit --audit-level=critical
|
||||
(cd ui/desktop/electron-app && pnpm audit --audit-level=critical)
|
||||
@@ -1,2 +0,0 @@
|
||||
# Anyone attached to this rule will always been assigned to a pull request
|
||||
* @hashicorp/boundary-ui
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Let us know about a bug!
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please reserve GitHub issues for bug reports and feature requests.
|
||||
|
||||
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
||||
|
||||
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose this by contacting us at security@hashicorp.com. Our PGP key is available on [our security page](https://www.hashicorp.com/security/).
|
||||
|
||||
Logging is available on Boundary Desktop Client version >= 2.1.0
|
||||
|
||||
Logs are found at:
|
||||
|
||||
Linux: ~/.config/Boundary/logs/desktop-client.log
|
||||
MacOS: ~/Library/Logs/Boundary/desktop-client.log
|
||||
Windows: $env:USERPROFILE\AppData\Roaming\Boundary\logs\desktop-client.log
|
||||
|
||||
If requests are taking longer than expected, please change the logging level to DEBUG. This will log the cache daemon request times as well.
|
||||
|
||||
-->
|
||||
|
||||
## :bug: Bug Report
|
||||
|
||||
### **Description**
|
||||
|
||||
_A clear and concise description of the bug._
|
||||
|
||||
### **Steps to Reproduce**
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See the error
|
||||
|
||||
## **Expected behavior**
|
||||
|
||||
_A clear description of what you expected to happen._
|
||||
|
||||
## **Actual behavior**
|
||||
|
||||
_A clear description of what actually happened._
|
||||
|
||||
## **Screenshots (if applicable)**
|
||||
|
||||
_Add screenshots or screen recordings to help explain the issue._
|
||||
|
||||
## **Environment**
|
||||
|
||||
- **OS and Version:** (e.g., Windows 10 (22H2), macOS (14.7.2))
|
||||
- **Architecture:** (e.g., arm64, x86_64)
|
||||
- **Browser/Version:** (if applicable)
|
||||
- **Desktop Client Version:** (if applicable)
|
||||
- **Controller/worker versions:** (if applicable)
|
||||
|
||||
## **Logs & Console Output (if applicable)**
|
||||
|
||||
_Paste any relevant error logs or browser console output here._
|
||||
|
||||
## **Additional Context**
|
||||
|
||||
_Any other details that might be relevant._
|
||||
@@ -1,7 +0,0 @@
|
||||
# Copyright IBM Corp. 2021, 2026
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://discuss.hashicorp.com/c/boundary
|
||||
about: For increased visibility, please post questions on the discussion forum.
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest something!
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Explain any additional use-cases**
|
||||
If there are any use-cases that would help us understand the use/need/value please share them as they can help us decide on acceptance and prioritization.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -1,30 +0,0 @@
|
||||
# Copyright IBM Corp. 2021, 2026
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
addons:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/**/*"
|
||||
|
||||
api-addon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/api/**/*"
|
||||
|
||||
auth-addon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/auth/**/*"
|
||||
|
||||
styles-addon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "addons/rose/**/*"
|
||||
|
||||
ui:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/**/*"
|
||||
|
||||
admin:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/admin/**/*"
|
||||
|
||||
desktop:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/desktop/**/*"
|
||||
@@ -1,33 +0,0 @@
|
||||
# Description
|
||||
<!-- Add a brief description of changes here -->
|
||||
|
||||
<!-- Uncomment line below to manually add link to JIRA ticket -->
|
||||
<!-- :tickets: [Jira ticket](https://hashicorp.atlassian.net/browse/JIRA_TICKET_NUMBER) -->
|
||||
|
||||
## Screenshots (if appropriate)
|
||||
|
||||
## How to Test
|
||||
<!-- Add steps to test this change. Include any other necessary relevant links -->
|
||||
|
||||
## Checklist
|
||||
<!-- strikethrough the checklist item that is not relevant to your change -->
|
||||
|
||||
<!-- If you are merging a long lived branch, major feature, or UI altering changes,
|
||||
please add the a11y-tests label. Other cases, like a dependency change or
|
||||
translation update likely does not need an a11y audit -->
|
||||
|
||||
- [ ] I have added before and after screenshots for UI changes
|
||||
- [ ] I have added JSON response output for API changes
|
||||
- [ ] I have added steps to reproduce and test for bug fixes in the description
|
||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have added `a11y-tests` label to run a11y audit tests if needed
|
||||
|
||||
## PCI review checklist
|
||||
|
||||
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
|
||||
- [ ] I have documented a clear reason for, and description of, the change I am making.
|
||||
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
|
||||
- [ ] If applicable, I've documented the impact of any changes to security controls.
|
||||
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright IBM Corp. 2021, 2026
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configure Git
|
||||
git config --global user.email "hc-github-team-secure-boundary@hashicorp.com"
|
||||
git config --global user.name "github-team-secure-boundary"
|
||||
# Fetch branches from ENT
|
||||
git fetch origin
|
||||
BRANCH=${1}
|
||||
|
||||
echo "BRANCH to merge is $BRANCH"
|
||||
git checkout "${BRANCH}"
|
||||
|
||||
# Checkout OSS repo
|
||||
git remote add oss-remote https://github.com/hashicorp/boundary-ui.git
|
||||
git fetch oss-remote
|
||||
|
||||
# Merge oss/${BRANCH} into ent/${BRANCH}
|
||||
git_merge_branch="${2}"
|
||||
git checkout -b "${git_merge_branch}"
|
||||
latest_oss_commit="$(git rev-parse --short oss-remote/${BRANCH})"
|
||||
echo "Trying to merge the latest oss commit - ${latest_oss_commit} from oss/${BRANCH} into ent/${BRANCH}"
|
||||
|
||||
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
|
||||
# Here we can try to auto resolve conflicts
|
||||
# This logic will vary from project to project. The below is just an example.
|
||||
conflicts=$(git diff --name-only --diff-filter=U)
|
||||
printf "Found conflicts:\n${conflicts}\n"
|
||||
# Resolve package.json conflicts by taking oss
|
||||
if echo "$conflicts" | grep -q package.json; then
|
||||
echo "Checking out oss package.json..."
|
||||
git checkout --theirs package.json
|
||||
git add package.json
|
||||
fi
|
||||
# Resolve pnpm-lock.yaml conflicts by re-running pnpm install:
|
||||
# https://pnpm.io/git#merge-conflicts
|
||||
if echo "$conflicts" | grep -q pnpm-lock.yaml; then
|
||||
echo "Re-running pnpm install to fix conflicts by regenerating pnpm-lock.yaml..."
|
||||
pnpm install
|
||||
git add pnpm-lock.yaml
|
||||
fi
|
||||
|
||||
# If we can commit all the changes without conflict, we have resolved everything automatically
|
||||
if ! git commit --no-edit; then
|
||||
echo "Auto Merge failed, creating a draft PR"
|
||||
git merge --abort
|
||||
git clean -xfd
|
||||
git fetch oss-remote
|
||||
git checkout "${OSS_BRANCH}"
|
||||
git checkout -b "${OSS_LOCAL_BRANCH}"
|
||||
git push -f --set-upstream origin "${OSS_LOCAL_BRANCH}"
|
||||
gh pr create \
|
||||
--draft \
|
||||
--fill-first \
|
||||
--label "oss-merge" \
|
||||
-B "${BASE_BRANCH}" \
|
||||
-H "${OSS_LOCAL_BRANCH}" \
|
||||
-a "${ACTOR}" \
|
||||
-t "[${BASE_BRANCH/oss\/release\//}] OSS to ENT merge of (${SHA::7})" \
|
||||
-b "${BASE_BRANCH/oss\//} upstream merge of (${SHA::7})
|
||||
Merge failed, Please fix the conflicts using github UI or using the following commands:
|
||||
\`\`\`bash
|
||||
git checkout ${BASE_BRANCH}
|
||||
git pull
|
||||
git checkout ${OSS_LOCAL_BRANCH}
|
||||
git merge ${BASE_BRANCH}
|
||||
# fix conflicts and commit
|
||||
git push origin ${OSS_LOCAL_BRANCH}
|
||||
\`\`\`"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check that there are actually changes to merge by comparing the
|
||||
# merge branch to the current checked out branch
|
||||
if (git diff ${BRANCH} --exit-code > /dev/null); then
|
||||
echo "there are no changes to merge"
|
||||
exit 0
|
||||
fi
|
||||
git push origin "${git_merge_branch}"
|
||||
gh pr create \
|
||||
--fill-first \
|
||||
--label "oss-merge" \
|
||||
-B "${BASE_BRANCH}" \
|
||||
-H "${MERGE_BRANCH}" \
|
||||
-a ${ACTOR} \
|
||||
-t "[${BASE_BRANCH/oss\/release\//}] OSS to ENT merge of (${SHA::7})" \
|
||||
-b "${BASE_BRANCH/oss\//} upstream merge of (${SHA::7})"
|
||||
gh pr merge --auto --merge
|
||||
|
||||
# This PR will be need to be approved manually
|
||||
@@ -1,56 +0,0 @@
|
||||
name: A11y Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
a11y-test:
|
||||
name: Run A11y Tests
|
||||
if: contains(github.event.pull_request.labels.*.name, 'a11y-tests')
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: browser-actions/setup-chrome@4f8e94349a351df0f048634f25fec36c3c91eded # v2.1.1
|
||||
with:
|
||||
install-dependencies: true
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install
|
||||
- name: Run a11y tests
|
||||
env:
|
||||
# this will generate the test reports but causes tests to pass even when there a11y failures
|
||||
ENABLE_A11Y_MIDDLEWARE_REPORTER: true
|
||||
run: pnpm test-a11y
|
||||
- name: Consolidate ember a11y reports
|
||||
run: |
|
||||
mkdir ember-a11y-reports
|
||||
jq -s 'map(.[])' ./ui/admin/ember-a11y-report/*.json > ember-a11y-reports/admin.json
|
||||
jq -s 'map(.[])' ./ui/desktop/ember-a11y-report/*.json > ember-a11y-reports/desktop.json
|
||||
- name: Output ember a11y test report summary (admin)
|
||||
run: 'cat ember-a11y-reports/admin.json | jq "map({moduleName,testName,routes,urls,violations: .violations | map({id, nodes: .nodes | map({failureSummary, targets: .[].[]?.relatedNodes?.[]?.target? })}) })"'
|
||||
- name: Output ember a11y test report summary (desktop)
|
||||
run: 'cat ember-a11y-reports/desktop.json | jq "map({moduleName,testName,routes,urls,violations: .violations | map({id, nodes: .nodes | map({failureSummary, targets: .[].[]?.relatedNodes?.[]?.target? })}) })"'
|
||||
- name: Upload ember a11y reports
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ember-a11y-reports
|
||||
path: ./ember-a11y-reports
|
||||
- name: Report a11y test results status
|
||||
if: always()
|
||||
# fail CI when the combined array length of admin and desktop a11y reports is not 0
|
||||
run: |
|
||||
EMBER_A11Y_FAILURE_COUNT=$(jq -s 'map(.[]) | length' ember-a11y-reports/admin.json ember-a11y-reports/desktop.json)
|
||||
echo "$EMBER_A11Y_FAILURE_COUNT ember-a11y test failures"
|
||||
if [ "$EMBER_A11Y_FAILURE_COUNT" != '0' ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,94 +0,0 @@
|
||||
name: Build Admin UI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "release/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dependencies:
|
||||
name: Install dependencies
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
build-admin-ui-oss:
|
||||
name: Build Admin UI OSS
|
||||
needs: [dependencies]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- name: Build
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm run build:ui:admin:oss
|
||||
- name: Upload artifact admin-ui
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: admin-ui
|
||||
path: ./ui/admin/dist/
|
||||
- name: Upload artifact OSS admin UI
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: admin-ui-oss
|
||||
path: ./ui/admin/dist/
|
||||
build-admin-ui-enterprise:
|
||||
name: Build Admin UI Enterprise
|
||||
needs: [dependencies]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- name: Build
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm run build:ui:admin:enterprise
|
||||
- name: Upload artifact enterprise admin UI
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: admin-ui-ent
|
||||
path: ./ui/admin/dist/
|
||||
build-admin-ui-hcp:
|
||||
name: Build Admin UI HCP
|
||||
needs: [dependencies]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- name: Build
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm run build:ui:admin:hcp
|
||||
- name: Upload artifact HCP admin UI
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: admin-ui-hcp
|
||||
path: ./ui/admin/dist/
|
||||
@@ -1,62 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 19 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Add Copyright Headers
|
||||
on: pull_request
|
||||
jobs:
|
||||
copywrite:
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# Ref and repository aren't normally needed, this is a workaround for a bug in auto commit
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
|
||||
- name: Add missing Copyright Headers
|
||||
run: copywrite headers
|
||||
- uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
with:
|
||||
commit_message: Add missing copyright headers
|
||||
@@ -1,11 +0,0 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Validate Monorepo
|
||||
on: pull_request
|
||||
jobs:
|
||||
|
||||
dependencies:
|
||||
name: Install dependencies
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
compliance:
|
||||
name: License Compliance
|
||||
needs: [dependencies]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm run compliance:licenses
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
needs: [dependencies, compliance]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm lint
|
||||
|
||||
test:
|
||||
name: Test
|
||||
needs: [lint]
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: browser-actions/setup-chrome@4f8e94349a351df0f048634f25fec36c3c91eded # v2.1.1
|
||||
with:
|
||||
install-dependencies: true
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
- run: pnpm install --offline
|
||||
- run: pnpm test
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Trigger OSS to Enterprise Merge
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
trigger-oss-merge:
|
||||
# Run this only on merge events in OSS repo
|
||||
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- name: Trigger Merge
|
||||
env:
|
||||
GIT_REF: ${{ github.ref_name }}
|
||||
GIT_SHA: ${{ github.sha }}
|
||||
GH_PAT: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
GIT_ACTOR: ${{ github.actor }}
|
||||
run: |
|
||||
curl -H "Authorization: token $GH_PAT" \
|
||||
-H 'Accept: application/json' \
|
||||
-d "{\"event_type\": \"oss-merge\", \"client_payload\": {\"git-ref\": \"${GIT_REF}\", \"git-sha\": \"${GIT_SHA}\", \"git-actor\": \"${GIT_ACTOR}\" }}" \
|
||||
"https://api.github.com/repos/hashicorp/boundary-ui-enterprise/dispatches"
|
||||
@@ -1,24 +0,0 @@
|
||||
name: OSS → ENT Merge
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ oss-merge ]
|
||||
jobs:
|
||||
oss-merge:
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
- run: |
|
||||
git fetch --prune --unshallow
|
||||
./.github/scripts/merge.sh "${BASE_BRANCH}" "${MERGE_BRANCH}"
|
||||
env:
|
||||
BASE_BRANCH: ${{ github.event.client_payload.git-ref }}
|
||||
OSS_BRANCH: oss-remote/${{ github.event.client_payload.git-ref }}
|
||||
OSS_LOCAL_BRANCH: oss/${{ github.event.client_payload.git-ref }}-${{ github.event.client_payload.git-sha }}
|
||||
MERGE_BRANCH: oss/merge-${{ github.event.client_payload.git-ref }}-${{github.event.client_payload.git-sha}}
|
||||
GITHUB_TOKEN: ${{secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
MERGE_APPROVE_TOKEN: ${{secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
ACTOR: ${{ github.event.client_payload.git-actor }}
|
||||
SHA: ${{github.event.client_payload.git-sha}}
|
||||
@@ -1,252 +0,0 @@
|
||||
name: Test E2E Admin UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
paths:
|
||||
- 'ui/admin/**'
|
||||
- 'e2e-tests/*'
|
||||
- 'e2e-tests/api-client/**'
|
||||
- 'e2e-tests/helpers/**'
|
||||
- 'e2e-tests/admin/**'
|
||||
- '.github/workflows/test-e2e-admin-ui.yaml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
boundary-enterprise-branch:
|
||||
description: 'Branch of boundary-enterprise to test against (default to main)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
boundary-branch:
|
||||
description: 'Branch of boundary to test against (default to main)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
# don't need to run e2e tests on ce -> ent merges since tests were already
|
||||
# run in the ce branch, and there are currently no code diffferences between
|
||||
# the two repos
|
||||
if: github.actor != 'hc-github-team-secure-boundary'
|
||||
runs-on: ${{ fromJSON(vars.RUNNER_UBUNTU_22) }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- boundary_edition: community
|
||||
github_repo: hashicorp/boundary
|
||||
folder_name: boundary
|
||||
boundary_edition_short: oss
|
||||
enos_scenario: e2e_ui_docker
|
||||
test_command: admin:ce:docker
|
||||
boundary_branch: ${{ github.event.inputs.boundary-branch || 'main' }}
|
||||
- boundary_edition: enterprise
|
||||
github_repo: hashicorp/boundary-enterprise
|
||||
folder_name: boundary-enterprise
|
||||
boundary_edition_short: ent
|
||||
enos_scenario: e2e_ui_docker_ent
|
||||
test_command: admin:ent:docker
|
||||
boundary_branch: ${{ github.event.inputs.boundary-enterprise-branch || 'main' }}
|
||||
fail-fast: false
|
||||
env:
|
||||
ENOS_VAR_e2e_debug_no_run: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: |
|
||||
pnpm-lock.yaml
|
||||
ui/desktop/electron-app/pnpm-lock.yaml
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
|
||||
|
||||
- name: Set up Enos
|
||||
uses: hashicorp/action-setup-enos@17b90fcf9591275b468a94aefb9dc6a93017de8a
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Vault CLI
|
||||
run: |
|
||||
wget https://releases.hashicorp.com/vault/1.17.6/vault_1.17.6_linux_amd64.zip -O /tmp/vault.zip
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
unzip -o -q /tmp/vault.zip -d /usr/local/bin
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
unzip -o -q /tmp/vault.zip -d "$HOME/bin"
|
||||
fi
|
||||
|
||||
- name: GPG setup
|
||||
run: |
|
||||
# Create a GPG key
|
||||
export KEY_PW=boundary
|
||||
gpg --generate-key --batch <<eoGpgConf
|
||||
%echo Started!
|
||||
Key-Type: RSA
|
||||
Key-Length: default
|
||||
Subkey-Type: RSA
|
||||
Name-Real: boundary
|
||||
Name-Comment: default
|
||||
Name-Email: default
|
||||
Expire-Date: 0
|
||||
Passphrase: $KEY_PW
|
||||
%commit
|
||||
%echo Done.
|
||||
eoGpgConf
|
||||
|
||||
# Enable gpg-preset-passphrase so that key passwords can be saved
|
||||
echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf
|
||||
gpg-connect-agent reloadagent /bye &>/dev/null
|
||||
|
||||
# Get information about the created keys
|
||||
export lines=$(gpg --list-secret-keys --with-colons --with-keygrip)
|
||||
export KEY_ID=""
|
||||
while read -r line
|
||||
do
|
||||
# Save the first key id to be used later
|
||||
if [[ $line =~ "fpr"* ]]; then
|
||||
if [[ $KEY_ID == "" ]]; then
|
||||
KEY_ID="$(echo "$line" | sed -r 's/fpr|://g')"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cache the passphrases for the keys so passwords do not need to be entered
|
||||
if [[ $line =~ "grp"* ]]; then
|
||||
export KEYGRIP_ID="$(echo "$line" | sed -r 's/grp|://g')"
|
||||
/usr/lib/gnupg/gpg-preset-passphrase --preset -P $KEY_PW $KEYGRIP_ID
|
||||
fi
|
||||
done <<< $lines
|
||||
|
||||
# Trust the key
|
||||
touch /tmp/test.txt
|
||||
gpg -a --encrypt -r $KEY_ID --trust-model always --batch --yes /tmp/test.txt
|
||||
echo "trusted-key $KEY_ID" >> ~/.gnupg/gpg.conf
|
||||
|
||||
# Initialize the password store
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pass
|
||||
pass init $KEY_ID &>/dev/null
|
||||
|
||||
- name: Checkout ${{ matrix.folder_name }} repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ matrix.github_repo }}
|
||||
path: support/src/${{ matrix.folder_name }}
|
||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
ref: ${{ matrix.boundary_branch }}
|
||||
|
||||
- name: GH fix for localhost resolution
|
||||
run: |
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
cat /etc/hosts && echo "-----------"
|
||||
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
|
||||
cat /etc/hosts
|
||||
ssh -V
|
||||
fi
|
||||
|
||||
- name: Set git config
|
||||
run: |
|
||||
git config --global url."https://oauth2:${{ secrets.ELEVATED_GITHUB_TOKEN }}@github.com".insteadOf "https://github.com"
|
||||
|
||||
- name: Install node dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
cd e2e-tests
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
pnpm playwright install --with-deps
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
# playwright dependencies can't be installed on a self-hosted runner
|
||||
# due to permission issues
|
||||
pnpm playwright install
|
||||
fi
|
||||
|
||||
- name: Determine Go version
|
||||
id: get-go-version
|
||||
run: |
|
||||
echo "Building with Go $(cat ./support/src/${{matrix.folder_name}}/.go-version)"
|
||||
echo "go-version=$(cat ./support/src/${{matrix.folder_name}}/.go-version)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ${{ steps.get-go-version.outputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- name: Set up test infra
|
||||
id: infra
|
||||
run: |
|
||||
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
|
||||
mkdir -p ./enos/terraform-plugin-cache
|
||||
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
echo ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} > ./support/src/${{matrix.folder_name}}/internal/ui/VERSION
|
||||
export ENOS_VAR_ui_build_override="oss"
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
echo ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} > ./support/src/${{matrix.folder_name}}/internal/ui/VERSION_ent
|
||||
export ENOS_VAR_ui_build_override="enterprise"
|
||||
fi
|
||||
|
||||
export ENOS_VAR_boundary_edition=${{ matrix.boundary_edition_short }} && \
|
||||
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||
export ENOS_VAR_boundary_license="${{ secrets.BOUNDARY_ENT_LICENSE }}" && \
|
||||
enos scenario launch --timeout 60m0s --chdir ./support/src/${{matrix.folder_name}}/enos ${{matrix.enos_scenario}} builder:local
|
||||
|
||||
- name: Move boundary binary
|
||||
run: |
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
mv ./support/src/${{matrix.folder_name}}/bin/boundary /usr/local/bin/
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
mv ./support/src/${{matrix.folder_name}}/bin/boundary $HOME/bin
|
||||
fi
|
||||
|
||||
boundary version
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
source <(bash ./support/src/${{matrix.folder_name}}/enos/scripts/test_e2e_env.sh)
|
||||
cd e2e-tests
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
pnpm run ${{ matrix.test_command }} --reporter=html
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
# only run tests on chromium because playwright dependencies can't
|
||||
# be installed to support other browsers
|
||||
pnpm run ${{ matrix.test_command }} --project=chromium --reporter=html
|
||||
fi
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-${{ matrix.boundary_edition }}
|
||||
path: e2e-tests/playwright-report
|
||||
|
||||
- name: Clean up test infra
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export ENOS_VAR_boundary_edition=${{ matrix.boundary_edition_short }} && \
|
||||
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||
enos scenario destroy --timeout 60m0s --chdir ./support/src/${{matrix.folder_name}}/enos ${{matrix.enos_scenario}} builder:local
|
||||
|
||||
- name: Send Slack message on failure
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
if: ${{ failure() && github.repository == 'hashicorp/boundary-ui' }}
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
text: ":x: admin ui e2e tests failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Branch:* ${{ github.repository }}:${{ github.head_ref }}"
|
||||
@@ -1,190 +0,0 @@
|
||||
name: Test E2E Desktop Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
paths:
|
||||
- 'ui/desktop/**'
|
||||
- 'e2e-tests/*'
|
||||
- 'e2e-tests/api-client/**'
|
||||
- 'e2e-tests/helpers/**'
|
||||
- 'e2e-tests/desktop/**'
|
||||
- '.github/workflows/test-e2e-desktop.yaml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
boundary-enterprise-branch:
|
||||
description: 'Branch of boundary-enterprise to test against (default to main)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
boundary-ui-enterprise-branch:
|
||||
description: 'Branch of boundary-ui-enterprise to test against (default to main)'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
# don't need to run e2e tests on ce -> ent merges since tests were already
|
||||
# run in the ce branch, and there are currently no code diffferences between
|
||||
# the two repos
|
||||
if: github.actor != 'hc-github-team-secure-boundary'
|
||||
runs-on: ${{ fromJSON(vars.RUNNER_UBUNTU_22) }}
|
||||
env:
|
||||
ENOS_VAR_boundary_edition: "ent"
|
||||
ENOS_VAR_e2e_debug_no_run: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: |
|
||||
pnpm-lock.yaml
|
||||
ui/desktop/electron-app/pnpm-lock.yaml
|
||||
|
||||
- name: Set up Terraform
|
||||
uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
|
||||
|
||||
- name: Set up Enos
|
||||
uses: hashicorp/action-setup-enos@17b90fcf9591275b468a94aefb9dc6a93017de8a
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Vault CLI
|
||||
run: |
|
||||
wget https://releases.hashicorp.com/vault/1.17.6/vault_1.17.6_linux_amd64.zip -O /tmp/vault.zip
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
unzip -o -q /tmp/vault.zip -d /usr/local/bin
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
unzip -o -q /tmp/vault.zip -d "$HOME/bin"
|
||||
fi
|
||||
|
||||
# TODO: We should consider modifying to also build CE binary. For now
|
||||
# building ENT binary using most recent UI commit is sufficient.
|
||||
- name: Checkout boundary-enterprise
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: hashicorp/boundary-enterprise
|
||||
ref: ${{ github.event.inputs.boundary-enterprise-branch || 'main' }}
|
||||
path: support/src/boundary-enterprise
|
||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
|
||||
- name: Checkout boundary-ui-enterprise
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: hashicorp/boundary-ui-enterprise
|
||||
ref: ${{ github.event.inputs.boundary-ui-enterprise-branch || 'main' }}
|
||||
path: support/src/boundary-ui-enterprise
|
||||
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||
|
||||
- name: Use boundary-ui-enterprise commit SHA for building binary
|
||||
run: |
|
||||
cd support/src/boundary-ui-enterprise
|
||||
git rev-parse HEAD > ../boundary-enterprise/internal/ui/VERSION_ent
|
||||
|
||||
- name: GH fix for localhost resolution
|
||||
run: |
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
cat /etc/hosts && echo "-----------"
|
||||
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
|
||||
cat /etc/hosts
|
||||
ssh -V
|
||||
fi
|
||||
|
||||
- name: Set git config
|
||||
run: |
|
||||
git config --global url."https://oauth2:${{ secrets.ELEVATED_GITHUB_TOKEN }}@github.com".insteadOf "https://github.com"
|
||||
|
||||
- name: Determine Go version
|
||||
id: get-go-version
|
||||
run: |
|
||||
echo "Building with Go $(cat ./support/src/boundary-enterprise/.go-version)"
|
||||
echo "go-version=$(cat ./support/src/boundary-enterprise/.go-version)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ${{ steps.get-go-version.outputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- name: Set up test infra
|
||||
id: infra
|
||||
run: |
|
||||
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
|
||||
mkdir -p ./enos/terraform-plugin-cache
|
||||
export ENOS_VAR_ui_build_override="ent" && \
|
||||
export ENOS_VAR_boundary_edition="ent" && \
|
||||
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||
export ENOS_VAR_boundary_license="${{ secrets.BOUNDARY_ENT_LICENSE }}" && \
|
||||
enos scenario launch --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:local
|
||||
|
||||
- name: Move boundary binary
|
||||
run: |
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
mv ./support/src/boundary-enterprise/bin/boundary /usr/local/bin/
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
mv ./support/src/boundary-enterprise/bin/boundary $HOME/bin
|
||||
fi
|
||||
boundary version
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
cd e2e-tests
|
||||
if [[ "${{ github.repository }}" == "hashicorp/boundary-ui" ]]; then
|
||||
pnpm playwright install --with-deps
|
||||
elif [[ "${{ github.repository }}" == "hashicorp/boundary-ui-enterprise" ]]; then
|
||||
# playwright dependencies can't be installed on a self-hosted runner
|
||||
# due to permission issues
|
||||
pnpm playwright install
|
||||
|
||||
sudo apt update -qq
|
||||
sudo apt install xvfb -y
|
||||
fi
|
||||
|
||||
# Runner is headless so we need to enable a display to run the client
|
||||
- name: Run Tests
|
||||
run: |
|
||||
source <(bash ./support/src/boundary-enterprise/enos/scripts/test_e2e_env.sh)
|
||||
cd e2e-tests
|
||||
|
||||
export DISPLAY=:99
|
||||
Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
pnpm run desktop
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e-tests/playwright-report
|
||||
|
||||
- name: Clean up test infra
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
|
||||
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
|
||||
enos scenario destroy --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:local
|
||||
|
||||
- name: Send Slack message on failure
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
if: ${{ failure() && github.repository == 'hashicorp/boundary-ui' }}
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
text: ":x: desktop client e2e tests failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Branch:* ${{ github.repository }}:${{ github.head_ref }}"
|
||||
@@ -1,22 +0,0 @@
|
||||
name: "Trigger Boundary Desktop release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
trigger-gha-release:
|
||||
runs-on: ${{ fromJSON(vars.RUNNER) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Release description
|
||||
run: |
|
||||
echo "Triggering release with a TAG ${GITHUB_REF#refs/tags/}"
|
||||
- name: Trigger Release workflow in boundary-desktop-releases
|
||||
run: |
|
||||
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
|
||||
gh workflow run release.yml \
|
||||
--repo hashicorp/boundary-desktop-releases \
|
||||
--ref main \
|
||||
-f TAG="${GITHUB_REF#refs/tags/}"
|
||||
@@ -1,6 +0,0 @@
|
||||
# Copyright IBM Corp. 2021, 2026
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
url_license = "https://github.com/hashicorp/boundary-ui/blob/main/LICENSE"
|
||||
url_project_website = "https://learn.hashicorp.com/tutorials/boundary/getting-started-desktop-app"
|
||||
url_source_repository = "https://github.com/hashicorp/boundary-ui"
|
||||
@@ -0,0 +1,28 @@
|
||||
Threshold UI
|
||||
Copyright (c) 2026 CyberCove LLC
|
||||
|
||||
This repository is a community fork of "Boundary UI"
|
||||
(https://github.com/hashicorp/boundary-ui), the admin web UI and desktop
|
||||
client for HashiCorp Boundary.
|
||||
|
||||
Upstream work: Copyright (c) HashiCorp, Inc. 2020-2023;
|
||||
Copyright (c) IBM Corp. 2024-2026
|
||||
Upstream license: Business Source License 1.1 (see LICENSE)
|
||||
|
||||
The upstream LICENSE and per-file copyright / SPDX headers are retained
|
||||
unchanged. Modifications made in this fork are provided under the same
|
||||
license terms (BUSL-1.1), including its Additional Use Grant and Change
|
||||
Date as stated in LICENSE.
|
||||
|
||||
This project is an independent community effort. It is NOT affiliated with,
|
||||
endorsed by, or sponsored by HashiCorp, Inc. or IBM Corp. "HashiCorp",
|
||||
"Boundary", and related marks and logos are trademarks of their respective
|
||||
owners; they are used here only to describe the upstream origin of this work
|
||||
and to name the upstream CLI/API this software interoperates with. The
|
||||
upstream logos and brand art have been removed and replaced with plain
|
||||
placeholder marks.
|
||||
|
||||
Third-party components keep their own licenses (see pnpm-lock.yaml and the
|
||||
`pnpm compliance:licenses` script). The HashiCorp Design System packages
|
||||
(@hashicorp/design-system-components, -tokens, @hashicorp/flight-icons) are
|
||||
consumed unmodified from the public npm registry under their own licenses.
|
||||
@@ -1,10 +1,41 @@
|
||||
# Boundary UI Monorepo
|
||||

|
||||
|
||||

|
||||
# Threshold UI Monorepo
|
||||
|
||||
This monorepo contains multiple projects, including UIs and addons, used by
|
||||
Boundary.
|
||||
Threshold (the admin web UI and the desktop client).
|
||||
|
||||
## About this fork
|
||||
|
||||
This repository is a community fork of
|
||||
[hashicorp/boundary-ui](https://github.com/hashicorp/boundary-ui), rebranded
|
||||
for the Threshold project (a community fork of Boundary CE). It is licensed
|
||||
under the upstream Business Source License 1.1 (see [LICENSE](LICENSE) and
|
||||
[NOTICE](NOTICE)) and is **not affiliated with, endorsed by, or sponsored by
|
||||
HashiCorp or IBM**.
|
||||
|
||||
What changed relative to upstream:
|
||||
|
||||
- User-visible product name is Threshold (window titles, menus, about dialog,
|
||||
installer/package names, app id `net.anhonesthost.cybercove.threshold`).
|
||||
API paths, field names, protocol strings and environment variable names are
|
||||
unchanged for compatibility with the server. The desktop client's persisted
|
||||
settings are copied from the former "Boundary" user-data directory on first
|
||||
launch.
|
||||
- The desktop client looks for a `threshold` CLI first, then `boundary`.
|
||||
- HashiCorp/IBM logos and brand art were removed and replaced with plain
|
||||
placeholder marks.
|
||||
- Nothing contacts HashiCorp services by default. The in-app updater, the
|
||||
"unsupported controller" release-metadata lookup, and the CLI bundling
|
||||
download are disabled until you configure your own hosts:
|
||||
`THRESHOLD_UPDATE_FEED_URL` (desktop build, updater feed),
|
||||
`THRESHOLD_RELEASES_METADATA_URL` / `THRESHOLD_RELEASES_PAGE_URL`
|
||||
(desktop build, release links) and `THRESHOLD_CLI_RELEASES_URL` /
|
||||
`THRESHOLD_CLI_ARCHIVE_PREFIX` (used with `SETUP_CLI=true` to bundle a CLI).
|
||||
- Documentation links still point at the upstream Boundary documentation,
|
||||
which is user-initiated only.
|
||||
- Upstream HashiCorp-internal GitHub workflows and release metadata were
|
||||
removed; CI lives in `.gitea/workflows/ci.yml`.
|
||||
|
||||
Upstream history, CHANGELOGs and copyright headers are preserved.
|
||||
|
||||
This top-level repository provides limited common tasks, such as installation
|
||||
and commit assistance. However, most tasks must be executed from within a
|
||||
|
||||
@@ -131,11 +131,34 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
// which indicates that there are more items in the list
|
||||
// or the batch size has not been reached
|
||||
do {
|
||||
result = await super.query(store, schema, queryObject);
|
||||
//add the result items to a data array
|
||||
try {
|
||||
result = await super.query(store, schema, queryObject);
|
||||
} catch (e) {
|
||||
// If we hit a rate limit mid-pagination, return whatever
|
||||
// data has been accumulated so far rather than throwing and leaving
|
||||
// the user stuck with nothing to show.
|
||||
// Without a list_token this is the very first request with nothing to
|
||||
// show, so rethrow and let normal error handling apply.
|
||||
if (e?.errors?.[0]?.isRateLimited && queryObject.list_token) {
|
||||
result ??= {};
|
||||
result.items = data;
|
||||
result.response_type = 'complete';
|
||||
result.meta = {
|
||||
...result.meta,
|
||||
rateLimitWarning: { retryAfter: e.errors[0].retryAfter },
|
||||
};
|
||||
return prenormalizeArrayResponse(result);
|
||||
}
|
||||
|
||||
// For all other errors (or a 429 on the very first request with
|
||||
// no data yet), rethrow so normal error handling applies.
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 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
|
||||
// Pass in the list token for subsequent calls to fetch the remaining list items
|
||||
queryObject.list_token = result.list_token;
|
||||
}
|
||||
} while (
|
||||
@@ -237,6 +260,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
* - `403 isForbidden`: the session is authenticated but does not have
|
||||
* permission to perform the requested action
|
||||
* - `404 isNotFound`: the requested resource could not be found
|
||||
* - `429 isRateLimited`: the client has exceeded the rate limit
|
||||
* - `500 isServer`: an internal server error occurred
|
||||
* - `isUnknown`: an error occurred, but we don't know which or
|
||||
* we don't distinguish it yet
|
||||
@@ -248,7 +272,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
* @param {Object} payload
|
||||
* @return {Array} errors payload
|
||||
*/
|
||||
normalizeErrorResponse(/*status, headers, payload*/) {
|
||||
normalizeErrorResponse(status, headers /*, payload*/) {
|
||||
const errors = super.normalizeErrorResponse(...arguments);
|
||||
if (isArray(errors)) {
|
||||
errors.forEach((error) => {
|
||||
@@ -262,6 +286,14 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
case 404:
|
||||
error.isNotFound = true;
|
||||
break;
|
||||
case 429:
|
||||
error.isRateLimited = true;
|
||||
// Attach rate-limit metadata from response headers so downstream
|
||||
// error handlers can inform the user when they may retry.
|
||||
error.retryAfter = Number(
|
||||
headers['Retry-After'] ?? headers['retry-after'],
|
||||
);
|
||||
break;
|
||||
case 500:
|
||||
error.isServer = true;
|
||||
break;
|
||||
|
||||
@@ -96,19 +96,19 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
||||
})
|
||||
duration;
|
||||
|
||||
@attr('string', {
|
||||
@attr({
|
||||
description:
|
||||
'The current state of the session recording. One of "started", "available" and "unknown".',
|
||||
'The detailed state of the session recording. This contains storage_state, syncing_error_details, and verification_error_details.',
|
||||
readOnly: true,
|
||||
})
|
||||
state;
|
||||
recording_state;
|
||||
|
||||
@attr('string', {
|
||||
description:
|
||||
'Any error seen during the closing of the session recording. Currently only set if state is "unknown".',
|
||||
'The availability of the session recording. One of "available" or "not available".',
|
||||
readOnly: true,
|
||||
})
|
||||
error_details;
|
||||
availability;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time until a session recording is required to be stored.',
|
||||
@@ -122,4 +122,19 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
||||
readOnly: true,
|
||||
})
|
||||
delete_after;
|
||||
|
||||
// Deprecated: These two fields should be removed once support for RDP is added.
|
||||
@attr('string', {
|
||||
description:
|
||||
'The current state of the session recording. One of "started", "available" and "unknown".',
|
||||
readOnly: true,
|
||||
})
|
||||
state;
|
||||
|
||||
@attr('string', {
|
||||
description:
|
||||
'Any error seen during the closing of the session recording. Currently only set if state is "unknown".',
|
||||
readOnly: true,
|
||||
})
|
||||
error_details;
|
||||
}
|
||||
@@ -59,17 +59,39 @@ export default class SqliteHandler {
|
||||
let payload,
|
||||
listToken,
|
||||
writeToDbPromise,
|
||||
totalInsert = 0;
|
||||
totalInsert = 0,
|
||||
rateLimitWarning;
|
||||
|
||||
if (!peekDb) {
|
||||
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
||||
let tokenKey = `${type}-${hashCode(remainingQuery)}`;
|
||||
// The global token key represents a recursive query from global scope,
|
||||
// which pulls back every resource of this type.
|
||||
const globalTokenKey = `${type}-${hashCode({ scope_id: 'global', recursive: true })}`;
|
||||
if (storeToken) {
|
||||
const [tokenObj] = await this.sqlite.fetchResource({
|
||||
...generateSQLExpressions('token', {
|
||||
filters: { id: [{ equals: tokenKey }] },
|
||||
}),
|
||||
});
|
||||
listToken = tokenObj?.token;
|
||||
// Always prefer the global token when it exists for
|
||||
// any more fine-grained query. The global query already fetched every
|
||||
// item for this resource type, so we should just re-use it.
|
||||
if (tokenKey !== globalTokenKey) {
|
||||
const [globalTokenObj] = await this.sqlite.fetchResource({
|
||||
...generateSQLExpressions('token', {
|
||||
filters: { id: [{ equals: globalTokenKey }] },
|
||||
}),
|
||||
});
|
||||
if (globalTokenObj?.token) {
|
||||
tokenKey = globalTokenKey;
|
||||
listToken = globalTokenObj.token;
|
||||
}
|
||||
}
|
||||
|
||||
// Only look up the specific token if no global token was found.
|
||||
if (!listToken) {
|
||||
const [tokenObj] = await this.sqlite.fetchResource({
|
||||
...generateSQLExpressions('token', {
|
||||
filters: { id: [{ equals: tokenKey }] },
|
||||
}),
|
||||
});
|
||||
listToken = tokenObj?.token;
|
||||
}
|
||||
} else {
|
||||
// This is a temporary fix of clearing the DB (specifically for auth-methods)
|
||||
// since we are not storing the token we do not get back a list of removed_ids
|
||||
@@ -86,6 +108,11 @@ export default class SqliteHandler {
|
||||
});
|
||||
totalInsert += payload.items?.length ?? 0;
|
||||
|
||||
// Propagate any rate-limit warning the adapter attached
|
||||
if (payload.meta?.rateLimitWarning) {
|
||||
rateLimitWarning = payload.meta.rateLimitWarning;
|
||||
}
|
||||
|
||||
// await the previous writeToDbPromise before writing to db again
|
||||
if (writeToDbPromise) {
|
||||
await writeToDbPromise;
|
||||
@@ -163,7 +190,14 @@ export default class SqliteHandler {
|
||||
// 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: count[0].total };
|
||||
records.meta = {
|
||||
totalItems: count[0].total,
|
||||
};
|
||||
|
||||
if (rateLimitWarning) {
|
||||
records.meta.rateLimitWarning = rateLimitWarning;
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -48,6 +48,18 @@ export default class AuthMethodSerializer extends ApplicationSerializer {
|
||||
} else {
|
||||
delete serialized.attributes.state;
|
||||
}
|
||||
|
||||
// Don't send account_claim_maps on update if there's a saved 'sub' mapping
|
||||
// The API doesn't allow updating when 'sub' claim mappings exist
|
||||
if (!snapshot.record.isNew && snapshot.record.account_claim_maps) {
|
||||
const hasSubClaimMap = snapshot.record.account_claim_maps.some(
|
||||
(item) => item?.value === 'sub',
|
||||
);
|
||||
|
||||
if (hasSubClaimMap) {
|
||||
delete serialized.attributes.account_claim_maps;
|
||||
}
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,21 +68,25 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
||||
*/
|
||||
handleCredentialMappingOverrides(serialized, isNew) {
|
||||
const { credential_type, credential_mapping_overrides } = serialized;
|
||||
|
||||
if (Object.keys(credential_mapping_overrides).length === 0) {
|
||||
serialized.credential_mapping_overrides = null;
|
||||
}
|
||||
// API expects to send null to fields if it is undefined or deleted
|
||||
if (credential_mapping_overrides && !isNew) {
|
||||
serialized.credential_mapping_overrides = options.mapping_overrides[
|
||||
credential_type
|
||||
].reduce((obj, key) => {
|
||||
if (credential_mapping_overrides[key]) {
|
||||
obj[key] = credential_mapping_overrides[key];
|
||||
} else {
|
||||
obj[key] = null;
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
if (!credential_mapping_overrides || isNew || !credential_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// API expects to send null to fields if it is undefined or deleted
|
||||
serialized.credential_mapping_overrides = options.mapping_overrides[
|
||||
credential_type
|
||||
].reduce((obj, key) => {
|
||||
if (credential_mapping_overrides[key]) {
|
||||
obj[key] = credential_mapping_overrides[key];
|
||||
} else {
|
||||
obj[key] = null;
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,15 @@ import {
|
||||
// See https://sqlite.org/rescode.html#corrupt
|
||||
const SQLITE_CORRUPT = 11;
|
||||
|
||||
/**
|
||||
* Throws if the database has not been initialized or has been closed.
|
||||
*/
|
||||
const requireDb = () => {
|
||||
if (!db) {
|
||||
throw new Error('Database is not available');
|
||||
}
|
||||
};
|
||||
|
||||
const methods = {
|
||||
initializeSQLite: async () => {
|
||||
sqlite3 = await sqlite3InitModule({
|
||||
@@ -57,6 +66,9 @@ import {
|
||||
});
|
||||
|
||||
function setupDb() {
|
||||
// Allow SQLite to wait up to 5 s when it encounters a busy lock
|
||||
db.exec('PRAGMA busy_timeout = 5000;');
|
||||
|
||||
const [row] = db.exec('PRAGMA user_version;', { rowMode: 'object' });
|
||||
// Check if we're on a newer schema version and clear the database if so
|
||||
if (SCHEMA_VERSION > row.user_version) {
|
||||
@@ -97,30 +109,40 @@ import {
|
||||
if (poolUtil.getCapacity() > 20) {
|
||||
await poolUtil.removeVfs();
|
||||
}
|
||||
} else {
|
||||
} else if (
|
||||
err?.resultCode === SQLITE_CORRUPT ||
|
||||
err instanceof WebAssembly.RuntimeError
|
||||
) {
|
||||
// We might have gotten ourselves into a bad state which corrupted the DB so clear it out and retry.
|
||||
console.error('SQLite initialization error:', err);
|
||||
|
||||
// We might have gotten ourselves into a bad state which corrupted the DB so clear it out and retry
|
||||
if (err?.resultCode === SQLITE_CORRUPT) {
|
||||
try {
|
||||
if (db) {
|
||||
db.close();
|
||||
db = null;
|
||||
}
|
||||
poolUtil.unlink(`/${dbName}`);
|
||||
} else {
|
||||
throw err;
|
||||
} catch (cleanupErr) {
|
||||
// If cleanup fails, wipe the entire VFS and reinstall to start fresh
|
||||
console.error('SQLite cleanup error:', cleanupErr);
|
||||
await poolUtil.removeVfs();
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
analyzeDatabase: () => {
|
||||
requireDb();
|
||||
db.exec('PRAGMA optimize');
|
||||
},
|
||||
clearDatabase: () => {
|
||||
requireDb();
|
||||
db.exec(CLEAR_DB);
|
||||
db.exec(CREATE_TABLES(SCHEMA_VERSION));
|
||||
},
|
||||
deleteDatabase: () => {
|
||||
if (!db) {
|
||||
throw new Error('No database was initialized');
|
||||
}
|
||||
requireDb();
|
||||
|
||||
const name = db.dbFilename();
|
||||
db.close();
|
||||
@@ -128,9 +150,11 @@ import {
|
||||
poolUtil.unlink(`/${name}`);
|
||||
},
|
||||
downloadDatabase: () => {
|
||||
requireDb();
|
||||
return sqlite3.capi.sqlite3_js_db_export(db);
|
||||
},
|
||||
fetchResource: ({ sql, parameters = [] }) => {
|
||||
requireDb();
|
||||
try {
|
||||
return db.exec({
|
||||
sql,
|
||||
@@ -147,6 +171,7 @@ import {
|
||||
if (items.length === 0) {
|
||||
return [];
|
||||
}
|
||||
requireDb();
|
||||
|
||||
const numberOfParameters = items.reduce((acc, item) => {
|
||||
return acc + item.length;
|
||||
@@ -155,11 +180,15 @@ import {
|
||||
// We want to use as many VALUES as part of the insert statement as it's quicker
|
||||
// than doing multiple inserts. If we have too many rows to insert, we'll manually
|
||||
// chunk them up to avoid hitting the maximum number of host parameters
|
||||
// and execute these separate insert statements in a transaction
|
||||
// and execute these separate insert statements in a transaction.
|
||||
if (numberOfParameters > MAX_HOST_PARAMETERS) {
|
||||
const chunkSize = Math.floor(MAX_HOST_PARAMETERS / items[0].length);
|
||||
|
||||
return db.transaction(() => {
|
||||
// Note: We use explicit BEGIN/COMMIT instead of db.transaction() because
|
||||
// db.transaction() uses SAVEPOINT internally, which fails with SQLITE_BUSY
|
||||
// when any statement is active on the connection.
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
const results = [];
|
||||
for (let i = 0; i < items.length; i += chunkSize) {
|
||||
const chunk = items.slice(i, i + chunkSize);
|
||||
@@ -170,20 +199,29 @@ import {
|
||||
});
|
||||
results.push(result);
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
return results;
|
||||
});
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
db.exec({
|
||||
return db.exec({
|
||||
sql: INSERT_STATEMENTS(resource, items, modelMapping),
|
||||
bind: items.flat(),
|
||||
rowMode: 'object',
|
||||
});
|
||||
},
|
||||
deleteResource: ({ resource, ids }) => {
|
||||
// Check if we have too many parameters for the deletion and chunk if so
|
||||
requireDb();
|
||||
// Check if we have too many parameters for the deletion and chunk if so.
|
||||
if (ids?.length > MAX_HOST_PARAMETERS) {
|
||||
return db.transaction(() => {
|
||||
// Note: We use explicit BEGIN/COMMIT instead of db.transaction() because
|
||||
// db.transaction() uses SAVEPOINT internally, which fails with SQLITE_BUSY
|
||||
// when any statement is active on the connection.
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
const results = [];
|
||||
for (let i = 0; i < ids.length; i += MAX_HOST_PARAMETERS) {
|
||||
const chunk = ids.slice(i, i + MAX_HOST_PARAMETERS);
|
||||
@@ -194,8 +232,12 @@ import {
|
||||
});
|
||||
results.push(result);
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
return results;
|
||||
});
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return db.exec({
|
||||
|
||||
@@ -169,6 +169,12 @@ const onBecomeProvider = async ({
|
||||
target,
|
||||
status,
|
||||
}) => {
|
||||
// Create a gate that prevents request handling until the provider is fully initialized.
|
||||
// This prevents a race condition where a client registers and sends requests before
|
||||
// the provider has finished initialization.
|
||||
const { promise: providerReady, resolve: providerReadyResolve } =
|
||||
Promise.withResolvers();
|
||||
|
||||
// Set an event listener for other tabs to register with the provider tab
|
||||
commonChannel.addEventListener('message', async (event) => {
|
||||
const { clientId, type } = event.data;
|
||||
@@ -194,6 +200,9 @@ const onBecomeProvider = async ({
|
||||
}
|
||||
const { method, args, id } = data;
|
||||
|
||||
// Wait for the provider to finish initialization before handling requests
|
||||
await providerReady;
|
||||
|
||||
let result, error;
|
||||
try {
|
||||
result = await target[method](...args);
|
||||
@@ -221,6 +230,7 @@ const onBecomeProvider = async ({
|
||||
});
|
||||
|
||||
await onProviderChange?.(status.isServiceProvider);
|
||||
providerReadyResolve();
|
||||
commonChannel.postMessage({ type: 'providerChange', serviceName });
|
||||
|
||||
// If we became the new provider and there were requests in flight, request it directly again.
|
||||
|
||||
@@ -11,6 +11,7 @@ import initializeMockDesktopContextBridgeAPI from './scenarios/ipc/desktop-conte
|
||||
import makeBooleanFilter from './helpers/bexpr-filter';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { asciicasts } from './data/asciicasts';
|
||||
import { grantsSchemaData } from './data/grants-schema';
|
||||
import { TYPE_WORKER_PKI } from 'api/models/worker';
|
||||
import environmentConfig from 'ember-get-config';
|
||||
|
||||
@@ -210,6 +211,10 @@ function routes() {
|
||||
return metadata;
|
||||
});
|
||||
|
||||
this.get('/internal/grants-schema.json', () => {
|
||||
return grantsSchemaData;
|
||||
});
|
||||
|
||||
// make this `/api`, for example, if your API is namespaced
|
||||
this.namespace = environmentConfig.api?.namespace;
|
||||
// delay for each request, automatically set to 0 during testing
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export const grantsSchemaData = {
|
||||
resource_types: [
|
||||
{
|
||||
type: 'account',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'set-password',
|
||||
'change-password',
|
||||
],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['apw', 'acctldap', 'acctpw', 'acctoidc'],
|
||||
parent_type: 'auth-method',
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'auth_method_id',
|
||||
'attributes',
|
||||
'password_account_attributes',
|
||||
'oidc_account_attributes',
|
||||
'ldap_account_attributes',
|
||||
'managed_group_ids',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'alias',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['global'],
|
||||
id_prefixes: ['alt'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'value',
|
||||
'destination_id',
|
||||
'type',
|
||||
'attributes',
|
||||
'target_alias_attributes',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'auth-method',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['authenticate', 'read', 'update', 'delete', 'change-state'],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['amoidc', 'amldap', 'ampw'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'password_auth_method_attributes',
|
||||
'oidc_auth_methods_attributes',
|
||||
'ldap_auth_methods_attributes',
|
||||
'is_primary',
|
||||
'authorized_actions',
|
||||
'authorized_collection_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'auth-token',
|
||||
collection_actions: ['list'],
|
||||
id_actions: ['read', 'read:self', 'delete', 'delete:self'],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['at'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'token',
|
||||
'user_id',
|
||||
'auth_method_id',
|
||||
'account_id',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'approximate_last_used_time',
|
||||
'expiration_time',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'billing',
|
||||
collection_actions: ['monthly-active-users'],
|
||||
scopes: ['global'],
|
||||
},
|
||||
{
|
||||
type: 'credential',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['delete', 'read', 'update'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: [
|
||||
'credp',
|
||||
'cdvlt',
|
||||
'credup',
|
||||
'cred',
|
||||
'credjson',
|
||||
'credupd',
|
||||
'credspk',
|
||||
],
|
||||
parent_type: 'credential-store',
|
||||
output_fields: [
|
||||
'id',
|
||||
'credential_store_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'username_password_attributes',
|
||||
'ssh_private_key_attributes',
|
||||
'json_attributes',
|
||||
'username_password_domain_attributes',
|
||||
'password_attributes',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'credential-library',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['delete', 'read', 'update'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['clvlt', 'clvllt', 'clvsclt'],
|
||||
parent_type: 'credential-store',
|
||||
output_fields: [
|
||||
'id',
|
||||
'credential_store_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'vault_credential_library_attributes',
|
||||
'vault_ssh_certificate_credential_library_attributes',
|
||||
'vault_generic_credential_library_attributes',
|
||||
'vault_ldap_credential_library_attributes',
|
||||
'authorized_actions',
|
||||
'credential_type',
|
||||
'credential_mapping_overrides',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'credential-store',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['csvlt', 'csst', 'cs'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'vault_credential_store_attributes',
|
||||
'authorized_actions',
|
||||
'authorized_collection_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'update',
|
||||
'delete',
|
||||
'add-members',
|
||||
'set-members',
|
||||
'remove-members',
|
||||
'read',
|
||||
],
|
||||
scopes: ['global', 'org', 'project'],
|
||||
id_prefixes: ['g'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'member_ids',
|
||||
'members',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'host',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['hst', 'hplg', 'h'],
|
||||
parent_type: 'host-catalog',
|
||||
output_fields: [
|
||||
'id',
|
||||
'host_catalog_id',
|
||||
'scope',
|
||||
'plugin',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'host_set_ids',
|
||||
'attributes',
|
||||
'static_host_attributes',
|
||||
'ip_addresses',
|
||||
'dns_names',
|
||||
'external_id',
|
||||
'external_name',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'host-catalog',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['hc', 'hcplg', 'hcst'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'plugin_id',
|
||||
'plugin',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'secrets',
|
||||
'secrets_hmac',
|
||||
'worker_filter',
|
||||
'authorized_actions',
|
||||
'authorized_collection_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'host-set',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'add-hosts',
|
||||
'set-hosts',
|
||||
'remove-hosts',
|
||||
],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['hsst', 'hsplg', 'hs'],
|
||||
parent_type: 'host-catalog',
|
||||
output_fields: [
|
||||
'id',
|
||||
'host_catalog_id',
|
||||
'scope',
|
||||
'plugin',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'host_ids',
|
||||
'preferred_endpoints',
|
||||
'sync_interval_seconds',
|
||||
'attributes',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'managed-group',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['mgoidc', 'mgldap'],
|
||||
parent_type: 'auth-method',
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'auth_method_id',
|
||||
'attributes',
|
||||
'oidc_managed_group_attributes',
|
||||
'ldap_managed_group_attributes',
|
||||
'member_ids',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'policy',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: ['read', 'update', 'delete'],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['pst'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'type',
|
||||
'version',
|
||||
'attributes',
|
||||
'storage_policy_attributes',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'role',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'remove-principals',
|
||||
'add-grants',
|
||||
'set-grants',
|
||||
'remove-grants',
|
||||
'delete',
|
||||
'add-grant-scopes',
|
||||
'set-grant-scopes',
|
||||
'remove-grant-scopes',
|
||||
'read',
|
||||
'update',
|
||||
'add-principals',
|
||||
'set-principals',
|
||||
],
|
||||
scopes: ['global', 'org', 'project'],
|
||||
id_prefixes: ['r'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'grant_scope_ids',
|
||||
'principal_ids',
|
||||
'principals',
|
||||
'grant_strings',
|
||||
'grants',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'scope',
|
||||
collection_actions: [
|
||||
'create',
|
||||
'list',
|
||||
'list-keys',
|
||||
'rotate-keys',
|
||||
'list-key-version-destruction-jobs',
|
||||
'destroy-key-version',
|
||||
],
|
||||
id_actions: [
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'attach-storage-policy',
|
||||
'detach-storage-policy',
|
||||
'set-alias-suffix',
|
||||
'remove-alias-suffix',
|
||||
],
|
||||
scopes: ['global', 'org', 'project'],
|
||||
id_prefixes: ['global', 'p', 'o'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'primary_auth_method_id',
|
||||
'authorized_actions',
|
||||
'authorized_collection_actions',
|
||||
'storage_policy_id',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'session',
|
||||
collection_actions: ['list'],
|
||||
id_actions: ['cancel:self', 'read', 'read:self', 'cancel'],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['s'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'target_id',
|
||||
'scope',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'expiration_time',
|
||||
'auth_token_id',
|
||||
'user_id',
|
||||
'host_set_id',
|
||||
'host_id',
|
||||
'scope_id',
|
||||
'endpoint',
|
||||
'states',
|
||||
'status',
|
||||
'certificate',
|
||||
'termination_reason',
|
||||
'authorized_actions',
|
||||
'connections',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'session-recording',
|
||||
collection_actions: ['list', 'list-exports'],
|
||||
id_actions: [
|
||||
'reapply-storage-policy',
|
||||
'export',
|
||||
'export:cancel',
|
||||
'read',
|
||||
'download',
|
||||
'delete',
|
||||
],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['sr'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope',
|
||||
'session_id',
|
||||
'storage_bucket_id',
|
||||
'bytes_up',
|
||||
'bytes_down',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'duration',
|
||||
'type',
|
||||
'state',
|
||||
'error_details',
|
||||
'mime_types',
|
||||
'endpoint',
|
||||
'connection_recordings',
|
||||
'create_time_values',
|
||||
'authorized_actions',
|
||||
'retain_until',
|
||||
'delete_after',
|
||||
'availability',
|
||||
'recording_state',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'storage-bucket',
|
||||
collection_actions: ['list', 'create'],
|
||||
id_actions: ['delete', 'read', 'update'],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['sb'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'plugin_id',
|
||||
'plugin',
|
||||
'name',
|
||||
'description',
|
||||
'bucket_name',
|
||||
'bucket_prefix',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'attributes',
|
||||
'secrets',
|
||||
'secrets_hmac',
|
||||
'worker_filter',
|
||||
'storage_bucket_credential_id',
|
||||
'authorized_actions',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'target',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'delete',
|
||||
'add-host-sources',
|
||||
'set-host-sources',
|
||||
'remove-host-sources',
|
||||
'add-credential-sources',
|
||||
'read',
|
||||
'update',
|
||||
'set-credential-sources',
|
||||
'remove-credential-sources',
|
||||
'authorize-session',
|
||||
],
|
||||
scopes: ['project'],
|
||||
id_prefixes: ['trdp', 'tssh', 'ttcp'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'type',
|
||||
'host_source_ids',
|
||||
'host_sources',
|
||||
'session_max_seconds',
|
||||
'session_connection_limit',
|
||||
'worker_filter',
|
||||
'egress_worker_filter',
|
||||
'ingress_worker_filter',
|
||||
'brokered_credential_source_ids',
|
||||
'brokered_credential_sources',
|
||||
'injected_application_credential_source_ids',
|
||||
'injected_application_credential_sources',
|
||||
'attributes',
|
||||
'tcp_target_attributes',
|
||||
'ssh_target_attributes',
|
||||
'rdp_target_attributes',
|
||||
'authorized_actions',
|
||||
'address',
|
||||
'aliases',
|
||||
'with_aliases',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
collection_actions: ['create', 'list'],
|
||||
id_actions: [
|
||||
'delete',
|
||||
'add-accounts',
|
||||
'set-accounts',
|
||||
'remove-accounts',
|
||||
'list-resolvable-aliases',
|
||||
'read',
|
||||
'update',
|
||||
],
|
||||
scopes: ['global', 'org'],
|
||||
id_prefixes: ['u'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'account_ids',
|
||||
'accounts',
|
||||
'authorized_actions',
|
||||
'login_name',
|
||||
'full_name',
|
||||
'email',
|
||||
'primary_account_id',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'worker',
|
||||
collection_actions: [
|
||||
'create:worker-led',
|
||||
'list',
|
||||
'read-certificate-authority',
|
||||
'reinitialize-certificate-authority',
|
||||
'create:controller-led',
|
||||
],
|
||||
id_actions: [
|
||||
'remove-worker-tags',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'add-worker-tags',
|
||||
'set-worker-tags',
|
||||
],
|
||||
scopes: ['global'],
|
||||
id_prefixes: ['w'],
|
||||
output_fields: [
|
||||
'id',
|
||||
'scope_id',
|
||||
'scope',
|
||||
'name',
|
||||
'description',
|
||||
'created_time',
|
||||
'updated_time',
|
||||
'version',
|
||||
'address',
|
||||
'canonical_tags',
|
||||
'config_tags',
|
||||
'last_status_time',
|
||||
'worker_generated_auth_token',
|
||||
'controller_generated_activation_token',
|
||||
'active_connection_count',
|
||||
'type',
|
||||
'api_tags',
|
||||
'release_version',
|
||||
'directly_connected_downstream_workers',
|
||||
'authorized_actions',
|
||||
'local_storage_state',
|
||||
'remote_storage_state',
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -45,6 +45,12 @@ export default factory.extend({
|
||||
username: faker.internet.userName(),
|
||||
key_bits: faker.number.int(999),
|
||||
path: faker.system.directoryPath(),
|
||||
critical_options: {
|
||||
[faker.word.noun()]: faker.word.words(2),
|
||||
},
|
||||
extensions: {
|
||||
[faker.word.noun()]: faker.word.words(2),
|
||||
},
|
||||
};
|
||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP:
|
||||
return {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default factory.extend({
|
||||
'set-grant-scopes',
|
||||
],
|
||||
grant_strings: () => [
|
||||
'ids=*;action=*',
|
||||
'ids=*;actions=*',
|
||||
'ids=*;type=host-catalog;actions=create,read',
|
||||
],
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import { faker } from '@faker-js/faker';
|
||||
import { trait } from 'miragejs';
|
||||
import {
|
||||
TYPES_SESSION_RECORDING as types,
|
||||
STATE_SESSION_RECORDING_STARTED,
|
||||
STATE_SESSION_RECORDING_AVAILABLE,
|
||||
STATE_SESSION_RECORDING_STARTED,
|
||||
STATE_SESSION_RECORDING_UNKNOWN,
|
||||
} from 'api/models/session-recording';
|
||||
|
||||
@@ -27,6 +27,29 @@ export default factory.extend({
|
||||
// Cycle through available types
|
||||
type: (i) => types[i % types.length],
|
||||
state: (i) => states[i % states.length],
|
||||
|
||||
recording_state(i) {
|
||||
// Only return error details if state is 'unknown' (failed)
|
||||
if (this.state !== STATE_SESSION_RECORDING_UNKNOWN) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const errorScenarios = [
|
||||
{
|
||||
syncing_error_details:
|
||||
'sync errors in container "sr_jovU2XLzBP.bsr": file "sr_jovU2XLzBP.bsr/session-recording.meta": storage.(syncingFile).sync: storage.parseStoragePluginError: plugin error, external system issue: error #3001: rpc error: \ncode = Internal desc = failed to put object into minio: Get "http://minio:9000/my-special-storage-bucket/?location=": dial tcp: lookup minio on 10.89.0.1:53: no such host',
|
||||
verification_error_details:
|
||||
'integrity check failed for session recording file "sr_jovU2XLzBP.bsr/channels/connection-1/channel-recording-data.bin": expected SHA-256 checksum "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6" but computed \nchecksum was "z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1" indicating potential data corruption during transfer or storage',
|
||||
},
|
||||
{
|
||||
syncing_error_details:
|
||||
'sync errors in container "sr_jovU2XLzBP.bsr": file "sr_jovU2XLzBP.bsr/session-recording.meta": storage.(syncingFile).sync: storage.parseStoragePluginError: plugin error, external system issue: error #3001: rpc error: code = Internal desc = failed to put object into minio: Get "http://minio:9000/my-special-storage-bucket/?location=": dial tcp: lookup minio on 10.89.0.1:53: no such host',
|
||||
verification_error_details: null,
|
||||
},
|
||||
];
|
||||
return errorScenarios[i % 2];
|
||||
},
|
||||
|
||||
start_time() {
|
||||
return this.state === STATE_SESSION_RECORDING_AVAILABLE
|
||||
? faker.date.recent({ days: 3, refDate: this.end_time })
|
||||
|
||||
@@ -9,7 +9,11 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
|
||||
|
||||
module.exports = function (defaults) {
|
||||
const app = new EmberAddon(defaults, {
|
||||
// Add options here
|
||||
babel: {
|
||||
plugins: [
|
||||
require.resolve('ember-concurrency/async-arrow-task-transform'),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,6 +18,11 @@ module.exports = {
|
||||
name: require('./package').name,
|
||||
|
||||
options: {
|
||||
babel: {
|
||||
plugins: [
|
||||
require.resolve('ember-concurrency/async-arrow-task-transform'),
|
||||
],
|
||||
},
|
||||
// Don't automatically import sqlite, we need to manually control how it gets used
|
||||
autoImport: {
|
||||
exclude: ['@sqlite.org/sqlite-wasm'],
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "0.0.0",
|
||||
"description": "API data layer for Boundary.",
|
||||
"description": "API data layer for Threshold.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"keywords": [
|
||||
"ember-addon"
|
||||
],
|
||||
@@ -34,8 +34,9 @@
|
||||
"broccoli-source": "^3.0.1",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-can": "^8.0.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-babel": "^8.3.1",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-concurrency": "^4.0.3",
|
||||
"ember-data": "~5.3.12",
|
||||
"ember-get-config": "^2.1.1",
|
||||
"ember-inflector": "^6.0.0",
|
||||
@@ -43,7 +44,7 @@
|
||||
"promise-worker-bi": "^5.0.1",
|
||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||
"tracked-built-ins": "^4.0.0",
|
||||
"uuid": "^11.0.3"
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
@@ -55,7 +56,7 @@
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"doctoc": "^2.2.0",
|
||||
"ember-cli": "~5.12.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
@@ -92,7 +93,7 @@
|
||||
"sinon": "^19.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -30,7 +30,7 @@ module.exports = function (environment) {
|
||||
},
|
||||
|
||||
mirage: {
|
||||
enabled: true
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -175,11 +175,46 @@ module('Unit | Adapter | application', function (hooks) {
|
||||
assert.ok(getResponse(401).errors[0].isUnauthenticated);
|
||||
assert.ok(getResponse(403).errors[0].isForbidden);
|
||||
assert.ok(getResponse(404).errors[0].isNotFound);
|
||||
assert.ok(getResponse(429).errors[0].isRateLimited);
|
||||
assert.ok(getResponse(500).errors[0].isServer);
|
||||
assert.ok(getResponse(0).errors[0].isUnknown);
|
||||
assert.ok(getResponse(false).errors[0].isUnknown);
|
||||
});
|
||||
|
||||
test('it attaches retryAfter from Retry-After header on 429 responses', function (assert) {
|
||||
const adapter = this.owner.lookup('adapter:application');
|
||||
const headers = { 'Retry-After': '30' };
|
||||
const response = adapter.handleResponse(429, headers, { status: 429 });
|
||||
assert.ok(response.errors[0].isRateLimited);
|
||||
assert.strictEqual(response.errors[0].retryAfter, 30);
|
||||
});
|
||||
|
||||
test('it returns partial data when rate limited mid-pagination', async function (assert) {
|
||||
const adapter = this.owner.lookup('adapter:application');
|
||||
const store = this.owner.lookup('service:store');
|
||||
let requestCount = 0;
|
||||
this.server.get('/groups', () => {
|
||||
requestCount++;
|
||||
if (requestCount === 1) {
|
||||
return {
|
||||
items: [{ id: 'g_1', scope_id: 'p_1' }],
|
||||
response_type: 'delta',
|
||||
list_token: 'token123',
|
||||
};
|
||||
}
|
||||
// Second request returns a 429
|
||||
return new Response(429, { 'Retry-After': '10' }, { status: 429 });
|
||||
});
|
||||
|
||||
const result = await adapter.query(
|
||||
store,
|
||||
{ modelName: 'group' },
|
||||
{ scope_id: 'p_1' },
|
||||
);
|
||||
assert.strictEqual(result.items.length, 1);
|
||||
assert.strictEqual(result.meta.rateLimitWarning.retryAfter, 10);
|
||||
});
|
||||
|
||||
test('it returns field-level errors in InvalidError from handleResponse', function (assert) {
|
||||
const adapter = this.owner.lookup('adapter:application');
|
||||
const payload = {
|
||||
|
||||
@@ -470,6 +470,62 @@ module('Unit | Handler | sqlite-handler', function (hooks) {
|
||||
assert.strictEqual(results[0].name, 'specific-target');
|
||||
});
|
||||
|
||||
test('it reuses the global token for a more specific query when the global token exists', async function (assert) {
|
||||
const targets = this.server.createList('target', 3, { scope });
|
||||
let capturedListToken;
|
||||
let responseToken = 'specific-list-token';
|
||||
|
||||
this.server.get('targets', (_schema, request) => {
|
||||
capturedListToken = request.queryParams.list_token;
|
||||
return {
|
||||
items: targets.map((t) =>
|
||||
this.server.serializerOrRegistry.serialize(t),
|
||||
),
|
||||
list_token: responseToken,
|
||||
response_type: 'complete',
|
||||
};
|
||||
});
|
||||
|
||||
// Run the specific query first
|
||||
await store.query('target', { scope_id: 'some-specific-scope' });
|
||||
|
||||
// Switch the response token and run the global query
|
||||
responseToken = 'global-list-token';
|
||||
await store.query('target', { scope_id: 'global', recursive: true });
|
||||
|
||||
capturedListToken = undefined;
|
||||
|
||||
// Re-run the specific query which has a cached token but should prefer the global token since it exists
|
||||
await store.query('target', { scope_id: 'some-specific-scope' });
|
||||
|
||||
assert.strictEqual(capturedListToken, 'global-list-token');
|
||||
});
|
||||
|
||||
test('it uses the specific token when no global token exists', async function (assert) {
|
||||
const targets = this.server.createList('target', 3, { scope });
|
||||
let capturedListToken;
|
||||
|
||||
this.server.get('targets', (_schema, request) => {
|
||||
capturedListToken = request.queryParams.list_token;
|
||||
return {
|
||||
items: targets.map((t) =>
|
||||
this.server.serializerOrRegistry.serialize(t),
|
||||
),
|
||||
list_token: 'specific-list-token',
|
||||
response_type: 'complete',
|
||||
};
|
||||
});
|
||||
|
||||
await store.query('target', { scope_id: 'some-specific-scope' });
|
||||
|
||||
capturedListToken = undefined;
|
||||
|
||||
// Re-run the specific query which should just use the normal specific token since no global token exists
|
||||
await store.query('target', { scope_id: 'some-specific-scope' });
|
||||
|
||||
assert.strictEqual(capturedListToken, 'specific-list-token');
|
||||
});
|
||||
|
||||
test('it retries query when invalid list token error occurs', async function (assert) {
|
||||
let targets = this.server.createList('target', 5, { scope });
|
||||
|
||||
|
||||
@@ -506,4 +506,90 @@ module('Unit | Serializer | auth method', function (hooks) {
|
||||
assert.strictEqual(record.client_certificate_key, '');
|
||||
assert.strictEqual(record.bind_password, '');
|
||||
});
|
||||
|
||||
test('it includes account_claim_maps when serializing new OIDC records', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
const record = store.createRecord('auth-method', {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'sub' },
|
||||
],
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.ok(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is included for new records',
|
||||
);
|
||||
|
||||
assert.deepEqual(serializedRecord.attributes.account_claim_maps, [
|
||||
'email=email',
|
||||
'name=sub',
|
||||
]);
|
||||
});
|
||||
|
||||
test('it excludes account_claim_maps when serializing existing OIDC records with "sub" mapping', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
|
||||
const record = store.push({
|
||||
data: {
|
||||
id: 'oidc123',
|
||||
type: 'auth-method',
|
||||
attributes: {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'sub' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.notOk(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is excluded when updating with "sub" mapping',
|
||||
);
|
||||
});
|
||||
|
||||
test('it includes account_claim_maps when serializing existing OIDC records without "sub" mapping', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
|
||||
const record = store.push({
|
||||
data: {
|
||||
id: 'oidc456',
|
||||
type: 'auth-method',
|
||||
attributes: {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'name' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.ok(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is included when updating without "sub" mapping',
|
||||
);
|
||||
|
||||
assert.deepEqual(serializedRecord.attributes.account_claim_maps, [
|
||||
'email=email',
|
||||
'name=name',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -138,6 +138,45 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes credential library updates without credential_type', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-library');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'credential-library',
|
||||
attributes: {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
version: 1,
|
||||
credential_type: null,
|
||||
credential_mapping_overrides: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('credential-library', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
credential_type: null,
|
||||
credential_mapping_overrides: null,
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
http_request_body: null,
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('it does not serialize http_request_body unless http_method is set to POST', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('credential-library', {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "auth",
|
||||
"version": "0.0.0",
|
||||
"description": "Authentication layer for Boundary.",
|
||||
"description": "Authentication layer for Threshold.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"keywords": [
|
||||
"ember-addon"
|
||||
],
|
||||
@@ -28,7 +28,7 @@
|
||||
"@babel/core": "^7.26.10",
|
||||
"@ember/test-waiters": "^4.1.0",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-babel": "^8.3.1",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-simple-auth": "^6.1.0"
|
||||
},
|
||||
@@ -41,7 +41,7 @@
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"doctoc": "^2.2.0",
|
||||
"ember-cli": "~5.12.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"ember-source": ">= 4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -12,6 +12,7 @@ import Component from '@glimmer/component';
|
||||
* - 403 - `error.isForbidden`: the session is authenticated but does not have
|
||||
* permission to perform the requested action
|
||||
* - 404 - `error.isNotFound`: the requested resource could not be found
|
||||
* - 429 - `error.isRateLimited`: the client has exceeded the rate limit
|
||||
* - 500 - `error.isServer`: an internal server error occurred
|
||||
*
|
||||
* For an unknown error state, i.e error state not matching to the above defined list:
|
||||
@@ -19,7 +20,7 @@ import Component from '@glimmer/component';
|
||||
* we don't distinguish it yet
|
||||
*/
|
||||
|
||||
const statuses = ['401', '403', '404', '500'];
|
||||
const statuses = ['401', '403', '404', '429', '500'];
|
||||
|
||||
export default class ErrorMessageComponent extends Component {
|
||||
// =methods
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"auth": "workspace:*",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-browser-services": "^4.0.3",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-babel": "^8.3.1",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-sass": "^11.0.1",
|
||||
"ember-concurrency": "^4.0.3",
|
||||
@@ -59,7 +59,7 @@
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"doctoc": "^2.2.0",
|
||||
"ember-cli": "~5.12.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
@@ -95,7 +95,7 @@
|
||||
"webpack": "^5.105.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 8C0 4.22876 0 2.34315 1.17157 1.17157C2.34315 0 4.22876 0 8 0H24C27.7712 0 29.6569 0 30.8284 1.17157C32 2.34315 32 4.22876 32 8V24C32 27.7712 32 29.6569 30.8284 30.8284C29.6569 32 27.7712 32 24 32H8C4.22876 32 2.34315 32 1.17157 30.8284C0 29.6569 0 27.7712 0 24V8Z" fill="url(#paint0_linear_4389_56864)"/>
|
||||
<path d="M10.9261 25.03V23.6294H13.8501V22.7694H12.4249V21.3688H19.0838L15.9878 16.0123L19.0838 10.6557H12.2529V18.0763H8.5918V6.96997H21.2215L23.3347 10.6311L20.2387 16.0123L23.4084 21.4917L21.3444 25.03H10.9261Z" fill="white"/>
|
||||
<path d="M11.4914 21.3688H10.0908V22.7694H11.4914V21.3688Z" fill="white"/>
|
||||
<path d="M9.99237 23.6294H8.5918V25.03H9.99237V23.6294Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4389_56864" x1="16" y1="0" x2="16" y2="32" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F66F74"/>
|
||||
<stop offset="1" stop-color="#F34D52"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Plain placeholder mark (not an official logo). -->
|
||||
<rect width="32" height="32" rx="6" fill="#2b3a4a"/>
|
||||
<rect x="7" y="7" width="18" height="4.5" fill="#fff"/>
|
||||
<rect x="13.25" y="11.5" width="5.5" height="13.5" fill="#fff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1009 B After Width: | Height: | Size: 341 B |
@@ -1,69 +1,8 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 377 132">
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M125.45,41.34V35.68h-5.18v5.66h-2.64V27.75h2.64v5.7h5.18v-5.7h2.64V41.34Zm12.32,0h-2.09l-.19-.67a5.69,5.69,0,0,1-3,.87c-1.87,0-2.66-1.23-2.66-2.92,0-2,.9-2.75,3-2.75h2.46v-1c0-1.09-.32-1.47-1.95-1.47a14.85,14.85,0,0,0-2.83.3l-.32-1.87a14,14,0,0,1,3.5-.46c3.21,0,4.15,1.08,4.15,3.54Zm-2.55-3.7h-1.89c-.84,0-1.07.22-1.07,1s.23,1,1,1a4.1,4.1,0,0,0,1.93-.51Zm7.91,3.9a12.53,12.53,0,0,1-3.52-.56L140,39.1a11.5,11.5,0,0,0,3,.43c1.13,0,1.3-.24,1.3-1s-.12-.9-1.78-1.29c-2.49-.58-2.79-1.18-2.79-3.08s.9-2.83,3.82-2.83a14.07,14.07,0,0,1,3.06.34l-.25,2a19,19,0,0,0-2.81-.28c-1.11,0-1.3.24-1.3.84,0,.79.06.85,1.44,1.19,2.85.73,3.13,1.09,3.13,3.1S146.21,41.54,143.13,41.54Zm11.7-.2V34.5c0-.53-.23-.79-.81-.79a7.22,7.22,0,0,0-2.67.83v6.8H148.8V27.56l2.55.38v4.34a9.38,9.38,0,0,1,3.73-.94c1.7,0,2.31,1.14,2.31,2.89v7.11Zm4.71-11.19v-2.4h2.55v2.4Zm0,11.19v-9.8h2.55v9.8Zm4.6-9.72c0-2.46,1.48-3.88,5-3.88a16.63,16.63,0,0,1,3.8.44l-.3,2.19a21.49,21.49,0,0,0-3.41-.34c-1.83,0-2.41.6-2.41,2v5.15c0,1.43.58,2,2.41,2a21.57,21.57,0,0,0,3.41-.35l.3,2.2a16.63,16.63,0,0,1-3.8.44c-3.48,0-5-1.43-5-3.88Zm14.41,9.92c-3.5,0-4.45-1.85-4.45-3.86V35.2c0-2,1-3.86,4.45-3.86S183,33.19,183,35.2v2.48C183,39.69,182.05,41.54,178.55,41.54Zm0-8.11c-1.37,0-1.89.58-1.89,1.69v2.64c0,1.1.52,1.69,1.89,1.69s1.88-.59,1.88-1.69V35.12C180.43,34,179.91,33.43,178.55,33.43Zm11.64.16A20.64,20.64,0,0,0,187.48,35v6.32h-2.55v-9.8h2.16l.16,1.09a11.23,11.23,0,0,1,2.69-1.29Zm10.22,4.49c0,2.17-1,3.46-3.38,3.46a14.74,14.74,0,0,1-2.72-.28v4l-2.56.38V31.54h2l.25.82a5.54,5.54,0,0,1,3.22-1c2.06,0,3.15,1.16,3.15,3.4Zm-6.1,1.11a11.85,11.85,0,0,0,2.26.26c.93,0,1.28-.43,1.28-1.31V34.68c0-.81-.31-1.25-1.26-1.25a3.66,3.66,0,0,0-2.28.89Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M139.64,70.53c3.08-1.33,4.48-3.21,4.48-6.53V59.82c0-5.45-3.09-8.59-10.72-8.59H117.55V91.52h16.94c7.33,0,11-3.56,11-9.25V78C145.51,74,143.33,71.31,139.64,70.53Zm-14.46-13h7.32c2.78,0,4,1,4,3.27v3.57c0,2.36-1.08,3.75-4.06,3.75h-7.25ZM137.89,81.3c0,3-1.46,3.93-4.91,3.93h-7.8v-11h8.47c3,0,4.24,1.33,4.24,4.06Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M161.54,61.45c-10.11,0-12.82,5.57-12.82,11.62v7.44c0,6,2.71,11.62,12.82,11.62s12.83-5.57,12.83-11.62V73.07C174.37,67,171.64,61.45,161.54,61.45ZM167,80.76c0,3.32-1.51,5.08-5.45,5.08s-5.44-1.76-5.44-5.08V72.83c0-3.33,1.51-5.08,5.44-5.08S167,69.5,167,72.83Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M196.51,62.06V82.51c-2.66,1.39-5.87,2.48-7.68,2.48s-2.36-.78-2.36-2.36V62.06h-7.38V83.42c0,5.26,1.75,8.71,6.65,8.71A29.44,29.44,0,0,0,197.54,89l.72,2.48h5.64V62.06Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M228.4,61.45a29.61,29.61,0,0,0-11.8,3.09l-.72-2.48h-5.64V91.52h7.39V71.08c2.66-1.39,5.87-2.48,7.68-2.48s2.36.78,2.36,2.35V91.52h7.38V70.17C235.05,64.9,233.3,61.45,228.4,61.45Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M257.5,50V62.31a40.19,40.19,0,0,0-7.87-.86c-6.83,0-9.74,3.88-9.74,10.41v10c0,6.71,3.15,10.22,9.08,10.22A15.49,15.49,0,0,0,258.29,89l.72,2.48h5.87V49Zm0,33.16a10.43,10.43,0,0,1-6.59,2.66c-2.73,0-3.63-1.33-3.63-3.75V71.68c0-2.66,1-3.93,3.68-3.93a32.33,32.33,0,0,1,6.54.79Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M280.8,61.45a38.34,38.34,0,0,0-10.11,1.4l.91,5.62a41.47,41.47,0,0,1,8.17-.9c4.71,0,5.62,1.15,5.62,4.42v3.08h-7.08c-6,0-8.59,2.3-8.59,8.29,0,5.08,2.3,8.77,7.68,8.77a16.18,16.18,0,0,0,8.78-2.6l.54,2h6.06V72.1C292.78,64.72,290.05,61.45,280.8,61.45Zm4.59,23.3a11.57,11.57,0,0,1-5.56,1.51c-2.3,0-3-.91-3-3,0-2.23.67-2.9,3.09-2.9h5.44Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M313,61.45a33,33,0,0,0-7.74,3.88l-.48-3.27h-6.24V91.52h7.38v-19a57.85,57.85,0,0,1,7.81-4.3Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M335.67,62.06,328.9,84.44l-6.72-22.38h-7.56l9.32,29.46h2.59l-3.68,11.92h7l3.93-11.92,9.43-29.46Z">
|
||||
</path>
|
||||
|
||||
<polygon
|
||||
fill="#f04e54"
|
||||
points="44.94 102.47 44.94 97.64 54.96 97.64 54.96 94.67 50.1 94.67 50.1 89.82 73.03 89.82 62.38 71.38 73.03 52.93 49.53 52.93 49.53 78.51 36.88 78.51 36.88 40.29 80.4 40.29 87.67 52.87 76.98 71.38 87.88 90.26 80.84 102.47 44.94 102.47">
|
||||
</polygon>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="42.04"
|
||||
y="89.82"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="36.88"
|
||||
y="97.61"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
<svg width="200" height="67" viewBox="0 0 200 67" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Threshold">
|
||||
<!-- Plain placeholder wordmark (not an official logo). -->
|
||||
<g class="logomark" fill="#ffffff">
|
||||
<rect x="2" y="14" width="40" height="9"></rect>
|
||||
<rect x="16.5" y="23" width="11" height="30"></rect>
|
||||
</g>
|
||||
<text class="logotype" x="54" y="43" fill="#ffffff" font-family="Helvetica, Arial, sans-serif" font-size="26" font-weight="600">Threshold</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 491 B |
@@ -1,71 +1,7 @@
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 67 67">
|
||||
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Plain placeholder mark (not an official logo). -->
|
||||
<g class="logomark">
|
||||
<polygon
|
||||
fill="#f04e54"
|
||||
points="9.89 64.1 9.89 59.28 19.9 59.28 19.9 56.3 15.05 56.3 15.05 51.45 37.97 51.45 27.32 33.01 37.97 14.57 14.47 14.57 14.47 40.15 1.82 40.15 1.82 1.92 45.34 1.92 52.61 14.5 41.92 33.01 52.83 51.9 45.78 64.1 9.89 64.1">
|
||||
</polygon>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="6.98"
|
||||
y="51.45"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="1.82"
|
||||
y="59.25"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
</g>
|
||||
|
||||
<g class="logotype">
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M104.58,32.17c3.08-1.33,4.48-3.21,4.48-6.54V21.45c0-5.44-3.09-8.59-10.72-8.59H82.5v40.3H99.44c7.32,0,11-3.57,11-9.26V39.61C110.45,35.67,108.27,33,104.58,32.17Zm-14.46-13h7.32c2.78,0,4,1,4,3.27V26c0,2.36-1.08,3.76-4.05,3.76H90.12Zm12.71,23.78c0,3-1.46,3.94-4.91,3.94h-7.8v-11h8.47c3,0,4.24,1.33,4.24,4Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M126.48,23.09c-10.1,0-12.82,5.56-12.82,11.62v7.44c0,6.05,2.72,11.61,12.82,11.61s12.83-5.56,12.83-11.61V34.71C139.31,28.65,136.58,23.09,126.48,23.09Zm5.45,19.3c0,3.33-1.51,5.08-5.45,5.08S121,45.72,121,42.39V34.47c0-3.34,1.51-5.09,5.44-5.09s5.45,1.75,5.45,5.09Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M161.46,23.69V44.15c-2.67,1.39-5.88,2.48-7.69,2.48s-2.36-.79-2.36-2.37V23.69H144V45.05c0,5.27,1.75,8.71,6.65,8.71a29.59,29.59,0,0,0,11.8-3.08l.73,2.48h5.63V23.69Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M193.34,23.09a29.59,29.59,0,0,0-11.8,3.08l-.72-2.48h-5.63V53.16h7.38V32.71c2.66-1.39,5.87-2.48,7.68-2.48s2.36.79,2.36,2.36V53.16H200V31.8C200,26.54,198.24,23.09,193.34,23.09Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M222.44,11.66V23.94a41,41,0,0,0-7.87-.85c-6.83,0-9.73,3.87-9.73,10.41v10c0,6.72,3.14,10.22,9.07,10.22a15.48,15.48,0,0,0,9.32-3.08l.72,2.48h5.87V10.63Zm0,33.15a10.43,10.43,0,0,1-6.59,2.66c-2.73,0-3.63-1.33-3.63-3.75V33.32c0-2.67,1-3.94,3.69-3.94a33,33,0,0,1,6.53.79Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M245.74,23.09a38.76,38.76,0,0,0-10.11,1.39l.91,5.63a40.74,40.74,0,0,1,8.17-.91c4.71,0,5.62,1.15,5.62,4.42V36.7h-7.07c-6,0-8.6,2.3-8.6,8.29,0,5.09,2.3,8.77,7.69,8.77a16.06,16.06,0,0,0,8.77-2.6l.54,2h6.06V33.74C257.72,26.35,255,23.09,245.74,23.09Zm4.59,23.29a11.57,11.57,0,0,1-5.56,1.51c-2.3,0-3-.9-3-3,0-2.24.67-2.9,3.09-2.9h5.44Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M277.92,23.09A33,33,0,0,0,270.18,27l-.48-3.27h-6.24V53.16h7.39v-19a56,56,0,0,1,7.8-4.29Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M300.61,23.69l-6.77,22.39-6.72-22.39h-7.56l9.32,29.47h2.59l-3.68,11.92h7l3.93-11.92,9.44-29.47Z">
|
||||
</path>
|
||||
<rect x="3" y="3" width="24" height="6" fill="currentColor"></rect>
|
||||
<rect x="12" y="9" width="6" height="18" fill="currentColor"></rect>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 336 B |
@@ -1,24 +1,4 @@
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g class="logomark">
|
||||
<path
|
||||
d="M0 7.19579L11.4553 0.58728V5.55192L4.30398 9.68052V25.5537L0 23.069V7.19579Z"
|
||||
fill="currentColor">
|
||||
</path>
|
||||
|
||||
<path
|
||||
d="M16.0285 0.58728V13.2291H11.4553V8.51084L7.15165 10.9956V27.1919L11.4553 29.6829V17.0771H16.0285V21.7641L20.3321 19.2788V3.07229L16.0285 0.58728Z"
|
||||
fill="currentColor">
|
||||
</path>
|
||||
|
||||
<path
|
||||
d="M27.4838 23.0789L16.0285 29.6875V24.7228L23.1798 20.5939V4.72074L27.4838 7.20576V23.0789Z"
|
||||
fill="currentColor">
|
||||
</path>
|
||||
</g>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Plain placeholder (no company logo). -->
|
||||
<rect x="3" y="3" width="24" height="24" rx="4" stroke="currentColor" stroke-width="2"></rect>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 636 B After Width: | Height: | Size: 248 B |
@@ -52,6 +52,16 @@ module('Integration | Component | error/message', function (hooks) {
|
||||
.hasText(
|
||||
'We could not find the requested resource. You can ask your administrator or try again later.',
|
||||
);
|
||||
|
||||
await render(hbs`<Error::Message @status='429' />`);
|
||||
|
||||
assert.dom(ERROR_MSG_TITLE).hasText('Rate limit exceeded');
|
||||
assert.dom(ERROR_MSG_CODE).hasText('ERROR 429');
|
||||
assert
|
||||
.dom(ERROR_MSG_BODY)
|
||||
.hasText(
|
||||
"You've exceeded the rate limit. Please wait a moment and try again.",
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders default error for unknown error status', async function (assert) {
|
||||
|
||||
@@ -23,13 +23,13 @@ retry: Retry
|
||||
play: Play
|
||||
pause: Pause
|
||||
resume: Resume
|
||||
export: Export
|
||||
get-topic-help: Get help for this topic
|
||||
copy-to-clipboard: Copy to Clipboard
|
||||
copy-error-detail-to-clipboard: Copy error detail to clipboard
|
||||
authenticate: Sign In
|
||||
deauthenticate: Sign Out
|
||||
choose-org: Choose Scope
|
||||
choose-different-org: Choose a different scope
|
||||
add-host-set: Create Host Set
|
||||
add-host: Create Host
|
||||
add-principals: Add Principals
|
||||
@@ -69,3 +69,5 @@ hide-errors: Hide Errors
|
||||
edit-worker-filter: Edit Worker Filter
|
||||
add-worker-filter: Add Worker Filter
|
||||
open: Open
|
||||
download: Download
|
||||
show-more: Show more
|
||||
@@ -24,9 +24,10 @@ titles:
|
||||
empty-set: No Items Yet
|
||||
global: Global
|
||||
actions: Actions
|
||||
action: Action
|
||||
documentation: Documentation
|
||||
abandon-confirm: Discard unsaved changes?
|
||||
welcome-to-boundary: Welcome to Boundary
|
||||
welcome-to-boundary: Welcome to Threshold
|
||||
label: Label
|
||||
value: Value
|
||||
no-results: No Results
|
||||
@@ -83,27 +84,27 @@ onboarding:
|
||||
target-port: Default Port
|
||||
target-setup-info: We’ll set up scopes for you
|
||||
descriptions:
|
||||
setup-first-target: In Boundary, a “target” defines a server that your users will connect to. We can help you set up your first target for proof-of-concept testing, or you can skip this step if you don’t want to do it right now.
|
||||
setup-first-target: In Threshold, a “target” defines a server that your users will connect to. We can help you set up your first target for proof-of-concept testing, or you can skip this step if you don’t want to do it right now.
|
||||
target-address: Host name or IP address.
|
||||
target-port: The default port on which to connect.
|
||||
target-setup-info: Targets live in projects, and projects live in orgs. We will create these example scopes for you but they can be edited or deleted later.
|
||||
success:
|
||||
titles:
|
||||
connect: Connect to your target
|
||||
desktop: Using Boundary Desktop
|
||||
desktop: Using Threshold Desktop
|
||||
developers: Developers
|
||||
controller: Controller URL
|
||||
creation-success: We have created your scopes and target
|
||||
descriptions:
|
||||
download: Download the Boundary Desktop app to browse and connect to targets (available for Windows, Mac, and Linux)
|
||||
developers: You can also connect to targets using the Boundary CLI or API.
|
||||
controller: Use this URL to sign in to Boundary
|
||||
role: You can now connect to the target with the Boundary Desktop app, command-line (CLI) or API. Boundary grants access to resources based on roles, and we have added you to the `demo` role to give you access to this target. Make sure to add this role to any new users if you want them to access it too.
|
||||
download: Download the Threshold Desktop app to browse and connect to targets (available for Windows, Mac, and Linux)
|
||||
developers: You can also connect to targets using the Threshold CLI or API.
|
||||
controller: Use this URL to sign in to Threshold
|
||||
role: You can now connect to the target with the Threshold Desktop app, command-line (CLI) or API. Threshold grants access to resources based on roles, and we have added you to the `demo` role to give you access to this target. Make sure to add this role to any new users if you want them to access it too.
|
||||
links:
|
||||
install: Install the Boundary Desktop app
|
||||
learn: Learn about Boundary Desktop app
|
||||
cli: Learn about Boundary command-line (CLI)
|
||||
api: Learn about Boundary API
|
||||
install: Install the Threshold Desktop app
|
||||
learn: Learn about Threshold Desktop app
|
||||
cli: Learn about Threshold command-line (CLI)
|
||||
api: Learn about Threshold API
|
||||
unsupported-controller:
|
||||
title: Unsupported controller version
|
||||
description:
|
||||
@@ -119,7 +120,7 @@ settings:
|
||||
cli: CLI
|
||||
cache-daemon: Cache Daemon
|
||||
client-agent:
|
||||
title: Boundary Client Agent
|
||||
title: Threshold Client Agent
|
||||
description: Required to use Transparent Sessions
|
||||
client: Client
|
||||
version: Version
|
||||
@@ -129,7 +130,7 @@ settings:
|
||||
self-managed: Self-managed
|
||||
server:
|
||||
title: Server
|
||||
description: Boundary connects you to targets using this server managed by your organization.
|
||||
description: Threshold connects you to targets using this server managed by your organization.
|
||||
logs:
|
||||
title: Logs
|
||||
logging-level: Logging level
|
||||
@@ -144,7 +145,7 @@ settings:
|
||||
client-agent: There may be a problem with the client agent
|
||||
preferred-clients:
|
||||
title: Preferred Clients
|
||||
description: Select the applications for Boundary to use when opening these target types.
|
||||
description: Select the applications for Threshold to use when opening these target types.
|
||||
table:
|
||||
header:
|
||||
protocol: Protocol
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
500:
|
||||
title: Server error
|
||||
description: We ran into a problem and could not continue. You can ask your administrator or try again later.
|
||||
429:
|
||||
title: Rate limit exceeded
|
||||
description: You've exceeded the rate limit. Please wait a moment and try again.
|
||||
description-partial: Currently showing partial results. You can reload for more in {seconds} {seconds, plural, one {second} other {seconds}}.
|
||||
description-ready: Currently showing partial results. You can reload now.
|
||||
unknown:
|
||||
title: Something went wrong
|
||||
description: We're not sure what happened. Please contact your administrator or try again later.
|
||||
|
||||
@@ -55,7 +55,7 @@ session_id:
|
||||
label: Session ID
|
||||
worker_release_version:
|
||||
label: Release Version
|
||||
help: The version of the Boundary binary the worker is running.
|
||||
help: The version of the Threshold binary the worker is running.
|
||||
worker_authorized_actions:
|
||||
label: Authorized Actions
|
||||
worker_last_seen:
|
||||
@@ -70,12 +70,12 @@ grant_scope:
|
||||
help: Specify the scope to which this role applies.
|
||||
cluster-url:
|
||||
label: Cluster URL
|
||||
description: The Boundary URL to which to connect (e.g. protocol://host:port)
|
||||
description: The Threshold URL to which to connect (e.g. protocol://host:port)
|
||||
proxy_url:
|
||||
label: Proxy URL
|
||||
issuer:
|
||||
label: Issuer
|
||||
help: The issuer URL. Boundary expects only the schema, host, and port and will strip off ".well-known/openid-configuration" if present. This will be used for configuration discovery as well as for validation of the "iss" claim.
|
||||
help: The issuer URL. Threshold expects only the schema, host, and port and will strip off ".well-known/openid-configuration" if present. This will be used for configuration discovery as well as for validation of the "iss" claim.
|
||||
client_id:
|
||||
label: Client ID
|
||||
help: An OAuth 2.0 Client Identifier valid at the Authorization Server.
|
||||
@@ -159,7 +159,7 @@ secret_access_key:
|
||||
help: The secret access key generated by AWS for the IAM user to use with this AWS Host Catalog.
|
||||
secret:
|
||||
label: Client Secret Value
|
||||
help: The client secret value generated by Azure for Boundary to use for authentication.
|
||||
help: The client secret value generated by Azure for Threshold to use for authentication.
|
||||
disable_credential_rotation:
|
||||
label: Disable credential rotation
|
||||
help: Azure host catalogs do not yet support credential rotation.
|
||||
@@ -173,16 +173,17 @@ ip_addresses:
|
||||
help: Read-only public and private IP addresses given by the plugin host catalog provider
|
||||
sync-interval:
|
||||
label: Sync Interval
|
||||
help: Number of seconds between the time Boundary syncs hosts in this set.
|
||||
help: Number of seconds between the time Threshold syncs hosts in this set.
|
||||
tags:
|
||||
label: Tags
|
||||
secret-editor:
|
||||
label: Secret Editor
|
||||
details: This secret is saved but won't be displayed
|
||||
edit: Click to replace this secret
|
||||
created:
|
||||
label: Created
|
||||
other_fields:
|
||||
label: Other Fields
|
||||
help: This information is provided by the server and cannot be edited in Boundary
|
||||
help: This information is provided by the server and cannot be edited in Threshold
|
||||
worker_filter:
|
||||
label: Worker Filter
|
||||
@@ -4,7 +4,7 @@
|
||||
account:
|
||||
title: Account
|
||||
title_plural: Accounts
|
||||
description: An account allows a user to log into Boundary with a particular auth method. Users may have multiple accounts for different auth methods.
|
||||
description: An account allows a user to log into Threshold with a particular auth method. Users may have multiple accounts for different auth methods.
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Accounts
|
||||
@@ -167,13 +167,13 @@ auth-method:
|
||||
help: This group includes inputs for configuring the LDAP server itself, such as the server name, port, and authentication settings.
|
||||
authenticated-search:
|
||||
title: Authenticated search
|
||||
help: Used when Boundary authenticates to the LDAP server.
|
||||
help: Used when Threshold authenticates to the LDAP server.
|
||||
anonymous-search:
|
||||
title: Anonymous search
|
||||
help: Allows you to bind anonymously to the directory when searching for user entries.
|
||||
user-entries:
|
||||
title: User entries
|
||||
help: Before authenticating an end user, Boundary must first find the end user’s entry in the directory.
|
||||
help: Before authenticating an end user, Threshold must first find the end user’s entry in the directory.
|
||||
group-entries:
|
||||
title: Group entries
|
||||
help: After authenticating an end user, we need to find out which groups the user is a member of. The configuration for this can vary depending on your LDAP server and your directory schema.
|
||||
@@ -239,7 +239,7 @@ host-catalog:
|
||||
help: AWS region of the hosts to be added to this AWS Host Catalog.
|
||||
azure_client_id:
|
||||
label: Client/Application ID
|
||||
help: The client (application) ID of an Azure service principal that Boundary will use to authenticate and discover hosts from Azure.
|
||||
help: The client (application) ID of an Azure service principal that Threshold will use to authenticate and discover hosts from Azure.
|
||||
worker_filter:
|
||||
help: Insert a worker filter in order to select the correct worker.
|
||||
project_id:
|
||||
@@ -302,7 +302,7 @@ host:
|
||||
title: Host
|
||||
title_plural: Hosts
|
||||
no_name_title: Unnamed Host
|
||||
description: A host is a resource that may be accessed by a Boundary target.
|
||||
description: A host is a resource that may be accessed by a Threshold target.
|
||||
details: Host Details
|
||||
messages:
|
||||
welcome:
|
||||
@@ -323,7 +323,7 @@ session:
|
||||
title: Session
|
||||
title_plural: Sessions
|
||||
active_plural: Active Sessions
|
||||
description: A session is a connection to a target initiated through Boundary.
|
||||
description: A session is a connection to a target initiated through Threshold.
|
||||
remaining: remaining
|
||||
status:
|
||||
active: Active
|
||||
@@ -344,7 +344,7 @@ session:
|
||||
title: Connected
|
||||
description: You can now access {targetDisplayName}
|
||||
stop-sessions-and-signout:
|
||||
title: Sign out of Boundary?
|
||||
title: Sign out of Threshold?
|
||||
description: Signing out will stop all active and pending target sessions. Are you sure you want to sign out?
|
||||
stop-sessions-before-quit:
|
||||
title: Close sessions before quitting?
|
||||
@@ -420,6 +420,13 @@ session-recording:
|
||||
title: Channels by connection
|
||||
messages:
|
||||
no-channels: There are no channels for this connection
|
||||
flyout:
|
||||
title: Session playback errors
|
||||
sync-error:
|
||||
title: This recording is not available
|
||||
flyout-section-title: Sync error
|
||||
verification-error:
|
||||
flyout-section-title: Verification error
|
||||
started-no-connections:
|
||||
title: Session Recording In Progress
|
||||
description: The Session has started but no connections have been made.
|
||||
@@ -532,7 +539,7 @@ storage-bucket:
|
||||
help: Filters to the worker(s) that can handle requests for this storage bucket.
|
||||
disable_credential_rotation:
|
||||
label: Disable credential rotation
|
||||
help: Disabling credential rotation could allow entities outside of Boundary to know the client secret in use.
|
||||
help: Disabling credential rotation could allow entities outside of Threshold to know the client secret in use.
|
||||
questions:
|
||||
delete-storage-bucket:
|
||||
title: Delete associated recordings?
|
||||
@@ -553,7 +560,7 @@ project:
|
||||
user:
|
||||
title: User
|
||||
title_plural: Users
|
||||
description: Users are entities authorized to access Boundary. Users may be assigned to roles as principals, thus receiving role grants.
|
||||
description: Users are entities authorized to access Threshold. Users may be assigned to roles as principals, thus receiving role grants.
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Users
|
||||
@@ -611,6 +618,7 @@ role:
|
||||
title: Grant
|
||||
title_plural: Grants
|
||||
description: Grants are permissions which allow roles to take actions and access resources.
|
||||
codeblock-description: Select "{editGrantsTitle}" to add or modify grants. Select "{addGrantTemplatesTitle}" to add in templates.
|
||||
actions:
|
||||
create: New Grant
|
||||
grant-templates:
|
||||
@@ -626,13 +634,19 @@ role:
|
||||
templates:
|
||||
global-admin:
|
||||
name: Global Admin
|
||||
description: Full administrative access to all resources in Boundary
|
||||
description: Full administrative access to all resources in Threshold
|
||||
org-access:
|
||||
name: Org Access
|
||||
description: Administrative access to perform actions on an org scope, but not the resources the org contains
|
||||
org-admin:
|
||||
name: Org Admin
|
||||
description: Administrative access to all resources within an organization
|
||||
description: Administrative access to perform actions on the resources an org contains
|
||||
project-access:
|
||||
name: Project Access
|
||||
description: Administrative access to perform actions on a project scope, but not the resources the project contains
|
||||
project-admin:
|
||||
name: Project Admin
|
||||
description: Administrative access to all resources within a project
|
||||
description: Administrative access to perform actions on the resources a project contains
|
||||
session-manager:
|
||||
name: Session Manager
|
||||
description: Can view and cancel sessions
|
||||
@@ -655,11 +669,188 @@ role:
|
||||
name: Session Auditor
|
||||
description: Can view session recordings
|
||||
target-user:
|
||||
name: Target User
|
||||
name: User
|
||||
description: Can connect to targets but cannot manage anything
|
||||
read-only-user:
|
||||
name: Read-Only User
|
||||
description: Can view but not modify any resources
|
||||
name: Resource Auditor
|
||||
description: Can only view but not modify any resources
|
||||
edit-grants:
|
||||
title: Edit Grants
|
||||
description: Modify existing grant strings or add new grant strings to this role.
|
||||
completion-info:
|
||||
no-suggestions: No suggestions
|
||||
wildcard-types: Wildcard - matches all types
|
||||
wildcard-ids: Wildcard - matches all IDs
|
||||
wildcard-actions: Wildcard - matches all actions
|
||||
template-value: Template value
|
||||
all-fields: All fields
|
||||
loading-ids: Still loading ID suggestions…
|
||||
linting-errors:
|
||||
general:
|
||||
whitespace-not-allowed: Whitespace is not allowed
|
||||
leading-semicolon-not-allowed: Leading semicolon is not allowed
|
||||
trailing-semicolon-not-allowed: Trailing semicolon is not allowed
|
||||
invalid-character: Invalid character "{character}"
|
||||
invalid-format: 'Invalid format: expected key=value'
|
||||
invalid-syntax: Invalid syntax
|
||||
fields:
|
||||
duplicate-field: Duplicate field "{field}"
|
||||
unknown-field: Unknown field "{field}"
|
||||
empty-value: '"{field}" value cannot be empty'
|
||||
missing-ids-or-type: Missing "ids" or "type" fields
|
||||
missing-actions: Missing "actions" field
|
||||
missing-actions-or-output-fields: Missing "actions" or "output_fields" fields
|
||||
type:
|
||||
invalid-type: Invalid resource type "{type}"
|
||||
not-top-level: Type "{type}" is not a top-level type
|
||||
not-child-type: Type "{type}" is not a child type of the pinned ID
|
||||
missing-for-wildcard-ids: Missing "type" field for wildcard ids
|
||||
wildcard-requires-ids: Missing "ids" field for wildcard type
|
||||
ids:
|
||||
empty-list: '"ids" field does not contain at least one id'
|
||||
wildcard-cannot-combine: Wildcard id "*" cannot be combined with other ids
|
||||
templates-cannot-combine: Template ids cannot be combined with each other
|
||||
duplicate-template: Duplicate id "{primary}" and "{secondary}"
|
||||
duplicate-id: Duplicate id "{id}"
|
||||
unknown-template: Unknown template "{id}"
|
||||
invalid-id: Invalid id "{id}"
|
||||
mismatched-types: Ids do not have the same type. Invalid id "{id}"
|
||||
must-support-child-types: Pinned IDs must support child types. Invalid id "{id}"
|
||||
must-match-parent-type: Pinned IDs must be "{parentType}" ids. Invalid id "{id}"
|
||||
actions:
|
||||
empty-list: '"actions" field does not contain at least one action'
|
||||
wildcard-cannot-combine: Wildcard action "*" cannot be combined with other actions
|
||||
duplicate-action: Duplicate action "{action}"
|
||||
invalid-action: Invalid action "{action}"
|
||||
id-only: Only id actions for "{type}" type are allowed. Invalid action "{action}"
|
||||
collection-only: Only collection actions are allowed. Invalid action "{action}"
|
||||
noop-requires-list: '"no-op" action should only be used with "list" action'
|
||||
list-requires-noop: '"list" action should be used with "no-op" action to have an effect'
|
||||
noop-unnecessary: '"no-op" action is unnecessary when other actions are specified'
|
||||
editor:
|
||||
remove: Remove
|
||||
remove-comma: Remove comma
|
||||
add-noop: Add "no-op" action
|
||||
output-fields:
|
||||
empty-list: '"output_fields" field does not contain at least one field'
|
||||
wildcard-cannot-combine: Wildcard output field "*" cannot be combined with other fields
|
||||
duplicate-field: Duplicate output field "{field}"
|
||||
invalid-field: Invalid output field "{field}"
|
||||
invalid-field-for-type: Invalid output field "{field}" for type "{type}"
|
||||
editor:
|
||||
title: Grant Editor
|
||||
description: Add each new grant string on a different line. Press Ctrl+Space to get suggestions.
|
||||
string-formats:
|
||||
title_plural: String formats
|
||||
syntax: Syntax
|
||||
syntax-format: Syntax format
|
||||
more-info: More info
|
||||
resource-type:
|
||||
label: Singular resource type
|
||||
help: Applies collection actions to a specific resource type in a single grant.
|
||||
resource:
|
||||
label: Specific resource(s)
|
||||
help: Applies ID actions to one or more individual resources in a single grant.
|
||||
pinned-id:
|
||||
label: Pinned ID
|
||||
help: '"Pins" actions to a non-top-level type within a specific ID in a single grant.'
|
||||
output-fields:
|
||||
label: Output fields
|
||||
help: Defines which fields from a request are returned to the user.
|
||||
account-template:
|
||||
label: Account template
|
||||
help: Substitutes the account ID from the token used to perform the request.
|
||||
user-template:
|
||||
label: User template
|
||||
help: Substitutes the user ID from the token used to perform the request.
|
||||
insert-actions: insert actions
|
||||
insert-resource-type: insert resource type
|
||||
insert-ids: insert ids
|
||||
insert-resource-ids: insert resource ids
|
||||
insert-output-fields: insert output fields
|
||||
export:
|
||||
format: Format
|
||||
formatted-export-aria-label: Formatted export
|
||||
options:
|
||||
terraform: Terraform
|
||||
native-hcl: Native HCL
|
||||
actions:
|
||||
collection-actions: Collection Actions
|
||||
id-actions: ID Actions
|
||||
create: Create a new {resourceType}
|
||||
list: List all {resourceType} resources
|
||||
read: Read {article} {resourceType}
|
||||
update: Update {article} {resourceType}
|
||||
delete: Delete {article} {resourceType}
|
||||
read:self: Read the {resourceType} associated with the current user
|
||||
delete:self: Delete {article} {resourceType} associated with the current user
|
||||
cancel: Cancel {article} {resourceType}
|
||||
cancel:self: Cancel {article} {resourceType} associated with the current user
|
||||
generic:
|
||||
create: Creates resources
|
||||
list: Lists resources
|
||||
read: Reads resources
|
||||
update: Updates resources
|
||||
delete: Deletes resources
|
||||
read:self: Read the resource associated with the current user
|
||||
delete:self: Delete a resource associated with the current user
|
||||
cancel: Cancels resources
|
||||
cancel:self: Cancel a resource associated with the current user
|
||||
add-members: Add members to a group
|
||||
set-members: Set the full list of members on a group
|
||||
remove-members: Remove members from a group
|
||||
add-hosts: Add hosts to a host set
|
||||
set-hosts: Set the full list of hosts on a host set
|
||||
remove-hosts: Remove hosts from a host set
|
||||
add-worker-tags: Add tags to a worker
|
||||
set-worker-tags: Set the full list of tags on a worker
|
||||
remove-worker-tags: Remove tags from a worker
|
||||
reinitialize-certificate-authority: Reinitialize the certificate authority that is used to authorize Threshold workers
|
||||
create:controller-led: Create a worker via controller-led workflow
|
||||
create:worker-led: Create a worker via worker-led workflow
|
||||
read-certificate-authority: Read the certificate authority that is used to authorize Threshold workers
|
||||
set-password: Set a password on an account, without requiring the current password
|
||||
change-password: Change the password on an account
|
||||
authenticate: Authenticate to an auth method
|
||||
change-state: Change the active and visibility state of an OIDC-type auth method
|
||||
add-accounts: Add accounts to a user
|
||||
set-accounts: Set the full list of accounts on a user
|
||||
remove-accounts: Remove accounts from a user
|
||||
list-resolvable-aliases: List all aliases that point to resources the user has permission to access
|
||||
add-principals: Add principals to a role
|
||||
set-principals: Set the full list of principals on a role
|
||||
remove-principals: Remove principals from a role
|
||||
set-grants: Set the full list of grants on a role
|
||||
add-grants: Add grants to a role
|
||||
remove-grants: Remove grants from a role
|
||||
add-grant-scopes: Add grant scopes to a role
|
||||
set-grant-scopes: Set the full list of grant scopes on a role
|
||||
remove-grant-scopes: Remove grant scopes from a role
|
||||
authorize-session: Authorize a session via a target
|
||||
add-host-sources: Add host sources to a target
|
||||
set-host-sources: Set the full list of host sources on a target
|
||||
remove-host-sources: Remove host sources from a target
|
||||
add-credential-sources: Add credential sources to a target
|
||||
set-credential-sources: Set the full list of credential sources on a target
|
||||
remove-credential-sources: Remove credential sources from a target
|
||||
attach-storage-policy: Attach a storage policy to all session recordings in the scope
|
||||
detach-storage-policy: Detach a storage policy from all session recordings in the scope
|
||||
set-alias-suffix: Set the alias suffix on a scope
|
||||
remove-alias-suffix: Remove the alias suffix from a scope
|
||||
list-keys: List the keys within a given scope
|
||||
rotate-keys: Replace a scope's current KEK and DEKs with a new set of keys
|
||||
list-key-version-destruction-jobs: List all pending key version destruction jobs within a scope
|
||||
destroy-key-version: Destroy a key version in the scope
|
||||
download: Download a session recording
|
||||
reapply-storage-policy: Reapply the storage policy to a session recording
|
||||
list-exports: List all export jobs for session recordings
|
||||
export: Export a session recording
|
||||
export:cancel: Cancel an in-progress session recording export
|
||||
monthly-active-users: Display the number of monthly active Threshold users to help predict billing
|
||||
no-actions: No actions available for this resource type.
|
||||
invalid-id-or-type: Invalid ID and type. No actions available.
|
||||
no-type-detected: No resource type detected.
|
||||
unavailable: Grant actions information not available.
|
||||
scope:
|
||||
title: Scope
|
||||
title_plural: Scopes
|
||||
@@ -745,7 +936,7 @@ target:
|
||||
title: Welcome to Targets
|
||||
none:
|
||||
title: No Targets Available
|
||||
description: No Targets to display yet. Contact your Boundary admin if you don't see a Target you expect access to.
|
||||
description: No Targets to display yet. Contact your Threshold admin if you don't see a Target you expect access to.
|
||||
connection-success:
|
||||
title: Successfully Connected
|
||||
connection-details:
|
||||
@@ -862,10 +1053,10 @@ target:
|
||||
description: Select injected Application credential sources to assign to this target.
|
||||
alert:
|
||||
title: Cannot connect to this target without injected application credentials
|
||||
description: Injected application credentials are used by Boundary to sign in to remote resources without showing them to the end-user.
|
||||
description: Injected application credentials are used by Threshold to sign in to remote resources without showing them to the end-user.
|
||||
action: Add injected application credentials
|
||||
workers:
|
||||
description: You can customize how your workers route traffic to this target by setting up a filter that matches specific worker tags. If your target is in a private network, we recommend setting up an egress filter to specify a worker inside the network so that Boundary can access the host. Ingress and egress filters are allowed to select the same worker.
|
||||
description: You can customize how your workers route traffic to this target by setting up a filter that matches specific worker tags. If your target is in a private network, we recommend setting up an egress filter to specify a worker inside the network so that Threshold can access the host. Ingress and egress filters are allowed to select the same worker.
|
||||
messages:
|
||||
none:
|
||||
description: You haven't added an {type} worker filter yet.
|
||||
@@ -891,7 +1082,7 @@ target:
|
||||
description: Specify workers that have access to the target (such as within a private network).
|
||||
edit-ingress-worker-filter:
|
||||
title: Edit Ingress Worker Filter
|
||||
description: Specify workers Boundary should use to start the route to this target.
|
||||
description: Specify workers Threshold should use to start the route to this target.
|
||||
worker-filter:
|
||||
description: The worker filter format for this target.
|
||||
accordion-label:
|
||||
@@ -907,7 +1098,7 @@ credential-store:
|
||||
titles:
|
||||
new: New Credential Store
|
||||
worker-filter:
|
||||
description: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Boundary.
|
||||
description: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Threshold.
|
||||
messages:
|
||||
none:
|
||||
description: You haven't added a worker filter yet.
|
||||
@@ -1151,20 +1342,20 @@ worker:
|
||||
view_more: View more tags
|
||||
form:
|
||||
cluster_id:
|
||||
label: Boundary Cluster ID
|
||||
label: Threshold Cluster ID
|
||||
help: The cluster ID is the UUID in the HCP Boundary Controller URL.
|
||||
ip_address:
|
||||
label: Worker public address
|
||||
help: Public IP or address.
|
||||
initial_upstreams:
|
||||
label: Initial Upstreams
|
||||
help: A list of hosts/IP addresses and optionally ports for reaching Boundary cluster. The port will default to :9201 if not specified.
|
||||
help: A list of hosts/IP addresses and optionally ports for reaching Threshold cluster. The port will default to :9201 if not specified.
|
||||
config_file_path:
|
||||
label: Config file path
|
||||
help: The path on your server where you will store your configuration file.
|
||||
worker_tags:
|
||||
label: Worker Tags
|
||||
help: Boundary can use tags to define key-value pairs which targets can use to determine where they should route connections.
|
||||
help: Threshold can use tags to define key-value pairs which targets can use to determine where they should route connections.
|
||||
local_session_recording_storage:
|
||||
label: Local Session Recording Storage
|
||||
help: To record sessions using this worker, specify a local storage path and ensure that it is provisioned with sufficient capacity. In progress recordings are stored locally until they complete, and then moved to the remote storage bucket associated with their target.
|
||||
@@ -1180,8 +1371,8 @@ worker:
|
||||
create_config: 'Open the file with a text editor, such as Vi. Paste the following configuration into the worker config file:'
|
||||
save_config: Save the configuration in your <code>pki-worker.hcl</code> file.
|
||||
2:
|
||||
title: 2. Install & start Boundary on your server
|
||||
run_command: 'Run this command on your server to install Boundary:'
|
||||
title: 2. Install & start Threshold on your server
|
||||
run_command: 'Run this command on your server to install Threshold:'
|
||||
copy_registration_request: When the server starts, it will print details related to this worker. Copy the value for
|
||||
3:
|
||||
title: 3. Confirm the worker when they connect
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div
|
||||
data-test-code-editor-field-editor
|
||||
...attributes
|
||||
class='rose-codemirror-container'
|
||||
{{code-mirror value=@value onInput=@onInput options=@options}}
|
||||
></div>
|
||||
@@ -1,13 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div data-test-code-editor>
|
||||
{{yield
|
||||
(hash
|
||||
fieldEditor=(component 'rose/code-editor/field-editor' value=@codeValue)
|
||||
toolbar=(component 'rose/code-editor/toolbar' copyText=@codeValue)
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div class='rose-code-editor-toolbar' data-test-code-editor-toolbar>
|
||||
<div data-test-code-editor-toolbar-action class='code-editor-toolbar__action'>
|
||||
{{yield to='action'}}
|
||||
</div>
|
||||
|
||||
<div class='rose-code-editor-toolbar__right-aligned'>
|
||||
{{#if (has-block 'menu')}}
|
||||
<div
|
||||
data-test-code-editor-toolbar-menu
|
||||
class='rose-code-editor-toolbar__menu'
|
||||
>
|
||||
{{yield to='menu'}}
|
||||
</div>
|
||||
<div
|
||||
class='rose-code-editor-toolbar__menu-divider'
|
||||
data-test-code-editor-toolbar-menu-divider
|
||||
></div>
|
||||
{{/if}}
|
||||
<Hds::Copy::Button
|
||||
@text='Copy code'
|
||||
@textToCopy={{@copyText}}
|
||||
@onSuccess={{this.copied}}
|
||||
data-test-code-editor-toolbar-copy-button
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class RoseCodeEditorToolbarComponent extends Component {
|
||||
@action
|
||||
copied() {
|
||||
this.args.onCopy?.();
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Modifier from 'ember-modifier';
|
||||
import codemirror from 'codemirror';
|
||||
|
||||
import '../utilities/register-codemirror-hcl';
|
||||
import 'codemirror/mode/go/go';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/addon/edit/matchbrackets';
|
||||
import 'codemirror/addon/edit/closebrackets';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/display/autorefresh';
|
||||
import 'codemirror/addon/lint/lint';
|
||||
import 'codemirror/addon/lint/json-lint';
|
||||
|
||||
// Here we define default options for the editor.
|
||||
// These should follow the codemirror configuration types
|
||||
//
|
||||
const _PRESET_DEFAULTS = {
|
||||
theme: 'monokai',
|
||||
lineNumbers: true,
|
||||
cursorBlinkRate: 500,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
styleActiveLine: true,
|
||||
mode: 'hcl',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* `CodeMirror` implements a modifier that creates a CodeMirror instance on the provided element.
|
||||
*
|
||||
* The supported modes for the editor are currently: hcl, shell, go, javascript
|
||||
* The supported themes are: monokai
|
||||
*
|
||||
* Sample usage:
|
||||
* ```
|
||||
* <div
|
||||
* {{code-mirror value=@value onInput=@onInput options=@options}}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @class CodeMirror
|
||||
*
|
||||
*/
|
||||
export default class CodeMirrorModifier extends Modifier {
|
||||
_editor;
|
||||
didSetup = false;
|
||||
|
||||
modify(element, _, named) {
|
||||
if (!this.didSetup) {
|
||||
this._setup(element, named);
|
||||
this.didSetup = true;
|
||||
}
|
||||
|
||||
if (this._editor?.getValue() !== named.value) {
|
||||
this._editor?.setValue(named.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when editor contents change.
|
||||
* @method CodeMirror#_onChange
|
||||
* See documentation for the CodeMirror editor events, specifically 'change' event here https://codemirror.net/doc/manual.html#events.
|
||||
*/
|
||||
_onChange(editor, named) {
|
||||
const { onInput } = named;
|
||||
const newVal = editor.getValue();
|
||||
|
||||
onInput && onInput(newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the editor with default configuration merged with consumer-provided configuration.
|
||||
* Also registers a 'change' event on the editor.
|
||||
* @method CodeMirror#_setup
|
||||
*/
|
||||
_setup(element, named) {
|
||||
if (!element) {
|
||||
throw new Error('CodeMirror modifier has no element');
|
||||
}
|
||||
|
||||
const editor = codemirror(element, {
|
||||
..._PRESET_DEFAULTS,
|
||||
...(named.options || {}),
|
||||
value: named.value,
|
||||
});
|
||||
|
||||
editor.on('change', (editor) => {
|
||||
this._onChange(editor, named);
|
||||
});
|
||||
|
||||
this._editor = editor;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
form {
|
||||
[class*='hds-form-field--layout'],
|
||||
[class*='hds-form-group--layout'],
|
||||
[class='hds-form-key-value-inputs'],
|
||||
.rose-form-actions.hds-button-set {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dark-mode-badge-count-override {
|
||||
// Override the neutral badge count to fix contrast in dark mode.
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.hds-badge-count--color-neutral.hds-badge-count--type-inverted {
|
||||
color: var(--token-color-palette-neutral-0);
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
}
|
||||
|
||||
@mixin dark-mode-ember-dropdown-override {
|
||||
.ember-basic-dropdown-content {
|
||||
background-color: var(--token-color-palette-neutral-0);
|
||||
@@ -191,20 +200,95 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides for CodeMirror 6 autocomplete and lint tooltips/panels.
|
||||
@mixin dark-mode-codemirror-override {
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
// Autocomplete dropdown and lint hover tooltips
|
||||
.cm-tooltip {
|
||||
background-color: var(--token-color-palette-neutral-50);
|
||||
border: 1px solid var(--token-color-palette-neutral-100);
|
||||
color: var(--token-color-palette-neutral-400);
|
||||
}
|
||||
|
||||
// Arrow pointer for tooltips shown above the cursor
|
||||
.cm-tooltip-above::before {
|
||||
border-top-color: var(--token-color-palette-neutral-50);
|
||||
}
|
||||
|
||||
// Arrow pointer for tooltips shown below the cursor
|
||||
.cm-tooltip-below::before {
|
||||
border-bottom-color: var(--token-color-palette-neutral-50);
|
||||
}
|
||||
|
||||
// Autocomplete list > selected item highlight
|
||||
.cm-tooltip-autocomplete > ul > li[aria-selected] {
|
||||
background-color: var(--token-color-palette-blue-300);
|
||||
color: var(--token-color-palette-neutral-700);
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
// Content/gutter border
|
||||
.cm-content {
|
||||
border-left: 1px solid var(--token-color-palette-neutral-100);
|
||||
}
|
||||
|
||||
// Lint diagnostics panel (shown at the bottom of the editor)
|
||||
.cm-panel.cm-panel-lint {
|
||||
border-top: 3px solid var(--token-color-palette-neutral-100);
|
||||
|
||||
ul {
|
||||
background-color: var(--token-color-palette-neutral-100);
|
||||
|
||||
// Lint panel > selected diagnostic item highlight
|
||||
&:focus li.cm-diagnostic[aria-selected] {
|
||||
background-color: var(--token-color-palette-neutral-50);
|
||||
color: var(--token-color-palette-neutral-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
}
|
||||
|
||||
// Override to improve visibility of search clear ("x") icon.
|
||||
@mixin dark-mode-search-clear-override {
|
||||
input[type='search']::-webkit-search-cancel-button {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
// Apply dark style overrides to scrollbar when system is in dark mode and app theme is not set to light
|
||||
html:has(.ember-application:not(.rose-theme-light)) {
|
||||
scrollbar-color: var(--token-color-palette-neutral-400)
|
||||
var(--token-color-palette-neutral-600);
|
||||
}
|
||||
|
||||
.ember-application:not(.rose-theme-light) {
|
||||
@include dark-mode;
|
||||
@include dark-mode-modal-override;
|
||||
@include dark-mode-badge-count-override;
|
||||
@include dark-mode-ember-dropdown-override;
|
||||
@include dark-mode-app-header-override;
|
||||
@include dark-mode-app-side-nav-override;
|
||||
@include dark-mode-codemirror-override;
|
||||
@include dark-mode-search-clear-override;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply dark style overrides to scrollbar when app theme is set to dark regardless of system theme
|
||||
html:has(.ember-application.rose-theme-dark) {
|
||||
scrollbar-color: var(--token-color-palette-neutral-400)
|
||||
var(--token-color-palette-neutral-600);
|
||||
}
|
||||
|
||||
.ember-application.rose-theme-dark {
|
||||
@include dark-mode;
|
||||
@include dark-mode-modal-override;
|
||||
@include dark-mode-badge-count-override;
|
||||
@include dark-mode-ember-dropdown-override;
|
||||
@include dark-mode-app-header-override;
|
||||
@include dark-mode-app-side-nav-override;
|
||||
@include dark-mode-codemirror-override;
|
||||
@include dark-mode-search-clear-override;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import codemirror from 'codemirror';
|
||||
import 'codemirror/addon/mode/simple';
|
||||
|
||||
// This utility defines 'hcl' mode for a codemirror editor and enables correct syntax highlighting.
|
||||
// Importing this file in the codemirror modifier will allow 'hcl' to be selected as a mode:
|
||||
// import 'cloud-ui-core/utils/register-codemirror-hcl'
|
||||
codemirror.defineSimpleMode('hcl', {
|
||||
start: [
|
||||
{ regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: 'string' }, // strings
|
||||
{ regex: /(\w+)(\s+)(=)/, token: ['keyword', 'null', 'null'] }, // assignment of variables
|
||||
{
|
||||
regex: /(build|deploy|release|hook|registry|type|runner|url)( )({)/,
|
||||
token: ['keyword', 'null', 'null'],
|
||||
}, // top level keywords
|
||||
{ regex: /(variable)\b/, token: 'keyword' }, // input variable keyword
|
||||
{ regex: /true|false|null|undefined/, token: 'atom' }, // bool keywords
|
||||
{
|
||||
regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
|
||||
token: 'number',
|
||||
}, // numbers
|
||||
{ regex: /(#|\/\/)(\s*\S*)/, token: 'comment' }, // single line comments
|
||||
{ regex: /(path)(.)/, token: ['variable-2', 'null'] }, // path variables
|
||||
{ regex: /(workspace)(.)(\S*)/, token: ['string', 'null', 'string'] }, // workspace variables
|
||||
{ regex: /\/\*/, token: 'comment', next: 'comment' }, // multi-line comment
|
||||
{ regex: /[-+/*=<>!]+/, token: 'operator' }, // operators
|
||||
{ regex: /[{[(]/, indent: true }, // for auto indent
|
||||
{ regex: /[}\])]/, dedent: true },
|
||||
],
|
||||
// The multi-line comment state.
|
||||
comment: [
|
||||
{ regex: /.*?\*\//, token: 'comment', next: 'start' },
|
||||
{ regex: /.*/, token: 'comment' },
|
||||
],
|
||||
meta: {
|
||||
dontIndentStates: ['comment'],
|
||||
lineComment: '#',
|
||||
},
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor/field-editor';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor/toolbar';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/modifiers/code-mirror';
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
// This list is sorted in ascending order to improve readability
|
||||
|
||||
@use 'code-editor';
|
||||
@use 'form';
|
||||
@use 'frame';
|
||||
@use 'layout';
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
@use '../../variables/sizing';
|
||||
|
||||
// Codemirror overrides
|
||||
.rose-codemirror-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-family: var(--token-typography-font-stack-code);
|
||||
}
|
||||
|
||||
.rose-autosize {
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-sizer {
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
.rose-code-editor-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--ui-gray-subtler-3);
|
||||
height: 2.75rem;
|
||||
|
||||
&__action {
|
||||
margin-left: sizing.rems(s);
|
||||
}
|
||||
|
||||
&__right-aligned {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
margin-right: sizing.rems(s);
|
||||
|
||||
.pds-dropdownTrigger {
|
||||
box-shadow: none;
|
||||
|
||||
&__layoutWrapper {
|
||||
padding-left: sizing.rems(s);
|
||||
padding-right: sizing.rems(s);
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
border-right: 1px solid var(--ui-gray-subtler-3);
|
||||
height: sizing.rems(xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
'header header header'
|
||||
'navigation navigation navigation'
|
||||
'body body body';
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(3, auto);
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
.rose-layout-page-header {
|
||||
|
||||
@@ -12,11 +12,6 @@ module.exports = {
|
||||
included(app) {
|
||||
this._super.included.apply(this, arguments);
|
||||
|
||||
this.import('node_modules/codemirror/lib/codemirror.css');
|
||||
this.import('node_modules/codemirror/theme/monokai.css');
|
||||
this.import('node_modules/codemirror/addon/lint/lint.css');
|
||||
this.import('node_modules/jsonlint/lib/jsonlint.js');
|
||||
|
||||
this.includeHDSStyles(app);
|
||||
this.includeFlightIcons(app);
|
||||
this.includePublic(app);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "rose",
|
||||
"version": "0.0.0",
|
||||
"description": "Styles and components for Boundary.",
|
||||
"description": "Styles and components for Threshold.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"keywords": [
|
||||
"ember-addon"
|
||||
],
|
||||
@@ -34,16 +34,14 @@
|
||||
"@hashicorp/design-system-tokens": "^2.3.0",
|
||||
"@hashicorp/flight-icons": "^3.10.0",
|
||||
"@nullvoxpopuli/ember-composable-helpers": "^5.2.10",
|
||||
"codemirror": "5.65.7",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-babel": "^8.3.1",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-sass": "^11.0.1",
|
||||
"ember-focus-trap": "^1.0.1",
|
||||
"ember-named-blocks-polyfill": "^0.2.5",
|
||||
"jsonlint": "^1.6.3",
|
||||
"sass": "^1.69.5",
|
||||
"uuid": "^11.0.3"
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
@@ -54,7 +52,7 @@
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"doctoc": "^2.2.0",
|
||||
"ember-cli": "~5.12.0",
|
||||
"ember-cli-clean-css": "^3.0.0",
|
||||
@@ -84,7 +82,7 @@
|
||||
"@eslint/js": "^9.18.0",
|
||||
"globals": "^15.14.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss": "^8.5.10",
|
||||
"prettier": "^3.3.3",
|
||||
"qunit": "^2.22.0",
|
||||
"qunit-dom": "^3.2.1",
|
||||
@@ -98,7 +96,7 @@
|
||||
"ember-source": ">= 4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | rose/code-editor', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const codeValue = 'export HCP_CLIENT_ID = {place your client_id here}';
|
||||
this.set('codeValue', codeValue);
|
||||
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor
|
||||
@codeValue={{this.codeValue}}
|
||||
as |c|
|
||||
>
|
||||
<c.toolbar />
|
||||
<c.fieldEditor />
|
||||
</Rose::CodeEditor>
|
||||
`);
|
||||
assert.dom('[data-test-code-editor]').isVisible();
|
||||
assert.dom('[data-test-code-editor-toolbar]').isVisible();
|
||||
assert.dom('[data-test-code-editor-field-editor]').isVisible();
|
||||
assert.dom('[data-test-code-editor-field-editor]').includesText(codeValue);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { typeIn, render, waitUntil } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module(
|
||||
'Integration | Component | rose/code-editor/field-editor',
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
const editorSelector = '[data-test-code-editor-field-editor]';
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
this.set('codeValue', codeValue);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
assert.dom(editorSelector).includesText(myNewCode);
|
||||
});
|
||||
|
||||
test('it renders readonly', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
this.set('codeValue', codeValue);
|
||||
this.set('options', {
|
||||
readOnly: true,
|
||||
});
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} @options={{this.options}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
waitUntil(async () => {
|
||||
try {
|
||||
// This should fail if the textarea is readonly
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
assert.dom(editorSelector).doesNotIncludeText(myNewCode);
|
||||
});
|
||||
|
||||
test('it calls onInput on change', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
const onInput = () => {
|
||||
this.set('called', true);
|
||||
};
|
||||
this.set('codeValue', codeValue);
|
||||
this.set('onInput', onInput);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} @onInput={{this.onInput}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
assert.dom(editorSelector).includesText(myNewCode);
|
||||
assert.true(this.called);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | rose/code-editor/toolbar', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
const toolbarSelector = '[data-test-code-editor-toolbar]';
|
||||
const copyButtonSelector = '[data-test-code-editor-toolbar-copy-button]';
|
||||
const menuDividerSelector = '[data-test-code-editor-toolbar-menu-divider]';
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
const copyText = 'copyText';
|
||||
this.set('copyText', copyText);
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @copyText={{this.copyText}} />
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
});
|
||||
|
||||
test('it displays action', async function (assert) {
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @onCopy={{this.copyText}}>
|
||||
<:action>
|
||||
<div data-test-my-action>Hello</div>
|
||||
</:action>
|
||||
</Rose::CodeEditor::Toolbar>
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
assert.dom('[data-test-my-action]').isVisible();
|
||||
});
|
||||
|
||||
test('it calls onCopy callback', async function (assert) {
|
||||
// `writeText` could fail if the document is not in focus which is common during test runs
|
||||
sinon.stub(window.navigator.clipboard, 'writeText').resolves();
|
||||
|
||||
const onCopy = () => {
|
||||
this.set('called', true);
|
||||
};
|
||||
this.set('onCopy', onCopy);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @copyText={{this.copyText}} @onCopy={{this.onCopy}} />
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
await click(copyButtonSelector);
|
||||
assert.true(this.called);
|
||||
|
||||
sinon.restore();
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 40 KiB |
@@ -97,31 +97,8 @@ If you would like to do it through the command line:
|
||||
```bash
|
||||
doormat login
|
||||
source <(doormat aws export --account boundary_team_acctest_dev)
|
||||
# Feel free to use a custom name for your key pair if you don't want to use $USER, just make sure it follows the <name>_enos format so it can be identified. e.g. zed_enos
|
||||
ssh-keygen -N '' -t ed25519 -f ~/.ssh/"$USER"_enos
|
||||
aws ec2 import-key-pair \
|
||||
--region us-east-1 \
|
||||
--key-name "$USER"_enos \
|
||||
--public-key-material fileb://~/.ssh/"$USER"_enos.pub
|
||||
```
|
||||
|
||||
### Setup Enos:
|
||||
|
||||
Make sure you're in the correct boundary repository for the version of Boundary you plan on using.
|
||||
The enos scenarios for CE need to be run in `boundary` and the enos scenarios for enterprise need to be run in `boundary-enterprise`.
|
||||
|
||||
Enos needs some configuration variables to run the scenario successfully. [See the configuration file](https://github.com/hashicorp/boundary/blob/main/enos/enos.vars.hcl). The file has comments per each variable, but some awareness:
|
||||
|
||||
- `boundary_edition`: The edition of Boundary you are using. Options are `oss` or `enterprise`.
|
||||
- `aws_region`: The AWS region you are using. Very important as mentioned within the EC2 Setup.
|
||||
- `aws_ssh_keypair_name`: The name of the AWS keypair.
|
||||
- `aws_ssh_private_key_path`: The path to the private key associated with your keypair.
|
||||
- `enos_user`: The user name to use for tagged resources in AWS.
|
||||
- `e2e_debug_no_run`: Make sure this is set to true.
|
||||
- `boundary_license_path` (enterprise only): Path to an enterprise license
|
||||
|
||||
More documentation about [scenario variables](https://github.com/hashicorp/boundary/tree/main/enos#scenarios-variables).
|
||||
|
||||
## Run tests:
|
||||
Make sure you followed all the steps within the [Getting started section](#getting-started).
|
||||
|
||||
@@ -157,6 +134,7 @@ Using Terminal 1:
|
||||
- `cd boundary/enos` or `cd boundary_enterprise/enos`.
|
||||
- `doormat login`. Login with Doormat.
|
||||
- `eval "$(doormat aws export --account boundary_team_acctest_dev)"`. Exporting AWS env variables from doormat to your terminal.
|
||||
- `export ENOS_VAR_boundary_license={LICENSE_KEY}`. If running enterprise, you will need a license key
|
||||
- `enos scenario launch e2e_ui_aws builder:local protocol:https` or `enos scenario launch e2e_ui_aws_ent builder:local protocol:https` if in enterprise.
|
||||
- Launches enos scenario, this will take from 5 to 10 minutes. When its done, you will see a Enos Operations finished! within your terminal. Check out more scenarios [here](https://github.com/hashicorp/boundary/tree/main/enos).
|
||||
- `bash scripts/test_e2e_env.sh`. Prints all the env variables within Enos scenario. Copy the output and paste it within your Terminal 2 (Boundary UI). These env variables are needed within Boundary UI to run the test against the enos scenario.
|
||||
@@ -324,7 +302,7 @@ If working on an E2E test that is working against a different version of the bou
|
||||
# in the following example $COMMITISH can be replaced with a branch name, commit, or tag
|
||||
OPENAPI_SWAGGER_URL_OR_FILE=https://raw.githubusercontent.com/hashicorp/boundary/$COMMITISH/internal/gen/controller.swagger.json pnpm generate:api-client
|
||||
|
||||
# using a local absolute or relative path
|
||||
# using a local absolute or relative path
|
||||
OPENAPI_SWAGGER_URL_OR_FILE=../../boundary-enterprise/internal/gen/controller.swagger.json pnpm generate:api-client
|
||||
```
|
||||
|
||||
|
||||
@@ -64,12 +64,21 @@ export class RolesPage extends BaseResourcePage {
|
||||
*/
|
||||
async addGrantsToRole(grants) {
|
||||
await this.page.getByRole('link', { name: 'Grants', exact: true }).click();
|
||||
await this.page.getByRole('textbox', { name: 'New Grant' }).fill(grants);
|
||||
await this.page.getByRole('button', { name: 'Add', exact: true }).click();
|
||||
await this.page
|
||||
.getByRole('link', { name: 'Edit Grants', exact: true })
|
||||
.click();
|
||||
await this.page.locator('.hds-code-editor').getByRole('textbox').click();
|
||||
await expect(
|
||||
this.page.locator('.hds-code-editor').getByRole('textbox'),
|
||||
).toBeFocused();
|
||||
await this.page
|
||||
.locator('.hds-code-editor')
|
||||
.getByRole('textbox')
|
||||
.fill(grants);
|
||||
await this.page.getByRole('button', { name: 'Save', exact: true }).click();
|
||||
await this.dismissSuccessAlert();
|
||||
await expect(
|
||||
this.page.getByRole('textbox', { name: 'Grant', exact: true }),
|
||||
).toHaveValue(grants);
|
||||
this.page.locator('.grant-codeblock .hds-code-block__code'),
|
||||
).toHaveText(grants);
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,10 @@ export class StorageBucketsPage extends BaseResourcePage {
|
||||
await this.page.getByLabel('Region').fill(region);
|
||||
await this.page.getByLabel('Access key ID').fill(accessKeyId);
|
||||
await this.page.getByLabel('Secret access key').fill(secretAccessKey);
|
||||
await this.page
|
||||
.getByLabel('Worker filter')
|
||||
.first()
|
||||
.scrollIntoViewIfNeeded();
|
||||
await this.page
|
||||
.getByLabel('Worker filter')
|
||||
.getByRole('textbox')
|
||||
|
||||
@@ -58,7 +58,7 @@ export class TargetsPage extends BaseResourcePage {
|
||||
if (alias) {
|
||||
await this.page
|
||||
.getByRole('group', { name: 'Aliases' })
|
||||
.getByLabel('value')
|
||||
.getByLabel('Aliases')
|
||||
.last()
|
||||
.fill(alias);
|
||||
await this.page.getByRole('button', { name: 'Add' }).click();
|
||||
@@ -145,7 +145,10 @@ export class TargetsPage extends BaseResourcePage {
|
||||
.getByText('Edit Ingress Worker Filter'),
|
||||
).toBeVisible();
|
||||
|
||||
await this.page.locator('.CodeMirror').getByRole('textbox').fill(filter);
|
||||
await this.page
|
||||
.locator('.hds-code-editor')
|
||||
.getByRole('textbox')
|
||||
.fill(filter);
|
||||
|
||||
await this.page.getByRole('button', { name: 'Save', exact: true }).click();
|
||||
await this.dismissSuccessAlert();
|
||||
@@ -174,7 +177,10 @@ export class TargetsPage extends BaseResourcePage {
|
||||
.getByText('Edit Egress Worker Filter'),
|
||||
).toBeVisible();
|
||||
|
||||
await this.page.locator('.CodeMirror').getByRole('textbox').fill(filter);
|
||||
await this.page
|
||||
.locator('.hds-code-editor')
|
||||
.getByRole('textbox')
|
||||
.fill(filter);
|
||||
|
||||
await this.page.getByRole('button', { name: 'Save', exact: true }).click();
|
||||
await this.dismissSuccessAlert();
|
||||
|
||||
@@ -174,8 +174,8 @@ test(
|
||||
// Log in using ldap account
|
||||
await loginPage.logout(adminLoginName);
|
||||
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: ldapAuthMethodName }).click();
|
||||
await loginPage.login(ldapUserName, ldapUserPassword);
|
||||
await expect(
|
||||
@@ -193,6 +193,9 @@ test(
|
||||
|
||||
// View the LDAP account and verify account attributes
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Projects' }),
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Primary' })
|
||||
.getByRole('link', { name: 'Auth Methods' })
|
||||
|
||||
@@ -129,8 +129,8 @@ test(
|
||||
|
||||
// Log in using oidc account
|
||||
await loginPage.logout(adminLoginName);
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: oidcAuthMethodName }).click();
|
||||
const pagePromise = context.waitForEvent('page');
|
||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||
|
||||
@@ -58,8 +58,8 @@ test(
|
||||
|
||||
// Log in using new account
|
||||
await loginPage.logout(adminLoginName);
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: authMethodName }).click();
|
||||
|
||||
await loginPage.login(username, password);
|
||||
@@ -97,8 +97,8 @@ test(
|
||||
// Log in using new password
|
||||
await loginPage.logout(adminLoginName);
|
||||
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: authMethodName }).click();
|
||||
await loginPage.login(username, newPassword);
|
||||
await expect(
|
||||
|
||||
@@ -52,8 +52,8 @@ test(
|
||||
|
||||
// Log in using new account
|
||||
await page.reload();
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: org.name }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: org.name }).click();
|
||||
await page.getByRole('link', { name: authMethod.name }).click();
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login(username, password);
|
||||
@@ -79,8 +79,8 @@ test(
|
||||
|
||||
// Confirm user cannot log in with old password
|
||||
await loginPage.logout(username);
|
||||
await page.getByText('Choose a different scope').click();
|
||||
await page.getByRole('link', { name: org.name }).click();
|
||||
await page.getByRole('button', { name: /global/i }).click();
|
||||
await page.getByRole('option', { name: org.name }).click();
|
||||
await page.getByRole('link', { name: authMethod.name }).click();
|
||||
await page.getByLabel('Login Name').fill(username);
|
||||
await page.getByLabel('Password', { exact: true }).fill(password);
|
||||
|
||||
@@ -195,7 +195,9 @@ test(
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page.getByRole('combobox', { name: 'Type' }).click();
|
||||
await page.getByRole('option', { name: 'JSON' }).click();
|
||||
await page.getByText('{}').click();
|
||||
await page.locator('.hds-code-editor').getByRole('textbox').click();
|
||||
// Move cursor to middle of '{}' to avoid typing outside of the braces
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
const testName = 'name-json';
|
||||
const testPassword = 'password-json';
|
||||
const testId = 'id-json';
|
||||
|
||||
@@ -52,6 +52,9 @@ test(
|
||||
.getByRole('navigation', { name: 'Primary' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Host Catalogs' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('link', { name: hostCatalogName }).click();
|
||||
const hostSetName2 = await hostCatalogsPage.createHostSet();
|
||||
|
||||
@@ -233,19 +236,16 @@ test(
|
||||
).toBeVisible();
|
||||
await expect(page.getByText('"dev" in "/tags/type"')).toBeVisible();
|
||||
|
||||
await page
|
||||
.locator('.CodeMirror')
|
||||
.getByRole('textbox')
|
||||
.click({ force: true });
|
||||
await page.locator('.hds-code-editor').getByRole('textbox').click();
|
||||
await expect(
|
||||
page.locator('.CodeMirror').getByRole('textbox'),
|
||||
page.locator('.hds-code-editor').getByRole('textbox'),
|
||||
).toBeFocused();
|
||||
const selectAllShortcut =
|
||||
process.platform === 'darwin' ? 'Meta+A' : 'Control+A';
|
||||
await page.keyboard.press(selectAllShortcut);
|
||||
await page.keyboard.press('Backspace');
|
||||
await page
|
||||
.locator('.CodeMirror')
|
||||
.locator('.hds-code-editor')
|
||||
.getByRole('textbox')
|
||||
.fill('"prod" in "/tags/type"');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
@@ -133,6 +133,8 @@ export const electronTest = test.extend({
|
||||
electronPage: async ({ electronApp }, use) => {
|
||||
const page = await electronApp.firstWindow();
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
// Reset the cluster URL stored by electron
|
||||
await page.evaluate(() => window.desktop.cluster.resetClusterUrl());
|
||||
await page.context().tracing.start({ screenshots: true, snapshots: true });
|
||||
|
||||
await use(page);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import * as boundaryCli from '../helpers/boundary-cli.js';
|
||||
import baseGlobalSetup from '../global-setup.js';
|
||||
|
||||
export default async function globalSetup() {
|
||||
await baseGlobalSetup();
|
||||
|
||||
// when running locally, there's a chance that the cache could be running an
|
||||
// older version.
|
||||
// stop the cache if it's running, and the desktop client will automatically
|
||||
// start the cache after logging in .
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
await boundaryCli.stopCache();
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { defineConfig } from '@playwright/test';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
export default defineConfig({
|
||||
globalSetup: '../global-setup',
|
||||
globalSetup: './global-setup',
|
||||
outputDir: './artifacts',
|
||||
workers: 1, // Tests need to be run in serial, otherwise there may be conflicts when using the CLI
|
||||
use: {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { chromium, test as baseTest, mergeTests } from '@playwright/test';
|
||||
|
||||
import { checkEnv } from './helpers/general.js';
|
||||
import { boundaryApiClientTest } from './helpers/boundary-api-client.js';
|
||||
import { LoginPage } from './admin/pages/login.js';
|
||||
|
||||
@@ -9,6 +9,7 @@ export * from './boundary-cli/accounts.js';
|
||||
export * from './boundary-cli/aliases.js';
|
||||
export * from './boundary-cli/auth-methods.js';
|
||||
export * from './boundary-cli/authenticate.js';
|
||||
export * from './boundary-cli/cache.js';
|
||||
export * from './boundary-cli/connect.js';
|
||||
export * from './boundary-cli/credential-stores.js';
|
||||
export * from './boundary-cli/credentials.js';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
/**
|
||||
* Stops the boundary cache used by the desktop client
|
||||
*/
|
||||
export async function stopCache() {
|
||||
try {
|
||||
execSync('boundary cache stop');
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "e2e-tests",
|
||||
"version": "0.0.0",
|
||||
"description": "End to end tests for boundary UI",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -25,15 +25,16 @@
|
||||
"admin:dev:firefox": "pnpm run admin --project=firefox --headed",
|
||||
"admin:dev:safari": "pnpm run admin --project=webkit --headed",
|
||||
"desktop": "pnpm run --filter desktop build && pnpm run desktop:dev",
|
||||
"desktop:local": "SETUP_CLI=true pnpm run desktop",
|
||||
"desktop:dev": "pnpm playwright test --config desktop/playwright.config.js --reporter=html"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@openapitools/openapi-generator-cli": "^2.30.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"@openapitools/openapi-generator-cli": "^2.38.0",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
@@ -44,7 +45,7 @@
|
||||
"tesseract.js": "^5.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"description": "Monorepo for Boundary UIs and addons.",
|
||||
"description": "Monorepo for Threshold UIs and addons.",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "HashiCorp",
|
||||
"author": "CyberCove LLC (fork of HashiCorp Boundary UI)",
|
||||
"scripts": {
|
||||
"build": "pnpm build:ui:admin",
|
||||
"build:ui:admin": "pnpm run --filter admin build",
|
||||
@@ -34,27 +34,62 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"doctoc": "^2.2.0",
|
||||
"esbuild": "^0.25.4",
|
||||
"git-cz": "^4.9.0",
|
||||
"husky": "^9.1.6",
|
||||
"license-checker": "^25.0.1",
|
||||
"lint-staged": "^15.2.10",
|
||||
"esbuild": "^0.25.4"
|
||||
"lint-staged": "^15.5.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nomnom>underscore": "^1.12.1",
|
||||
"micromatch": "^4.0.8",
|
||||
"css-select>nth-check": "^2.0.1",
|
||||
"node-gyp": "^10.0.0",
|
||||
"ansi-html": "^0.0.9",
|
||||
"fireworm>async": "^2.6.4",
|
||||
"ember-cli-babel>@babel/runtime": "7.27.6",
|
||||
"ember-try-config>package-json": "^10.0.1",
|
||||
"ember-loading>ember-concurrency": "^4.0.0",
|
||||
"tmp": "^0.2.5",
|
||||
"tar": "^7.5.10"
|
||||
"tmp": "^0.2.7",
|
||||
"tar": "^7.5.22",
|
||||
"nomnom>underscore": "^1.13.8",
|
||||
"ember-cli>markdown-it": "^14.3.2",
|
||||
"diff@>=5.0.0 <6.0.0": "^5.2.2",
|
||||
"qs@>=6.0.0 <7.0.0": "^6.16.0",
|
||||
"follow-redirects@>=1.0.0 <2.0.0": "^1.16.1",
|
||||
"postcss@>=8.0.0 <9.0.0": "^8.5.29",
|
||||
"ws@>=8.0.0 <9.0.0": "^8.22.0",
|
||||
"brace-expansion@>=5.0.0 <6.0.0": "^5.0.12",
|
||||
"brace-expansion@>=2.0.0 <3.0.0": "^2.1.7",
|
||||
"brace-expansion@>=1.0.0 <2.0.0": "^1.1.21",
|
||||
"form-data@>=4.0.0 <5.0.0": "^4.0.6",
|
||||
"js-yaml@>=3.0.0 <4.0.0": "^3.15.2",
|
||||
"js-yaml@>=4.0.0 <5.0.0": "^4.3.2",
|
||||
"morgan@>=1.0.0 <2.0.0": "^1.12.1",
|
||||
"@babel/core@>=7.0.0 <8.0.0": "^7.29.7",
|
||||
"engine.io@>=6.0.0 <7.0.0": "^6.6.11",
|
||||
"shell-quote@>=1.0.0 <2.0.0": "^1.12.0",
|
||||
"body-parser@>=1.0.0 <2.0.0": "^1.20.8",
|
||||
"linkify-it@>=5.0.0 <6.0.0": "^5.0.2",
|
||||
"seroval@>=1.0.0 <2.0.0": "^1.6.9",
|
||||
"socket.io-parser@>=4.0.0 <5.0.0": "^4.2.7",
|
||||
"ip-address@>=10.0.0 <11.0.0": "^10.7.4",
|
||||
"axios@>=1.0.0 <2.0.0": "^1.20.0",
|
||||
"nanoid@>=5.0.0 <6.0.0": "^5.1.16",
|
||||
"nanoid@>=3.0.0 <4.0.0": "^3.3.20",
|
||||
"postcss-selector-parser@>=7.0.0 <8.0.0": "^7.1.6",
|
||||
"browserslist@>=4.0.0 <5.0.0": "^4.29.3",
|
||||
"immutable@>=5.0.0 <6.0.0": "^5.1.9",
|
||||
"colord@>=2.0.0 <3.0.0": "^2.10.0",
|
||||
"baseline-browser-mapping@>=2.0.0 <3.0.0": "^2.11.28",
|
||||
"source-map-js@>=1.0.0 <2.0.0": "^1.2.2",
|
||||
"proxy-addr@>=2.0.0 <3.0.0": "^2.0.8",
|
||||
"compression@>=1.0.0 <2.0.0": "^1.8.2",
|
||||
"handlebars@>=4.0.0 <5.0.0": "^4.7.10",
|
||||
"websocket-driver@>=0.7.0 <0.8.0": "^0.7.5",
|
||||
"@humanfs/node@>=0.16.0 <0.17.0": "^0.16.8",
|
||||
"@xmldom/xmldom@>=0.8.0 <0.9.0": "^0.8.15"
|
||||
},
|
||||
"ignoredBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
@@ -75,7 +110,7 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.* || 22.*"
|
||||
"node": "22.* || 24.*"
|
||||
},
|
||||
"packageManager": "pnpm@10.8.1"
|
||||
"packageManager": "pnpm@10.33.3"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# package.json Overrides
|
||||
|
||||
This file documents the rationale for each dependency override defined in `package.json` under `pnpm.overrides`.
|
||||
These overrides force specific versions of transitive dependencies, primarily for security and compatibility reasons.
|
||||
|
||||
| Package | Version | Pulled in by | What to check before removing |
|
||||
|---------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `micromatch` | `^4.0.8` | `anymatch` (`^3.1.4`), `common-tags` (`3.1.10`), `sane` (`^3.1.4`), `find-yarn-workspace-root` / `findup-sync` (`^4.0.2`), `ember-template-lint` / `stylelint` (`^4.0.5`) all declare ranges that would resolve below `4.0.8`. | Once all dependents declare a range of `^4.0.8` or higher, the override can be removed. |
|
||||
| `css-select>nth-check` | `^2.0.1` | `css-select` pins an older `nth-check`. | Check whether a newer `css-select` release drops the pinned `nth-check` `<2.0.0` requirement. Used by `ember-inline-svg`. |
|
||||
| `ansi-html` | `^0.0.9` | Both `broccoli-middleware` and `broccoli` pin `"ansi-html": "^0.0.7"`. | Check whether newer `broccoli` / `ember-cli` releases update these to drop the sub-`0.0.9` pin. |
|
||||
| `fireworm>async` | `^2.6.4` | `fireworm` pins an older `async`. | Check whether `fireworm` has been updated or replaced; if `testem` updates `fireworm`, this override can be removed. |
|
||||
| `ember-cli-babel>@babel/runtime` | `7.27.6` | `ember-cli-babel` restricts the version it resolves. | Review when upgrading `ember-cli-babel`; once its `@babel/runtime` range is broad enough, remove and verify the build. |
|
||||
| `ember-try-config>package-json` | `^10.0.1` | `ember-try-config` pins an older `package-json`. | Check whether a newer `ember-try-config` release widens its `package-json` range to include v10+. |
|
||||
| `ember-loading>ember-concurrency` | `^4.0.0` | `ember-loading` declares `"ember-concurrency": "^2.0.3"`. | The override and the patch (`patches/ember-loading.patch`) are a pair — the override forces v4 to be installed, the patch updates the source to use v4. Both must be removed together if `ember-loading` ever ships a version that natively supports v4. |
|
||||
| `tmp` | `^0.2.7` | 10+ packages pin old versions of `tmp` (`broccoli`, `can-symlink`, `ember-cli-typescript`, `external-editor`, `fixturify-project`, `sane`, `testem`, and others) | Check whether the main Ember toolchain packages (`broccoli`, `ember-cli`, `testem`) have been updated to allow `tmp ^0.2.x`. |
|
||||
| `tar` | `^7.5.22` | Both `cacache` (`^6.1.11`) and `@electron/rebuild` (`^6.0.5`) pin old ranges. | Check whether a newer `ember-electron` brings in versions of `@electron/rebuild` and `cacache` that allow `tar ^7.x`. |
|
||||
| `nomnom>underscore` | `^1.13.8` | `nomnom` pins `"underscore": "~1.6.0"`. All other consumers already declare ranges that include 1.13.8. | `nomnom@1.8.1` is the last published version and the package is abandoned. `jsonlint` is the actual consumer, currently pinned to `^1.6.3` which still depends on `nomnom`. |
|
||||
| `ember-cli>markdown-it` | `^14.3.2` | `ember-cli` (`~5.12.0`) pins old ranges. | Once `ember-cli` has been updated to at least `6.3.0` then this override can be removed. |
|
||||
|
||||
## Security overrides added by the Threshold fork
|
||||
|
||||
The overrides keyed `<name>@>=X.0.0 <Y.0.0` were generated from `pnpm audit` (see the "About this fork" section in the README). Each one only raises a transitive dependency to the latest patched release **within the same major line** (patch/minor, lockfile-only effect). They are build/test tooling dependencies (`ember-cli`, `testem`, `stylelint`, `openapi-generator`, ...), not shipped code. Remove an entry once the dependents release versions that no longer need it; re-run `pnpm audit` after removing.
|
||||
|
||||
Packages covered: `@babel/core`, `@humanfs/node`, `@xmldom/xmldom`, `axios`, `baseline-browser-mapping`, `body-parser`, `brace-expansion`, `browserslist`, `colord`, `compression`, `diff`, `engine.io`, `follow-redirects`, `form-data`, `handlebars`, `immutable`, `ip-address`, `js-yaml`, `linkify-it`, `morgan`, `nanoid`, `postcss`, `postcss-selector-parser`, `proxy-addr`, `qs`, `seroval`, `shell-quote`, `socket.io-parser`, `source-map-js`, `websocket-driver`, `ws`.
|
||||
|
||||
Known unfixed without a major bump or an upstream fix: `svgo@1` (via `ember-inline-svg`), `@faker-js/faker@8` (api addon mirage data), `uuid@8` (testem), `diff@7` (sinon), `postcss-selector-parser@6` (stylelint), `basic-ftp@5` (openapi-generator), `extract-zip@2`, `http-cache-semantics@4`, `braces@3`, `sprintf-js@1` (no patched release).
|
||||
|
||||
@@ -90,10 +90,10 @@ export default function transformer(file, api) {
|
||||
} else {
|
||||
// if an array is provided and it's argument is a literal (number, boolean, string) then this is used for the test name
|
||||
// otherwise if it's an object the index in the array is what is used for the test name
|
||||
eachTestNames = testArguments[1].elements.map((element, i) => element.type.endsWith('Literal') ? element.value : i);
|
||||
eachTestNames = testArguments[1].elements?.map((element, i) => element.type.endsWith('Literal') ? element.value : i);
|
||||
}
|
||||
|
||||
testName = eachTestNames.map(eachTestName => `${testName} [${eachTestName}]`);
|
||||
testName = eachTestNames?.map(eachTestName => `${testName} [${eachTestName}]`) ?? [];
|
||||
} else {
|
||||
testName = [testName];
|
||||
}
|
||||
@@ -270,16 +270,27 @@ export default function transformer(file, api) {
|
||||
}
|
||||
};
|
||||
|
||||
const existingEmberA11yImport = f.find(j.ImportDeclaration, {
|
||||
source: {
|
||||
value: 'ember-a11y-testing/test-support',
|
||||
},
|
||||
});
|
||||
|
||||
tests.forEach(testHandler);
|
||||
testsEach.forEach(testHandler);
|
||||
|
||||
const code = f.toSource();
|
||||
|
||||
// the codemod will insert a newline between existing import statements,
|
||||
// this regex removes the extra new line
|
||||
//
|
||||
return code.replace(
|
||||
/(?<newline>\n)(import .*ember-a11y-testing\/test-support.*)/g,
|
||||
'$2',
|
||||
);
|
||||
|
||||
|
||||
if (existingEmberA11yImport.length > 0) {
|
||||
return code;
|
||||
} else {
|
||||
// the codemod will insert a newline between existing import statements,
|
||||
// this regex removes the extra new line
|
||||
return code.replace(
|
||||
/(?<newline>\n)(import .*ember-a11y-testing\/test-support.*)/g,
|
||||
'$2',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,11 @@ module.exports = {
|
||||
'at-rule-no-vendor-prefix': null,
|
||||
|
||||
// As a team we want to stick to using kebab-case as our selector class pattern, but there are a couple
|
||||
// of exceptions in which we overrride CSS from addons like CodeMirror and HDS, in which they use
|
||||
// of exceptions in which we overrride CSS from addons like HDS, in which they use
|
||||
// pascal case and a double-kebab-case. This regex checks for all of those 3 patterns and won't fail
|
||||
// lint check, but will fail if its any other pattern.
|
||||
'selector-class-pattern': [
|
||||
'(^([a-z][a-z0-9]*)(-{1,2}[a-z0-9]+)*$)|(^CodeMirror)',
|
||||
'(^([a-z][a-z0-9]*)(-{1,2}[a-z0-9]+)*$)',
|
||||
{
|
||||
message: (selector) =>
|
||||
`Expected class selector "${selector}" to be kebab-case`,
|
||||
|
||||
@@ -82,37 +82,30 @@
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Form::Field::ListWrapper
|
||||
@layout='horizontal'
|
||||
@isOptional={{true}}
|
||||
<Form::Field::KeyValue
|
||||
@name='certificates'
|
||||
@model={{@model}}
|
||||
@properties={{array 'value'}}
|
||||
@data={{@model.certificates}}
|
||||
@legend={{t 'resources.auth-method.form.certificates.label'}}
|
||||
@helperText={{t 'resources.auth-method.form.certificates.help'}}
|
||||
@disabled={{form.disabled}}
|
||||
@isOptional={{true}}
|
||||
>
|
||||
<:fieldset as |F|>
|
||||
<F.Legend>
|
||||
{{t 'resources.auth-method.form.certificates.label'}}
|
||||
</F.Legend>
|
||||
<F.HelperText>
|
||||
{{t 'resources.auth-method.form.certificates.help'}}
|
||||
</F.HelperText>
|
||||
|
||||
{{#if @model.errors.certificates}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.certificates as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
<:row as |R|>
|
||||
<R.Field as |F|>
|
||||
<F.Textarea
|
||||
aria-label={{t 'resources.auth-method.form.certificates.label'}}
|
||||
data-test-value-input
|
||||
@value={{R.rowData.value}}
|
||||
{{on 'input' (fn R.updateRow 'value')}}
|
||||
/>
|
||||
</R.Field>
|
||||
{{#if R.canDelete}}
|
||||
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
|
||||
{{/if}}
|
||||
</:fieldset>
|
||||
|
||||
<:field as |F|>
|
||||
<F.Textarea
|
||||
@name='certificates'
|
||||
@options={{@model.certificates}}
|
||||
@model={{@model}}
|
||||
/>
|
||||
</:field>
|
||||
|
||||
</Form::Field::ListWrapper>
|
||||
</:row>
|
||||
</Form::Field::KeyValue>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@@ -406,45 +399,46 @@
|
||||
}}</F.HelperText>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Form::Field::ListWrapper
|
||||
@layout='horizontal'
|
||||
@isOptional={{true}}
|
||||
<Form::field::KeyValue
|
||||
@name='account_attribute_maps'
|
||||
@model={{@model}}
|
||||
@properties={{array 'key' 'value'}}
|
||||
@data={{@model.account_attribute_maps}}
|
||||
@legend={{t 'resources.auth-method.form.account_attribute_maps.label'}}
|
||||
@helperText={{t 'resources.auth-method.form.account_attribute_maps.help'}}
|
||||
@disabled={{form.disabled}}
|
||||
@isOptional={{true}}
|
||||
>
|
||||
<:fieldset as |F|>
|
||||
<F.Legend>
|
||||
{{t 'resources.auth-method.form.account_attribute_maps.label'}}
|
||||
</F.Legend>
|
||||
<F.HelperText>
|
||||
{{t 'resources.auth-method.form.account_attribute_maps.help'}}
|
||||
</F.HelperText>
|
||||
|
||||
{{#if @model.errors.account_attribute_maps}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.account_attribute_maps as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
<:row as |R|>
|
||||
<R.Field as |F|>
|
||||
<F.Label>{{t 'resources.auth-method.titles.from-attr'}}</F.Label>
|
||||
<F.TextInput
|
||||
data-test-key-input
|
||||
@value={{R.rowData.key}}
|
||||
{{on 'input' (fn R.updateRow 'key')}}
|
||||
/>
|
||||
</R.Field>
|
||||
<R.Field as |F|>
|
||||
<F.Label>{{t 'resources.auth-method.titles.to-attr'}}</F.Label>
|
||||
<F.Select
|
||||
data-test-value-select
|
||||
{{on 'change' (fn R.updateRow 'value')}}
|
||||
>
|
||||
<option disabled hidden selected value=''>
|
||||
{{t 'titles.choose-an-option'}}
|
||||
</option>
|
||||
{{#each this.toAttributes as |attr|}}
|
||||
<option value={{attr}} selected={{eq R.rowData.value attr}}>
|
||||
{{attr}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
</F.Select>
|
||||
</R.Field>
|
||||
{{#if R.canDelete}}
|
||||
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
|
||||
{{/if}}
|
||||
</:fieldset>
|
||||
<:field as |F|>
|
||||
<F.KeyValue
|
||||
@name='account_attribute_maps'
|
||||
@options={{@model.account_attribute_maps}}
|
||||
@model={{@model}}
|
||||
@keyLabel={{t 'resources.auth-method.titles.from-attr'}}
|
||||
@valueLabel={{t 'resources.auth-method.titles.to-attr'}}
|
||||
@width='100%'
|
||||
>
|
||||
<:key as |K|>
|
||||
<K.text />
|
||||
</:key>
|
||||
<:value as |V|>
|
||||
<V.select @selectOptions={{this.toAttributes}} />
|
||||
</:value>
|
||||
</F.KeyValue>
|
||||
</:field>
|
||||
</Form::Field::ListWrapper>
|
||||
</:row>
|
||||
</Form::field::KeyValue>
|
||||
|
||||
<div class='ldap-section'>
|
||||
<Hds::Text::Display
|
||||
|
||||