Compare commits

..
307 Commits
Author SHA1 Message Date
Zhihe Li 72639bd6a3 fix: 🐛 Pass in session time out when creating session (#3008)
* fix: 🐛 Pass in session time out when creating session

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17701
2025-09-22 16:31:44 -04:00
Zhihe Li 31f0044c0f fix: 🐛 Fix schema for sqlite inserts (#3006)
* fix: 🐛 Fix schema for sqlite inserts

* fix: 🐛 Also fix delete trigger not working with REPLACE INTO
2025-09-18 10:16:01 -04:00
Lisbet Alvarez d0924f7e6d fix: 🐛 max depth error for active sessions filter in targets (#3004)
* fix: 🐛 max depth error for active sessions filter in targets

* refactor: 💡 revert route and convert operators in parser

* refactor: 💡 add expr limit

* refactor: 💡 add parameters to IN/NOT IN conditions

* test: 💍 fix tests

* docs: ✏️ add comment
2025-09-17 15:32:39 -07:00
Zhihe Li d2616acf36 fix: 🐛 Pass the addr to CLI when canceling sessions (#3007) 2025-09-17 18:15:39 -04:00
Zhihe Li dfd116bd56 chore: 🤖 Update dependency for security alert (#3005) 2025-09-17 16:59:35 -04:00
Michael Li 830acef847 test(e2e): Make role creation sequential (#3003)
When running this test against an AWS cluster, this operation was resulting in 504s. This was potentially hitting some AWS rate limits when making these requests concurrently.
2025-09-16 17:50:11 -04:00
Zhihe Li ee7c916494 fix: 🐛 Fix sessions not properly getting cleaned up (#3002)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17645
2025-09-15 18:11:43 -04:00
hashicc 46d477ea5b Refresh target active sessions for admin and desktop (#3001)
* fix: 🐛 refresh sessions for targets list on admin and desktop

Refresh the sessions for the targets list pages on admin and desktop so
that the active sessions column is up to date

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646

* test: 💍 add test for targets list refreshing sessions on admin

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646

* refactor: 💡 allow ipcStub to return current mirage data

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646

* test: 💍 ensure that desktop targets sessions are refreshed

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646

* test: 💍 update existing tests affected by sessions refresh ipc

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646

* refactor: 💡 remove force_refresh from sessions sync

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17646
2025-09-15 12:53:17 -04:00
hashicc 177b4af695 refactor: 💡 rdp default client port and max connections labels (#2998) 2025-09-12 14:41:46 -07:00
Zhihe Li d2b8805605 fix: 🐛 Fix usage of top level await in worker which fails build (#2997) 2025-09-10 16:44:24 -04:00
Zhihe Li 39dedc68ec fix: 🐛 Add better check for determining if we can use OPFS (#2996) 2025-09-10 11:35:02 -04:00
hashicc bd67756cda fix: 🐛 fix bug with the apiClientTestAfter noop fixture hanging and update e2e label username, password locators with required text (#2994)
* fix: 🐛 fix bug with the e2e test fixture hanging

* test: 💍 update cred store label locators with exact match

* test: 💍 update createTargetWithAddressAndAlias with target type
2025-09-10 10:14:55 -04:00
Dharini dd37fa4270 fix: 🐛 var name (#2970) 2025-09-09 15:27:35 -07:00
hashicc 3c5c048152 docs: ✏️ update alias value helper text (#2995) 2025-09-09 16:49:11 -04:00
hashicc dd6e4ef2d0 chore: 🤖 update desktop client changelog & bundled cli version (#2992)
* chore: 🤖 update cli version bundled with desktop client

* docs: ✏️ update changelog
2025-09-08 11:47:27 -04:00
Zhihe Li bce79f3488 fix: 🐛 Add a better db check (#2993) 2025-09-08 10:42:39 -04:00
Zhihe Li 74793a2399 chore: 🤖 Update form-data dependency (#2991) 2025-09-05 14:00:03 -04:00
Zhihe Li 6b120b053a chore: 🤖 Update electron dependency (#2989) 2025-09-04 16:49:50 -04:00
Dharini 1d0d62337e fix: 🐛 a11y test failures (#2988) 2025-09-04 10:30:44 -07:00
Dharini df8bfc42bb chore: 🤖 vault ldap feature flag 2025-09-03 17:02:25 -07:00
Dharini ff087738fc fix: 🐛 parser for handling username with domain 2025-09-03 17:02:05 -07:00
Dharini 27567c88c9 fix: 🐛 a11y tests 2025-09-03 17:01:43 -07:00
Priya Patel 9077a747d6 refactor: 💡 change default target type to tcp 2025-09-03 17:01:27 -07:00
Dharini b359d9f525 fix: 🐛 remove optional tags 2025-09-03 17:01:04 -07:00
Dharini ad7bd2e52d fix: 🐛 add required tags 2025-09-03 17:00:43 -07:00
Priya Patel 36aed9588b refactor: 💡 remove dropdown for rdp type 2025-09-03 17:00:22 -07:00
Dharini f8c81a29c4 chore: 🤖 vault-ldap target injection 2025-09-03 16:59:48 -07:00
Priya Patel c91771e152 feat: 🎸 vault ldap CRUDL support 2025-09-03 16:59:20 -07:00
Priya Patel 3b7873302b feat: 🎸 vault ldap cred 2025-09-03 16:08:00 -07:00
Dharini d79bd54f0b fix: 🐛 remove redudant cert warning 2025-09-03 16:06:40 -07:00
Priya Patel 51dc9a0e49 chore: 🤖 update brokered cred tests 2025-09-03 16:06:07 -07:00
Dharini b49e55d400 test: 💍 refactor e2e 2025-09-03 16:05:42 -07:00
Priya Patel 33cb7e0a50 test: 💍 acceptance tests for rdp target brokered creds 2025-09-03 16:05:21 -07:00
Dharini 9edc90306e test: 💍 e2e tests 2025-09-03 16:04:50 -07:00
Dharini 0a75e05b28 feat: 🎸 display toast notifications for rdp sessions 2025-09-03 16:04:17 -07:00
Priya Patel 4a179ac3e3 chore: 🤖 rdp desktop changes 2025-09-03 16:03:48 -07:00
Dharini 42e32f9568 chore: 🤖 filter rdp credentials 2025-09-03 16:02:59 -07:00
Priya Patel 0223755b2a feat: 🎸 rdp target form- read, list and delete 2025-09-03 16:02:00 -07:00
Priya Patel 30a5f2a701 feat: 🎸 rdp form - create and update 2025-09-03 16:01:25 -07:00
Dharini Jeeva d2d351c9f7 chore: 🤖 add rdp target type 2025-09-03 16:00:12 -07:00
Dharini Jeeva e1696e9a4d feat: 🎸 add support for upd credential 2025-09-03 15:59:29 -07:00
Zhihe Li 25f015b179 chore: 🤖 Remove timer console statements (#2985) 2025-09-03 11:41:43 -04:00
Zhihe Li 9e263379a7 refactor: 💡 Remove usage of indexed DB (#2979)
* refactor: 💡 Remove usage of indexed DB
2025-09-03 11:41:43 -04:00
Lisbet Alvarez 4c7ea161cd feat: 🎸 Add sqlite support to sessions (#2978)
* feat: 🎸 Add sqlite support to sessions

* refactor: 💡 only peek sqlite db when batching queries

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17411
2025-09-03 11:41:42 -04:00
Zhihe Li dc026c44a1 feat: 🎸 Handle corrupted sqlite DB (#2976)
* feat: 🎸 Handle corrupted sqlite DB
2025-09-03 11:41:42 -04:00
Zhihe Li cb8b21c10c feat: 🎸 Add sqlite support for session recordings (#2974)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17419
2025-09-03 11:41:42 -04:00
Lisbet Alvarez 47c1beadcd feat: 🎸 Add sqlite support to host-catalogs (#2973)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17417
2025-09-03 11:41:42 -04:00
Lisbet Alvarez 8f7eb2abea feat: 🎸 add fts tables to groups (#2971)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17413
2025-09-03 11:41:42 -04:00
Zhihe Li c2b0f321de feat: 🎸 Add sqlite support for auth methods (#2969)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17418
2025-09-03 11:41:42 -04:00
Lisbet Alvarez ba7b8b8824 feat: 🎸 add sqlite support for scopes (#2968)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17415
2025-09-03 11:41:42 -04:00
Zhihe Li 9e612cf83d feat: 🎸 Add sqlite support for credential stores (#2967)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17416
2025-09-03 11:41:42 -04:00
Lisbet Alvarez 2154251c30 feat: 🎸 Add sqlite support for users (#2966)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17412
2025-09-03 11:41:42 -04:00
Zhihe Li 2d38266609 feat: 🎸 Add sqlite support for roles (#2965)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17414
2025-09-03 11:41:41 -04:00
Lisbet AlvarezandZhihe Li d414df4ca4 feat: 🎸 Add custom sort support to sql parser (#2959)
* feat: 🎸 Add custom sort support to sql parser

* test: 💍 fix column numner

* refactor: 💡 apply pr feedback

* fix: 🐛 use every method for checking attrs

* refactor: 💡 PR feedback + ensure default sort

* refactor: 💡 Consolidate attributes field when sorting

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17441

---------

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-09-03 11:41:41 -04:00
Zhihe Li eab93cf6c5 Icu 17516 use in memory sqlite in non secure contexts (#2958)
* feat: 🎸 Add checks to see if sqlite is in secure environment

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17516
2025-09-03 11:41:41 -04:00
Zhihe Li 3304def44a feat: 🎸 Add sqlite support for aliases (#2957)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17420
2025-09-03 11:41:41 -04:00
Zhihe Li a4bf02b21b refactor: 💡 Change variable name for peeking db (#2954) 2025-09-03 11:41:41 -04:00
Zhihe Li e0982da744 feat: 🎸 Add sqlite handler and parser (#2933)
* feat: 🎸 Add sqlite handler and parser

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17410
2025-09-03 11:41:41 -04:00
Zhihe Li 8abed1a809 feat: 🎸 Add a shared service between web workers (#2932)
* feat: 🎸 Add a shared service between web workers

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17409
2025-09-03 11:41:40 -04:00
Zhihe Li a9a6518b23 feat: 🎸 Allow building and bundling of web workers in ember (#2923)
* feat: 🎸 Allow building and bundling of web workers in ember

* refactor: 💡 Cleanup code and add comments
2025-09-03 11:41:40 -04:00
Michael Li 1bafbe1ad2 test(e2e): Address some Admin UI test flakiness (#2934)
* fix(e2e): Address flakiness during screen navigation

* fix(e2e): Fix function call

* fix(e2e): Address flakiness in connection

* fix(e2e): Make locator explicit to avoid conflict when an ID has `ok`

* fix(e2e): Fix URL now that project page defaults to showing targets
2025-09-03 11:28:33 -04:00
Lisbet Alvarez a26074a3b8 fix: 🐛 host header renders multiple times (#2980)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17584
2025-09-02 16:28:53 -07:00
Zhihe Li 90fd2f1447 fix: 🐛 Fix bug that prevents setting cluster URL (#2975)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17579
2025-08-28 12:27:12 -04:00
Priya Patel e59690e703 chore: 🤖 update ember-feature-flags and tracked-built-ins (#2903)
* chore: 🤖 update ember-feature-flags and tracked-built-ins

* chore: 🤖 update glimmer/component@2.0.0 for ember-feature-flag

* chore: 🤖 test ember-feature-flag update

* chore: 🤖 test ember-feature-flag@6.0.0

* refactor: 💡 undo test commit changes
2025-08-20 13:37:58 -05:00
cameronperera bf06e9f654 test: 💍 update known a11y violations 2025-08-19 10:51:44 -05:00
cameronperera 84359d8e79 docs: ✏️ fix flag name and a11y references 2025-08-19 10:51:44 -05:00
cameronperera 7801429530 refactor: 💡 remove body reference from typography style 2025-08-19 10:51:44 -05:00
hashicc dc9376a460 (HACK WEEK 2025) a11y-audit-ignore codemod (#2946)
* chore: 🤖 add ember-a11y-ignore tool

* chore: 🤖 fix handling existing setRunOptions imports

* chore: 🤖 move codemod to support folder

* chore: 🤖 fix edge case for handling property key names

* chore: 🤖 handle case where multiple of same violation in test

This fixes a bug where multiple cases of the same violation would end up
with multiple entries

* chore: 🤖 ignore axe rules that are currently failing in admin

* chore: 🤖 ignore axe rules that are currently failing in desktop

* docs: ✏️ add a comment explaining the codemod

* chore: 🤖 include test.each cases for a11y ignores

* fix: 🐛 handle test.each test cases for a11y ignore codemod
2025-08-19 10:51:44 -05:00
hashicc 1f4076d8d4 (HACK WEEK 2025) Upload a11y reports in ci (#2949)
* ci: 🎡 consolidate ember a11y reports and upload as gh artifact

* ci: 🎡 Bump test timeout for a11y tests

* chore: 🤖 run a11y tests in parallel

* chore: 🤖 only run a11y audit against acceptance tests

* chore: 🤖 restore ember test in place of ember exam

testing whether or not ember test hangs with three tests timing out like
ember exam does

* ci: 🎡 output a11y reports filtered down in ci

* ci: 🎡 attempt to fix shell exit code for a11y test failures

* ci: 🎡 add step names for outputting a11y test report summary
2025-08-19 10:51:44 -05:00
Cameron Perera f202d0f533 (HACK WEEK 2025) docs: ✏️ add a11y testing info to READMEs (#2945)
* docs: ✏️ add a11y testing info to READMEs

* chore: 🤖 remove light theme setup for acceptance tests in admin

* docs: ✏️ add link to test-helper.js in README

* chore: 🤖 update PR template for a11y-tests

* test: 💍 fix color theme switching tests
2025-08-19 10:51:44 -05:00
hashicc 4344dcbb36 Fix a11y-tests label (#2947) 2025-08-19 10:51:44 -05:00
Cameron Perera 87d6bbae2a (HACK WEEK 2025) chore: 🤖 add new gh workflow for a11y tests (#2942)
* chore: 🤖 add new gh workflow for a11y tests

* chore: 🤖 remove a11y dark tests job

* chore: 🤖 increase a11y tests timeout
2025-08-19 10:51:44 -05:00
Cameron Perera 7a42bf456e (HACK WEEK 2025) Dark mode a11y audit rules (#2939)
* chore: 🤖 change axe runOptions based on color theme

* fix: 🐛 fix background-color and color styles application
2025-08-19 10:51:44 -05:00
hashicc a51f1eee8e (HACK WEEK 2025) a11y desktop test helper (#2938)
* chore: 🤖 run ember-a11y-testing conditionally in desktop

* chore: 🤖 control launched testem chrome theme via COLOR_THEME

* chore: 🤖 add package.json scripts for desktop a11y tests

* chore: 🤖 gitignore a11y report

* chore: 🤖 update desktop package.json test command

Explicitly call test and finalize separately, previously "concurrently"
would pick up the finalize task

* chore: 🤖 remove unneeded console.logs
2025-08-19 10:51:44 -05:00
Carlos Alcaide CorvoandLisbet Alvarez 5b72a916e4 (HACK WEEK 2025) A11y admin test helper (#2937)
* chore: 🤖 add a11y audit test helper config

* chore: 🤖 remove report files

* refactor: 💡 remove console.log

* Apply PR feedback

* Delete the flag --server when running a11y tests

---------

Co-authored-by: Lisbet Alvarez <lisbet.alvarez@hashicorp.com>
2025-08-19 10:51:44 -05:00
Cameron Pereraandcalcaide 42013a919f (HACK WEEK 2025) refactor: 💡 remove explicit a11yAudit usage (#2936)
* refactor: 💡 remove explicit usage of a11yAudit

* Delete a11yAudit method from readme

---------

Co-authored-by: calcaide <ccorvo@hashicorp.com>
2025-08-19 10:51:44 -05:00
Cameron Perera 3918f82e14 chore: 🤖 add DARK_MODE env variable to testem config (#2935)
for admin and desktop
2025-08-19 10:51:44 -05:00
Carlos Alcaide Corvo 6e79a2a133 Delete development environment variables for the appUpdater (#2961) 2025-08-15 14:12:08 -07:00
dependabot[bot]andZhihe Li 088deaed3a chore(deps): bump tmp from 0.2.3 to 0.2.5 in /ui/desktop/electron-app (#2962)
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.5.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.5)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-08-14 14:50:15 -04:00
Zhihe Li 7504e7710c ci: 🎡 Use repo variables to determine which runner to use (#2963) 2025-08-14 14:28:58 -04:00
hashicc bb7352ca54 fix: 🐛 fix the dependency on apiClientTestAfter with a noop (#2964) 2025-08-14 10:40:52 -04:00
hashiccandZhihe Li b42ecbff36 Add api client for playwright e2e tests (#2901)
* chore: 🤖 add @openapitools/openapi-generator-cli

* chore: 🤖 Add @nestjs/core and @openapitools/openapi-generator-cli to ignoredBuiltDependencies

These packages use postinstall for a opencollective notice

* chore: 🤖 add package.json scripts for open-api client gen

* chore: 🤖 add openapi generated files

* chore: 🤖 add boundary api client fixture

* chore: 🤖 incorporate api client fixture with base fixture

* chore: 🤖 migrate existing test to use boundary api fixture

* refactor: 💡 remove org cleanup from test

* refactor: 💡 update sorting e2e test to use api client

* refactor: 💡 clean up api client resource clean up logging

* docs: ✏️ update jsdoc links for domain clients

* chore: 🤖 avoid adding copywright headers to generated api

* Add missing copyright headers

* chore: 🤖 remove fetch argument from api client class

The hope was to use request.fetch but playwright's fetch is a
non-standard fetch which causes the generated api client to fail when
using .clone() on the response

* chore: 🤖 add optional OPENAPI_SWAGGER_URL_OR_FILE for openapi-client
generation

The OPENAPI_SWAGGER_URL_OR_FILE env var can be used to specify the
-i flag the generate:api-client script with either:
* a relative path
* an absolute path
* a url like: https://raw.githubusercontent.com/hashicorp/boundary/refs/heads/main/internal/gen/controller.swagger.json

* refactor: 💡 clean up and refactor boundary-api-client fixture

* chore: 🤖 add extra doc links and delete methods

* test: 💍 add test to ensure that api-client fixture works

* test: 💍 update existing e2e test examples with api-client

* docs: ✏️ add comment for api-client e2e test

* docs: ✏️ add additional links to service docs for api clients

* refactor: 💡 cleanup api-client and corresponding test

* docs: ✏️ add api client fixture docs to e2e README

* fix: 🐛 fix regex

* remove unnecessary capture group
* add "-" to regex to allow for url segments like "storage-buckets" in
  "/v1/storage-buckets"

* docs: ✏️ comment potential edge case in e2e api client cleanup

* docs: ✏️ fix comment typo

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>

* docs: ✏️ remove auth flag for generating e2e client

* docs: ✏️ add mention that java is required for generate:api

* chore: 🤖 implement pr feedback

* chore: 🤖 Clean up api client and add more extensive test

* chore: 🤖 fix casing in api-client test

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>

---------

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-08-12 18:08:35 -04:00
Carlos Alcaide Corvo 5fd9f0216a Fix security vulnerability within desktop appUpdater (#2953)
* use app.isPackaged instead of electron-is-dev
2025-08-12 11:19:43 -07:00
dependabot[bot]andCarlos Alcaide Corvo 942ea99459 chore(deps): bump browser-actions/setup-chrome from 1.7.3 to 2.1.0 (#2952)
Bumps [browser-actions/setup-chrome](https://github.com/browser-actions/setup-chrome) from 1.7.3 to 2.1.0.
- [Release notes](https://github.com/browser-actions/setup-chrome/releases)
- [Changelog](https://github.com/browser-actions/setup-chrome/blob/master/CHANGELOG.md)
- [Commits](https://github.com/browser-actions/setup-chrome/compare/c785b87e244131f27c9f19c1a33e2ead956ab7ce...b94431e051d1c52dcbe9a7092a4f10f827795416)

---
updated-dependencies:
- dependency-name: browser-actions/setup-chrome
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Carlos Alcaide Corvo <ccorvo@hashicorp.com>
2025-08-08 09:28:25 -07:00
LaurenandZhihe Li 990e2d6b9d [Desktop][Bug fix]: Stop target sessions before unauthenticating user session (#2891)
* add stopAll invocation to ipc handler

* trigger modal on both signout btns

* refactor invalidateSession fn

* fix application controller tests

* fix failing tests settings-card/user

* add test to Acceptance | authentication

* migrate electron dialog.showMessageBoxSync to Hds

* rename channel and tracked prop, consolidate cancel fns

* combine modals, add willDestroy, refactor

* test signout and close sessions modal

* address feedback

* refactor window.electron in authentication-test

* address feedback

* refactor: 💡 Fix failing tests

---------

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-08-04 16:44:27 -04:00
Zhihe Li f776ba0617 Fix auto commit of copyright headers (#2948)
* fix: 🐛 Fix copyright header updates failing in CI
2025-07-28 13:50:35 -04:00
hashicc fb57965457 chore: 🤖 update @glimmer/component to v2 (#2871)
* chore: 🤖 update @glimmer/component to v2

* chore: 🤖 bump ember-a11y-refocus to fix focus errors in tests

Previous runs had three failing tests related to focusing from
ember-a11y-refocus:
* global failure: TypeError: Cannot read properties of null (reading 'focus')

Tests failing with this error were fixed as part of this PR:
https://github.com/ember-a11y/ember-a11y-refocus/pull/347

After bumping to a later version of ember-a11y-refocus, the
existing failing tests now pass
2025-07-18 13:12:34 -04:00
Tony b99fce7d55 refactor: 💡 e2e admin tests - update locators (#2931)
* refactor: 💡 e2e tests - update locators

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17174

* refactor: 💡 addressing comments

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17174

* refactor: 💡 update test for session-recording-aws-ent

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17174
2025-07-17 15:48:20 -04:00
Zhihe Li 0a2d743ea2 fix: 🐛 Fix removing worker tag (#2928) 2025-07-16 13:27:13 -04:00
Lisbet Alvarez fbd613084d fix: 🐛 prevent worker registration icon from shrinking (#2929)
* fix: 🐛 prevent worker registration icon from shrinking

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17392
2025-07-15 17:13:07 -07:00
Cameron Perera 4cd56f8ae5 Icu 17393 manage resource button permissions bug (#2927)
* fix: 🐛 hide manage button if no action items

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for credential-stores

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for groups

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for host-sets

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for roles

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage action for targets

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for workers

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for users

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393

* fix: 🐛 update manage actions for auth-methods

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17393
2025-07-15 14:03:27 -05:00
Carlos Alcaide Corvo c0d41c0a62 Delete on-click-inside and on-click-outside rose modifiers (#2926) 2025-07-15 09:11:32 -07:00
Carlos Alcaide Corvo 389c635030 Modify string sorting to benefit upperCase (#2924)
* Modify the string sorting to benefit uppercase over lowercase at comparison
2025-07-11 10:08:39 -07:00
Lisbet Alvarez d93a0c170c test: 💍 refactor targets manage alias test selectors (#2921)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16437
2025-07-10 16:20:37 -07:00
Lisbet Alvarez aa18f599ce test: 💍 refactor target worker test selectors (#2910)
* test: 💍 refactor target worker test selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16438

* test: 💍 update selector

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16438
2025-07-10 15:34:24 -07:00
Tony aa7eb2e844 test: 💍 Add admin UI test for change passsword flow (#2920)
* test: 💍 Add admin UI test for change passsword flow

This PR adds a test to the Admin UI e2e test suite to validate the
workflow of changing user password from settings menu

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 create resources via REST API

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 consistent code structure, comments

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 code structure consistency

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 removed version parameter from boundary-http funcs

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 rm unneeded params from https makeAuthMethodPrimary

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173

* refactor: 💡 rm ui login step

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17173
2025-07-10 11:26:52 -04:00
Dharini 8e31adb9db fix: 🐛 release date (#2922) 2025-07-09 10:08:40 -07:00
Cameron Perera f0feb9e563 Icu 16439 enable session recording create storage bucket & index (#2886)
* test: 💍 update selectors for create-storage-bucket-test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16439

* test: 💍 add selectors for enable session recording index test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16439

* fix: 🐛 clean up duplicate test selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16439

* refactor: 💡 move single file use selectors back to file

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16439
2025-07-07 11:47:04 -05:00
Carlos Alcaide Corvo 44624e3820 Refactor test selectors for host-sets domain (#2911) 2025-07-07 09:17:06 -07:00
Zhihe Li 225c94b853 chore: 🤖 Allow DC E2E tests to run in ent (#2919) 2025-07-03 16:46:22 -04:00
Lisbet Alvarez bd1b8542eb fix: 🐛 transition aborted error when deleting or saving (#2918) 2025-07-02 13:06:17 -07:00
Dharini c92e93ab1c chore: 🤖 change log and cli version (#2917) 2025-07-02 10:29:12 -07:00
Lisbet Alvarez 1358727832 test: 💍 refactor target host sources test selectors (#2906)
* test: 💍 refactor target host sources test selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16435
2025-07-02 09:48:52 -07:00
dependabot[bot] 9bc84a2c59 chore(deps): bump stefanzweifel/git-auto-commit-action (#2915) 2025-07-01 12:27:49 -07:00
Katrina Schwark 7f00a83b19 Pci pr template v2 (#2914)
* Update pull_request_template.md

Adding PCI PR Template to existing checklist.

* Update pull_request_template.md

Removing whitespace
2025-06-30 15:07:15 -04:00
Lisbet Alvarez 19426cfd51 chore: resolve brace expansion to patched versions for vulnerability fix (#2912)
* chore: 🤖 resolve brace-expansion 1.1.11 to 1.1.12

* chore: 🤖 resolve brace-expansion 2.0.1 to 2.0.2
2025-06-27 14:39:01 -07:00
Lisbet Alvarez 30c05eb834 refactor: 💡 update label and description for AWS DHC (#2909)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17308
2025-06-27 14:17:06 -07:00
Lisbet Alvarez f1d334b848 chore: 🤖 upgrade got package to solve vulnerability (#2885)
* chore: 🤖 upgrade got package to solve vulnerability

* chore: 🤖 add missing ember try config in core addon

* chore: 🤖 add usePnpm to ember-try config file for all addons

* chore: 🤖 add resolution for package-json dep. instead of got
2025-06-27 13:58:38 -07:00
Carlos Alcaide Corvo 961f497b6b Replace this.server.db with this.server.schema (#2907) 2025-06-26 14:14:26 -07:00
Lisbet Alvarez 89f33f3714 refactor: 💡 add last seen time column to workers list view (#2908)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17305
2025-06-26 11:18:28 -07:00
Carlos Alcaide Corvo 4bad52f43a Refactor host set update test (#2902)
* Refactor host set update test
2025-06-25 08:28:14 -07:00
Carlos Alcaide Corvo fd6cd3ddf7 Refactor test selectors aliases (#2897)
* Refactor delete test selectors

* Refactor list test selectors
2025-06-25 07:39:10 -07:00
Lisbet Alvarez adf7f8603b test: 💍 refactor targets create alias & enable SR test selectors (#2904)
* test: 💍 refactor targets create alias & enable SR test selector

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16433
2025-06-24 16:09:33 -07:00
Lauren 2a2e4350ee conditionally render separator (#2900) 2025-06-23 20:07:41 -04:00
Carlos Alcaide Corvo f563a4ef45 Fix SASS deprecation (#2899) 2025-06-23 15:59:54 -07:00
hashicc 37b6ef6d3f Add e2e test for sorting (#2883)
* chore: 🤖 add sorting e2e test

* docs: ✏️ update docs with details around @ce and @ent tags

* test: 💍 Add @ent tag to sorting test

* test: 💍 update roles page with goto method

* test: 💍 assert column header labels and improve locators
2025-06-23 14:27:36 -04:00
Sohail 96ff0ddf27 Enhanced the refresh functionality in targets route (#2889)
* Enhanced the refresh functionality in targets and sessions route
2025-06-23 09:08:59 +05:30
Zhihe Li 746862a897 docs: ✏️ Fix E2E readme instruction (#2898) 2025-06-20 17:42:38 -04:00
hashicc 5d6e65f206 refactor: 💡 remove cached allSessions and related models (#2862) 2025-06-20 16:16:17 -04:00
Lisbet Alvarez 6f9a464302 chore: 🤖 upgrade hds to newest version 4.20.2 (#2895) 2025-06-20 11:39:04 -07:00
Carlos Alcaide Corvo abf7866292 Refactor scopes test selectors (#2888) 2025-06-20 10:32:28 -07:00
Lisbet Alvarez 10c98c66e7 test: 💍 Refactor test selectors for targets credential sources (#2890)
* test: 💍 Refactor test selectors for targets credential sources

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16432
2025-06-19 18:04:18 -07:00
Lisbet Alvarez c1f7fe2ddd chore: 🤖 revert hds from 4.19.0 to 4.18.2 (#2892) 2025-06-19 11:27:08 -07:00
hashicc 7f4586ed9e chore: 🤖 migrate away from component helper for form/account (#2880) 2025-06-18 10:09:28 -04:00
Carlos Alcaide Corvo 0b6049c8b0 Refactor authentication test selectors (#2887) 2025-06-17 11:51:04 -07:00
Lisbet Alvarez 2d347171a9 refactor: 💡 add title to primary column in auth-methods table (#2882) 2025-06-13 15:55:06 -07:00
Carlos Alcaide Corvo a085d4d4f7 Clean up commented mirage authentication route handler. (#2879) 2025-06-13 14:25:37 -07:00
Carlos Alcaide Corvo 7e2f3e3df3 Refactor credential-store/credentials list test selectors (#2884)
* Refactor credential-store/credentials list test selectors
2025-06-13 08:24:18 -07:00
Carlos Alcaide Corvo 3899daea7c Add sortNameWithIdFallback to auth methods (#2878)
* Add sortNameWithIdFallback to auth methods
2025-06-12 15:35:39 -07:00
Cameron Perera 3d1ee732b4 Icu 17232 improve host source resource fetching (#2877)
* refactor: 💡 add fallback value for address column

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* fix: 🐛 make host address optional in search for plugin hosts

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* feat: 🎸 remove pageSize reset for user preference

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* test: 💍 update e2e tests for host sources

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* feat: 🎸 improve host source resource fetching

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* refactor: 💡 remove em dash translation

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* refactor: 💡 add comment to target route for duplicate hosts

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* refactor: 💡 add filter to query for hosts

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232

* refactor: 💡 use Promise.all() when fetching hosts

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17232
2025-06-12 17:19:56 -05:00
Lisbet Alvarez 899d7beee0 chore: 🤖 upgrade @babel/runtime to v7.27 (#2881)
* chore: 🤖 upgrade @babel/runtime to v7.26
2025-06-12 10:17:12 -07:00
Carlos Alcaide Corvo 4c7f3fad75 Icu 15478 refactor credentials delete test selectors (#2867)
* Refactor credential-store/credentials update test selectors

* Refactor credential-store/credentials delete test selectors
2025-06-11 09:29:46 -07:00
hashicc b8eb2c3c9b Upgrade ember-cli-flash (#2870)
* chore: 🤖 upgrade ember-cli-flash to v6

* chore: 🤖 remove unneeded timer disabling

This is no longer needed since the test helpers as of v5 are explicit:
disableTimeout and enableTimeout

Old world:
> Essentially importing this helper would cause the init hook of the FlashObject to become a no-op and disable the timeout running in consuming apps test runs.

> To get the same behaviour as "Old world 🌐 " a consuming app needs only to remove the blueprint import. ✅

https://github.com/adopted-ember-addons/ember-cli-flash/pull/392

* chore: 🤖 add @embroider/macros for ember-cli-flash

ember-cli-flash requires @embroider/macros as a dev dependency:
https://github.com/adopted-ember-addons/ember-cli-flash/pull/392/files/6c5743f424d14143c9309bcb0be8152fdaec11a2#r1369522866
https://github.com/adopted-ember-addons/ember-cli-flash/pull/395
2025-06-10 18:37:36 -04:00
hashicc f5d612c8b5 chore: 🤖 use @nullvoxpopuli/ember-composable-helpers (#2869)
use @nullvoxpopuli/ember-composable-helpers instead of ember-composable-helpers
2025-06-10 18:05:30 -04:00
Carlos Alcaide Corvo 4020f2d4e2 Icu 15477 refactor credentials update test selectors (#2866)
* Refactor credential-store/credentials update test selectors
2025-06-10 14:19:23 -07:00
Carlos Alcaide Corvo 5794cfc7b2 Delete rose cards and rose card old styles (#2872) 2025-06-10 08:46:35 -07:00
Carlos Alcaide Corvo 1284a16978 Refactor credential-store/credentials read test selectors (#2864)
* Refactor credential-store/credentials read test selectors
2025-06-10 08:24:18 -07:00
f255a470cd Improve Project Navigation in Sidebar for Admin UI (#2829)
* When clicking on project in list, go to targets page

* Move Sessions link to bottom of main list on Project scope sidebar

* Add backlink to Orgs

* Add backlink to Global

* Fix some tests

* test: 💍 fix a couple more tests

---------

Co-authored-by: Lisbet Alvarez <lisbet.alvarez@hashicorp.com>
Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
2025-06-09 16:48:59 -07:00
hashicc f738e71657 chore: 🤖 explicitly require managing pnpm "built dependencies" (#2873)
pnpm by default refuses to run install scripts, some packages require
these and others are use install scripts as a way to provide messages.
Instead of a pnpm warning this commit ensures that the package that
is detected with an install script must be either listed in
ignoredBuiltDependencies or onlyBuiltDependencies. Running  pnpm
install will fail until a package has been categorized

As part of this commit the packages that were previously listed
as warnings have been categorized under ignoredBuiltDependencies
since their install scripts are not required for the functionality
of the package
2025-06-09 10:26:02 -04:00
hashicc beeee851ac fix: 🐛 import codemirror assets correctly (#2875)
Use addon.import to have the correct node_modules path used
2025-06-09 10:05:07 -04:00
Lisbet Alvarez eb365f0d39 Icu 17197 update logic in global roles to only add project scopes when using children (#2874)
* refactor: 💡 hide manage custom scopes button when using keyword

* refactor: 💡 update to only allow projects for global roles

* refactor: 💡 clean-up

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17197
2025-06-06 16:26:34 -07:00
Cameron Pereraandcameronperera 57e614e8db feat: 🎸 add search capability for host sources (#2861)
* feat: 🎸 add search capability for host sources

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094

* refactor: 💡 use Rose::Pagination component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094

* refactor: 💡 move target detail body to index route

to fix whole page reload issue

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094

* Add missing copyright headers

* refactor: 💡 PR feedback

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094

---------

Co-authored-by: cameronperera <29386339+cameronperera@users.noreply.github.com>
2025-06-06 16:07:48 -05:00
Lisbet Alvarez 738fa1f15f refactor: 💡 add custom sort to resources (#2860)
* refactor: 💡 add custom sort

* test: 💍 fix tests

* test: 💍 fix more tests

* refactor: 💡 use reusable sort function for name

* refactor: 💡 use sort util in all resources w/ optional name

* Add missing copyright headers

* test: 💍 fix broken tests & add tests for new util

* refactor: 💡 add name custom sort to users

* test: 💍 fix import in tests

* refactor: 💡 add custom name sort to scopes

* test: 💍 pr feedback on util test cases

* refactor: 💡 move sort function into object (pr feedback)
2025-06-05 11:00:25 -07:00
Lisbet Alvarez 4c959ab297 refactor: 💡 hide manage custom scopes button when using keyword (#2865)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17195
2025-06-04 09:41:02 -07:00
Carlos Alcaide Corvo e685295d04 Icu 17166 implement sort for auth methods (#2855)
* Implement sorting for auth methods
2025-06-04 09:05:19 +02:00
hashicc 204bd10e04 ICU-16782 sort admin aliases (#2843)
* feat: 🎸 add sorting to aliases/index route

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16782

* refactor: 💡 use hds table components directly for aliases table

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16782

* feat: 🎸 wire up sorting with alias table, incl. query params

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16782

* test: 💍 add tests for sorting aliases

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16782

* fix: 🐛 use {{true}} instead of 'true' for @isVisuallyHidden

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16782
2025-06-03 13:55:11 -04:00
hashicc 5a302b0bcc Migrate to pnpm (#2828)
* chore: 🤖 remove packageManager field from package.json

* chore: 🤖 migrate yarn resolutions to pnpm overrides

* chore: 🤖 generate pnpm-lock.yml by using: pnpm import yarn.lock

* chore: 🤖 remove yarn.lock file

* chore: 🤖 migrate workspace configuration

* chore: 🤖 remove .yarn and .yarnrc.yml config

* chore: 🤖 update .copyright.hcl to ignore root pnpm files

* chore: 🤖 migrate husky pre-commit hook from yarn to pnpm

* chore: 🤖 migrate package.json scripts from yarn to pnpm

* chore: 🤖 update rose package.json & build to use explicit deps

* chore: 🤖 remove unused cp metadata.json command in pjson script

The cp metadata.json to dist was added in:
d19a251

The addons/rose/metadata.json no longer exists so it can't be copied

* chore: 🤖 migrate electron-app package.json scripts to pnpm

* chore: 🤖 create pnpm workspace for electron-app w/ hoisted deps

* chore: 🤖 migrate electron-app yarn lockfile to pnpm lockfile

Used "pnpm import yarn.lock"

* chore: 🤖 migrate electron-app overrides

node-gyp isn't needed since the fork is used via @electron/node-gyp
see: https://github.com/electron/node-gyp?tab=readme-ov-file#node-gyp---nodejs-native-addon-build-tool

* chore: 🤖 add trusted deps to be built to onlyBuiltDependencies

This is a pnpm feature that increases security. By defining these
dependencies they are being trusted to allow install hooks,
including postinstall. See:
https://pnpm.io/settings#onlybuiltdependencies

* chore: 🤖 add electron-app pnpm files to copyright header ignore

* chore: 🤖 remove yarn references from .*ignore files

* chore: 🤖 migrate --yarn to --pnpm for ember-cli-update.json

This isn't clearly documented but something that is supported
and can be found in other ember apps:
https://github.com/search?q=--pnpm+ember-cli-update.json&type=code

* chore: 🤖 migrate ci and related scripts to use pnpm

The cache is migrated to use the cache supported by
actions/setup-node@v4, an action we are already using. This is the
recommended way by pnpm:
https://pnpm.io/continuous-integration#github-actions

This commit also ensures that all github actions are using
consistent versions of actions/setup-node and pnpm/action-setup

* docs: ✏️ update documentation to use pnpm instead of yarn

* chore: 🤖 use explicit asciinema-player dependency

Because we override styles on asciinema-player we depend on their
styles. What might be a minor non-breaking change in their version could
(and did) break our style expectations in our overrides.

* chore: 🤖 run pnpm install and commit lockfile changes

* chore: 🤖 Remove ember-inflector from being overidden

https://github.com/hashicorp/boundary-ui/pull/2828#discussion_r2110380711

* chore: 🤖 remove highlight.js from pnpm overrides

https://github.com/hashicorp/boundary-ui/pull/2828#discussion_r2109847938

* chore: 🤖 remove additional unnecessary files from ignore files

* docs: ✏️ fix pnpm doc links

* chore: 🤖 use corepack to specify packageManager in package.json

* chore: 🤖 let pnpm action in ci use packageManager for version

https://github.com/pnpm/action-setup?tab=readme-ov-file#version

* docs: ✏️ fix broken pnpm website links
2025-06-03 12:55:12 -04:00
hashicc daa23f66c4 ICU-16785 sort admin scopes (#2842)
* feat: 🎸 add sorting to scopes/scope/scopes/index route

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785

* refactor: 💡 use direct hds components to support sorting

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785

* feat: 🎸 wire up sorting to the scopes table

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785

* test: 💍 add tests for sorting scopes

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785
2025-06-03 09:40:32 -04:00
Priya Patel b06c396086 Refactor - Rose::Page::Layout (#2849)
* Refactor: Rose::Page::Layout - header, actions and breadcrumbs (#2813)

* refactor: 💡 page::layout- header,breadcrumb and actions

* refactor: 💡 added new class for the page layout, fixed a11y

* refactor: 💡 addressed comments

* refactor: 💡 moved copy snippet to PH.Generic

* refactor: 💡 addressed comments

* Refactor: Rose::Page::Layout - header, actions and breadcrumbs Part 2 (#2825)

* refactor: 💡 rose page layout header

* refactor: 💡 removed extra classes

* Refactor: Rose::Page::Layout - header, actions and breadcrumbs Final Part (#2840)

* refactor: 💡 rose page layout header

* Refactor: Rose::Page::Layout - header, actions and breadcrumbs (#2813)

* refactor: 💡 page::layout- header,breadcrumb and actions

* refactor: 💡 added new class for the page layout, fixed a11y

* refactor: 💡 addressed comments

* refactor: 💡 moved copy snippet to PH.Generic

* refactor: 💡 addressed comments

* Refactor: Rose::Page::Layout - header, actions and breadcrumbs Part 2 (#2825)

* refactor: 💡 rose page layout header

* refactor: 💡 removed extra classes

* refactor: 💡 fixed a11y heading, actions dropdown

* refactor: 💡 fixed page header

* refactor: 💡 fix heading margin and failing tests

* refactor: 💡 add back margin for hds header tags

* refactor: 💡 fix style
2025-06-02 16:20:50 -05:00
Lauren 5555603a5c [Retire] Rose::Dropdown (#2858)
* remove dropdown styling

* remove rose/dropdown child component integration tests

* refactor tests, delete tests

* remove rose dropdown component from addons/

* refactor tests, delete tests

* run yarn format

* remove additional rose dropdown files from addons

* remove ref to rose dropdown from readme

* remove skipped test, comment states no longer relevant

* remove unused ref to rose-header-dropdown selector

* refactor tests using .rose-dropdown selector, cleanup

* run yarn format

* add selector to commonSelectors, refactor more tests

* nest .hds-dropdown selector in place of .rose-dropdown

* revert @use dropdpwn

* remove line, styling already applied on btn selector

* removed unused dropdown-radio-btn selector
2025-06-02 14:06:06 -04:00
hashicc 7c3b6d6d9e ICU-16787 sort admin users (#2844)
* feat: 🎸 add sorting to users route, feat. query params

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16787

* refactor: 💡 use hds components directly to support custom sort

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16787

* feat: 🎸 wire user sorting from controller to template

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16787

* test: 💍 add sorting tests for users

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16787
2025-06-02 13:26:11 -04:00
Carlos Alcaide Corvoandhashicc ce87d7728c Implement sorting for host catalogs (#2857)
Co-authored-by: hashicc <191911133+hashicc@users.noreply.github.com>
2025-05-30 16:14:00 -04:00
Carlos Alcaide Corvo e4da851172 Implement sorting for credential stores (#2856) 2025-05-30 15:36:43 -04:00
Cameron Perera e3433ec1f7 feat: 🎸 add pagination to host source table (#2854)
* feat: 🎸 add pagination to host source table

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17093

* test: 💍 add e2e tests for host source pagination

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17093

* refactor: 💡 change paginatedHosts to getter

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17093

* test: 💍 refactor e2e pagination test

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17093
2025-05-29 15:55:05 -05:00
hashicc 96f30d1b31 Add sorting to Groups in boundary admin (#2846)
* feat: 🎸 wire up sorting for groups/index route

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16783

* refactor: 💡 use underlying hds components directly for sorting

This step refactors the table to use the components directly.
With this setup a sorting refactor can be done more easily.

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16783

* feat: 🎸 wire up sorting from controller to template

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16783

* test: 💍 add sorting tests for groups table

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16783
2025-05-28 18:20:43 -04:00
hashicc f4a48467e3 Add sorting to Roles in boundary admin (#2827)
* feat: 🎸 add sorting to roles/index route

Add sortAttribute and sortDirection query params, and use values in
this.store.query sorting option

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16784

* refactor: 💡 use manual hds table components for custom sort

This refactor uses the underlying hds components directly in order to
support custom sorting, see:
https://helios.hashicorp.design/components/table/table?tab=code#custom-sorting-using-the-yielded-sorting-argumentsfunctions

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16784

* feat: 🎸 wire-up query param state, controller action with table

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16784

* test: 💍 add sorting tests for admin roles list

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16784
2025-05-28 10:40:31 -04:00
Lisbet Alvarez bada405511 Icu 17087 implement sort for sessions (#2845)
* feat: 🎸 implement sort for sessions

* test: 💍 fix bug + add tests

* Add missing copyright headers

* refactor: 💡 remove fix so it can be its own PR

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17087
2025-05-27 17:31:55 -07:00
Cameron Perera 96d7c055b0 Icu 17092 replace hosts connect modal with table in target detail page (#2841)
* feat: 🎸 add host source table to target detail view

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17092

* chore: 🤖 remove unused translation

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17092

* test: 💍 update e2e tests for host source change

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17092

* test: 💍 add test for target details main connect button

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17092

* test: 💍 update e2e test selector

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17092
2025-05-23 11:45:27 -05:00
dependabot[bot]andZhihe Li a747614a74 chore(deps): bump hashicorp/setup-terraform from 3.0.0 to 3.1.2 (#2853)
Bumps [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) from 3.0.0 to 3.1.2.
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/setup-terraform/compare/a1502cd9e758c50496cc9ac5308c4843bcd56d36...b9cd54a3c349d3f38e8881555d616ced269862dd)

---
updated-dependencies:
- dependency-name: hashicorp/setup-terraform
  dependency-version: 3.1.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-05-23 10:53:03 -04:00
dependabot[bot]andZhihe Li 84856407ee chore(deps): bump stefanzweifel/git-auto-commit-action (#2852)
Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases)
- [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/e348103e9026cc0eee72ae06630dbe30c8bf7a79...b863ae1933cb653a53c021fe36dbb774e1fb9403)

---
updated-dependencies:
- dependency-name: stefanzweifel/git-auto-commit-action
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-05-23 10:40:43 -04:00
dependabot[bot] 9bfd624713 chore(deps): bump actions/setup-node from 4.3.0 to 4.4.0 (#2851)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/cdca7365b2dadb8aad0a33bc7601856ffabcc48e...49933ea5288caeca8642d1e84afbd3f7d6820020)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-23 10:22:03 -04:00
Jan Harrie b2a6e4d47f configure dependabot to automatically update GitHub Actions dependencies (#2848) 2025-05-23 15:03:29 +02:00
Lisbet Alvarez 308240be89 fix: 🐛 refocus on route changes when attempting to search (#2839)
* fix: 🐛 refocus on route changes when attempting to search

* refactor: 💡 update condition

* refactor: 💡 prevent refocus for more use cases like filtering

* refactor: 💡 use router service instead of window object

* refactor: 💡 PR feedback (use param finder instead of regex)

* test: 💍 update test title

* test: 💍 refactor tests to use test.each
2025-05-22 16:34:40 -07:00
Lisbet Alvarez c12edc7f73 feat: 🎸 implement sort for session recordings (#2837)
* feat: 🎸 implement sort for session recordings

* refactor: 💡 update selectors w/ pr feedback

* refactor: 💡 remove click in tests

* test: 💍 update selectors due to merge

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17065
2025-05-22 10:27:19 -07:00
Lisbet Alvarez 44bd5ee1c6 Icu 16786 implement sort for targets (#2830)
* feat: 🎸 add sorting for targets

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16786
2025-05-21 12:20:48 -07:00
Lauren 8f3cccce32 [Retire] Rose::Button (#2838)
* remove rose::button files and imports

* remove rose btn utilities

* remove btn class from app.scss

* delete rose btn styling, component files

* update test

* remove playground.hbs, team agreed

* refactor dummy/app/templates/index.hbs to use hds btn
2025-05-21 05:23:21 -04:00
Cameron Perera 1ef7d9b5a7 Icu 16413 roles principals (#2835)
* test: 💍 add selectors to roles principals

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16413

* test: 💍 add selectors for roles project scope

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16413

* change exists() to isVisible()
2025-05-20 17:08:08 -05:00
Dharini Jeeva c445f95aa4 chore: desktop header nav dropdown changes (#2833) 2025-05-20 13:24:46 -07:00
Cameron Perera 96db738f4b refactor: 💡 fix table density and remove desc margin (#2836) 2025-05-20 12:56:28 -05:00
Lauren 6181df4c69 [Replace][Desktop] Rose::Button (#2812)
* replace rose::button in ui/desktop

* replace .rose-button in ui/admin acceptance tests

* update @iconOnly to @isIconOnly

* add required @text arg for isIconOnly btns

* re-add title html element

* add _button.scss for custom styling

* add hover styling over close btn

* fix a bug when we hover in dark mode

* fix bug in main, hover white should trigger in dark mode

* remove &:not(:disabled) on btn close window class

* fix merge conflict

* fix styling
2025-05-20 12:15:45 -04:00
Priya Patel 376787fda1 refactor: 💡 rose dialog cleanup (#2831) 2025-05-20 09:40:57 -05:00
Cameron Perera 80ffa856bd fix: 🐛 fix host source description reference (#2834) 2025-05-19 09:35:26 -05:00
Cameron Perera eb9fe573b8 Icu 11937 foundation components retire rose typography styles (#2810)
* refactor: 💡 update _typography stylesheet

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 update header margin styles into single rule

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 add remove-header-margin class

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 remove 'header' class

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* test: 💍 fix failing tests for branded-card in desktop

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 update style rule to use :where()

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 rename remove-header-margin to remove-margin

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937

* refactor: 💡 update style comment

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-11937
2025-05-15 14:48:27 -05:00
Dharini Jeeva e222d5b909 chore: Migrate rose dropdown to HDS (#2826) 2025-05-15 11:05:05 -07:00
Lauren 1c774aa783 [Retire] Rose::Form::Label (#2815)
* remove rose::form::label integration test

* update label on fieldset element to ref .hds-form-label

* remove file _label.scss

* remove import

* delete components/rose/form/label/index.hbs

* remove styling from app.scss
2025-05-15 13:01:58 -04:00
Lisbet Alvarez 04b51da60f test: 💍 refactor targets test selectors (#2824)
* test: 💍 refactor targets test selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16427
2025-05-14 14:11:03 -07:00
Lauren 28ddec6898 [Retire] Rose::Form::Input (#2821)
* remove rose form input component folder

* remove component integration test

* remove _input styling and import ref

* remove import from addons

* remove app.scss form input styling

* replace form.input with Hds::Form::TextInput::Field

* remove assertions for disabled state, remove Hds replacement

* remove test that checks input field that no longer exists

* remove rose::form::input component from playgrouund.hbs

* remove @use 'input'
2025-05-14 09:21:18 -04:00
hashicc d987f844de Use @use for scss explicit imports (#2820) 2025-05-13 21:01:49 -04:00
Carlos Alcaide Corvo b0d26fe1e6 Add sorting foundation as util and using it within indexed-db-handler (#2773) 2025-05-13 16:18:04 -04:00
hashicc e96131a933 chore: 🤖 update @ember/test-helpers to 5.2.1 (#2797) 2025-05-13 13:23:24 -04:00
Sohailandsohail-hashicorp 3d58745bf1 feat: Clear flash messages during login (#2772)
* feat: Make the auth error message non-sticky in nature

---------

Co-authored-by: sohail-hashicorp <198640889+sohail-hashicorp@users.noreply.github.com>
2025-05-12 10:33:01 +05:30
Lisbet Alvarez 9560148253 chore: 🤖 update changelog with date (#2822) 2025-05-08 09:33:25 -07:00
Sohail ec00efb656 feat: 🎉 add minimum window size for the app (#2814) 2025-05-08 21:44:50 +05:30
Carlos Alcaide Corvo ba34307918 Add check to only show windows buttons on windows (#2817)
Default windows action buttons to true and return false only for mac
2025-05-08 14:05:19 +02:00
Carlos Alcaide Corvo 28d8afae2e Dedupe dependencies (#2819) 2025-05-08 12:40:54 +02:00
Michael Li 2e507be454 test(e2e): Address some Admin UI test issues for 0.19.2 (#2818)
* test(e2e): Update navigation to create a storage bucket

* test(e2e): Address flake when creating host catalogs
2025-05-06 14:07:36 -04:00
dillanb-hashi dbf3617224 test(ui): Test the scope picker on the top menu bar (#2788)
* test(ui): Test the scope picker on the top menu bar
* test(ui): Fixing based on recent menu bar changes
2025-05-06 09:39:48 -07:00
Lisbet Alvarez 200a7944e6 Icu 16545 upgrade ember addons api (#2791)
* v4.12.1...v5.12.0

* chore: 🤖 yarn.lock changes from ember upgrade

* chore: 🤖 ember init blueprint changes

* chore: 🤖 yarn.lock updates

* chore: 🤖 fix build errors

* chore: 🤖 yarn.lock file changes

* refactor: 💡 undo style path change

* chore: 🤖 fix build errors

* chore: 🤖 yarn.lock file changes

* test: 💍 add @ember/string back in to fix test error

* chore: 🤖 remove eslint prettier plugin

* chore: 🤖 remove prettier eslint plugin in api addon

* docs: ✏️ add comments back in
2025-05-06 08:49:29 -07:00
Lisbet Alvarez 8dafc2b9a0 test: 💍 refactor session-recording test selectors (#2811)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16423
2025-05-05 10:41:42 -07:00
Lisbet Alvarez b7559c1f4e Icu 16542 upgrade ember addons rose (#2784)
* v4.12.2...v5.12.0

* fix: 🐛 update path name for hds styles

* chore: 🤖 ember init changes

* style: 💄 clear lint errors

* chore: 🤖 run yarn format in rose addon

* chore: 🤖 fix build failures

* chore: 🤖 yarn.lock file changes

* chore: 🤖 yarn.lock file updates

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16542
2025-05-02 15:13:45 -07:00
Lauren dd8e1864db [Replace] Rose::Form::Label and <input.field> elements (#2783)
* replace with hds::form::label

* test Hds::Form::TextInput::Field

* add event handler

* add hds text componenet to grant.value fields

* fix test failures

* replace with set-from-event

* address feedback

* add title html element to hds component
2025-05-02 11:12:30 -04:00
Lauren d85a84980f [Retire] Rose::Icon (#2807)
* remove rose::icon test

* remove .rose-icon class styling from ui/admin

* remove .rose-icon class styling from ui/desktop

* remove .rose-icon class styling from addons/

* remove @import 'icon'

* remove icon.js
2025-05-02 10:53:21 -04:00
Priya Patel 4295966b7f Rose::Dialog::Cover component cleanup (#2806)
* refactor: 💡 rose::dialog::cover cleanup

* refactor: 💡 removed cover addon file
2025-05-01 16:14:53 -06:00
Lisbet Alvarez a87c079ade refactor: 💡 convert rose/anonymous from classic to glimmer comp (#2799) 2025-05-01 10:36:34 -07:00
Cameron Perera b3604e3e40 refactor: 💡 update code tag to use Hds::Text::Code (#2805)
* refactor: 💡 update code tag to use Hds::Text::Code

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16961

* refactor: 💡 remove explicit code tag attribute

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16961

* refactor: 💡 fix extra white space

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16961

* refactor: 💡 remove color from p span style for workers

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16961
2025-05-01 11:59:10 -05:00
hashicorp-tsccr[bot]andhashicorp-tsccr[bot] 6be8130329 Result of tsccr-helper -log-level=info gha update -latest .github/ (#2809)
Co-authored-by: hashicorp-tsccr[bot] <hashicorp-tsccr[bot]@users.noreply.github.com>
2025-05-01 09:15:31 +02:00
Lisbet Alvarez ca3972c04f fix: 🐛 polyfillUUID config for ember-data (#2808) 2025-04-30 16:54:59 -07:00
Lisbet Alvarez 4d98729cde chore: 🤖 update changelog and cli version (#2801)
* chore: 🤖 update changelog and cli version
2025-04-30 11:55:56 -07:00
Priya Patel 185af6f00e refactor: 💡 remove card component from desktop (#2798) 2025-04-29 17:28:59 -06:00
Lisbet Alvarez c5353d6e5e Icu 16547 upgrade ember desktop UI (#2796)
* v4.12.2...v5.12.0

* chore: 🤖 yarn.lock file changes

* chore: 🤖 ember init blueprint updates

* chore: 🤖 yarn.lock changes

* style: 💄 run linter and formatter

* chore: 🤖 yarn.lock file changes

* chore: 🤖 remove ember-cli-app-version

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16547
2025-04-29 15:32:50 -07:00
Lisbet Alvarez 20a05dc96a style: 💄 fix formatting for long credential names & description (#2804)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16869
2025-04-29 12:17:14 -07:00
Cameron Perera 30028b5ba0 refactor: 💡 convert p tags to Hds::Text component (#2800)
* refactor: 💡 convert p tags to Hds::Text component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16865

* style: 💄 remove comment

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16865
2025-04-29 12:26:20 -05:00
Priya Patel 3bafb708ea refactor: 💡 update Rose::Frame to use Hds::Card::Container (#2795) 2025-04-29 09:15:17 -07:00
Cameron Perera 24d86a230a refactor: 💡 use Hds::Text::Code in place of pre tag (#2802)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16956
2025-04-29 09:24:24 -05:00
Lisbet Alvarez 620f822504 Icu 16546 upgrade ember for admin UI (#2793)
* v4.12.2...v5.12.0

* chore: 🤖 yarn.lock changes

* chore: 🤖 ember init blueprint changes

* chore: 🤖 yarn.lock file changes

* style: 💄 run linter and formatter

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16546
2025-04-25 14:39:59 -07:00
Priya Patel 93a57f26ee Replace Rose::Icon with HDS::Icon (#2762)
* refactor: 💡 rose icons with hds icons

* fix: 🐛 remove child selector for h2

* fix: 🐛 removed unwanted changes

* fix: 🐛 remove unncessary change

* refactor: 💡 rose icon for rose components

* fix: 🐛 default size for doc-link, updated tests, removed TODO

* fix: 🐛 update doc-link test
2025-04-25 13:18:23 -07:00
Cameron Perera 40ec4493ea fix: 🐛 credential library serializer for SSH certificate (#2790)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16880
2025-04-25 10:23:51 -05:00
hashicc de83d59bde Add additional indexeddb-handler tests (#2786) 2025-04-24 11:05:44 -04:00
Lisbet Alvarez a937c5e8de style: 💄 remove table borders from list-wrapper component (#2787) 2025-04-22 18:11:49 -07:00
Priya Patel b820bb19d7 Refactor: Hosts-sets test - delete, read and create (#2778)
* refactor: 💡 hosts-sets test refactor - delete,read,create

* fix: 🐛 updated test selectors
2025-04-22 14:16:15 -07:00
Lauren 36db2d3f1d Replace Rose::Button with Hds::Button (#2771)
* refactor btn

* refactor scope/workers/index.hbs

* refactor add-members, add-principals

* refactor btns, fix tests

* update json-secret-skeleton btn

* remove class from app.scss
2025-04-22 16:25:12 -04:00
Cameron Perera 5786b86521 refactor: 💡 update text span elements with Hds::Text::Body (#2779)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16864
2025-04-22 11:54:00 -05:00
Priya Patel c6a5f93b44 Replace Rose::Dialog::Cover with HDS::ApplicationState (#2770)
* refactor: 💡 rose dialog cover with hds application state

* fix: 🐛 i18n key

* fix: 🐛 addressed comments

* fix: 🐛 CSP issue for svg

* docs: ✏️ added CSP issue context
2025-04-18 12:42:18 -07:00
Carlos Alcaide Corvo 8814e2eacb Ember upgrade for addons/core (#2777) 2025-04-17 13:59:14 -07:00
Lisbet Alvarez 9b1e4a8291 Icu 16541 upgrade ember addons auth (#2776)
* v4.12.2...v5.12.0

* chore: 🤖 yarn.lock file changes

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* chore: 🤖 run ember init to use updated blueprints as ref

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* chore: 🤖 undo node change

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* style: 💄 fix lint error

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* fix: 🐛 undo deleted gitkeep file

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* refactor: 💡 use workspace protocol for addons

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541

* chore: 🤖 undo removal of stylistic

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16541
2025-04-17 11:36:46 -07:00
Priya Patel 01c2468f86 Refactor: Replace Rose::Dialog with HDS::Modal for desktop client (#2766)
* refactor: 💡 rose dialog to hds modal

* fix: 🐛 dark theme hds overlay

* fix: 🐛 dark mode hds modal shadow

* fix: 🐛 move dark mode modal override to a mixin

* fix: 🐛 update exists to isVisible
2025-04-17 10:57:45 -07:00
Cameron Perera af705718ff Reveal & Toolbar::Info Component cleanup (#2782)
* refactor: 💡 remove desktop Reveal component

* refactor: 💡 remove Toolbar::Info component
2025-04-17 10:11:14 -05:00
Carlos Alcaide Corvo e22ef12529 Chore icu 16553 deprecate a like array (#2767) 2025-04-15 09:29:01 -07:00
Carlos Alcaide Corvo 62510d9243 Chore icu 16553 deprecate a like array (#2767) 2025-04-15 09:13:55 -07:00
Carlos Alcaide Corvo 0fd25a6bb4 Delete electron-squirrel-startup from electron-app (#2774) 2025-04-15 08:22:35 -07:00
Cameron Perera a268a7b75e refactor: 💡 update all header tags to use HDS::Text::Display (#2765)
* refactor: 💡 update all header tags to use HDS::Text::Display

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16863

* refactor: 💡 update minor css changes for page header

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16863

* refactor: 💡 remove duplicate title and lint hbs files

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16863
2025-04-14 21:05:37 -05:00
3dded0a7bc Llb/side nav refactor (#2763)
* feat: 🎸 scope picker component (#2646)

* feat: 🎸 scope picker component
---------

Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com>
Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>

* refactor: 💡 use hds side nav w/ hds frame instead of rose (#2688)

* refactor: 💡 use hds side nav w/ hds frame instead of rose

* test: 💍 clean up selectors

* fix: 🐛 specificity of style override

* refactor: 💡 use hds side nav list instead of target and portal

* refactor: 💡 revert to using portal component & update scss

* docs: ✏️ update comment

* refactor: 💡 use custom class names for containers

* refactor: 💡 pr feedback (use @hasSidebar arg)

* refactor: 💡 fix onboarding page & fix settings title

* refactor: 💡 remove unneeded template file (pr feedback)

* test: 💍 fix tests due to merge conflicts

* test: 💍 add missing test selector due to merge conflict

* test: 💍 fix more broken tests due to merge conflicts

* chore: 🤖 update yarn.lock & update hds version to 4.18.1

* chore: 🤖 fix e2e test cases due to side nav refactor (#2751)

* chore: 🤖 fix e2e test cases due to side nav refactor

* test: 💍 fix e2e test for desktop

---------

Co-authored-by: lisbet-alvarez <lisbet-alvarez@users.noreply.github.com>
Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-04-14 14:27:02 -07:00
Lauren ab3140be0a refactor: 💅 Aliases: update-test (#2760)
* refactor aliases update-test

* refactor test

* address feedback
2025-04-14 12:09:22 -04:00
Sohail 0fcd58c080 feat: Conditionally display 'Check for Updates' menu item based on CLI type (#2754) 2025-04-10 07:58:23 -07:00
Carlos Alcaide Corvo 52f0d9f9e8 Update browserslist db (#2768) 2025-04-10 07:43:24 -07:00
hashicc 6585fa8e41 Add tests for indexed db handler (#2759) 2025-04-09 14:52:02 -04:00
Carlos Alcaide Corvo bef9b3b276 Refactor test selectors for credential-store/credentials create test (#2756) 2025-04-08 15:40:08 -07:00
Dharini Jeeva af411b854a chore: 🤖 include ipv6 support in mocks (#2764)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16862
2025-04-04 16:01:47 -07:00
Cameron Perera a697dba5b9 chore: 🤖 add test selectors to roles org-scope tests (#2761) 2025-04-04 13:33:41 -05:00
Carlos Alcaide Corvo 1203081ec7 Chore icu 15394 upgrade electron (#2753)
* Upgrade electron v35
2025-04-04 09:03:19 -07:00
Zhihe Li 2db63ae2f8 feat: 🎸 Populate subject for OIDC account name displays (#2757)
OIDC accounts are guaranteed to have a subject claim which we should
display. Currently we only look at email and full_name which
are all optional for OIDC claims. Also added deep links for accounts.

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16634
2025-04-03 13:33:08 -04:00
Lisbet AlvarezandCarlos Alcaide Corvo 9dd1617b57 chore: 🤖 upgrade ember-data to v5.3.11 (#2745)
* chore: 🤖 upgrade ember-data to v5.3.11

* Add missing copyright headers

* refactor: 💡 remove comment

* chore: 🤖 update yarn.lock due to merge conflict

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* fix: 🐛 undo ember-source upgrade (should be upgraded w/ ember)

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* chore: 🤖 move dep to devDeps & resolve test-helper version

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* fix: 🐛 unique record id error bug fix using store.push

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* refactor: 💡 cleanup

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* Delete old deprecation initializer (#2748)

* refactor: 💡 import from'ember-data/store'

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* refactor: 💡 clean up comments

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

* refactor: 💡 remove compatWith comment

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16544

---------

Co-authored-by: Carlos Alcaide Corvo <ccorvo@hashicorp.com>
2025-04-03 09:32:44 -07:00
Zhihe Li 834058ff45 refactor: 💡 Batch writes to indexedDB (#2752)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16795
2025-04-01 14:04:31 -04:00
Cameron Perera 0f41a0a576 chore: 🤖 add test selectors to roles grants tests (#2749)
* chore: 🤖 add test selectors to roles list tests

* chore: 🤖 add test selectors for roles global-scope tests

* chore: 🤖 add TABLE_RESOURCE_LINK to global-scope tests

* test: 💍 change exists to isVisible

* chore: 🤖 add test selectors to roles grants tests

* refactor: 💡 create custom selector for grants tests

* refactor: 💡 use new FORM selector
2025-03-31 16:24:40 -05:00
Zhihe Li 1025cbfb87 refactor: 💡 Force requests in playwright to use controller addr (#2755) 2025-03-28 19:42:35 -04:00
Priya Patel ca3c1c7623 Refactor: Scopes tests (#2744)
* refactor: 💡 scopes test

* fix: 🐛 addressed comments

* fix: 🐛 replace exists with isVisible, use hasText

* fix: 🐛 move form to commonSelectors
2025-03-28 15:00:59 -07:00
Dharini Jeeva 69d127a89e chore: 🤖 remove rose::nav::breadcrumbs (#2750)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16790
2025-03-28 11:27:43 -07:00
Cameron Perera b4d9cd5bb7 Icu 16409 roles list & global-scope (#2747)
* chore: 🤖 add test selectors to roles list tests

* chore: 🤖 add test selectors for roles global-scope tests

* chore: 🤖 add TABLE_RESOURCE_LINK to global-scope tests

* test: 💍 change exists to isVisible
2025-03-28 13:11:33 -05:00
Carlos Alcaide Corvo 4b1327db24 Upgrade ember-electron from 4.2.2 to 6.0.0 (#2740)
* Upgrade to ember-electron 6
2025-03-27 09:08:12 -07:00
Priya Patel 2f2ceed24e Refactor: Workers test - Create-tags, list and tags (#2742)
* refactor: 💡 workers test - create-tags,list,tags

* fix: 🐛 addressed comment
2025-03-25 15:03:13 -07:00
Zhihe Li de0f20809d chore: 🤖 Fix git-cz usage for auto populating issues in hook (#2746)
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16777
2025-03-25 13:23:31 -04:00
Dharini Jeeva b1f5857cac refactor: 💡 use test selector in host-catalog/list (#2743) 2025-03-24 14:50:09 -07:00
Cameron Perera ddbd1ca163 Icu 16406 roles read, update, delete (#2741)
* chore: 🤖 add test selectors to roles read tests

* chore: 🤖 add test selectors to roles update tests

* refactor: 💡 small test clean up

* chore: 🤖 add test selectors to roles delete tests
2025-03-21 15:34:17 -05:00
Carlos Alcaide Corvo 777e40fff8 Upgrade electron forge from 7.5.0 to 7.7.0 (#2739) 2025-03-21 11:27:04 -07:00
Priya Patel 68460331f5 Refactor: Worker tests - create, read, update and delete (#2737)
* refactor: 💡 workers test - create,delete,read,update

* fix: 🐛 address comments
2025-03-20 14:02:13 -07:00
Cameron Perera 32e9fe8b6a chore: 🤖 use test selectors for roles create tests (#2738) 2025-03-20 14:08:33 -05:00
Priya Patel b3a2460b3f Refactor: Policy tests - list and delete (#2736)
* fix: 🐛 policy test refactor - list, delete

* fix: 🐛 chain assertions

* fix: 🐛 fixed failing test
2025-03-18 15:03:53 -07:00
Dharini Jeeva 3260bf1b2a refactor: 💡 use test selector in host-catalog read, delete and update (#2734) 2025-03-18 12:06:39 -07:00
Priya Patel e69c6456d1 Refactor: Policy tests - create, read and update (#2732)
* refactor: 💡 policy tests: create, read and update

* fix: 🐛 use commonselector for table link
2025-03-18 10:47:46 -07:00
Zhihe Li f8b3792e62 Fix Searchbar debouncing in admin UI (#2735)
Applied ember-concurrency task usage to admin UI model hooks to help fix the debouncing issue.
2025-03-18 11:20:50 -04:00
Cameron Perera 01c34c0a79 Small test selector updates (#2733)
* refactor: 💡 update sidebar nav selector name

* refactor: 💡 move generic selector to common selector file
2025-03-17 11:07:21 -05:00
Priya Patel 825751239f Refactor: 💡 User acceptance tests: list, update and accounts (#2726)
* refactor: 💡 users selector tests: list, update and accounts

* fix: 🐛 follow AAA pattern

* refactor: 💡 updated common selectors
2025-03-14 16:04:56 -07:00
Dharini Jeeva 2465739060 Icu 15486 host catalogs create test (#2729) 2025-03-14 09:42:29 -07:00
Zhihe Li e6834d2033 chore: 🤖 Add chrome for self hosted runners when running tests (#2731) 2025-03-14 12:10:44 -04:00
Cameron Perera 924100df4c chore: 🤖 update session list tests with selectors (#2727)
* chore: 🤖 update session list tests with selectors

* chore: 🤖 add TABLE_ROW selector to session list
2025-03-14 10:32:44 -05:00
Zhihe Li 1ac7d1e08b feat: 🎸 Use repo vars to determine the runner size (#2724) 2025-03-14 11:15:53 -04:00
Zhihe Li 7becccd628 fix: 🐛 Fix search field race condition in DC targets (#2723)
* fix: 🐛 Fix search field race condition in DC targets
* feat: 🎸 Keep track of last search to know when to debounce

✅ Closes: ICU-14775
2025-03-13 18:50:33 -04:00
Dharini Jeeva dff5cac4fe refactor: 💡 worker filter feature flags (#2720) 2025-03-13 15:12:36 -07:00
Lisbet Alvarez ecd0c1de20 Icu 16535 refactor usage of did insert modifiers (#2722)
* refactor: 💡 remove ember-render-modifiers {{did-insert}} usage
2025-03-13 12:58:39 -07:00
Dharini Jeeva 878cb17b62 refactor: 💡 use test selector in groups/members route (#2719) 2025-03-13 09:51:37 -07:00
Carlos Alcaide Corvo 706981c231 Refactor test selectors within auth-methods oidc test (#2717)
* Refactor test selectors within auth-methods oidc test
2025-03-13 08:39:11 -07:00
Priya Patel d212cf7c40 Refactor: 💡 User acceptance tests - create, delete and read (#2718)
* refactor: 💡 user - create, delete and read tests

* fix: 🐛 removed 'this' usage, updated user create test
2025-03-12 16:49:38 -07:00
Carlos Alcaide Corvo 19b6e5154f Fix locationType deprecations. (#2721)
* Deprecated usage of locationType auto to history. Define locationType before setting configuration
2025-03-12 16:16:33 -07:00
Lisbet Alvarez 89263918ea Icu 16534 refactor usage of tracked using this args (#2711)
* refactor: 💡 remove tracked usage with this.args
2025-03-12 10:09:16 -07:00
Carlos Alcaide Corvo e8fc6d163b Deprecate import inject from @ember/service (#2716)
* Deprecate usage of import inject
2025-03-12 08:29:54 -07:00
Priya Patel 34a670f8d7 Refactor: Onboarding tests selectors refactor (#2715)
* refactor: 💡 Onboarding tests

Refactor test selectors for onboarding acceptance tests

✅ Closes: #ICU-16398, #ICU-16399

* fix: 🐛 failing test

* chore: 🤖 add copyright header

* fix: 🐛 use AAA pattern and server.schema

* fix: 🐛 moved done button to selectors

* fix: 🐛 renamed copy text btn selector
2025-03-11 14:53:32 -07:00
hashicc 6a46e2dd57 chore: 🤖 add chrome flags to keep from backgrounding (#2714)
There was an issue in tests where events fired in
Hds::FlyOut and Hds::Modal were not being dispatched.
These close events were depended on by test waiters
to know when the element was removed. In recent
versions of chrome, when ran headless and on macos,
these events were not firing. This meant the tests
waited indefinitely on developers local machines,
until the tests timed out. This issue was seen
across multiple macbooks and even on the hds repo.

This issue was *only* experienced in headless
chrome on recent macos.
It was **not** happening in:
* macos chrome headed locally
* linux chrome headless in CI

This made the issue only impact people running tests
locally, getting obscure timeouts. This issue is fixed
by the addition of this flag:
--disable-backgrounding-occluded-windows

The other flags added in this commit are not required
for the fix but should help keep a browser "active",
which was loosely what researching this bug hinted at
the cause of finding the issue fix.
2025-03-11 13:29:19 -04:00
Dharini Jeeva 5e0fa7c744 test: 💍 use test selectors for groups (#2713)
* test: 💍 use test selectors for groups
2025-03-10 13:32:32 -07:00
Zhihe LiandMichael Li 474ce17a13 Initial E2E DC setup (#2710)
* Initial setup

* refactor: 💡 Update dependency job

* refactor: 💡 Use boundary ent

* chore: 🤖 Cleanup workflow

* refactor: 💡 Adjust run condition

---------

Co-authored-by: Michael Li <michael.li@hashicorp.com>
2025-03-10 15:36:21 -04:00
Cameron Perera f00eed8633 test: 💍 fix false positive test (#2712) 2025-03-10 13:35:13 -05:00
Cameron Perera 42bcefa068 Icu 16393 managed groups read (#2709)
* chore: 🤖 update managed-groups read test selectors

* chore: 🤖 add test selectors to managed-groups delete

* chore: 🤖 use test selectors in managed-groups list tests

* chore: 🤖 refactor managed-groups members tests

* refactor: 💡 update selector name for DELETE action
2025-03-06 14:42:45 -06:00
Lisbet AlvarezandZhihe Li 29bde998ed refactor: 💡 remove usage of ember/runloop & ember-pollster (#2705)
* refactor: 💡 remove usage of ember/runloop & ember-pollster

---------

Co-authored-by: Zhihe Li <zhihe.li@hashicorp.com>
2025-03-06 12:20:48 -08:00
Michael LiandDharini Jeeva e29a83ec25 test(e2e): Enable Admin UI e2e tests to run against a local branch (#2700)
* test(e2e): decouple url used for browser vs cli

* test(e2e): Add instructions for using the UI of a local branch

* CR

* Fix typo in e2e-tests/README.md

Co-authored-by: Dharini Jeeva <dharini@hashicorp.com>

---------

Co-authored-by: Dharini Jeeva <dharini@hashicorp.com>
2025-03-05 11:38:12 -05:00
Zhihe Li ddfcbde7d4 chore: 🤖 audit security alerts (#2707)
* chore: 🤖 Update solid-js to resolve security alert

* chore: 🤖 Update path-to-regexp package for security alert

* chore: 🤖 Update nanoid to resolve security alert
2025-03-05 11:16:45 -05:00
Cameron Perera 42e31d3ece Icu 16392 managed groups create (#2704)
* chore: 🤖 update manage-gropus tests to ues selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16392

* refactor: 💡 small clean up
2025-03-04 15:09:08 -06:00
Michael Li cd8480bc67 test(e2e): Add check for credential store worker filters (#2696) 2025-03-04 14:43:01 -05:00
Carlos Alcaide Corvo 06dc05be08 Update CLI version to 0.19.1 and update changelog (#2706) 2025-03-04 11:09:19 -08:00
Zhihe Li d3fda81af3 fix: 🐛 Fix race condition in browser window loading (#2701) 2025-03-04 11:50:19 -05:00
Carlos Alcaide Corvo f58d345b63 Refactor unnecessary curly parenthese (#2699)
* Refactor unnecessary curly parentheses
2025-03-03 09:42:38 -08:00
Cameron Perera f35382a4e0 refactor: 💡 update adapter when transforming filter error (#2702)
* refactor: 💡 update adapter when transforming filter error

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16580

* style: 💄 add comment about adapter changes for translated field

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16580

* style: 💄 move comment to adapter

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16580

* style: 💄 update old comment about model fragments

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16580

* refactor: 💡 use extractErrors in serializers

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16580
2025-03-03 09:58:45 -06:00
Dharini Jeeva 8087430dd2 chore: upgrade to yarn 4 (#2697) 2025-02-28 14:19:35 -08:00
Zhihe Li 2a5f1f3668 fix: 🐛 Fix development build for desktop client (#2703) 2025-02-28 10:10:40 -05:00
Carlos Alcaide Corvo a889cb30e0 Upgrade electron-forge to 7.5.0 (#2526)
* Upgrade electron-forge to 7.5.0. Delete lodash path for electron-app, its not need anymore
2025-02-26 17:59:35 -08:00
dependabot[bot]andLisbet Alvarez e411c0ab7b chore(deps): bump solid-js from 1.7.6 to 1.9.5 (#2698)
Bumps [solid-js](https://github.com/solidjs/solid) from 1.7.6 to 1.9.5.
- [Release notes](https://github.com/solidjs/solid/releases)
- [Changelog](https://github.com/solidjs/solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/solid/compare/v1.7.6...v1.9.5)

---
updated-dependencies:
- dependency-name: solid-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
2025-02-26 17:10:08 -08:00
Lauren a136371c1f refactor: 💅 Aliases: create-test (#2685)
* refactor: 💡 Create aliases test suite

Refactor test suite for creating aliases

✅ Closes: ICU-15454

* use FIELD_TEXT_ERROR selector

* revert SAVE_BTN

* revert auth-method changes

* revert scopes changes

* revert scopes changes

* revert targets changes

* add FIELD prefix to const

* remove selector suffix

* revert read-test

* remove FIELD_TEXT_ERROR

* add data-test att to component

* fix merge conflict
2025-02-25 14:17:45 -05:00
Lauren 83fa213854 refactor: 💅 Aliases: read-test (#2691)
* refactor: 💡 Read aliases test suite

Refactor tests for read aliases test suite

✅ Closes: ICU-15455

* replace with alert toast body, replace with this.server.schema

* revert change

* revert oidc test

* revert roles, create-test

* add const to commonSelectors file, revert selectors file

* rename selector

* use TABLE_RESOURCE_LINK common selector

* fix test failure
2025-02-25 13:16:41 -05:00
Cameron Perera 750cc1ecae feat: 🎸 add logging to notifyError decorator (#2694)
* feat: 🎸 add logging to notifyError decorator

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334

* refactor: 💡 update reference to global electronLog

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334

* refactor: 💡 update __electronLog global references

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334

* refactor: 💡 add log to other cluster URL actions

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334

* refactor: 💡 update how we trigger electron log in notifyError

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334

* refactor: 💡 minor log change

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16334
2025-02-21 10:01:48 -06:00
Cameron Perera e25a854d24 docs: ✏️ update bug report template (#2695)
* docs: ✏️ update bug report template

* docs: ✏️ update debug log info

* docs: ✏️ small comment changes

* docs: ✏️ add arch and os version examples

* docs: ✏️ change App Version to Desktop Client Version

* docs: ✏️ small change

* docs: ✏️ another small change to bug template
2025-02-20 09:20:31 -06:00
Zhihe Li 3b32195271 feat: 🎸 Use OCR for E2E tests when comparing terminal (#2639)
* feat: 🎸 add OCR to interpret terminal
2025-02-19 13:21:33 -05:00
Michael Li f9411794c4 test(e2e): Remove attempt to delete session recording due to UI change (#2687)
This test attempted to delete a session recording when the policy did not allow it. However, there was a UI change to remove that option entirely, so we are removing the check.
2025-02-14 11:10:53 -05:00
hashicc c5fdbfd62d chore: fix eslint-related issues (#2690)
* chore: 🤖 add .eslintignore for e2e-tests directory

* chore: 🤖 add auto-fixable eslint changes

* chore: 🤖 make js tasks more explicit to avoid race conditions

avoid having js files be in two separate concurrent branches where tasks
could result in race-condition writes

* chore: 🤖 expand .eslintignore to include more generated dirs

* chore: 🤖 fix additional lint-staged tasks

Fix additional lint-staged tasks that could result in race conditions
2025-02-13 17:09:10 -05:00
Lauren f12ae90aba refactor: 💅 Use common selector ALERT_TOAST_BODY across test suite (#2692)
* replace with alert toast body, replace with this.server.schema

* revert changes to db,schema

* use common selector, alert toast body

* replace ERROR_MSG_SELECTOR with common selector ALERT_TOAST_BODY
2025-02-13 15:54:40 -05:00
Zhihe Li 20c4bbcee2 feat: 🎸 Add migration check to delete index DB when updating (#2241)
* feat: 🎸 Add migration check to delete index DB when updating

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13223
2025-02-13 11:00:19 -05:00
Michael Li f3232f34c2 test(e2e): Use new tags syntax (#2686)
* test(e2e): Use new tag syntax

* chore: Update yarn lock
2025-02-12 17:24:51 -05:00
hashiccandMichael Li 8fedb23399 update e2e docs with supported enos scenarios (#2678)
* docs: ✏️ update e2e docs with supported enos scenarios

* Update grammatical typo in e2e-tests/README.md

Co-authored-by: Michael Li <michael.li@hashicorp.com>

---------

Co-authored-by: Michael Li <michael.li@hashicorp.com>
2025-02-12 17:08:48 -05:00
hashicc 4ca519ce62 chore: add eslint-plugin-playwright rules for e2e tests and fix errors (#2680)
* chore: 🤖 add eslint-plugin-playwright

* chore: 🤖 add recommended eslint rules for playwright to config

* test: 💍 remove describe blocks with async functions

* fix errors related to playwright/valid-describe-callback rule

* test: 💍 fix all auto-fixable playwright rules tests
2025-02-12 12:39:16 -05:00
hashicc 6d4768272a test: 💍 update e2e tests with afterEach cleanup (#2677) 2025-02-12 10:16:44 -05:00
Lauren e8cc22879f [ICU-15403] Trim whitespace on storage buckets and host sets json attrs serializers (#2636)
* trim whitespace on json attrs in storage bucket serializer

* add test for storage bucket serializer

* move trimWhitespace check to application level

* add trimWhitespace to preferred_endpoints attr

* add conditional for string arr

* trim whitespace on str arr

* add check for null val

* refactor, move logic

* relocate comment

* add unit test for application serializer
2025-02-11 18:31:01 -05:00
Zhihe Li b287683a61 chore: 🤖 Upgrade cross-spawn versions to resolve security alert (#2684) 2025-02-11 17:41:47 -05:00
dependabot[bot] ea43ee99d6 chore(deps): bump serialize-javascript from 6.0.1 to 6.0.2 (#2683)
Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 12:01:20 -05:00
Carlos Alcaide Corvo 48db68e7ac Upgrade node engines to work with node 20 and 22 (#2486)
* Upgrade node engines to work with node 20 and 22
2025-02-10 17:37:04 -08:00
Cameron Perera 7807b7a280 Icu 15471 credential store tests (#2663)
* test: 💍 update cred store read tests to use selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15471

* test: 💍 update cred store update to use selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15471

* test: 💍 update cred store delete to use selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15471

* test: 💍 update cred store list to use selectors

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15471

* chore: 🤖 remove refactored selector

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15471
2025-02-10 17:41:37 -06:00
Cameron Perera ad0e223742 chore: 🤖 update desktop release date in CHANGELOG (#2682) 2025-02-07 16:56:17 -06:00
Lisbet Alvarez c9a74f2800 fix: 🐛 reapply storage policy errors out after a failed delete (#2674)
* fix: 🐛 reapply storage policy errors out after a failed delete

* refactor: 💡 hide delete button when policy is retain forever

* refactor: 💡 pr feedback (move check to model)
2025-02-04 14:09:10 -08:00
hashicorp-tsccr[bot]andhashicorp-tsccr[bot] 570ed61edb Result of tsccr-helper -log-level=info gha update -latest .github/ (#2675)
Co-authored-by: hashicorp-tsccr[bot] <hashicorp-tsccr[bot]@users.noreply.github.com>
2025-02-04 13:55:48 -08:00
Dharini Jeeva 835b302c19 chore: add more test cases to credential display e2e test (#2673)
* chore: 🤖 add more test cases
2025-01-31 14:47:03 -08:00
Carlos Alcaide Corvo a0fad9874a Refactor auth-method delete test selectors (#2671) 2025-01-31 08:27:06 -08:00
1412 changed files with 95014 additions and 43867 deletions

No files matched your search

+3
View File
@@ -13,7 +13,10 @@ project {
"**/node_modules/**",
"**/dist/**",
"**/coverage/**",
"**/pnpm-lock.yaml",
"**/pnpm-workspace.yaml",
"ui/desktop/electron-app/out/**",
"ui/desktop/electron-app/ember-dist/**",
"e2e-tests/api-client/**"
]
}
+48 -12
View File
@@ -11,21 +11,57 @@ assignees: ''
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose by contacting us at security@hashicorp.com. Our PGP key is available at [our security page](https://www.hashicorp.com/security/).
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose this by contacting us at security@hashicorp.com. Our PGP key is available on [our security page](https://www.hashicorp.com/security/).
Logging is available on Boundary Desktop Client version >= 2.1.0
Logs are found at:
Linux: ~/.config/Boundary/logs/desktop-client.log
MacOS: ~/Library/Logs/Boundary/desktop-client.log
Windows: $env:USERPROFILE\AppData\Roaming\Boundary\logs\desktop-client.log
If requests are taking longer than expected, please change the logging level to DEBUG. This will log the cache daemon request times as well.
-->
**Describe the bug**
A clear and concise description of what the bug is.
## :bug: Bug Report
**To Reproduce**
Steps to reproduce the behavior:
1. Run `boundary authenticate ...`
2. Run `boundary connect ...`
3. See error
### **Description**
**Expected behavior**
A clear and concise description of what you expected to happen.
_A clear and concise description of the bug._
**Additional context**
Add any other context about the problem here.
### **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._
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
+13
View File
@@ -12,9 +12,22 @@
## 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.
+6 -6
View File
@@ -27,7 +27,6 @@ echo "Trying to merge the latest oss commit - ${latest_oss_commit} from oss/${BR
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
# Here we can try to auto resolve conflicts
# This logic will vary from project to project. The below is just an example.
# We'll try to resolve package.json and yarn.lock conflicts by taking the OSS changes
conflicts=$(git diff --name-only --diff-filter=U)
printf "Found conflicts:\n${conflicts}\n"
# Resolve package.json conflicts by taking oss
@@ -36,11 +35,12 @@ if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit $
git checkout --theirs package.json
git add package.json
fi
# Resolve yarn.lock conflicts by taking oss
if echo "$conflicts" | grep -q yarn.lock; then
echo "Checking out oss yarn.lock..."
git checkout --theirs yarn.lock
git add yarn.lock
# 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
+56
View File
@@ -0,0 +1,56 @@
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@c785b87e244131f27c9f19c1a33e2ead956ab7ce # v1.7.3
with:
install-dependencies: true
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
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
+35 -40
View File
@@ -9,91 +9,86 @@ on:
jobs:
dependencies:
name: Install dependencies
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: "**/node_modules"
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn install
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: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: "**/node_modules"
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
run_install: false
- name: Build
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn run build:ui:admin:oss
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:oss
- name: Upload artifact admin-ui
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: admin-ui
path: ./ui/admin/dist/
- name: Upload artifact OSS admin UI
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: admin-ui-oss
path: ./ui/admin/dist/
build-admin-ui-enterprise:
name: Build Admin UI Enterprise
needs: [dependencies]
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: "**/node_modules"
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
run_install: false
- name: Build
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn run build:ui:admin:enterprise
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:enterprise
- name: Upload artifact enterprise admin UI
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: admin-ui-ent
path: ./ui/admin/dist/
build-admin-ui-hcp:
name: Build Admin UI HCP
needs: [dependencies]
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: "**/node_modules"
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
run_install: false
- name: Build
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn run build:ui:admin:hcp
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:hcp
- name: Upload artifact HCP admin UI
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: admin-ui-hcp
path: ./ui/admin/dist/
+5 -5
View File
@@ -17,7 +17,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
strategy:
fail-fast: false
@@ -27,18 +27,18 @@ jobs:
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0
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.
# 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
@@ -54,4 +54,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0
uses: github/codeql-action/analyze@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
+6 -2
View File
@@ -2,14 +2,18 @@ name: Add Copyright Headers
on: pull_request
jobs:
copywrite:
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.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@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
- uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1
with:
commit_message: Add missing copyright headers
+1 -1
View File
@@ -11,7 +11,7 @@ name: Jira Sync
jobs:
sync:
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
name: Jira sync
steps:
- name: Check if community user
+1 -1
View File
@@ -4,7 +4,7 @@ on:
jobs:
triage:
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
with:
+35 -35
View File
@@ -4,71 +4,71 @@ jobs:
dependencies:
name: Install dependencies
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn install
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
compliance:
name: License Compliance
needs: [dependencies]
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn run compliance:licenses
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run compliance:licenses
lint:
name: Lint
needs: [dependencies, compliance]
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
timeout-minutes: 5
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn lint
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm lint
test:
name: Test
needs: [lint]
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: node-modules-cache
- uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
install-dependencies: true
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
run_install: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- run: yarn test
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm test
+1 -1
View File
@@ -10,7 +10,7 @@ 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: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- name: Trigger Merge
env:
+1 -1
View File
@@ -4,7 +4,7 @@ on:
types: [ oss-merge ]
jobs:
oss-merge:
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+115
View File
@@ -0,0 +1,115 @@
name: Test E2E Desktop Client
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
workflow_dispatch:
jobs:
test:
if: ${{contains(github.event.pull_request.labels.*.name, 'desktop')}}
name: Run Desktop E2E Tests
runs-on: ${{ fromJSON(vars.RUNNER_UBUNTU_22) }}
env:
ENOS_VAR_boundary_edition: "ent"
ENOS_VAR_e2e_debug_no_run: true
ENOS_VAR_boundary_license_path: "./license.hclic"
SETUP_CLI: true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda #v4.1.0
with:
run_install: false
- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
ui/desktop/electron-app/pnpm-lock.yaml
- name: Set up Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
- name: Set up Enos
uses: hashicorp/action-setup-enos@b9fa53484a1e8fdcc7b02a118bcf01d65b9414c9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Vault CLI
run: |
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/vault.zip
unzip /tmp/vault.zip -d /usr/local/bin
- name: Checkout boundary-enterprise
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: hashicorp/boundary-enterprise
# ref: ${{ github.event.inputs.boundary-enterprise-branch }}
path: support/src/boundary-enterprise
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- name: GH fix for localhost resolution
run: |
cat /etc/hosts && echo "-----------"
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
cat /etc/hosts
ssh -V
echo "127.0.0.1 localhost boundary" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost worker" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost vault" | sudo tee -a /etc/hosts
# TODO: Build image ourselves in case we don't want to use latest?
- name: Get Boundary docker image
run: |
docker pull hashicorp/boundary-enterprise:latest
docker save hashicorp/boundary-enterprise:latest | gzip > /tmp/boundary-enterprise.zip
# TODO: Set up license in a better way? Use directly rather than writing to file and pull from secrets manager
- name: Set up test infra
id: infra
run: |
echo "${{ secrets.BOUNDARY_ENT_LICENSE }}" > ./support/src/boundary-enterprise/enos/license.hclic
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
mkdir -p ./enos/terraform-plugin-cache
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
export ENOS_VAR_boundary_docker_image_name=hashicorp/boundary-enterprise:latest && \
export ENOS_VAR_boundary_docker_image_file=/tmp/boundary-enterprise.zip && \
enos scenario launch --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
cd e2e-tests
pnpm playwright install --with-deps
# Runner is headless so we need to enable a display to run the client
- name: Run Tests
run: |
source <(bash ./support/src/boundary-enterprise/enos/scripts/test_e2e_env.sh)
cd e2e-tests
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
pnpm run desktop
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: playwright-report
path: e2e-tests/playwright-report
- name: Clean up test infra
if: ${{ always() }}
run: |
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
enos scenario destroy --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
trigger-gha-release:
runs-on: ubuntu-latest
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Release description
-1
View File
@@ -19,7 +19,6 @@ coverage-*
libpeerconnection.log
npm-debug.log*
testem.log
yarn-error.log
.watchman-cookie*
### VisualStudioCode ###
+2 -2
View File
@@ -1,2 +1,2 @@
yarn compliance:licenses
npx lint-staged
pnpm compliance:licenses
pnpm lint-staged
-1
View File
@@ -21,4 +21,3 @@ else
# This is to allow interaction with the terminal for commitizen
exec < /dev/tty && npx git-cz --hook || true
fi
Binary file not shown.
+22 -22
View File
@@ -25,7 +25,7 @@ subproject, e.g. running or testing.
- [Prerequisites](#prerequisites)
- [Optional Tooling](#optional-tooling)
- [Installation](#installation)
- [Yarn Commands](#yarn-commands)
- [Pnpm Commands](#pnpm-commands)
- [Contributing](#contributing)
- [Branching](#branching)
- [Building ToC](#building-toc)
@@ -33,8 +33,8 @@ subproject, e.g. running or testing.
- [Building with a Container](#building-with-a-container)
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
- [For admin](#for-admin)
- [For desktop](#for-desktop)
- [For Admin](#for-admin)
- [For Desktop](#for-desktop)
- [Committing](#committing)
- [License Checking](#license-checking)
@@ -67,8 +67,8 @@ You will need the following things properly installed on your computer.
* [Git][git]
* [Node.js][node]
* Supported versions: `v18` and `v20`.
* [Yarn][yarn] installed globally
* Supported versions: `v20` and `v22`.
* [Pnpm][pnpm] installed globally
* [Google Chrome][chrome]
* [Firefox][firefox]
@@ -90,18 +90,18 @@ that best suit you and your personal style.
* `git clone <repository-url>` this repository
* `cd boundary-ui`
* `yarn install`
* `pnpm install`
## Yarn Commands
## Pnpm Commands
List of available project commands. `yarn run <command-name>`
List of available project commands. `pnpm run <command-name>`
| Command | Description |
|---------------------|---|
| build | Builds Admin UI for production. |
| test | Runs tests in CI mode. |
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
| compliance:audit | Checks for issues using `pnpm audit` with moderate and above criticality.|
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
| doc:toc | Automatically generates a table of contents for the README file. |
@@ -114,7 +114,7 @@ We follow [GitHub Flow][github-flow].
### Building ToC
To autogenerate a ToC (table of contents) for this README,
run `yarn doc:toc`. Please update the ToC whenever editing the structure
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
of README.
### Building Admin UI for Production
@@ -126,8 +126,8 @@ necessary for your target environment, as described in the [Environment Variable
To quickly produce a production build of Admin UI, run from this folder:
```bash
yarn install
yarn build
pnpm install
pnpm build
```
Assets are saved to `ui/admin/dist/`.
@@ -151,20 +151,20 @@ necessary for your target environment, as described in the [Environment Variable
To quickly produce a production build of Desktop UI, run from this folder:
```bash
yarn install
yarn build:ui:desktop
pnpm install
pnpm build:ui:desktop
```
In windows, UI is generated using docker to provide a stable UI across platforms.
```cmd
yarn install
pnpm install
docker-compose -f docker-compose-embedding.yml run build-desktop-production
yarn build:ui:desktop:app
pnpm build:ui:desktop:app
```
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn build:ui:desktop:app`.
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `pnpm build:ui:desktop:app`.
Assets are saved to `ui/desktop/electron-app/out/make/`.
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
@@ -180,7 +180,7 @@ This assumes you already have up and running a Boundary dev instance in your loc
This assumes you are within `boundary-ui/ui/admin`.
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 yarn start`.
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 pnpm start`.
Following terminal instructions, open in your browser: `http://localhost:4200/`.
@@ -190,7 +190,7 @@ Once you open the UI you will see the login screen, authenticate using generated
This assumes you are within `boundary-ui/ui/desktop`.
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start:desktop`.
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false pnpm start:desktop`.
In login screen, authenticate using generated admin login name and password.
@@ -199,7 +199,7 @@ Enter the cluster URL of your Boundary dev instance, by default is: `http://loca
### Committing
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
commit is completed. Simply do a `yarn install` to make sure the hooks are installed.
commit is completed. Simply do a `pnpm install` to make sure the hooks are installed.
Use a normal `git commit` to go through the checks, if you need to skip these checks,
use `git commit --no-verify` to bypass them. However, a consistent commit message will
@@ -218,10 +218,10 @@ will not be accepted.
[git]: https://git-scm.com/
[node]: https://nodejs.org/
[yarn]: https://classic.yarnpkg.com/lang/en/
[pnpm]: https://pnpm.io
[chrome]: https://google.com/chrome/
[firefox]: https://firefox.com/
[yarn-workspaces]: https://classic.yarnpkg.com/en/docs/workspaces/
[pnpm-workspaces]: https://pnpm.io/workspaces
[homebrew]: https://brew.sh
[warp]: https://www.warp.dev
[vscode]: https://code.visualstudio.com/
+3 -3
View File
@@ -12,8 +12,8 @@ insert_final_newline = true
indent_style = space
indent_size = 2
[*.{diff,md}]
trim_trailing_whitespace = false
[*.hbs]
insert_final_newline = false
[*.{diff,md}]
trim_trailing_whitespace = false
-8
View File
@@ -1,12 +1,4 @@
{
/**
Ember CLI sends analytics information by default. The data is completely
anonymous, but there are times when you might want to disable this behavior.
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"disableAnalytics": false,
/**
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
+1 -12
View File
@@ -1,25 +1,14 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/declarations/
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.*/
.eslintcache
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try
+2 -1
View File
@@ -45,6 +45,7 @@ module.exports = {
'./blueprints/*/index.js',
'./config/**/*.js',
'./tests/dummy/config/**/*.js',
'./broccoli-plugins/**/*.js',
],
parserOptions: {
sourceType: 'script',
@@ -56,7 +57,7 @@ module.exports = {
extends: ['plugin:n/recommended'],
},
{
// Test files:
// test files
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
rules: {
+20
View File
@@ -0,0 +1,20 @@
# compiled output
/dist/
/declarations/
# dependencies
/node_modules/
# misc
/.env*
/.pnp*
/.eslintcache
/coverage/
/testem.log
# ember-try
/.node_modules.ember-try/
/package.json.ember-try
# broccoli-debug
/DEBUG/
+2 -12
View File
@@ -2,11 +2,7 @@
/dist/
/tmp/
# dependencies
/bower_components/
# misc
/.bowerrc
/.editorconfig
/.ember-cli
/.env*
@@ -19,21 +15,15 @@
/.prettierignore
/.prettierrc.js
/.template-lintrc.js
/.travis.yml
/.watchmanconfig
/bower.json
/CONTRIBUTING.md
/ember-cli-build.js
/testem.js
/tests/
/yarn-error.log
/yarn.lock
/tsconfig.declarations.json
/tsconfig.json
.gitkeep
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try
+1 -13
View File
@@ -1,25 +1,13 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.eslintcache
.lint-todo/
.*/
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try
+2
View File
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: BUSL-1.1
*/
'use strict';
module.exports = {
overrides: [
{
+1 -1
View File
@@ -1,3 +1,3 @@
{
"ignore_dirs": ["tmp", "dist"]
"ignore_dirs": ["dist"]
}
+15 -15
View File
@@ -8,7 +8,7 @@ This addon contains the API data access layer for Boundary.
- [Add API to an App](#add-api-to-an-app)
- [Installation](#installation)
- [Yarn Commands](#yarn-commands)
- [Pnpm Commands](#pnpm-commands)
- [Linting](#linting)
- [Formatting](#formatting)
- [Running tests](#running-tests)
@@ -21,7 +21,7 @@ This addon contains the API data access layer for Boundary.
## Add API to an App
Add this addon to an Ember application's `devDependencies` as:
`"api": "*"`, for applications included in this monorepo.
`"api": "workspace:*"`, for applications included in this monorepo.
Since this addon also includes Mirage mocks, be sure to install
`ember-cli-mirage` and add the following config to your UI project:
@@ -38,9 +38,9 @@ See monorepo README for installation instructions.
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
## Yarn Commands
## Pnpm Commands
List of available project commands. `yarn run <command-name>`
List of available project commands. `pnpm run <command-name>`
| Command | Description |
| ------- | ----------- |
@@ -64,28 +64,28 @@ Additional commands in the monorepo package may affect this projects.
## Linting
* `yarn lint:fix`
* `yarn lint:hbs`
* `yarn lint:hbs:fix`
* `yarn lint:js`
* `yarn lint:js:fix`
* `pnpm lint:fix`
* `pnpm lint:hbs`
* `pnpm lint:hbs:fix`
* `pnpm lint:js`
* `pnpm lint:js:fix`
## Formatting
Before submitting your work, be sure to run auto-formatters
(see commands above). This helps to ensure consistency among authors.
* `yarn format`
* `pnpm format`
## Running tests
* `yarn test` – Runs the test suite on the current Ember version
* `yarn test --server` – Runs the test suite in "watch mode"
* `yarn test:all` – Runs the test suite against multiple Ember versions
* `pnpm test` – Runs the test suite on the current Ember version
* `pnpm test --server` – Runs the test suite in "watch mode"
* `pnpm test:all` – Runs the test suite against multiple Ember versions
## Running the dummy application
* `yarn start`
* `pnpm start`
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
## Contributing
@@ -95,5 +95,5 @@ See monorepo README for more contribution instructions.
### Building ToC
To autogenerate a ToC (table of contents) for this README,
run `yarn doc:toc`. Please update the ToC whenever editing the structure
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
of README.
@@ -56,32 +56,33 @@ export default function setupStubs(hooks) {
stubTypes = types;
types.forEach((type, i) => {
let models;
let resourceName;
if (typeOf(type) === 'object') {
models = type.func();
resourceName = type.resource;
} else {
models = this.server.schema[type].all().models;
resourceName = type;
}
this.ipcStub
.withArgs('searchCacheDaemon')
.onCall(i)
.returns({
[underscore(resourceNames[singularize(resourceName)])]: models.map(
(model) => {
// Use internal serializer to serialize the model correctly
// according to our mirage serializers
const modelData =
this.server.serializerOrRegistry.serialize(model);
.callsFake(() => {
let models;
let resourceName;
if (typeOf(type) === 'object') {
models = type.func();
resourceName = type.resource;
} else {
models = this.server.schema[type].all().models;
resourceName = type;
}
// Serialize the data properly to standard JSON as that is what
// we're expecting from the cache daemon response
return JSON.parse(JSON.stringify(modelData));
},
),
return {
[underscore(resourceNames[singularize(resourceName)])]:
models.map((model) => {
// Use internal serializer to serialize the model correctly
// according to our mirage serializers
const modelData =
this.server.serializerOrRegistry.serialize(model);
// Serialize the data properly to standard JSON as that is what
// we're expecting from the cache daemon response
return JSON.parse(JSON.stringify(modelData));
}),
};
});
});
};
@@ -1,35 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/**
* This test helper can help set up and cleanup the database in your tests.
*
* ```js
* import { setupIndexedDb } from 'api/test-support/helpers/indexed-db';
*
* module('Acceptance | my test', function(hooks) {
* setupApplicationTest(hooks);
* setupIndexedDb(hooks);
*
* // add your actual tests here
* });
* ```
*
* @param hooks
*/
export function setupIndexedDb(hooks) {
hooks.beforeEach(function () {
const indexedDb = this.owner.lookup('service:indexed-db');
indexedDb.setup(`test-indexed-db-${Date.now()}`);
});
hooks.afterEach(async function () {
const indexedDb = this.owner.lookup('service:indexed-db');
await indexedDb.db.delete();
});
}
export default setupIndexedDb;
@@ -0,0 +1,37 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { v4 as uuidv4 } from 'uuid';
/**
* This test helper can help set up and cleanup the database in your tests.
*
* ```js
* import { setupSqlite } from 'api/test-support/helpers/sqlite';
*
* module('Acceptance | my test', function(hooks) {
* setupApplicationTest(hooks);
* setupSqlite(hooks);
*
* // add your actual tests here
* });
* ```
*
* @param hooks
*/
export function setupSqlite(hooks) {
hooks.beforeEach(async function () {
const sqliteDb = this.owner.lookup('service:sqlite');
await sqliteDb.setup(`test-sqlite-${uuidv4()}`);
});
hooks.afterEach(async function () {
const sqliteDb = this.owner.lookup('service:sqlite');
await sqliteDb.deleteDatabase();
});
}
export default setupSqlite;
@@ -4,7 +4,7 @@
*/
import ModelAbility from './model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
/**
* Provides abilities for channel recordings.
@@ -4,7 +4,7 @@
*/
import ModelAbility from './model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
/**
* Provides abilities for credential store.
*/
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import ModelAbility from './model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
class InvalidRolePrincipalTypeError extends Error {
name = 'InvalidRolePrincipalTypeError';
+2 -1
View File
@@ -44,7 +44,8 @@ export default class TargetAbility extends ModelAbility {
*/
get canAddInjectedApplicationCredentialSources() {
return (
this.model.isSSH && this.hasAuthorizedAction('add-credential-sources')
(this.model.isSSH || this.model.isRDP) &&
this.hasAuthorizedAction('add-credential-sources')
);
}
/**
+9 -6
View File
@@ -115,7 +115,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
* @return {Promise} promise
*/
async query(store, schema, query) {
const queryObject = structuredClone(query);
const { batchLimit, ...queryObject } = query;
let result;
let data = [];
@@ -126,8 +126,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
result = await super.query(store, schema, query);
return prenormalizeArrayResponse(result);
}
// Run this loop as long as the response_type is delta,
// which indicates that there are more items in the list
// or the batch size has not been reached
do {
result = await super.query(store, schema, queryObject);
//add the result items to a data array
@@ -136,7 +138,10 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
//pass in the list token for subsequent calls to fetch the remaining list items
queryObject.list_token = result.list_token;
}
} while (result && result.response_type === 'delta');
} while (
result?.response_type === 'delta' &&
(!batchLimit || data.length < batchLimit)
);
result.items = data;
return prenormalizeArrayResponse(result);
@@ -163,7 +168,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
}
/**
* Overrides default ajax method by rewritting PUT requests as PATCH.
* Overrides default ajax method by rewriting PUT requests as PATCH.
* PATCH is the request method used by our API for updates.
*
* @override
@@ -291,9 +296,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
const errors = fieldErrors.map((error) => ({
detail: error.description,
// Going forward, nested attributes will be hoisted. Thus "attributes."
// must be stripped from the error name key. Until all models are
// transitioned away from model fragments, however, both this and the
// prior error key mappings are necessary.
// must be stripped from the error name key.
source: {
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
},
@@ -76,13 +76,13 @@ export default class GeneratedCredentialLibraryModel extends BaseModel {
path;
@attr('string', {
for: 'vault-generic',
for: ['vault-generic', 'vault-ldap'],
description: 'It indicates the type of credential the library returns.',
})
credential_type;
@attr('object-as-array', {
for: 'vault-generic',
for: ['vault-generic', 'vault-ldap'],
description: 'It indicates the credential mapping overrides.',
})
credential_mapping_overrides;
@@ -51,17 +51,17 @@ export default class GeneratedCredentialModel extends BaseModel {
})
version;
// =attributes (username_password, ssh_private_key)
// =attributes (username_password, ssh_private_key, username_password_domain)
@attr('string', {
for: ['username_password', 'ssh_private_key'],
for: ['username_password', 'ssh_private_key', 'username_password_domain'],
isNestedAttribute: true,
description: 'The username for credential.',
})
username;
// =attributes (username_password)
// =attributes (username_password, username_password_domain)
@attr('string', {
for: 'username_password',
for: ['username_password', 'username_password_domain'],
isNestedAttribute: true,
isSecret: true,
description: 'The password for credential.',
@@ -92,4 +92,12 @@ export default class GeneratedCredentialModel extends BaseModel {
description: 'The secret associated with credential.',
})
json_object;
// =attributes (username_password_domain)
@attr('string', {
for: 'username_password_domain',
isNestedAttribute: true,
description: 'The domain for credential.',
})
domain;
}
@@ -88,6 +88,7 @@ export default class GeneratedHostSetModel extends BaseModel {
name: ['aws', 'azure', 'gcp'],
},
emptyArrayIfMissing: true,
trimWhitespace: true,
})
preferred_endpoints;
@@ -70,6 +70,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
@attr('string', {
isNestedAttribute: true,
description: 'The AWS region to use.',
trimWhitespace: true,
})
region;
@@ -109,6 +110,7 @@ export default class GeneratedStorageBucketModel extends BaseModel {
@attr('string', {
isNestedAttribute: true,
description: 'The role ARN to use.',
trimWhitespace: true,
})
role_arn;
@@ -3,13 +3,14 @@
* SPDX-License-Identifier: BUSL-1.1
*/
/* global __electronLog */
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
import { getOwner, setOwner } from '@ember/application';
import { generateMQLExpression } from '../utils/mql-query';
import { paginateResults } from '../utils/paginate-results';
import { underscore } from '@ember/string';
const { __electronLog } = globalThis;
/**
* Not all types are yet supported by the cache daemon so we'll
* just whitelist the ones we need to start with. Keys are the resource
@@ -42,7 +43,10 @@ const fetchControllerData = async (context, next) => {
// If we get an error or cache daemon is unavailable, fall back to calling the API
context.request.data.query = remainingQuery;
const results = await next(context.request);
const models = results.content.toArray();
// As a result of https://rfcs.emberjs.com/id/0846-ember-data-deprecate-proxies/ we cannot use proxy array methods like .toArray
// use slice() instead as indicated in the RFC.
const models = results.content.slice();
const paginatedResult = paginateResults(models, page, pageSize);
paginatedResult.meta = { totalItems: models.length };
@@ -1,172 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { modelIndexes } from 'api/services/indexed-db';
import { inject as service } from '@ember/service';
import { getOwner, setOwner } from '@ember/application';
import { queryIndexedDb } from '../utils/indexed-db-query';
import { paginateResults } from '../utils/paginate-results';
import { hashCode } from '../utils/hash-code';
/**
* Handler to sit in front of the API request layer
* so we can handle any caching with indexedDb first
*/
export default class IndexedDbHandler {
@service indexedDb;
constructor(context) {
setOwner(this, getOwner(context));
}
async request(context, next) {
switch (context.request.op) {
case 'query': {
const { store, data } = context.request;
// TODO: Remove storeToken option as this is a temporary fix for auth-methods.
const {
type,
query,
options: {
pushToStore = true,
peekIndexedDB = false,
storeToken = true,
} = {},
} = data;
const supportedModels = Object.keys(modelIndexes);
const { db: indexedDb } = this.indexedDb ?? {};
// Go through normal flow if we don't yet support the model
// or if we don't have an indexedDb instance
if (!supportedModels.includes(type) || !indexedDb) {
return next(context.request);
}
let { page, pageSize, query: queryObj, ...remainingQuery } = query;
const adapter = store.adapterFor(type);
const schema = store.modelFor(type);
const serializer = store.serializerFor(type);
if (!peekIndexedDB) {
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
let listToken;
if (storeToken) {
listToken = await indexedDb.token.get(tokenKey);
}
let payload;
try {
payload = await adapter.query(store, schema, {
...remainingQuery,
list_token: listToken?.token,
});
} catch (e) {
// If we get an invalid list token, we'll delete the token and
// clear the DB and try again without a token.
if (
e?.errors[0].status === 400 &&
e?.errors[0].code === 'invalid list token'
) {
await indexedDb[type].clear();
// Delete all tokens of the same resource type so we don't keep clearing the DB.
// Even if other tokens may still be valid, we might have cleared data that they
// depended on so we should clear all tokens of the same type when clearing the DB.
const tokenKeys = await indexedDb.token
.where(':id')
.startsWith(type)
.primaryKeys();
await indexedDb.token.bulkDelete(tokenKeys);
payload = await adapter.query(store, schema, {
...remainingQuery,
});
} else {
throw e;
}
}
// Store the token we just got back from the payload if it exists
if (payload.list_token && storeToken) {
await indexedDb.token.put({
id: tokenKey,
token: payload.list_token,
});
}
// Remove any records from the DB if the API indicates they've been deleted
if (payload.removed_ids?.length > 0) {
await indexedDb[type].bulkDelete(payload.removed_ids);
}
// This is a temporary fix of clearing the DB (specifically for auth-methods)
// since we are not storing the token we do not get back a list of removed_ids
// from the API call and we do not want deleted items from showing in list view.
if (!storeToken) {
await indexedDb[type].clear();
}
const normalizedPayload = serializer.normalizeResponse(
store,
schema,
payload,
null,
'query',
);
const { data: payloadData } = normalizedPayload;
// Store the new data we just got back from the API refresh
const items = payloadData.map((datum) =>
this.indexedDb.normalizeData({
data: datum,
cleanData: true,
schema,
serializer,
}),
);
await indexedDb[type].bulkPut(items);
}
const indexedDbResults = await queryIndexedDb(
indexedDb,
type,
queryObj,
);
const dbRecords = paginateResults(indexedDbResults, page, pageSize).map(
(item) =>
this.indexedDb.normalizeData({
data: item,
cleanData: false,
schema,
serializer,
}),
);
// Return the raw data if we don't push to the store.
let records = dbRecords.map((record) => ({
...record.attributes,
id: record.id,
}));
if (pushToStore) {
records = store.push({ data: dbRecords });
}
// Set a meta property on the array to store the total items of the results.
// This isn't conventional but is better than returning an ArrayProxy
// or EmberArray since the ember store query method asserts it has to be an array
// so we can't just return an object.
records.meta = { totalItems: indexedDbResults.length };
return records;
}
default:
return next(context.request);
}
}
}
+258
View File
@@ -0,0 +1,258 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { getOwner, setOwner } from '@ember/owner';
import { service } from '@ember/service';
import { modelMapping } from 'api/services/sqlite';
import { get } from '@ember/object';
import { typeOf } from '@ember/utils';
import { hashCode } from '../utils/hash-code';
import { generateSQLExpressions } from '../utils/sqlite-query';
const isISODateString = (str) =>
typeOf(str) === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z?$/.test(str);
export default class SqliteHandler {
@service sqlite;
batchLimit = 5_000;
constructor(context) {
setOwner(this, getOwner(context));
}
async request(context, next) {
switch (context.request.op) {
case 'query': {
const { store, data } = context.request;
const {
type,
query,
options: {
pushToStore = true,
peekDb = false,
storeToken = true,
} = {},
} = data;
const supportedModels = Object.keys(modelMapping);
// Go through normal flow if we don't yet support the model
// or if we don't have a sqlite db instance
if (!supportedModels.includes(type) || !this.sqlite.worker) {
return next(context.request);
}
const adapter = store.adapterFor(type);
const schema = store.modelFor(type);
const serializer = store.serializerFor(type);
let { page, pageSize, query: queryObj, ...remainingQuery } = query;
let payload,
listToken,
writeToDbPromise,
totalInsert = 0;
if (!peekDb) {
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
if (storeToken) {
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
// from the API call and we do not want deleted items from showing in list view.
await this.sqlite.deleteResource(type);
}
do {
try {
payload = await adapter.query(store, schema, {
...remainingQuery,
list_token: listToken,
batchLimit: this.batchLimit,
});
totalInsert += payload.items?.length ?? 0;
// await the previous writeToDbPromise before writing to db again
if (writeToDbPromise) {
await writeToDbPromise;
}
} catch (err) {
payload = await this.retryQueryFailure({
err,
type,
adapter,
store,
schema,
remainingQuery,
});
totalInsert += payload.items?.length ?? 0;
}
listToken = payload.list_token;
writeToDbPromise = this.writeToDb(
payload,
storeToken,
tokenKey,
serializer,
store,
schema,
type,
);
} while (payload.response_type === 'delta');
await writeToDbPromise;
}
if (totalInsert > 0) {
// If there were any inserts, let sqlite handle running analyze on the DB
await this.sqlite.analyzeDatabase();
}
const { sql, parameters } = generateSQLExpressions(type, queryObj, {
page,
pageSize,
select: ['data'],
});
const rows = await this.sqlite.fetchResource({
sql,
parameters,
});
const { sql: countSql, parameters: countParams } =
generateSQLExpressions(type, queryObj, {
select: ['count(*) as total'],
});
const count = await this.sqlite.fetchResource({
sql: countSql,
parameters: countParams,
});
const results = rows.map((item) =>
JSON.parse(item.data, (key, value) =>
// Reviver function to convert any ISO strings back to date objects
// as we had to initially convert them to strings when stored in SQLite
isISODateString(value) ? new Date(value) : value,
),
);
// If we are not pushing to the store, use the raw data with id property
const records = pushToStore
? store.push({ data: results })
: results.map((record) => ({
...record.attributes,
id: record.id,
}));
// Set a meta property on the array to store the total items of the results.
// This isn't conventional but is better than returning an ArrayProxy
// or EmberArray since the ember store query method asserts it has to be an array
// so we can't just return an object.
records.meta = { totalItems: count[0].total };
return records;
}
default:
return next(context.request);
}
}
async retryQueryFailure({
err,
type,
adapter,
store,
schema,
remainingQuery,
}) {
// If we get an invalid list token, we'll delete the token and
// clear the DB and try again without a token.
if (
err?.errors?.[0].status === 400 &&
err?.errors?.[0].code === 'invalid list token'
) {
await this.sqlite.deleteResource(type);
// Delete all tokens of the same resource type so we don't keep clearing the DB.
// Even if other tokens may still be valid, we might have cleared data that they
// depended on so we should clear all tokens of the same type when clearing the DB.
const { sql, parameters } = generateSQLExpressions('token', {
filters: { id: [{ contains: type }] },
});
const tokenRows = await this.sqlite.fetchResource({
sql,
parameters,
});
const tokenIds = tokenRows.map((row) => row.id);
if (tokenIds.length > 0) {
await this.sqlite.deleteResource('token', tokenIds);
}
return await adapter.query(store, schema, {
...remainingQuery,
});
} else {
throw err;
}
}
async writeToDb(
payload,
storeToken,
tokenKey,
serializer,
store,
schema,
type,
) {
if (payload.list_token && storeToken) {
await this.sqlite.insertResource('token', [
[tokenKey, payload.list_token],
]);
}
// Remove any records from the DB if the API indicates they've been deleted
if (payload.removed_ids?.length > 0) {
await this.sqlite.deleteResource(type, payload.removed_ids);
}
const normalizedPayload = serializer.normalizeResponse(
store,
schema,
payload,
null,
'query',
);
const { data: payloadData } = normalizedPayload;
// Store the new data we just got back from the API refresh
const items = payloadData.map((datum) => {
const params = Object.values(modelMapping[type]).map((key) => {
let value;
if (key === 'id') {
value = datum.id;
} else {
value = get(datum, `attributes.${key}`);
}
if (typeOf(value) === 'date') {
return value.toISOString();
}
return value;
});
return [...params, JSON.stringify(datum)];
});
await this.sqlite.insertResource(type, items);
}
}
+2 -2
View File
@@ -6,7 +6,7 @@
import Mixin from '@ember/object/mixin';
/**
* Overrides URL-building methods in an adpater for greater flexibility.
* Overrides URL-building methods in an adapter for greater flexibility.
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
* `snapshot`, which aren't normally passed. And adds support for a
* `urlSuffix` method, the return value of which is appended to the end of
@@ -22,7 +22,7 @@ export default Mixin.create({
/**
* This override behaves identically to the default method by default.
* It exists only to document the additional paramters to the method.
* It exists only to document the additional parameters to the method.
* @override
* @param {string} path
* @param {string} parentURL
+2 -2
View File
@@ -47,8 +47,8 @@ export default class AccountModel extends GeneratedAccountModel {
* @type {string}
*/
get accountName() {
const { email, full_name, login_name } = this;
return email || full_name || login_name;
const { email, full_name, login_name, subject } = this;
return email || full_name || login_name || subject;
}
// =methods
+1 -1
View File
@@ -6,7 +6,7 @@
import Model from '@ember-data/model';
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
/**
* Base model class provides common functionality for all models.
+27 -3
View File
@@ -4,14 +4,22 @@
*/
import GeneratedCredentialLibraryModel from '../generated/models/credential-library';
import {
TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
} from 'api/models/credential';
/**
* Enum options for credential library.
*/
export const options = {
http_method: ['GET', 'POST'],
key_type: ['ed25519', 'ecdsa', 'rsa'],
credential_types: ['ssh_private_key', 'username_password'],
credential_types: [
TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
],
mapping_overrides: {
username_password: ['username_attribute', 'password_attribute'],
ssh_private_key: [
@@ -19,12 +27,18 @@ export const options = {
'private_key_passphrase_attribute',
'username_attribute',
],
username_password_domain: [
'username_attribute',
'password_attribute',
'domain_attribute',
],
},
};
export const TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC = 'vault-generic';
export const TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE =
'vault-ssh-certificate';
export const TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP = 'vault-ldap';
/**
* Supported Credential Library types.
@@ -32,6 +46,7 @@ export const TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE =
export const TYPES_CREDENTIAL_LIBRARY = Object.freeze([
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP,
]);
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
@@ -52,7 +67,8 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo
get isVault() {
return (
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC ||
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE ||
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP
);
}
@@ -71,4 +87,12 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo
get isVaultSSHCertificate() {
return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE;
}
/**
* True if credential is a vault ldap type.
* @type {boolean}
*/
get isVaultLDAP() {
return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP;
}
}
+2
View File
@@ -12,10 +12,12 @@ import GeneratedCredentialModel from '../generated/models/credential';
export const TYPE_CREDENTIAL_USERNAME_PASSWORD = 'username_password';
export const TYPE_CREDENTIAL_SSH_PRIVATE_KEY = 'ssh_private_key';
export const TYPE_CREDENTIAL_JSON = 'json';
export const TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN = 'username_password_domain';
export const TYPES_CREDENTIAL = Object.freeze([
TYPE_CREDENTIAL_USERNAME_PASSWORD,
TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_JSON,
]);
+1 -1
View File
@@ -7,7 +7,7 @@ import GeneratedGroupModel from '../generated/models/group';
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { A } from '@ember/array';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
export default class GroupModel extends GeneratedGroupModel {
// =services
+8 -6
View File
@@ -4,6 +4,7 @@
*/
import GeneratedHostCatalogModel from '../generated/models/host-catalog';
import { tracked } from '@glimmer/tracking';
export const TYPE_HOST_CATALOG_STATIC = 'static';
export const TYPE_HOST_CATALOG_DYNAMIC = 'plugin';
@@ -40,7 +41,8 @@ export const STATIC_CREDENTIAL_FIELDS = ['access_key_id, secret_access_key'];
export default class HostCatalogModel extends GeneratedHostCatalogModel {
// =attributes
#credentialType;
@tracked _credentialTypeValue;
/**
* True if the host catalog is static.
* @type {boolean}
@@ -62,14 +64,14 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {string}
*/
get credentialType() {
if (!this.#credentialType) {
if (!this._credentialTypeValue) {
if (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) {
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
return TYPE_CREDENTIAL_DYNAMIC;
} else {
this.#credentialType = TYPE_CREDENTIAL_STATIC;
return TYPE_CREDENTIAL_STATIC;
}
}
return this.#credentialType;
return this._credentialTypeValue;
}
/**
@@ -77,7 +79,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {string}
*/
set credentialType(type) {
this.#credentialType = type;
this._credentialTypeValue = type;
}
/**
+1 -1
View File
@@ -5,7 +5,7 @@
import GeneratedRoleModel from '../generated/models/role';
import { attr } from '@ember-data/model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
export const GRANT_SCOPE_THIS = 'this';
export const GRANT_SCOPE_CHILDREN = 'children';
+10 -2
View File
@@ -5,7 +5,7 @@
import GeneratedSessionRecordingModel from '../generated/models/session-recording';
import { hasMany } from '@ember-data/model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
export const TYPE_SESSION_RECORDING_SSH = 'ssh';
export const TYPES_SESSION_RECORDING = Object.freeze([
@@ -71,7 +71,15 @@ export default class SessionRecordingModel extends GeneratedSessionRecordingMode
}
/**
* Reapply policy dates via the `reapply-staorage-policy` method.
* Returns true if retention of session-recording is forever.
* @type {boolean}
*/
get retainForever() {
return this.retain_until?.toISOString() === '9999-12-31T23:23:23.999Z';
}
/**
* Reapply policy dates via the `reapply-storage-policy` method.
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
+1 -1
View File
@@ -7,7 +7,7 @@ import GeneratedSessionModel from '../generated/models/session';
import { equal } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import { A } from '@ember/array';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
import { flattenObject } from '../utils/flatten-nested-object';
import { TYPES_CREDENTIAL_LIBRARY } from 'api/models/credential-library';
import { TYPE_CREDENTIAL_JSON } from 'api/models/credential';
+9 -6
View File
@@ -4,6 +4,7 @@
*/
import GeneratedStorageBucketModel from '../generated/models/storage-bucket';
import { tracked } from '@glimmer/tracking';
export const TYPE_STORAGE_BUCKET_PLUGIN = 'plugin';
export const TYPES_STORAGE_BUCKET = Object.freeze([TYPE_STORAGE_BUCKET_PLUGIN]);
@@ -26,7 +27,8 @@ export const TYPES_CREDENTIALS = Object.freeze([
export default class StorageBucketModel extends GeneratedStorageBucketModel {
// attributes
#credentialType;
@tracked _credentialTypeValue;
/**
* True if the storage bucket is a plugin.
* @type {boolean}
@@ -40,14 +42,14 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
* @type {string}
*/
get credentialType() {
if (!this.#credentialType) {
if (!this._credentialTypeValue) {
if (this.role_arn) {
this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
return TYPE_CREDENTIAL_DYNAMIC;
} else {
this.#credentialType = TYPE_CREDENTIAL_STATIC;
return TYPE_CREDENTIAL_STATIC;
}
}
return this.#credentialType;
return this._credentialTypeValue;
}
/**
@@ -55,8 +57,9 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
* @type {string}
*/
set credentialType(type) {
this.#credentialType = type;
this._credentialTypeValue = type;
}
/**
* True if the storage bucket is an unknown type.
* @type {boolean}
+16 -2
View File
@@ -5,11 +5,17 @@
import GeneratedTargetModel from '../generated/models/target';
import { attr } from '@ember-data/model';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
export const TYPE_TARGET_TCP = 'tcp';
export const TYPE_TARGET_SSH = 'ssh';
export const TYPES_TARGET = Object.freeze([TYPE_TARGET_TCP, TYPE_TARGET_SSH]);
export const TYPE_TARGET_RDP = 'rdp';
export const TYPES_TARGET = Object.freeze([
TYPE_TARGET_TCP,
TYPE_TARGET_SSH,
TYPE_TARGET_RDP,
]);
export default class TargetModel extends GeneratedTargetModel {
// =services
@@ -347,4 +353,12 @@ export default class TargetModel extends GeneratedTargetModel {
get isSSH() {
return this.type === TYPE_TARGET_SSH;
}
/**
* True if the target type is rdp.
* @type {boolean}
*/
get isRDP() {
return this.type === TYPE_TARGET_RDP;
}
}
+18 -10
View File
@@ -60,6 +60,18 @@ export default class ApplicationSerializer extends RESTSerializer {
// Do not serialize `disabled` fields.
// TODO: disabled is temporarily disabled
if (key === 'disabled') delete json[key];
// Before we transform our json
// Let's trim the values in the json obj
// Currently, json values will only ever be a string or an array
if (options.trimWhitespace && json[key]) {
if (typeOf(json[key]) === 'array') {
json[key] = json[key].map((str) => str.trim());
} else if (typeOf(json[key] === 'string')) {
json[key] = json[key].trim();
}
}
// Push nested attributes down into the attributes key
if (options.isNestedAttribute && json[key] !== undefined) {
if (!json.attributes) json.attributes = {};
@@ -232,12 +244,10 @@ export default class ApplicationSerializer extends RESTSerializer {
* @return {object}
*/
normalizeMissingArrays(store, primaryModelClass, payload) {
const attrDefs = store
.getSchemaDefinitionService()
.attributesDefinitionFor({ type: primaryModelClass.modelName });
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
if (attrDefs) {
Object.keys(attrDefs).forEach((key) => {
if (!payload[key] && attrDefs[key]?.options?.emptyArrayIfMissing) {
attrDefs.keys().forEach((key) => {
if (!payload[key] && attrDefs.get(key)?.options?.emptyArrayIfMissing) {
payload[key] = [];
}
});
@@ -256,12 +266,10 @@ export default class ApplicationSerializer extends RESTSerializer {
* @return {object}
*/
normalizeMissingObjects(store, primaryModelClass, payload) {
const attrDefs = store
.getSchemaDefinitionService()
.attributesDefinitionFor({ type: primaryModelClass.modelName });
const attrDefs = store.schema.fields({ type: primaryModelClass.modelName });
if (attrDefs) {
Object.keys(attrDefs).forEach((key) => {
if (!payload[key] && attrDefs[key]?.options?.emptyObjectIfMissing) {
attrDefs.keys().forEach((key) => {
if (!payload[key] && attrDefs.get(key)?.options?.emptyObjectIfMissing) {
payload[key] = {};
}
});
@@ -7,6 +7,7 @@ import ApplicationSerializer from './application';
import {
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP,
options,
} from '../models/credential-library';
@@ -28,6 +29,9 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
return this.serializeVaultGeneric(...arguments);
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
return this.serializeVaultSshCertificate(...arguments);
case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP:
return this.serializeVaultLdap(...arguments);
default:
return super.serialize(...arguments);
}
@@ -43,22 +47,52 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
if (!serialized.attributes?.http_method?.match(/post/i)) {
serialized.attributes.http_request_body = null;
}
const { credential_type, credential_mapping_overrides } = serialized;
this.handleCredentialMappingOverrides(serialized, isNew);
return serialized;
}
}
// 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;
}, {});
serializeVaultSshCertificate() {
const serialized = super.serialize(...arguments);
if (serialized.attributes) {
const { key_type } = serialized.attributes;
if (key_type !== 'rsa' && key_type !== 'ecdsa') {
// If the key type is not RSA or ECDSA, set key_bits to null
serialized.attributes.key_bits = null;
}
return serialized;
}
}
serializeVaultLdap(snapshot) {
const { isNew } = snapshot;
const serialized = super.serialize(...arguments);
if (serialized.attributes) {
this.handleCredentialMappingOverrides(serialized, isNew);
return serialized;
}
}
/**
* Handle credential mapping overrides for serialization.
* @param {Object} serialized - The serialized object.
* @param {boolean} isNew - Indicates if the record is new.
*/
handleCredentialMappingOverrides(serialized, isNew) {
const { credential_type, credential_mapping_overrides } = serialized;
// 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;
}, {});
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
) {
if (options.isNestedAttribute) {
json.attributes[key] = null;
} else if (options.isNestedSecret && json.secrets[key]) {
} else if (options.isNestedSecret && json.secrets?.[key]) {
delete json.secrets[key];
if (Object.keys(json.secrets).length === 0) {
delete json.secrets;
+14
View File
@@ -95,4 +95,18 @@ export default class HostSetSerializer extends ApplicationSerializer {
return super.normalize(typeClass, normalizedHash, ...rest);
}
/**
* Extracts errors from the payload and transforms specific keys if needed.
* @override
* @return {object}
*/
extractErrors() {
const errors = super.extractErrors(...arguments);
if (errors?.filter) {
errors.filter_string = errors.filter;
delete errors.filter;
}
return errors;
}
}
@@ -53,4 +53,18 @@ export default class ManagedGroupSerializer extends ApplicationSerializer {
return super.normalize(typeClass, normalizedHash, ...rest);
}
/**
* Extracts errors from the payload and transforms specific keys if needed.
* @override
* @return {object}
*/
extractErrors() {
const errors = super.extractErrors(...arguments);
if (errors?.filter) {
errors.filter_string = errors.filter;
delete errors.filter;
}
return errors;
}
}
@@ -72,6 +72,7 @@ export default class StorageBucketSerializer extends ApplicationSerializer {
const value = super.serializeAttribute(...arguments);
const { credentialType } = snapshot.record;
const { options } = attribute;
// This deletes any fields that don't belong to the record's credential type
if (options.isNestedAttribute && json.attributes) {
// The key must be included in the fieldsByType list above
-151
View File
@@ -1,151 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Service from '@ember/service';
import Dexie from 'dexie';
import { typeOf } from '@ember/utils';
// List of resources we support to be cached and searched in
// indexedDb. Any field that can be searched upon should be added to
// this index. Increment the version number of the database whenever
// indexes change.
export const modelIndexes = {
token: '&id, token',
target:
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.address, attributes.scope.scope_id',
session:
'&id, attributes.created_time, attributes.type, attributes.status, attributes.endpoint, attributes.target_id, attributes.user_id, attributes.scope.scope_id',
user: '&id, attributes.created_time, attributes.name, attributes.description, attributes.scope.scope_id',
group:
'&id, attributes.created_time, attributes.name, attributes.description, attributes.scope.scope_id',
role: '&id, attributes.created_time, attributes.name, attributes.description, attributes.scope.scope_id',
scope:
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.scope.scope_id',
'credential-store':
'&id, attributes.created_time, attributes.name, attributes.description, attributes.type, attributes.scope.scope_id',
'host-catalog':
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.scope.scope_id, attributes.plugin.name',
'auth-method':
'&id, attributes.created_time, attributes.type, attributes.name, attributes.description, attributes.is_primary, attributes.scope.scope_id',
'session-recording':
'&id, attributes.created_time, attributes.type, attributes.state, attributes.start_time, attributes.end_time, attributes.duration, attributes.scope.scope_id, attributes.create_time_values.user.id, attributes.create_time_values.user.name, attributes.create_time_values.target.id, attributes.create_time_values.target.name, attributes.create_time_values.target.scope.id, attributes.create_time_values.target.scope.name',
alias:
'&id, attributes.created_time, attributes.type, attributes.value, attributes.name, attributes.description, attributes.destination_id, attributes.scope.scope_id',
};
export const formatDbName = (userId, clusterUrl) =>
`boundary-${userId}-${clusterUrl}`;
/**
* Recursively copy over the object and convert any booleans to "true" or "false" so they can be indexed.
* Also converts any ember-like arrays back to a standard array.
* @param result
* @param object
* @returns {*}
*/
const convertFields = (result, object) => {
Object.entries(object).forEach(([key, value]) => {
if (typeOf(value) === 'object') {
result[key] = convertFields({}, value);
} else if (typeOf(value) === 'boolean') {
result[key] = value ? 'true' : 'false';
} else if (typeOf(value) === 'array') {
result[key] = [...value];
} else {
result[key] = value;
}
});
return result;
};
/**
* Recursively copy over the object and convert any string "true" or "false" back to booleans.
* Will also convert any array fields back using their model transformation.
* @param result
* @param object
* @param schema
* @param serializer
* @returns {*}
*/
const unconvertFields = (result, object, schema, serializer) => {
Object.entries(object).forEach(([key, value]) => {
if (typeOf(value) === 'object') {
result[key] = unconvertFields({}, value, schema, serializer);
} else if (value === 'true' || value === 'false') {
result[key] = value === 'true';
} else if (typeOf(value) === 'array') {
const attribute = schema.attributes.get(key);
// If attribute type is array, we transform it back to a tracked array
if (attribute?.type === 'array') {
const transformer = serializer.transformFor(attribute.type);
result[key] = transformer.deserialize(value, attribute.options);
return;
}
result[key] = value;
} else {
result[key] = value;
}
});
return result;
};
/**
* Service to encapsulate the IndexedDB implementation. To use this service, call
* `setup` from the application root and
*/
export default class IndexedDbService extends Service {
// =attributes
#db;
get db() {
return this.#db;
}
setup(dbName) {
// Don't run setup again if we already have one or if we didn't get a name
if (this.#db || !dbName) {
return;
}
this.#db = new Dexie(dbName);
this.#db.version(2).stores(modelIndexes);
}
/**
* Cleanup the data by converting all fields to plain javascript objects.
* We will also convert booleans to "false" or "true".
* IndexedDB doesn't support indexes on boolean values so we have to
* convert them to be able to support using boolean values.
*
* Undo the operation by setting `convertFields` to false.
*
* @param {Object} data
* @param {Boolean} cleanData
* @param {Object} schema
* @param {Object} serializer
* @return {{id, type, attributes: {}, relationships: {}}}
*/
normalizeData({ data, cleanData, schema, serializer }) {
if (!data) {
return data;
}
let { id, type, attributes = {}, relationships = {} } = data;
return {
id,
type,
attributes: cleanData
? convertFields({}, attributes)
: unconvertFields({}, attributes, schema, serializer),
relationships: cleanData
? convertFields({}, relationships)
: unconvertFields({}, relationships, schema, serializer),
};
}
}
@@ -4,7 +4,7 @@
*/
import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
/**
* Takes a POJO representing a query and converts it to a boolean expression
+229
View File
@@ -0,0 +1,229 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Service from '@ember/service';
import { PWBHost } from 'promise-worker-bi';
export const formatDbName = (userId, clusterUrl) =>
`boundary-${userId}-${clusterUrl}`;
// A mapping of columns to their expected model attributes for supported models
// The JSON data column is not included here but is assumed to always be last.
export const modelMapping = {
target: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
address: 'address',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
alias: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
destination_id: 'destination_id',
value: 'value',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
group: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
role: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
user: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'credential-store': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
scope: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'auth-method': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
is_primary: 'is_primary',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'host-catalog': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
plugin_name: 'plugin.name',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'session-recording': {
id: 'id',
type: 'type',
state: 'state',
start_time: 'start_time',
end_time: 'end_time',
duration: 'duration',
scope_id: 'scope.scope_id',
user_id: 'create_time_values.user.id',
user_name: 'create_time_values.user.name',
target_id: 'create_time_values.target.id',
target_name: 'create_time_values.target.name',
target_scope_id: 'create_time_values.target.scope.id',
target_scope_name: 'create_time_values.target.scope.name',
created_time: 'created_time',
},
session: {
id: 'id',
type: 'type',
status: 'status',
endpoint: 'endpoint',
target_id: 'target_id',
user_id: 'user_id',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
};
// A list of tables that we support searching using FTS5 in SQLite.
export const searchTables = new Set([
'target',
'alias',
'group',
'role',
'user',
'credential-store',
'scope',
'auth-method',
'host-catalog',
'session-recording',
'session',
]);
export default class SqliteDbService extends Service {
// =attributes
// We need a handle on the original web worker to properly clean it up.
webWorker;
// This will be the worker we interact with that is wrapped by PWBHost.
worker;
setup(dbName) {
if (this.worker) {
// If the worker already exists, we don't need to do anything.
return;
}
// In production, our JS files will be fingerprinted with an MD5 hash
// so we need a way to map the worker file name to be able to find it.
//
// Ember is automagically replacing any URL path with the fingerprinted
// asset, and this is done by broccoli-asset-rev.
//
// The problem is it's a bit finicky as seen in this issue
// https://github.com/ember-cli/broccoli-asset-rewrite/issues/66
// and will not work properly if it's part of a template string or interpolated.
// Therefore, we need the URL path in a string somewhere to be replaced properly.
this.webWorker = new Worker('/workers/sqlite-worker.js', {
type: 'module',
});
// We use PWBHost to simplify communication so we can just send and receive
// messages with promises rather than having to manually set up event listeners
this.worker = new PWBHost(this.webWorker);
this.worker.register(async ({ method }) => {
if (method === 'getDatabaseName') {
return dbName;
}
});
}
fetchResource({ sql, parameters }) {
return this.worker.postMessage({
method: 'fetchResource',
payload: { sql, parameters },
});
}
insertResource(resource, items) {
return this.worker.postMessage({
method: 'insertResource',
payload: { resource, items, modelMapping },
});
}
deleteResource(resource, ids) {
return this.worker.postMessage({
method: 'deleteResource',
payload: { resource, ids },
});
}
analyzeDatabase() {
return this.worker.postMessage({ method: 'analyzeDatabase' });
}
async downloadDatabase() {
const byteArray = await this.worker.postMessage({
method: 'downloadDatabase',
});
const blob = new Blob([byteArray.buffer], {
type: 'application/x-sqlite3',
});
// Create temporary elements to initialize the download
const a = window.document.createElement('a');
window.document.body.appendChild(a);
a.href = window.URL.createObjectURL(blob);
a.download = 'boundary.db';
a.addEventListener('click', function () {
setTimeout(function () {
window.URL.revokeObjectURL(a.href);
a.remove();
}, 500);
});
a.click();
}
async clearDatabase() {
await this.worker.postMessage({ method: 'clearDatabase' });
}
async deleteDatabase() {
await this.worker.postMessage({ method: 'deleteDatabase' });
}
willDestroy() {
this.webWorker?.terminate();
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ class MemoryStorage {
*
* @example
* import Route from '@ember/routing/route';
* import { inject as service } from '@ember/service';
* import { service } from '@ember/service';
* export default class ApplicationRoute extends Route {
* @service storage;
* afterModel() {
+8 -4
View File
@@ -3,10 +3,14 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Store, { CacheHandler } from '@ember-data/store';
// Importing 'Store' from 'ember-data/store' to get default configuration automatically.
// TODO: Remove eslint-disable after upgrading to eslint-plugin-ember >= v12.2.1
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
import Store from 'ember-data/store';
import { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
import IndexedDbHandler from 'api/handlers/indexed-db-handler';
import SqliteHandler from 'api/handlers/sqlite-handler';
export default class extends Store {
requestManager = new RequestManager();
@@ -14,9 +18,9 @@ export default class extends Store {
constructor(args) {
super(args);
const indexedDbHandler = new IndexedDbHandler(this);
const sqliteHandler = new SqliteHandler(this);
this.requestManager.use([indexedDbHandler, LegacyNetworkHandler]);
this.requestManager.use([sqliteHandler, LegacyNetworkHandler]);
this.requestManager.useCache(CacheHandler);
}
}
@@ -3,9 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
export default class AccountValueMapArrayTransform extends Transform {
export default class AccountValueMapArrayTransform {
deserialize(serialized) {
const accountValues = serialized || [];
return accountValues.map((accountValueString) => ({
@@ -18,4 +16,8 @@ export default class AccountValueMapArrayTransform extends Transform {
const accountValues = deserialized || [];
return accountValues.map(({ key, value }) => `${key}=${value}`);
}
static create() {
return new this();
}
}
+5 -2
View File
@@ -3,10 +3,9 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
import { TrackedArray } from 'tracked-built-ins';
export default class ArrayTransform extends Transform {
export default class ArrayTransform {
deserialize(serialized) {
const arr = new TrackedArray(serialized || []);
return arr;
@@ -16,4 +15,8 @@ export default class ArrayTransform extends Transform {
const arr = deserialized || [];
return arr;
}
static create() {
return new this();
}
}
+5 -3
View File
@@ -3,9 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
export default class DurationTransform extends Transform {
export default class DurationTransform {
/**
* Normalize a duration string in the format "3.0001s" to milliseconds.
* The backend returns a precision of nanoseconds but we round to whole
@@ -37,4 +35,8 @@ export default class DurationTransform extends Transform {
maximumFractionDigits: 9,
})}s`;
}
static create() {
return new this();
}
}
@@ -3,9 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
export default class HostSourceIdArrayTransform extends Transform {
export default class HostSourceIdArrayTransform {
deserialize(serialized) {
const stringValues = serialized || [];
return stringValues.map(({ id: host_source_id, ...obj }) => ({
@@ -18,4 +16,8 @@ export default class HostSourceIdArrayTransform extends Transform {
const strings = deserialized || [];
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
}
static create() {
return new this();
}
}
@@ -3,13 +3,12 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
export default class ObjectAsArrayTransform extends Transform {
export default class ObjectAsArrayTransform {
deserialize(serialized) {
const obj = serialized || {};
return Object.entries(obj).map(([key, value]) => ({ key, value }));
}
serialize(deserialized) {
const array = deserialized || [];
@@ -24,4 +23,8 @@ export default class ObjectAsArrayTransform extends Transform {
return result;
}, {});
}
static create() {
return new this();
}
}
+5 -2
View File
@@ -3,10 +3,9 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
import { TrackedObject } from 'tracked-built-ins';
export default class ObjectTransform extends Transform {
export default class ObjectTransform {
/**
* @param serialized
* @returns {object}
@@ -26,4 +25,8 @@ export default class ObjectTransform extends Transform {
}
return deserialized;
}
static create() {
return new this();
}
}
+5 -3
View File
@@ -3,9 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
export default class StringArrayTransform extends Transform {
export default class StringArrayTransform {
deserialize(serialized) {
const stringValues = serialized || [];
return stringValues.map((value) => ({ value }));
@@ -15,4 +13,8 @@ export default class StringArrayTransform extends Transform {
const strings = deserialized || [];
return strings.map(({ value }) => value);
}
static create() {
return new this();
}
}
-263
View File
@@ -1,263 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { get } from '@ember/object';
import { modelIndexes } from '../services/indexed-db';
/**
* Takes a POJO representing a filter query and builds a query to
* query indexedDb with. Note that the first filter is important as that
* will use the actual index in indexedDb while any subsequent ones will be less efficient.
*
* @example
* const filter = queryIndexedDb(indexedDb, type, {
* search: generated',
* filters: {
* scope_id: {
* logicalOperator: 'and',
* values: [{ notEquals: 'scope1' }, { notEquals: 'scope2' }],
* },
* },
* });
*/
export async function queryIndexedDb(indexedDb, resource, query) {
let { search, filters = {} } = query ?? {};
let filterCollection = indexedDb[resource];
search = getSearchObject(search, resource);
filters = cleanObject(filters);
if (Object.keys(filters).length) {
// Remove the first item to use as the initial collection and then reduce the remaining ones
const entries = Object.entries(filters);
const [firstKey, firstFilterArrayOrObject] = entries.shift();
filterCollection = await entries.reduce(
async (currCollection, [key, filterArrayOrObject]) => {
// Grab the results from the set of one filters to be `AND` together with the rest of the filters
const filterResults = await buildInitialWhereClause({
table: indexedDb[resource],
key,
filterArrayOrObject,
}).primaryKeys();
const resultIdsSet = new Set(filterResults);
return (await currCollection).and((record) => {
return resultIdsSet.has(record.id);
});
},
buildInitialWhereClause({
key: firstKey,
table: indexedDb[resource],
filterArrayOrObject: firstFilterArrayOrObject,
}),
);
}
if (search) {
const { text, fields } = search;
filterCollection = filterCollection.filter((record) =>
fields
.map((key) => get(record, key))
.some((element) => {
if (typeof element === 'string') {
return element?.toLowerCase().includes(text?.toLowerCase());
}
}),
);
}
// OrderBy has to be used on where clauses while if it's a
// collection already we have to use sortBy. We'll also sort by
// descending order based on created time to match the API as any
// "or" clause can change the order
if (filterCollection.orderBy) {
return filterCollection.orderBy(getKey('created_time')).reverse().toArray();
}
return filterCollection.reverse().sortBy(getKey('created_time'));
}
const buildInitialWhereClause = ({ filterArrayOrObject, table, key }) => {
const filterValueArray = Array.isArray(filterArrayOrObject)
? filterArrayOrObject
: filterArrayOrObject.values;
const { logicalOperator } = filterArrayOrObject;
// Short circuit if all the filter types were equals or notEquals since indexedDb
// can more efficiently query if we pass all values at once
const checkAllFilterType = (type) =>
filterValueArray.every((obj) => Object.keys(obj)[0] === type);
if (checkAllFilterType('equals')) {
return table
.where(getKey(key))
.anyOf(filterValueArray.map((obj) => obj['equals']));
}
if (checkAllFilterType('notEquals')) {
return table
.where(getKey(key))
.noneOf(filterValueArray.map((obj) => obj['notEquals']));
}
// Remove the first item to use as the initial where clause and then reduce the remaining ones
const [firstOperation, firstFilterValue] = Object.entries(
filterValueArray[0],
)[0];
// Not all are filters are equals or notEquals,
// manually build the initial where clause and resulting collections
return filterValueArray.slice(1).reduce(
(result, currObj) => {
const [operation, value] = Object.entries(currObj)[0];
return buildIndexedDbCollection({
key: getKey(key),
value,
collection: result,
operation,
logicalOperator,
});
},
buildIndexedDbWhere({
table,
key: getKey(key),
operation: firstOperation,
value: firstFilterValue,
}),
);
};
/**
* Builds the initial where clause for indexed DB to filter on. Note that this makes
* the first filter important as that will do the real filtering from indexed DB while
* any subsequent filter has to be done in memory.
*/
const buildIndexedDbWhere = ({ table, key, value, operation }) => {
const baseWhereClause = table.where(key);
switch (operation) {
case 'contains':
// We return an error due to not being able to support contains on first filter due to performance.
throw new Error('Contains is not supported as the first filter.');
case 'gt':
return baseWhereClause.above(value);
case 'gte':
return baseWhereClause.aboveOrEqual(value);
case 'lt':
return baseWhereClause.below(value);
case 'lte':
return baseWhereClause.belowOrEqual(value);
case 'notEquals':
return baseWhereClause.notEqual(value);
default:
return baseWhereClause.equals(value);
}
};
/**
* Continues building the queries for indexed DB to filter on. Note that these are not true
* indexed DB queries and in fact just standard JS filters for logical "and" queries and parallel queries
* to indexed DB for logical "or" queries that get deduped.
*/
const buildIndexedDbCollection = ({
key,
value,
collection,
operation,
logicalOperator,
}) => {
if (logicalOperator === 'and') {
switch (operation) {
case 'contains':
return collection.and((record) => get(record, key)?.includes(value));
case 'gt':
return collection.and((record) => get(record, key) > value);
case 'gte':
return collection.and((record) => get(record, key) >= value);
case 'lt':
return collection.and((record) => get(record, key) < value);
case 'lte':
return collection.and((record) => get(record, key) <= value);
case 'notEquals':
return collection.and((record) => get(record, key) !== value);
default:
return collection.and((record) => get(record, key) === value);
}
}
switch (operation) {
case 'contains':
// We return an error due to not being able to support contains on this filter due to performance.
throw new Error(
'Contains is not supported as a filter option with "or" operator.',
);
case 'gt':
return collection.or(key).above(value);
case 'gte':
return collection.or(key).aboveOrEqual(value);
case 'lt':
return collection.or(key).below(value);
case 'lte':
return collection.or(key).belowOrEqual(value);
case 'notEquals':
return collection.or(key).notEqual(value);
default:
return collection.or(key).equals(value);
}
};
/**
* Translates the fields passed in from the UI for resources to the keys indexed DB knows them as.
* Usually it's the path of the actual normalized model.
*/
const getKey = (key) => {
switch (key) {
case 'scope_id':
return `attributes.scope.${key}`;
case 'id':
return key;
default:
return `attributes.${key}`;
}
};
/**
* Cleans the object to remove any keys that have a value of an empty array. This makes
* it easier so we don't have to worry about empty arrays getting queried in indexed db.
*/
const cleanObject = (obj) => {
if (!obj) {
return {};
}
return Object.entries(obj).reduce((result, [key, value]) => {
if (Array.isArray(value)) {
if (value.length > 0) {
result[key] = value;
}
} else if (value.values.length > 0) {
result[key] = value;
}
return result;
}, {});
};
/**
* Makes the search object that is expected when a user searches. Uses
* the indexes we setup in indexed db.
*/
const getSearchObject = (text, resource) => {
if (!text) {
return null;
}
return {
text,
fields: modelIndexes[resource]
.split(', ')
.map((index) => index.replace('&', '')),
};
};
+233
View File
@@ -0,0 +1,233 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { modelMapping } from 'api/services/sqlite';
import { searchTables } from 'api/services/sqlite';
import { typeOf } from '@ember/utils';
import { underscore } from '@ember/string';
/**
* Takes a POJO representing a filter query and builds a SQL query.
*
* @example
* const filter = generateSQLExpressions(type, {
* search: generated',
* filters: {
* scope_id: {
* logicalOperator: 'and',
* values: [{ notEquals: 'scope1' }, { notEquals: 'scope2' }],
* },
* },
* });
*/
export function generateSQLExpressions(
resource,
query = {},
{ page, pageSize, select } = {},
) {
let { search, filters, sort = {} } = query;
const conditions = [];
const parameters = [];
const tableName = underscore(resource);
addFilterConditions({ filters, parameters, conditions });
addSearchConditions({ search, resource, tableName, parameters, conditions });
const orderByClause = constructOrderByClause(resource, sort);
const whereClause = conditions.length ? `WHERE ${and(conditions)}` : '';
const paginationClause = page && pageSize ? `LIMIT ? OFFSET ?` : '';
if (paginationClause) {
parameters.push(pageSize, (page - 1) * pageSize);
}
const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${tableName}"`;
return {
// Replace any empty newlines or leading whitespace on each line to be consistent with formatting
// This is mainly to help us read and test the generated SQL as it has no effect on the actual SQL execution.
sql: `
${selectClause}
${whereClause}
${orderByClause}
${paginationClause}`
.replace(/^\s+/gm, '')
.trim(),
parameters,
};
}
function addFilterConditions({ filters, parameters, conditions }) {
if (!filters) {
return;
}
for (const [key, filterArrayOrObject] of Object.entries(filters)) {
// Filter can be an array or an object with a `values` property
const filterValueArray = Array.isArray(filterArrayOrObject)
? filterArrayOrObject
: filterArrayOrObject.values;
if (!filterValueArray || !filterValueArray.length) {
continue;
}
// When attribute conditions are a series of equals/notEquals
// replace with in/notIn to avoid hitting the sqlite maximum
// expression tree depth.
const firstOperator = Object.keys(filterValueArray[0])[0];
const allOperatorsEqual = filterValueArray
.flatMap((item) => Object.keys(item))
.every((op) => op === firstOperator);
if (
filterValueArray.length > 1 &&
(firstOperator === 'equals' || firstOperator === 'notEquals') &&
allOperatorsEqual
) {
const operation = firstOperator === 'equals' ? 'in' : 'notIn';
const values = filterValueArray
.filter((f) => f)
.map((filterObjValue) => {
let value = Object.values(filterObjValue)[0];
if (typeOf(value) === 'date') {
value = value.toISOString();
}
parameters.push(value);
return value;
});
const filterCondition = `${key}${OPERATORS[operation](values)}`;
conditions.push(parenthetical(filterCondition));
continue;
}
const filterConditions = filterValueArray
.filter((f) => f)
.map((filterObjValue) => {
let [operation, value] = Object.entries(filterObjValue)[0];
// SQLite needs to be working with ISO strings
if (typeOf(value) === 'date') {
value = value.toISOString();
}
// Handle LIKE operator separately
if (operation === 'contains') {
parameters.push(`%${value}%`);
} else {
parameters.push(value);
}
return `${key}${OPERATORS[operation]}`;
});
const { logicalOperator } = filterArrayOrObject;
// Default to `or` if we don't have a logical operator
conditions.push(
parenthetical(
logicalOperator === 'and'
? and(filterConditions)
: or(filterConditions),
),
);
}
}
function addSearchConditions({
search,
resource,
tableName,
parameters,
conditions,
}) {
if (!search) {
return;
}
if (searchTables.has(resource)) {
// Use the special prefix indicator "*" for full-text search
parameters.push(`"${search}"*`);
// Use a subquery to match against the FTS table with rowids as SQLite is
// much more efficient with FTS queries when using rowids or MATCH (or both).
// We could have also used a join here but a subquery is simpler.
conditions.push(
`rowid IN (SELECT rowid FROM ${tableName}_fts WHERE ${tableName}_fts MATCH ?)`,
);
return;
}
const fields = Object.keys(modelMapping[resource]);
const searchConditions = parenthetical(
or(
fields.map((field) => {
parameters.push(`%${search}%`);
return `${field}${OPERATORS['contains']}`;
}),
),
);
conditions.push(searchConditions);
}
function constructOrderByClause(resource, sort) {
const defaultOrderByClause = 'ORDER BY created_time DESC';
const { attributes, customSort, direction, isCoalesced } = sort;
const sortDirection = direction === 'desc' ? 'DESC' : 'ASC';
// We have to check if the attributes are valid for the resource
// as we can't use parameterized queries for ORDER BY
const validAttributes = Object.keys(modelMapping[resource] ?? {});
if (attributes?.some((attr) => !validAttributes.includes(attr))) {
return modelMapping[resource]?.created_time ? defaultOrderByClause : '';
}
if (customSort?.attributeMap) {
const whenClauses = Object.keys(customSort.attributeMap).reduce(
(acc, key) => {
return acc + `WHEN '${key}' THEN '${customSort.attributeMap[key]}' `;
},
'',
);
return `ORDER BY CASE ${attributes.join(', ')} ${whenClauses}END ${sortDirection}`;
} else if (attributes?.length > 0) {
const commaSeparatedVals = attributes.join(', ');
// In places where `collate nocase` is used, it is to ensure case is ignored on the initial sort.
// Then, a sort on the same condition is performed to ensure upper-case strings are given preference in a tie.
if (isCoalesced) {
return `ORDER BY COALESCE(${attributes.join(', ')}) COLLATE NOCASE ${sortDirection}, COALESCE(${commaSeparatedVals}) ${sortDirection}`;
}
const attributesWithNoCollate = attributes
.map((attr) => `${attr} COLLATE NOCASE ${sortDirection}`)
.join(', ');
const attributesWithDirection = attributes
.map((attr) => `${attr} ${sortDirection}`)
.join(', ');
return `ORDER BY ${attributesWithNoCollate}, ${attributesWithDirection}`;
} else if (modelMapping[resource]?.created_time) {
return defaultOrderByClause;
} else {
return '';
}
}
// Comparison Operators
const OPERATORS = {
equals: ' = ?',
notEquals: ' != ?',
gt: ' > ?',
gte: ' >= ?',
lt: ' < ?',
lte: ' <= ?',
contains: ' LIKE ?',
in: (values) => ` IN (${values.map(() => '?').join(', ')})`,
notIn: (values) => ` NOT IN (${values.map(() => '?').join(', ')})`,
};
// Logical Operators
const and = (clauses) => clauses.join(' AND ');
const or = (clauses) => clauses.join(' OR ');
const parenthetical = (expression) => (expression ? `(${expression})` : '');
+240
View File
@@ -0,0 +1,240 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { PWBWorker } from 'promise-worker-bi';
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import sharedServiceInit from './utils/shared-service';
import {
CLEAR_DB,
CREATE_TABLES,
DELETE_STATEMENT,
INSERT_STATEMENTS,
} from './utils/schema';
// Production builds error out when using top level awaits so we'll just use an async IIFE
(async () => {
/** @type {import('@sqlite.org/sqlite-wasm').OpfsSAHPoolDatabase} */
let db;
/** @type {import('@sqlite.org/sqlite-wasm').SAHPoolUtil} */
let poolUtil;
/** @type {import('@sqlite.org/sqlite-wasm').Sqlite3Static} */
let sqlite3;
const { promise: ready, resolve: readyResolve } = Promise.withResolvers();
// Maximum number of host parameters for SQLite prepared statements.
// See "Maximum Number Of Host Parameters In A Single SQL Statement" in
// https://www.sqlite.org/limits.html
const MAX_HOST_PARAMETERS = 32766;
const SCHEMA_VERSION = 1;
// Some browsers do not allow calling getDirectory in private browsing modes even
// if we're in a secure context. This will cause the SQLite setup to fail so we should
// check ahead of time to see if we're able to call the API.
let canCallGetDirectory;
try {
canCallGetDirectory = Boolean(await navigator.storage.getDirectory());
} catch {
canCallGetDirectory = false;
}
const isSecure = self.isSecureContext && canCallGetDirectory;
// Error code for sqlite corruption errors.
// See https://sqlite.org/rescode.html#corrupt
const SQLITE_CORRUPT = 11;
const methods = {
initializeSQLite: async () => {
sqlite3 = await sqlite3InitModule({
print: console.log,
printErr: console.error,
});
const dbName = await promiseWorker.postMessage({
method: 'getDatabaseName',
});
function setupDb() {
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) {
db.exec(CLEAR_DB);
}
db.exec(CREATE_TABLES(SCHEMA_VERSION));
// Force an analyze on initial connection
db.exec('PRAGMA optimize=0x10002;');
}
// Instantiate an in memory DB if we're not in a secure context as OPFS is unavailable otherwise
if (!isSecure) {
db = new sqlite3.oo1.DB(':memory:');
setupDb();
return;
}
let done = false;
while (!done) {
try {
poolUtil = await sqlite3.installOpfsSAHPoolVfs();
db = new poolUtil.OpfsSAHPoolDb(dbName);
setupDb();
done = true;
} catch (/** @type {import('@sqlite.org/sqlite-wasm').SQLite3Error} */ err) {
// If we get a SQLite3Error and the pool is at capacity, we'll assume the error is
// because we hit the cap so we add more capacity and see if that solves it.
if (
err.name === 'SQLite3Error' &&
poolUtil.getCapacity() === poolUtil.getFileCount()
) {
await poolUtil.addCapacity(1);
// If we have more than 20 files, that might indicate a problem so let's just
// remove the VFS and wipe all the client data across users and restart.
if (poolUtil.getCapacity() > 20) {
await poolUtil.removeVfs();
}
} else {
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) {
poolUtil.unlink(`/${dbName}`);
} else {
throw err;
}
}
}
}
},
analyzeDatabase: () => {
db.exec('PRAGMA optimize');
},
clearDatabase: () => {
db.exec(CLEAR_DB);
db.exec(CREATE_TABLES(SCHEMA_VERSION));
},
deleteDatabase: () => {
if (!db) {
throw new Error('No database was initialized');
}
const name = db.dbFilename();
db.close();
db = null;
poolUtil.unlink(`/${name}`);
},
downloadDatabase: () => {
return sqlite3.capi.sqlite3_js_db_export(db);
},
fetchResource: ({ sql, parameters = [] }) => {
try {
return db.exec({
sql,
bind: parameters,
rowMode: 'object',
});
} catch (err) {
// TODO: Add error handling to rest of methods and properly return the error back to caller
console.error('Fetch resource error:', err, sql, parameters);
throw err;
}
},
insertResource: ({ resource, items, modelMapping }) => {
if (items.length === 0) {
return [];
}
const numberOfParameters = items.reduce((acc, item) => {
return acc + item.length;
}, 0);
// 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
if (numberOfParameters > MAX_HOST_PARAMETERS) {
const chunkSize = Math.floor(MAX_HOST_PARAMETERS / items[0].length);
return db.transaction(() => {
const results = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
const result = db.exec({
sql: INSERT_STATEMENTS(resource, chunk, modelMapping),
bind: chunk.flat(),
rowMode: 'object',
});
results.push(result);
}
return results;
});
}
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
if (ids?.length > MAX_HOST_PARAMETERS) {
return db.transaction(() => {
const results = [];
for (let i = 0; i < ids.length; i += MAX_HOST_PARAMETERS) {
const chunk = ids.slice(i, i + MAX_HOST_PARAMETERS);
const result = db.exec({
sql: DELETE_STATEMENT(resource, chunk),
bind: chunk,
rowMode: 'object',
});
results.push(result);
}
return results;
});
}
return db.exec({
sql: DELETE_STATEMENT(resource, ids),
bind: ids,
rowMode: 'object',
});
},
};
const promiseWorker = new PWBWorker();
promiseWorker.register(async ({ method, payload }) => {
if (!sharedService) {
await ready;
return methods[method](payload);
}
await sharedService.status.ready;
return sharedService.proxy[method](payload);
});
const initializeOnProviderChange = async (isProvider) => {
if (!isProvider) {
return;
}
// Initialize the SQLite database when we become a provider
await sharedService.proxy.initializeSQLite();
};
// Only use the shared service in a secure context as weblocks aren't available otherwise
let sharedService;
if (isSecure) {
sharedService = sharedServiceInit(
'sqlite',
methods,
initializeOnProviderChange,
);
} else {
await methods.initializeSQLite();
readyResolve();
}
})();
+478
View File
@@ -0,0 +1,478 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { underscore } from '@ember/string';
export const CLEAR_DB = `
PRAGMA writable_schema = 1;
DELETE FROM sqlite_master;
PRAGMA writable_schema = 0;
VACUUM;
PRAGMA integrity_check;`;
const createTargetTables = `
CREATE TABLE IF NOT EXISTS target (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
address TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_target_scope_id_created_time ON target(scope_id, created_time DESC);
-- Create a contentless FTS table as we will only use the rowids.
-- Note that this only creates the FTS index and cannot reference the target content
CREATE VIRTUAL TABLE IF NOT EXISTS target_fts USING fts5(
id,
type,
name,
description,
address,
scope_id,
created_time,
content='',
);
-- Create triggers to keep the FTS table in sync with the target table
-- Note: The FTS table does not have an update trigger, as it's updated only on
-- INSERT and DELETE as we do an INSERT OR REPLACE INTO on the table
-- which replaces the row and doesn't execute an UPDATE trigger.
CREATE TRIGGER IF NOT EXISTS target_ai AFTER INSERT ON target BEGIN
INSERT INTO target_fts(
rowid, id, type, name, description, address, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.address, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS target_ad AFTER DELETE ON target BEGIN
INSERT INTO target_fts(target_fts, rowid, id, type, name, description, address, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.address, old.scope_id, old.created_time);
END;`;
const createAliasTables = `
CREATE TABLE IF NOT EXISTS alias (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
destination_id TEXT,
value TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_alias_created_time ON alias(created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS alias_fts USING fts5(
id,
type,
name,
description,
destination_id,
value,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS alias_ai AFTER INSERT ON alias BEGIN
INSERT INTO alias_fts(
rowid, id, type, name, description, destination_id, value, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.destination_id, new.value, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS alias_ad AFTER DELETE ON alias BEGIN
INSERT INTO alias_fts(alias_fts, rowid, id, type, name, description, destination_id, value, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.destination_id, old.value, old.scope_id, old.created_time);
END;`;
const createGroupTables = `
CREATE TABLE IF NOT EXISTS "group" (
id TEXT NOT NULL PRIMARY KEY,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_group_scope_id_created_time ON "group"(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS group_fts USING fts5(
id,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS group_ai AFTER INSERT ON "group" BEGIN
INSERT INTO group_fts(
rowid, id, name, description, scope_id, created_time
) VALUES (
new.rowid, new.id, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS group_ad AFTER DELETE ON "group" BEGIN
INSERT INTO group_fts(group_fts, rowid, id, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createRoleTables = `
CREATE TABLE IF NOT EXISTS role (
id TEXT NOT NULL PRIMARY KEY,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_role_scope_id_created_time ON role(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS role_fts USING fts5(
id,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS role_ai AFTER INSERT ON role BEGIN
INSERT INTO role_fts(
rowid, id, name, description, scope_id, created_time
) VALUES (
new.rowid, new.id, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS role_ad AFTER DELETE ON role BEGIN
INSERT INTO role_fts(role_fts, rowid, id, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createUserTables = `
CREATE TABLE IF NOT EXISTS user (
id TEXT NOT NULL PRIMARY KEY,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_scope_id_created_time ON user(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS user_fts USING fts5(
id,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS user_ai AFTER INSERT ON user BEGIN
INSERT INTO user_fts(
rowid, id, name, description, scope_id, created_time
) VALUES (
new.rowid, new.id, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS user_ad AFTER DELETE ON user BEGIN
INSERT INTO user_fts(user_fts, rowid, id, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createCredentialStoreTables = `
CREATE TABLE IF NOT EXISTS credential_store (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_credential_store_scope_id_created_time ON credential_store(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS credential_store_fts USING fts5(
id,
type,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS credential_store_ai AFTER INSERT ON credential_store BEGIN
INSERT INTO credential_store_fts(
rowid, id, type, name, description, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS credential_store_ad AFTER DELETE ON credential_store BEGIN
INSERT INTO credential_store_fts(credential_store_fts, rowid, id, type, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createScopeTables = `
CREATE TABLE IF NOT EXISTS scope (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_scope_scope_id_created_time ON scope(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS scope_fts USING fts5(
id,
type,
name,
description,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS scope_ai AFTER INSERT ON scope BEGIN
INSERT INTO scope_fts(
rowid, id, type, name, description, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS scope_ad AFTER DELETE ON scope BEGIN
INSERT INTO scope_fts(scope_fts, rowid, id, type, name, description, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.scope_id, old.created_time);
END;`;
const createAuthMethodTables = `
CREATE TABLE IF NOT EXISTS auth_method (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
is_primary INT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_auth_method_scope_id_created_time ON auth_method(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS auth_method_fts USING fts5(
id,
type,
name,
description,
is_primary,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS auth_method_ai AFTER INSERT ON auth_method BEGIN
INSERT INTO auth_method_fts(
rowid, id, type, name, description, is_primary, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.is_primary, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS auth_method_ad AFTER DELETE ON auth_method BEGIN
INSERT INTO auth_method_fts(auth_method_fts, rowid, id, type, name, description, is_primary, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.is_primary, old.scope_id, old.created_time);
END;`;
const createHostCatalogTables = `
CREATE TABLE IF NOT EXISTS host_catalog (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
name TEXT,
description TEXT,
plugin_name TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_host_catalog_scope_id_created_time ON host_catalog(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS host_catalog_fts USING fts5(
id,
type,
name,
description,
plugin_name,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS host_catalog_ai AFTER INSERT ON host_catalog BEGIN
INSERT INTO host_catalog_fts(
rowid, id, type, name, description, plugin_name, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.name, new.description, new.plugin_name, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS host_catalog_ad AFTER DELETE ON host_catalog BEGIN
INSERT INTO host_catalog_fts(host_catalog_fts, rowid, id, type, name, description, plugin_name, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.plugin_name, old.scope_id, old.created_time);
END;`;
const createSessionRecordingTables = `
CREATE TABLE IF NOT EXISTS session_recording (
id TEXT NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
state TEXT,
start_time TEXT,
end_time TEXT,
duration TEXT,
scope_id TEXT NOT NULL,
user_id TEXT,
user_name TEXT,
target_id TEXT,
target_name TEXT,
target_scope_id TEXT,
target_scope_name TEXT,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_session_recording_created_time ON session_recording(created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS session_recording_fts USING fts5(
id,
type,
state,
start_time,
end_time,
duration,
scope_id,
user_id,
user_name,
target_id,
target_name,
target_scope_id,
target_scope_name,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS session_recording_ai AFTER INSERT ON session_recording BEGIN
INSERT INTO session_recording_fts(
rowid, id, type, state, start_time, end_time, duration, scope_id, user_id, user_name, target_id, target_name, target_scope_id, target_scope_name, created_time
) VALUES (
new.rowid, new.id, new.type, new.state, new.start_time, new.end_time, new.duration, new.scope_id, new.user_id, new.user_name, new.target_id, new.target_name, new.target_scope_id, new.target_scope_name, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS session_recording_ad AFTER DELETE ON session_recording BEGIN
INSERT INTO session_recording_fts(session_recording_fts, rowid, id, type, state, start_time, end_time, duration, scope_id, user_id, user_name, target_id, target_name, target_scope_id, target_scope_name, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.state, old.start_time, old.end_time, old.duration, old.scope_id, old.user_id, old.user_name, old.target_id, old.target_name, old.target_scope_id, old.target_scope_name, old.created_time);
END;`;
const createSessionTables = `
CREATE TABLE IF NOT EXISTS session (
id TEXT NOT NULL PRIMARY KEY,
type TEXT,
status TEXT,
endpoint TEXT,
target_id TEXT,
user_id TEXT,
scope_id TEXT NOT NULL,
created_time TEXT NOT NULL,
data TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_session_scope_id_created_time ON session(scope_id, created_time DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS session_fts USING fts5(
id,
type,
status,
endpoint,
target_id,
user_id,
scope_id,
created_time,
content='',
);
CREATE TRIGGER IF NOT EXISTS session_ai AFTER INSERT ON session BEGIN
INSERT INTO session_fts(
rowid, id, type, status, endpoint, target_id, user_id, scope_id, created_time
) VALUES (
new.rowid, new.id, new.type, new.status, new.endpoint, new.target_id, new.user_id, new.scope_id, new.created_time
);
END;
CREATE TRIGGER IF NOT EXISTS session_ad AFTER DELETE ON session BEGIN
INSERT INTO session_fts(session_fts, rowid, id, type, status, endpoint, target_id, user_id, scope_id, created_time)
VALUES('delete', old.rowid, old.id, old.type, old.status, old.endpoint, old.target_id, old.user_id, old.scope_id, old.created_time);
END;`;
export const CREATE_TABLES = (version) => `
BEGIN;
-- This ensures multiple triggers can activate in the scenario of using REPLACE INTO
PRAGMA recursive_triggers = true;
PRAGMA user_version = ${version};
CREATE TABLE IF NOT EXISTS token (
id TEXT NOT NULL PRIMARY KEY,
token TEXT NOT NULL
);
${createTargetTables}
${createAliasTables}
${createGroupTables}
${createRoleTables}
${createUserTables}
${createCredentialStoreTables}
${createScopeTables}
${createAuthMethodTables}
${createHostCatalogTables}
${createSessionRecordingTables}
${createSessionTables}
COMMIT;`;
export const INSERT_STATEMENTS = (resource, items, modelMapping) => {
if (resource === 'token') {
return `REPLACE INTO token (id, token) VALUES ${items.map(() => '(?, ?)').join(', ')};`;
}
if (!modelMapping?.[resource]) {
throw new Error(`modelMapping is required for ${resource} insertions.`);
}
const columns = Object.keys(modelMapping[resource]);
// Extra column for the data column
const numColumns = Array(columns.length + 1);
const placeholders = `(${numColumns.fill('?').join(', ')})`;
return `REPLACE INTO "${underscore(resource)}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`;
};
export const DELETE_STATEMENT = (resource, ids) => {
const baseDeleteStatement = `DELETE FROM "${underscore(resource)}"`;
return ids?.length > 0
? `${baseDeleteStatement} WHERE id IN (${ids.map(() => '?').join(',')})`
: baseDeleteStatement;
};
@@ -0,0 +1,269 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { v4 as uuidv4 } from 'uuid';
let clientChannel = null;
let commonChannel = null;
const requestsInFlight = new Map();
export default (serviceName, target, onProviderChange) => {
commonChannel = new BroadcastChannel(
`shared-service-common-channel-${serviceName}`,
);
let { promise: ready, resolve: readyResolve } = Promise.withResolvers();
const status = {
ready,
isServiceProvider: false,
};
const proxy = getProxy(target, status);
// This lock is used to ensure that only one tab can become the provider with an indefinite lock.
navigator.locks.request(serviceName, { mode: 'exclusive' }, async () => {
// If a client was promoted to provider, reset the ready status and resolver
if (readyResolve === null) {
const { promise: newReady, resolve: newResolve } =
Promise.withResolvers();
status.ready = newReady;
readyResolve = newResolve;
}
status.isServiceProvider = true;
await onBecomeProvider({
serviceName,
onProviderChange,
target,
status,
});
readyResolve();
readyResolve = null;
await new Promise(() => {});
});
(async () => {
let providerExists = false;
// Make sure a lock was acquired which means we have a provider
while (!providerExists) {
const locks = await navigator.locks.query();
providerExists = locks.held?.some((lock) => lock.name === serviceName);
}
if (!status.isServiceProvider) {
await onNotProvider({
serviceName,
onProviderChange,
status,
});
readyResolve();
readyResolve = null;
}
})();
return { proxy, status };
};
const getClientBroadcastChannelName = (clientId, serviceName) =>
`${clientId}-${serviceName}`;
// Generate a random ID for the tab to use as a lock name to hold indefinitely until the tab is closed
const getClientId = () => {
const clientId = uuidv4();
// Keep the lock until this context is destroyed (which means the tab was closed).
navigator.locks.request(clientId, { mode: 'exclusive' }, async () => {
await new Promise(() => {});
});
return clientId;
};
// Sends a message to the provider to call the method on behalf of a client.
const requestFromProvider = (uuid, resolve, reject, method, args) => {
const onRequestListener = getOnRequestListener(uuid, resolve, reject);
// Listen for a response to know we finished and remove from the list of requests in flight
clientChannel?.addEventListener('message', onRequestListener);
clientChannel?.postMessage({
id: uuid,
type: 'request',
method,
args,
});
};
// Return the listener used for responding to requests. Only listens for response messages.
const getOnRequestListener = (uuid, resolve, reject) => {
const listener = ({ data }) => {
const { id, type } = data;
if (id !== uuid || type === 'request') {
return;
}
requestsInFlight.delete(uuid);
clientChannel?.removeEventListener('message', listener);
if (data.error) {
reject(data.error);
}
resolve(data.result);
};
return listener;
};
// When we become a client instead of a provider we do two things:
// 1. Register ourselves with the provider and wait for a response that says we registered.
// 2. Setup an event listener to know when a provider has changed.
const onNotProvider = async ({ serviceName, onProviderChange, status }) => {
const clientId = getClientId();
clientChannel = new BroadcastChannel(
getClientBroadcastChannelName(clientId, serviceName),
);
const register = async () =>
new Promise((resolve) => {
const onRegisteredListener = ({ data }) => {
if (data.clientId === clientId && data.type === 'registered') {
commonChannel.removeEventListener('message', onRegisteredListener);
resolve();
}
};
// Add an event listener to check for the registration response
commonChannel.addEventListener('message', onRegisteredListener);
commonChannel.postMessage({ type: 'register', clientId });
});
commonChannel.addEventListener('message', async ({ data }) => {
// Check if this was a provider change. If so and we're not the new provider, re-register.
if (data.type === 'providerChange' && !status.isServiceProvider) {
await onProviderChange?.(status.isServiceProvider);
await register();
// If there were requests in flight when the provider was changed,
// we need to resend them to the new provider.
if (requestsInFlight.size > 0) {
requestsInFlight.forEach(({ method, args, resolve, reject }, uuid) => {
requestFromProvider(uuid, resolve, reject, method, args);
});
}
}
});
await register();
};
// When we become the provider, we do two things:
// 1. Set an event listener to check for clients trying to register with us. As part of registration,
// we also setup a listener with the individual client to listen for requests.
// 2. Send a message to all tabs that we've now become the provider
const onBecomeProvider = async ({
serviceName,
onProviderChange,
target,
status,
}) => {
// Set an event listener for other tabs to register with the provider tab
commonChannel.addEventListener('message', async (event) => {
const { clientId, type } = event.data;
if (type !== 'register') {
return;
}
const clientChannel = new BroadcastChannel(
getClientBroadcastChannelName(clientId, serviceName),
);
// Acquire another lock that immediately is waiting for the
// initial lock on the client tab to be resolved to handle cleanup.
navigator.locks.request(clientId, { mode: 'exclusive' }, async () => {
// The client tab was closed, clean up.
clientChannel.close();
});
// Setup an event listener for communication between the provider and subscriber to respond to requests
clientChannel.addEventListener('message', async ({ data }) => {
if (data.type === 'response') {
return;
}
const { method, args, id } = data;
let result, error;
try {
result = await target[method](...args);
} catch (e) {
// Error objects won't be properly serialized, we need to reconstruct an error like object
error =
e instanceof Error
? Object.fromEntries(
Object.getOwnPropertyNames(e).map((k) => [k, e[k]]),
)
: e;
}
clientChannel.postMessage({
id,
type: 'response',
result,
error,
method,
});
});
// Send back a message to the client that we finished registering
commonChannel.postMessage({ type: 'registered', clientId, serviceName });
});
await onProviderChange?.(status.isServiceProvider);
commonChannel.postMessage({ type: 'providerChange', serviceName });
// If we became the new provider and there were requests in flight, request it directly again.
if (requestsInFlight.size > 0) {
await Promise.all(
Array.from(
requestsInFlight,
async ([uuid, { method, args, resolve, reject }]) => {
try {
const result = await target[method](...args);
resolve(result);
} catch (error) {
reject(error);
} finally {
requestsInFlight.delete(uuid);
}
},
),
);
}
};
// Returns a proxy that we will have all caller methods go through.
// If we're the provider, we just call the method directly. If we're not the provider, we'll
// send the request through our established client channel to have the provider respond with the data.
const getProxy = (target, status) =>
new Proxy(target, {
get: (target, method) => {
if (method === 'then' || method === 'catch' || method === 'finally') {
// Return undefined for these methods to allow promise chaining to work correctly
return;
}
return async (...args) => {
if (status.isServiceProvider) {
return await target[method](...args);
}
return new Promise((resolve, reject) => {
const uuid = uuidv4();
requestFromProvider(uuid, resolve, reject, method, args);
requestsInFlight.set(uuid, { method, args, resolve, reject });
});
};
},
});
@@ -3,5 +3,4 @@
* SPDX-License-Identifier: BUSL-1.1
*/
@import 'cover';
@import 'dialog';
export { default } from 'api/services/sqlite';
+7
View File
@@ -0,0 +1,7 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
// Ember-data no longer automatically installs BooleanTransform for apps.
export { BooleanTransform as default } from '@ember-data/serializer/transform';
@@ -3,4 +3,4 @@
* SPDX-License-Identifier: BUSL-1.1
*/
export { default, initialize } from 'core/initializers/deprecations';
export { DateTransform as default } from '@ember-data/serializer/transform';
@@ -3,4 +3,4 @@
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'rose/components/rose/dialog';
export { NumberTransform as default } from '@ember-data/serializer/transform';
+7
View File
@@ -0,0 +1,7 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
// Ember-data no longer automatically installs StringTransform for apps.
export { StringTransform as default } from '@ember-data/serializer/transform';
@@ -0,0 +1,56 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
const Plugin = require('broccoli-plugin');
const path = require('path');
const fs = require('fs');
const esbuild = require('esbuild');
module.exports = class BuildWorkers extends Plugin {
build() {
this.inputPaths.forEach((inputPath) => {
this.#buildWorkers(inputPath);
});
}
#buildWorkers(inputPath) {
const workers = this.#getWorkers(inputPath);
const workerBuilder = this.#configureWorkerBuilder({
isProduction: this.isProduction,
buildDir: this.outputPath,
});
Object.entries(workers).map(workerBuilder);
}
#getWorkers(inputPath) {
let workers = {};
let dir = fs.readdirSync(inputPath);
dir
.filter((name) => name.endsWith('-worker.js'))
.forEach((name) => {
workers[name] = path.join(inputPath, name);
});
return workers;
}
#configureWorkerBuilder({ isProduction, buildDir }) {
return ([name, entryPath]) => {
esbuild.buildSync({
loader: { '.js': 'js' },
entryPoints: [entryPath],
bundle: true,
outfile: path.join(buildDir, name),
format: 'esm',
minify: isProduction,
sourcemap: !isProduction,
});
};
}
};
+2 -1
View File
@@ -6,7 +6,6 @@
'use strict';
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
const { maybeEmbroider } = require('@embroider/test-setup');
module.exports = function (defaults) {
const app = new EmberAddon(defaults, {
@@ -19,6 +18,8 @@ module.exports = function (defaults) {
This build file does *not* influence how the addon or the app using it
behave. You most likely want to be modifying `./index.js` or app's build file
*/
const { maybeEmbroider } = require('@embroider/test-setup');
return maybeEmbroider(app, {
skipBabel: [
{
+63
View File
@@ -5,6 +5,69 @@
'use strict';
const { WatchedDir } = require('broccoli-source');
const funnel = require('broccoli-funnel');
const merge = require('broccoli-merge-trees');
const path = require('path');
const buildWorkers = require('./broccoli-plugins/build-workers');
const workerRoot = path.join(__dirname, 'addon', 'workers');
module.exports = {
name: require('./package').name,
options: {
// Don't automatically import sqlite, we need to manually control how it gets used
autoImport: {
exclude: ['@sqlite.org/sqlite-wasm'],
},
},
treeForAddon() {
// Exclude anything in the workers folder from being bundled in the final
// build as we're manually bundling the files ourselves below.
const tree = this._super.treeForAddon.apply(this, arguments);
return funnel(tree, {
exclude: ['api/workers/**/*'],
});
},
treeForPublic(tree) {
const env = this.parent?.app?.env ?? 'production';
const { enableSqlite } = this.parent?.app?.options[this.name] ?? {};
// If sqlite is not enabled and we're not running a test directly in the addon,
// we don't need to build the workers
if (!enableSqlite && this.app?.env !== 'test') {
return tree;
}
// We need to process and build our workers separately from ember as these
// workers are not part of the app and are not getting properly bundled otherwise
let workerTree = new WatchedDir(workerRoot);
workerTree = new buildWorkers([workerTree]);
workerTree.isProduction = env === 'production';
const workers = funnel(workerTree, {
destDir: 'workers',
});
// Also include the sqlite wasm file directly to be placed next to the workers
const sqliteWasmDirectoryPath = path.resolve(
require.resolve('@sqlite.org/sqlite-wasm/sqlite3.wasm'),
'..',
);
const wasmFile = funnel(sqliteWasmDirectoryPath, {
files: ['sqlite3.wasm'],
destDir: 'workers',
});
const trees = [workers, wasmFile];
if (tree) {
trees.push(tree);
}
return merge(trees);
},
};
+7 -2
View File
@@ -70,11 +70,16 @@ function routes() {
let resultSet;
// Default parent scope is global
if (!scope_id) scope_id = 'global';
// We do not return global scope to mimic our API.
if (recursive && scope_id === 'global') {
resultSet = scopes.all();
resultSet = scopes.where((scope) => {
return scope.scope ? scope.type !== 'global' : false;
});
} else {
resultSet = scopes.where((scope) => {
return scope.scope ? scope.scope.id === scope_id : false;
return scope.scope
? scope.scope.id === scope_id && scope.type !== 'global'
: false;
});
}
const filteredResultSet = resultSet.filter(makeBooleanFilter(filter));
+2 -1
View File
@@ -50,7 +50,8 @@ export default factory.extend({
}
case TYPE_AUTH_METHOD_OIDC:
return {
issuer: faker.internet.ip(),
issuer:
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
subject: 'sub',
email: faker.internet.email(),
full_name: faker.person.fullName(),
@@ -10,6 +10,7 @@ import generateId from '../helpers/id';
import {
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP,
TYPES_CREDENTIAL_LIBRARY,
options,
} from 'api/models/credential-library';
@@ -22,6 +23,9 @@ export default factory.extend({
if (this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC) {
return options.credential_types[i % options.credential_types.length];
}
if (this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP) {
return options.credential_types[2]; // Only supports username, password & domain credential type
}
},
authorized_actions: () =>
permissions.authorizedActionsFor('credential-library') || [
@@ -45,6 +49,10 @@ export default factory.extend({
key_bits: faker.number.int(999),
path: faker.system.directoryPath(),
};
case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP:
return {
path: faker.system.directoryPath(),
};
}
},
});
@@ -39,7 +39,8 @@ export default factory.extend({
switch (this.type) {
case 'vault':
return {
address: faker.internet.ip(),
address:
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
namespace: faker.word.words(1),
ca_cert: 'ca-cert-123',
tls_server_name: faker.internet.domainName(),
@@ -57,7 +58,7 @@ export default factory.extend({
const { scope, type } = credentialStore;
switch (type) {
case 'vault':
server.createList('credential-library', 2, {
server.createList('credential-library', 3, {
scope,
credentialStore,
});
+25 -4
View File
@@ -7,8 +7,14 @@ import factory from '../generated/factories/credential';
import { faker } from '@faker-js/faker';
import permissions from '../helpers/permissions';
import generatedId from '../helpers/id';
import {
TYPES_CREDENTIAL,
TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
} from 'api/models/credential';
const types = ['username_password', 'ssh_private_key', 'json'];
const types = [...TYPES_CREDENTIAL];
export default factory.extend({
type: (i) => types[i % types.length],
@@ -20,6 +26,8 @@ export default factory.extend({
return generatedId('credup_');
case 'json':
return generatedId('credjson_');
case 'username_password_domain':
return generatedId('credupd_');
}
},
authorized_actions: () =>
@@ -30,8 +38,21 @@ export default factory.extend({
'delete',
],
attributes() {
return {
username: faker.internet.userName(),
};
switch (this.type) {
case TYPE_CREDENTIAL_USERNAME_PASSWORD:
return { username: faker.internet.userName() };
case TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN:
return {
username: faker.internet.userName(),
domain: faker.internet.domainName(),
};
case TYPE_CREDENTIAL_SSH_PRIVATE_KEY:
return { username: faker.internet.userName() };
default:
return {};
}
},
});
+1 -3
View File
@@ -26,8 +26,6 @@ const pluginTypes = [
'foobar',
];
let pluginTypeCounter = 1;
export default factory.extend({
id: () => generateId('hc_'),
@@ -64,7 +62,7 @@ export default factory.extend({
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
return {
id: `plugin-id-${i}`,
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
name: pluginTypes[i % pluginTypes.length],
description: faker.word.words(),
};
}
+5 -3
View File
@@ -43,10 +43,12 @@ export default factory.extend({
},
preferred_endpoints() {
if (this.type === 'plugin') {
const enpointsAmount = faker.number.int({ min: 1, max: 5 });
const endpointsAmount = faker.number.int({ min: 1, max: 5 });
let result = [];
for (let i = 0; i < enpointsAmount; ++i) {
result.push(faker.internet.ip());
for (let i = 0; i < endpointsAmount; ++i) {
result.push(
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
);
}
return result;
}
+3 -1
View File
@@ -36,7 +36,9 @@ export default factory.extend({
const addressesAmount = Math.floor(Math.random() * 6) + 1;
let result = [];
for (let i = 0; i < addressesAmount; ++i) {
result.push(faker.internet.ip());
result.push(
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
);
}
return result;
},
+9
View File
@@ -78,6 +78,15 @@ export default factory.extend({
return name;
},
scope() {
if (this.type === 'global') {
return {
id: 'global',
type: 'global',
};
}
},
withChildren: trait({
afterCreate(record, server) {
if (record.type === 'global') {
+140 -25
View File
@@ -8,7 +8,16 @@ import { trait } from 'miragejs';
import { faker } from '@faker-js/faker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
import { TYPES_TARGET, TYPE_TARGET_SSH } from 'api/models/target';
import {
TYPES_TARGET,
TYPE_TARGET_SSH,
TYPE_TARGET_RDP,
} from 'api/models/target';
import {
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
TYPE_CREDENTIAL_JSON,
} from 'api/models/credential';
const randomBoolean = (chance = 0.5) => Math.random() < chance;
const randomFilter = () =>
@@ -16,6 +25,35 @@ const randomFilter = () =>
const hostSetChance = 0.3;
const types = [...TYPES_TARGET];
/**
* Helper to select items from a collection based on a condition.
*/
const selectItems = (collection, condition) =>
collection
.where(condition)
.models.filter(() => randomBoolean())
.map((item) => item.id);
/**
* Helper to update target with selected credential sources and host sets.
*/
const updateTarget = (
target,
hostSets,
isInjectable,
injectedSources,
brokeredSources,
) => {
const fieldsToUpdate = {
hostSets,
brokeredCredentialSourceIds: brokeredSources,
};
if (isInjectable) {
fieldsToUpdate.injectedApplicationCredentialSourceIds = injectedSources;
}
target.update(fieldsToUpdate);
};
export default factory.extend({
authorized_actions: () =>
permissions.authorizedActionsFor('target') || [
@@ -45,29 +83,83 @@ export default factory.extend({
*/
withAssociations: trait({
afterCreate(target, server) {
let randomlySelectedHostSets,
randomlySelectedCredentialLibraries,
randomlySelectedCredentials;
randomlySelectedHostSets = server.schema.hostSets
const { scope } = target;
const randomlySelectedHostSets = server.schema.hostSets
// BLERG: fun fact, for no reason at all, the element passed
// into a where function is not a full model instance, as you might
// expect at this level of abstraction, but appears to be a serialized
// representation of the model instance. It's very confusing since
// the result set of `where` _is a collection of model instances_.
.where((hostSet) => hostSet.scopeId === target.scope.id)
.where((hostSet) => hostSet.scopeId === scope.id)
.models.filter(() => randomBoolean(hostSetChance));
randomlySelectedCredentialLibraries = server.schema.credentialLibraries
.where(
const randomlySelectedBrokeredCredentialSources = [
...selectItems(
server.schema.credentialLibraries,
(credentialLibrary) => credentialLibrary.scopeId === target.scope.id,
)
.models.filter(() => randomBoolean())
.map((cred) => cred.id);
randomlySelectedCredentials = server.schema.credentials
.where((credential) => credential.scopeId === target.scope.id)
.models.filter(() => randomBoolean())
.map((cred) => cred.id);
),
...selectItems(
server.schema.credentials,
(credential) => credential.scopeId === target.scope.id,
),
];
// Select compatible credential libraries and credentials for injection
// Since not every credential or credential library can be injected, we filter them to mimic the expected behavior.
const filteredCredentialLibraries = selectItems(
server.schema.credentialLibraries,
(cred) =>
cred.scopeId === scope.id &&
cred.credential_type !== TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
);
const filteredCredentials = selectItems(
server.schema.credentials,
(cred) =>
cred.scopeId === scope.id &&
![
TYPE_CREDENTIAL_JSON,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
].includes(cred.type),
);
// RDP specific credential libraries and credentials
const filteredCredentialLibrariesForRDP = selectItems(
server.schema.credentialLibraries,
(cred) =>
cred.scopeId === scope.id &&
[
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
].includes(cred.credential_type),
);
const filteredCredentialsForRDP = selectItems(
server.schema.credentials,
(cred) =>
cred.scopeId === scope.id &&
[
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_USERNAME_PASSWORD,
].includes(cred.type),
);
// Update target based on type
const isInjectable =
target.type === TYPE_TARGET_SSH || target.type === TYPE_TARGET_RDP;
const injectedCredentialSources =
target.type === TYPE_TARGET_RDP
? [...filteredCredentialLibrariesForRDP, ...filteredCredentialsForRDP]
: [...filteredCredentialLibraries, ...filteredCredentials];
updateTarget(
target,
randomlySelectedHostSets,
isInjectable,
injectedCredentialSources,
randomlySelectedBrokeredCredentialSources,
);
// Add storage bucket for SSH targets
const storageBuckets = server.schema.storageBuckets.all().models;
const randomlySelectedStorageBucket =
storageBuckets.length === 0
@@ -81,6 +173,7 @@ export default factory.extend({
});
}
// Add Aliases
const aliases = server.schema.aliases.all().models;
const randomlySelectedAliases =
aliases.length === 0 ? undefined : faker.helpers.arrayElement(aliases);
@@ -89,17 +182,31 @@ export default factory.extend({
aliases: randomlySelectedAliases.aliases,
});
}
target.update({
hostSets: randomlySelectedHostSets,
brokeredCredentialSourceIds: [
...randomlySelectedCredentialLibraries,
...randomlySelectedCredentials,
],
injectedApplicationCredentialSourceIds: [
...randomlySelectedCredentialLibraries,
...randomlySelectedCredentials,
],
},
}),
withManyHosts: trait({
afterCreate(target, server) {
const { scope } = target;
const hostCatalog = server.create('host-catalog', { scope });
const hosts = server.createList('host', 15, {
scope,
hostCatalog,
type: hostCatalog.type,
});
const hostSets = [
server.create('host-set', {
scope,
hostCatalog,
hosts,
type: hostCatalog.type,
}),
];
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
hosts.forEach((host) => {
host.update({ hostSetIds });
});
target.update({ hostSets });
},
}),
@@ -120,6 +227,10 @@ export default factory.extend({
type: hostCatalog.type,
}),
];
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
hosts.forEach((host) => {
host.update({ hostSetIds });
});
target.update({ hostSets });
},
}),
@@ -139,6 +250,10 @@ export default factory.extend({
type: hostCatalog.type,
}),
];
const hostSetIds = hostSets.map((hostSet) => hostSet.id);
hosts.forEach((host) => {
host.update({ hostSetIds });
});
target.update({ hostSets });
},
}),
@@ -16,6 +16,9 @@ export default Factory.extend({
updated_time: () => faker.date.recent(),
version: () => faker.number.int(),
attributes: () => {
return { address: faker.internet.ipv6() };
return {
address:
Math.random() > 0.5 ? faker.internet.ip() : faker.internet.ipv6(),
};
},
});
Loaded 100 of 1412 files, more files were not shown because too many files have changed in this diff. Show more