Compare commits

..
200 Commits
Author SHA1 Message Date
Randall Morey 23de0695d0 Merge pull request #604 from hashicorp/chore-windows-container-build
Chore windows container build
2021-06-09 17:05:51 -04:00
Randall Morey 8d4cb1bfea Merge branch 'main' into chore-windows-container-build 2021-06-09 16:56:51 -04:00
Randall Morey d67f6dab2f chore: add --frozen-lockfile --non-interactive commands to yarn install in docker 2021-06-09 16:41:32 -04:00
Carlos Alcaide Corvo 8ee0e0b92d Merge pull request #600 from hashicorp/chore-update-artifact-version-for-release
Bump artifact version for 0.3.0 release
2021-06-09 13:36:45 -07:00
Randall Morey be9b68eeff chore: add windows build command that builds assets inside a container 2021-06-09 16:35:17 -04:00
Carlos Alcaide Corvo 1e20f7a149 Merge branch 'main' into chore-update-artifact-version-for-release 2021-06-09 13:25:35 -07:00
Randall Morey b413e9d8dd Merge pull request #603 from hashicorp/docs-desktop-known-issue
docs:  add known issue statement to changelog for 1.1.0 desktop
2021-06-09 16:18:15 -04:00
Randall Morey fd3c9eb876 docs: add known issue statement to changelog for 1.1.0 desktop 2021-06-09 15:41:40 -04:00
Carlos Alcaide 9979fe8464 Bump artifact version for 0.3.0 release 2021-06-09 09:33:47 -07:00
Randall Morey 9049bb2087 Merge pull request #597 from hashicorp/fix-desktop-about
fix: about dialog should display properly
2021-06-09 12:16:14 -04:00
Carlos Alcaide Corvo 82135a4b20 Merge branch 'main' into fix-desktop-about 2021-06-09 08:47:46 -07:00
Carlos Alcaide Corvo dd37718c62 Merge pull request #595 from hashicorp/chore-update-css-what-resolutions
chore: 🤖 Update resolutions for css-what
2021-06-08 14:42:54 -07:00
Carlos Alcaide Corvo e6f0166ff0 Merge branch 'main' into chore-update-css-what-resolutions 2021-06-08 14:30:00 -07:00
Carlos Alcaide Corvo 606a48ca49 Merge pull request #594 from hashicorp/chore-update-trim-newlines
chore: 🤖 Update resolutions for trim-newlines
2021-06-08 14:28:13 -07:00
Carlos Alcaide Corvo d1e46cb9b1 Merge branch 'main' into chore-update-trim-newlines 2021-06-08 14:14:23 -07:00
Carlos Alcaide Corvo 11ceb8b5ca Merge pull request #592 from hashicorp/chore-update-version-ember-a11y-testing
chore: 🤖 Upgrade ember-a11y-testing version
2021-06-08 14:14:07 -07:00
Randall Morey 3854d32e92 fix: about dialog should display properly 2021-06-08 17:07:39 -04:00
Carlos Alcaide Corvo bbb703c671 Merge branch 'main' into chore-update-version-ember-a11y-testing 2021-06-08 14:03:42 -07:00
Randall Morey f507edae03 Merge pull request #593 from hashicorp/remove-shell-config-for-cli-on-windows
fix: Remove shell option for spawning clis on windows
2021-06-08 16:40:13 -04:00
Randall Morey 9231963ac1 Merge branch 'main' into remove-shell-config-for-cli-on-windows 2021-06-08 16:28:33 -04:00
Carlos Alcaide 9bfb136247 chore: 🤖 Upgrade ember-a11y-testing version
Upgrade ember-a11y-testing package version
2021-06-08 12:11:56 -07:00
Carlos Alcaide 4af4ee7cc0 chore: 🤖 Update resolutions for css-what
Update resolution version for css-what transitive dependency.
2021-06-08 08:09:04 -07:00
Carlos Alcaide 12a9f72c68 chore: 🤖 Update resolutions for trim-newlines
Update resolution version for trim-newlines transitive dependency.
2021-06-08 07:56:09 -07:00
Carlos Alcaide Corvo bb88672aff Merge pull request #591 from hashicorp/chore-update-doctoc-version
chore: 🤖 Upgrade version of doctoc
2021-06-08 07:23:46 -07:00
Susmitha Girumala 25e004976e fix: Remove shell option for spawning clis on windows 2021-06-07 18:50:39 -04:00
Carlos Alcaide 60993e455a chore: 🤖 Upgrade version of doctoc
Upgrade version of doctoc and update some readme files in the process.
2021-06-07 14:30:21 -07:00
Susmitha Girumala 40ee317f47 Merge pull request #588 from hashicorp/frameless-desktop-shell-on-windows-os
Use frameless desktop shell on Windows OS
2021-06-07 16:45:20 -04:00
Susmitha Girumala 530cd0298a chore: Simplify Windows styles and cli name population 2021-06-07 14:18:07 -04:00
Susmitha Girumala 374a13cd07 fix: Remove debugger statement 2021-06-07 11:53:15 -04:00
Randall Morey 6f5da333e0 Merge branch 'main' into frameless-desktop-shell-on-windows-os 2021-06-07 10:53:42 -04:00
Randall Morey d121bcf204 Merge pull request #590 from hashicorp/fix-oidc-create
fix: 🐛 add read-only hmac field and enable oidc auth creation
2021-06-07 10:53:30 -04:00
Susmitha Girumala 2acd555966 docs: Update changelog for next release 2021-06-06 11:58:57 -04:00
Susmitha Girumala 820843c51d chore: Fix desktop tests 2021-06-06 11:29:23 -04:00
Susmitha Girumala 243f165644 chore: clean yarn.lock 2021-06-06 10:15:34 -04:00
Susmitha Girumala 5036c62740 Merge remote-tracking branch 'origin/main' into frameless-desktop-shell-on-windows-os 2021-06-06 02:59:09 -04:00
Susmitha Girumala 3ffe70cf5d fix: Ignore app updater for windows
Ignore app updater for windows until first release. Use version packaged
at build time in app updater.
2021-06-06 02:58:42 -04:00
Susmitha Girumala c5b0061428 feat: Use tree-kill module to kill sessions on Windows 2021-06-06 02:47:00 -04:00
Susmitha Girumala fefae2b73d fix: Fix scrollbars and window minimize/close actions for win 2021-06-06 02:12:39 -04:00
Susmitha Girumala 196d01aae2 fix: Fix cli path on windows to launch connect shell 2021-06-05 22:11:47 -04:00
Randall Morey 96707ebe15 fix: 🐛 add read-only hmac field and enable oidc auth creation 2021-06-05 12:29:12 -04:00
Randall Morey 79f5db0441 Merge pull request #587 from hashicorp/feature-oidc-array-fields-crud
Feature oidc array fields crud
2021-06-04 16:50:03 -04:00
Randall Morey d4fee0af34 chore: manual merge from main 2021-06-04 14:35:45 -04:00
Randall Morey a8c65e73f5 Merge pull request #589 from hashicorp/feature-oidc-accounts
Feature oidc accounts
2021-06-04 14:32:09 -04:00
Randall Morey 13df776c18 feat: 🎸 add OIDC account listing 2021-06-04 14:23:08 -04:00
Randall Morey 12588a17ab refactor: 💡 type-specific account serialization 2021-06-04 13:38:23 -04:00
Susmitha Girumala 7b2c8b7d58 Merge remote-tracking branch 'origin/main' into frameless-desktop-shell-on-windows-os 2021-06-04 12:21:49 -04:00
Susmitha Girumala 873965d7b7 feat: Hook in browser actions into electon's BrowserWindow 2021-06-04 12:21:08 -04:00
Randall Morey f02f488edd Merge pull request #583 from hashicorp/ICU-1369-oidc-status-change
[draft] feat: Add oidc status change dropdown to auth method actions
2021-06-04 11:48:16 -04:00
Susmitha Girumala a2ee17e918 Merge pull request #586 from hashicorp/fix-sass-division-deprecation-warning
fix: 🐛 Deprecate Slash as division operator
2021-06-04 11:16:02 -04:00
Randall Morey bf8620d75c chore: 🤖 clean up change-state handler 2021-06-04 10:11:07 -04:00
Randall Morey d0eb41e03b feat: 🎸 add icons and clean up state change language 2021-06-03 17:08:40 -04:00
Carlos Alcaide 6e932f8054 fix: 🐛 Deprecate Slash as division operator
Deprecation of slash as division operator as we were getting warnings in
the terminal about it. More info
[here](https://sass-lang.com/documentation/breaking-changes/slash-div)
2021-06-03 11:32:24 -07:00
Susmitha Girumala 76291896c3 feat: Add window action buttons for windows OS only 2021-06-03 14:28:44 -04:00
Susmitha Girumala c803adccc1 feat: Remove header brand icon padding on windows OS only 2021-06-03 14:03:04 -04:00
Susmitha Girumala f7854b642c chore: Add in-progress oidc test for visibility 2021-06-02 22:44:40 -04:00
Susmitha Girumala a7a6bdedf9 refactor: API: Add AuthMethodOIDC class to hold static states 2021-06-02 22:22:02 -04:00
Carlos Alcaide Corvo 4bcd0e7d06 Merge pull request #580 from hashicorp/chore-update-ember-cli-storybook
chore: 🤖 Update ember-cli-storybook
2021-06-02 15:17:12 -07:00
Carlos Alcaide 895a25218e Fix merge conflict 2021-06-02 15:04:25 -07:00
Carlos Alcaide 8356e7d47a chore: 🤖 Update ember-cli-storybook
Update the version of @storybook/ember-cli-storybook we are using.
2021-06-02 15:01:38 -07:00
Susmitha Girumala 680ae9980b Merge pull request #581 from hashicorp/ICU-1356-enable-windows-platform-support
[desktop] Enable windows platform support
2021-06-02 17:37:12 -04:00
Susmitha Girumala e106454bb5 Merge remote-tracking branch 'origin' into ICU-1356-enable-windows-platform-support 2021-06-02 17:21:28 -04:00
Carlos Alcaide Corvo aa227a0db8 Merge pull request #584 from hashicorp/chore-ws-security-vulnerability
chore: 🤖 Fix WS security vulnerability
2021-06-02 11:59:01 -07:00
Carlos Alcaide 8aca872c66 chore: 🤖 Fix WS security vulnerability
Update the resolutions to resolve a version of WS that has no security
vulnerabilities.
2021-06-02 11:40:33 -07:00
Randall Morey 269c56bf83 fix: lint issue 2021-06-02 14:33:31 -04:00
Randall Morey a89b27c813 Merge branch 'ICU-1369-oidc-status-change' of https://github.com/hashicorp/boundary-ui into ICU-1369-oidc-status-change 2021-06-02 14:25:36 -04:00
Randall Morey 2804b4322b manual merge 2021-06-02 14:25:19 -04:00
Randall Morey 66d16d84d0 fix: 🐛 use flattened attrs to capture oidc field errors 2021-06-02 14:23:54 -04:00
Carlos Alcaide f420d01fb8 fix: 🐛 Fix lint errors
Fix lint errors
2021-06-02 11:19:44 -07:00
Susmitha Girumala 4bd80a716e Revert "Merge branch 'ICU-1369-oidc-status-change' of github.com:hashicorp/boundary-ui into ICU-1369-oidc-status-change"
This reverts commit 8098beb450, reversing
changes made to a6672ecef4.
2021-06-02 14:07:38 -04:00
Susmitha Girumala 8098beb450 Merge branch 'ICU-1369-oidc-status-change' of github.com:hashicorp/boundary-ui into ICU-1369-oidc-status-change 2021-06-02 14:06:06 -04:00
Susmitha Girumala a6672ecef4 chore: Rebase onto feature-oidc-array-fields-crud branch 2021-06-02 14:03:53 -04:00
Susmitha Girumala 79b6737aaa chore: clean yarn install 2021-06-02 13:55:12 -04:00
Susmitha Girumala 31afafff4e Merge branch 'main' into ICU-1356-enable-windows-platform-support 2021-06-02 13:33:04 -04:00
Susmitha Girumala d4a4828b67 Merge pull request #568 from hashicorp/generate-forge-config-on-build
feat: Generate config file for desktop build
2021-06-02 13:32:09 -04:00
Susmitha Girumala 44fa206619 feat: Add oidc status change dropdown to auth method actions 2021-06-02 13:10:34 -04:00
Carlos Alcaide bc10b00874 chore: 🤖 Fix lint errors
Fix lint errors
2021-06-02 13:10:33 -04:00
Susmitha Girumala bbd73b36d1 chore: Fix js lint errors 2021-06-02 13:10:33 -04:00
Randall Morey 3a52b40214 chore: add meta description 2021-06-02 13:10:33 -04:00
Randall Morey c41855ccc4 chore: 🤖 fix imports that don't make sense 2021-06-02 13:10:32 -04:00
Randall Morey c013b178d7 feat: 🎸 add oidc-account-crud feature flag 2021-06-02 12:38:15 -04:00
Randall Morey 683fffaa0e fix: correct path to logo-app 2021-06-02 12:09:34 -04:00
Randall Morey 31f67e568c chore: add missing translations 2021-06-02 12:09:17 -04:00
Susmitha Girumala 9c15d02080 Merge branch 'main' into generate-forge-config-on-build 2021-06-02 11:28:10 -04:00
Susmitha Girumala c14be070f7 feat: Add oidc status change dropdown to auth method actions 2021-06-01 22:29:24 -04:00
Randall Morey 7b1410d832 chore: move squirrel maker to devdeps 2021-06-01 15:25:53 -04:00
Randall Morey 06cf112b61 chore: bump CLI version 2021-06-01 15:25:02 -04:00
Susmitha Girumala 95e3410540 Merge pull request #582 from hashicorp/fix-lint-error
chore: Fix js lint errors
2021-06-01 14:20:17 -04:00
Carlos Alcaide 77d7187116 chore: 🤖 Fix lint errors
Fix lint errors
2021-06-01 11:07:48 -07:00
Susmitha Girumala 28d18e389c chore: Fix js lint errors 2021-06-01 12:55:35 -04:00
Susmitha Girumala d95ae373ab refactor: Move windows app icon to new assets folder 2021-06-01 11:28:47 -04:00
Susmitha Girumala a784eeecc2 Merge branch 'ICU-1356-enable-windows-platform-support' of https://github.com/hashicorp/boundary-ui into ICU-1356-enable-windows-platform-support 2021-06-01 10:20:42 -04:00
Susmitha Girumala 84962c9ed3 docs: Add windows build to readme 2021-06-01 10:20:25 -04:00
Susmitha Girumala cfd2e13832 refactor: Reset titlebarstyle to mac 2021-06-01 09:58:43 -04:00
Susmitha Girumala 880f8f6b39 refactor: Cleanup frameless windows shell work for a futurePR 2021-06-01 09:55:46 -04:00
Susmitha Girumala c5b1cc1498 Merge branch 'main' into generate-forge-config-on-build 2021-06-01 09:35:07 -04:00
Susmitha Girumala 343f85f8e7 Merge remote-tracking branch 'origin/generate-forge-config-on-build' into ICU-1356-enable-windows-platform-support 2021-05-27 10:36:00 -04:00
Susmitha Girumala 654bc18422 chore: Separate desktop ember app build from electron build 2021-05-27 10:34:13 -04:00
Susmitha Girumala 81b59af3b4 Merge pull request #575 from hashicorp/chore-contextual-form-components
feat: 🎸 textarea and select should support contexual usage
2021-05-27 09:22:14 -04:00
Randall Morey e50d0ac00b feat: 🎸 add account claim map support 2021-05-26 18:09:28 -04:00
Randall Morey 9fe89c9b18 feature: add addItem functionality to array fields 2021-05-26 17:12:51 -04:00
Randall Morey 8c20d29806 feat: 🎸 add remove array item support to oidc form 2021-05-26 15:28:41 -04:00
Susmitha Girumala 10d93992d4 refactor: Move assets out of config folder in desktop 2021-05-26 15:05:30 -04:00
Randall Morey 0552d1db5a Merge branch 'chore-contextual-form-components' into feature-oidc-array-fields-crud 2021-05-26 11:14:20 -04:00
Susmitha Girumala 07919409d9 refactor: Static electron config to contain windows platform 2021-05-26 10:39:52 -04:00
Susmitha Girumala d691c1fa11 Merge remote-tracking branch 'origin/generate-forge-config-on-build' into ICU-1356-enable-windows-platform-support 2021-05-26 10:32:57 -04:00
Susmitha Girumala b632df901b Merge remote-tracking branch 'origin/main' into ICU-1356-enable-windows-platform-support 2021-05-26 10:16:25 -04:00
Randall Morey ecb67ea344 Merge branch 'main' into chore-contextual-form-components 2021-05-26 09:29:55 -04:00
Susmitha Girumala 4c2e8f7a76 Merge remote-tracking branch 'origin/main' into generate-forge-config-on-build 2021-05-25 18:07:26 -04:00
Susmitha Girumala 22c4581d4d refactor: 💡 Import generated config into electron forge config 2021-05-25 18:07:04 -04:00
Randall Morey 34c668b4d9 feat: 🎸 textarea and select should support contexual usage 2021-05-25 17:56:22 -04:00
Randall Morey 9437edbf8a Merge pull request #574 from hashicorp/chore-embroider
chore: 🤖 fix imports that don't make sense
2021-05-25 17:50:53 -04:00
Randall Morey 5890b1b2e8 Merge branch 'main' into chore-embroider 2021-05-25 17:36:49 -04:00
Randall Morey 5ea26dc7ea feature: add array fields to oidc form 2021-05-25 17:36:28 -04:00
Susmitha Girumala 6afde1fb18 Merge pull request #572 from hashicorp/chore-meta-description
chore: add meta description
2021-05-25 16:44:39 -04:00
Susmitha Girumala 708a0836cc Merge branch 'main' into chore-meta-description 2021-05-25 15:21:58 -04:00
Susmitha Girumala 3fc51ee54a Merge pull request #571 from hashicorp/fix-logo-app
fix:  logo-app.svg should load properly
2021-05-25 15:21:37 -04:00
Randall Morey e85a75305f Merge branch 'main' into fix-logo-app 2021-05-25 15:10:21 -04:00
Randall Morey 9f469aec38 Merge pull request #569 from hashicorp/chore-dependabot
chore: 🤖 bump browserslist version for dependabot
2021-05-25 15:10:13 -04:00
Susmitha Girumala dd7907da8c Merge remote-tracking branch 'origin/main' into generate-forge-config-on-build 2021-05-25 15:00:29 -04:00
Susmitha Girumala 634c522eaf refactor: Update electron config console log tag 2021-05-25 15:00:20 -04:00
Randall Morey 57a924703e Merge branch 'main' into chore-dependabot 2021-05-25 14:56:51 -04:00
Randall Morey 89285dcfba Merge branch 'main' into fix-logo-app 2021-05-25 14:56:41 -04:00
Randall Morey 05c14d7bbb Merge pull request #570 from hashicorp/chore-docker-builds
Add desktop static assets Docker build support
2021-05-25 14:56:33 -04:00
Randall Morey 8c4228016c Merge branch 'main' into chore-dependabot 2021-05-25 14:54:52 -04:00
Randall Morey 8d00730653 chore: 🤖 fix imports that don't make sense 2021-05-25 14:52:20 -04:00
Randall Morey 38ed272fc2 Merge branch 'main' into fix-logo-app 2021-05-25 14:51:58 -04:00
Randall Morey d0a11d6054 Merge branch 'main' into chore-meta-description 2021-05-25 14:51:52 -04:00
Susmitha Girumala 8022234e84 Merge branch 'main' into chore-docker-builds 2021-05-25 14:26:56 -04:00
Susmitha Girumala b58bde0461 Merge branch 'main' into generate-forge-config-on-build 2021-05-25 14:26:31 -04:00
Susmitha Girumala 48c7538172 Merge pull request #566 from hashicorp/ICU-1560-fix-app-update-prompt-desktop
fix: Desktop should show update prompt for 1.0.1 release
2021-05-25 14:26:18 -04:00
Susmitha Girumala cb938d0e33 Merge branch 'main' into ICU-1560-fix-app-update-prompt-desktop 2021-05-25 13:43:21 -04:00
Susmitha Girumala f2544684ec Merge pull request #573 from hashicorp/remove-yarn-audit-precommit-check
chore: Remove yarn audit check
2021-05-25 13:43:01 -04:00
Susmitha Girumala 131ce44ecc chore: Remove yarn audit from github actions 2021-05-25 13:24:58 -04:00
Randall Morey b91f8c1661 chore: add meta description 2021-05-25 13:14:32 -04:00
Susmitha Girumala 3943a740bc chore: Remove yarn audit check
Json output option for yarn audit is broken:
https://github.com/yarnpkg/yarn/issues/7404
2021-05-25 13:14:16 -04:00
Randall Morey d88a4ac103 fix: logo-app.svg should load properly 2021-05-25 12:55:43 -04:00
Randall Morey afeaf3a671 chore: 🤖 bump browserslist version for dependabot 2021-05-25 11:08:26 -04:00
Randall Morey 1a968a7d24 chore: fix typo 2021-05-24 18:20:00 -04:00
Randall Morey ac5f7fa1c9 chore: add desktop build support with docker-compose 2021-05-24 17:33:03 -04:00
Susmitha Girumala c1cd762da4 chore: Make windows electron shell frameless 2021-05-24 16:37:35 -04:00
Susmitha Girumala 5f9230c458 Merge branch 'main' into ICU-1560-fix-app-update-prompt-desktop 2021-05-24 16:04:33 -04:00
Susmitha Girumala 2381b3372c Merge pull request #567 from hashicorp/feature-oidc-standard-crud
OIDC standard crud - ICU-1365
2021-05-24 16:04:20 -04:00
Susmitha Girumala 4a07478f1e fix: Use os.platform instead of os.type in desktop for platfo 2021-05-24 15:53:10 -04:00
Susmitha Girumala 5c4ceb7f0f Merge branch 'main' into feature-oidc-standard-crud 2021-05-24 15:47:49 -04:00
Susmitha Girumala e940ec98d4 Merge pull request #565 from hashicorp/feature-checkbox-helper-text
feat: 🎸 add helper text option to checkbox component
2021-05-24 15:47:34 -04:00
Susmitha Girumala b7e0f42d47 Merge remote-tracking branch 'origin/generate-forge-config-on-build' into ICU-1356-enable-windows-platform-support 2021-05-24 15:27:03 -04:00
Susmitha Girumala 1b300c676b Merge branch 'main' into feature-checkbox-helper-text 2021-05-24 15:20:37 -04:00
Susmitha Girumala a7cc1b9ba2 Merge pull request #563 from hashicorp/feature-more-oidc-modeling
Feature more oidc modeling
2021-05-24 15:20:24 -04:00
Susmitha Girumala c99449d8e5 refactor: Rename forge config generator to electron.js 2021-05-24 15:20:02 -04:00
Susmitha Girumala 2038a463db Merge branch 'main' into feature-more-oidc-modeling 2021-05-24 14:48:36 -04:00
Susmitha Girumala 4f0e2794ec chore: Add windows icon in desktop 2021-05-24 11:21:34 -04:00
Susmitha Girumala 89891a401a refactor: Use platform helper instead of os api in desktop 2021-05-24 10:41:09 -04:00
Susmitha Girumala d572d5157d chore: desktop: use OS agnostic paths 2021-05-22 18:18:16 -04:00
Susmitha Girumala 6ffb4a2350 chore: Add cross-os package to desktop 2021-05-22 17:12:01 -04:00
Susmitha Girumala 1e1ea1c49a chore: Add cross-env package to desktop 2021-05-22 17:00:48 -04:00
Susmitha Girumala fb4238b035 chore: Refactor windows electron config to use new generator 2021-05-22 16:53:56 -04:00
Susmitha Girumala 3d6d7c99c2 Merge remote-tracking branch 'origin/generate-forge-config-on-build' into ICU-1356-enable-windows-platform-support 2021-05-22 15:59:21 -04:00
Susmitha Girumala 5bb7d41903 feat: Add copyright info to 'About' menu option
Ignore generated forge config file
2021-05-21 17:38:26 -04:00
Susmitha Girumala 47c453e979 feat: Generate forge config file based on platform
In addition, store release variables in forge config for display in
"About" menu option
2021-05-21 17:20:49 -04:00
Randall Morey e16bfdfb03 feat: 🎸 add OIDC crud without array fields or state change 2021-05-21 16:49:07 -04:00
Randall Morey 3138aecc37 chore: add OIDC form field translations 2021-05-21 16:40:29 -04:00
Randall Morey b19583c08d chore: 🤖 tweak oidc attributes serialization 2021-05-21 16:38:59 -04:00
Randall Morey 3d8a70d676 chore: 🤖 remove unnecessary awaits 2021-05-21 15:26:26 -04:00
Randall Morey 7e6158b847 feat: 🎸 add helper text option to checkbox component 2021-05-21 14:12:48 -04:00
Susmitha Girumala 23e82b352e fix: Desktop should show update prompt for 1.0.1 release 2021-05-21 14:12:23 -04:00
Carlos Alcaide Corvo 2ccd08a8d4 Merge pull request #564 from hashicorp/clean-default-tests-comment
style: 💄 Clean default tests comments
2021-05-21 10:13:14 -07:00
Carlos Alcaide 6159378a52 style: 💄 Clean default tests comments
Clean the default test comment added when creating a new test file with
ember-cli.
2021-05-21 08:19:48 -07:00
Susmitha Girumala 65039ce820 fix: Use robocopy in windows instead of xcopy 2021-05-21 10:27:00 -04:00
Randall Morey fde633331c feat: 🎸 add missing array fields to auth method attributes 2021-05-20 18:00:48 -04:00
Randall Morey 597b945aca chore: 🤖 complete the fragment-string serializer with normalize 2021-05-20 17:54:00 -04:00
Susmitha Girumala d23684c42f chore: Update desktop build vars to work on windows 2021-05-20 16:53:18 -04:00
Randall Morey cb3bcf1b9e Merge pull request #558 from hashicorp/feature-oidc-account-claim-maps
Feature oidc account claim maps
2021-05-20 16:18:30 -04:00
Randall Morey c8c5c2e7ef Merge branch 'main' into feature-oidc-account-claim-maps 2021-05-20 16:00:43 -04:00
Randall Morey 8886f14996 Merge pull request #557 from hashicorp/feature-oidc-state-change
feat: 🎸 add OIDC change state custom method
2021-05-20 16:00:32 -04:00
Randall Morey e8c7e81828 Merge branch 'main' into feature-oidc-state-change 2021-05-20 15:37:15 -04:00
Randall Morey 1f47ca8ea9 Merge pull request #556 from hashicorp/feature-oidc-modeling
feat: 🎸 implement basic OIDC auth method attributes modeling
2021-05-20 15:37:08 -04:00
Randall Morey f61cad986f Merge branch 'main' into feature-oidc-modeling 2021-05-20 15:16:56 -04:00
Carlos Alcaide Corvo 6256bfc3cf Merge pull request #554 from hashicorp/new-table-style
Refactor rose table component
2021-05-19 13:37:39 -07:00
Carlos Alcaide 3133997e78 feat: 🎸 Deleting forgot test
Deleting a test that is not need it anymore after last changes
2021-05-19 13:15:04 -07:00
Carlos Alcaide Corvo ed9324a0de Merge branch 'main' into new-table-style 2021-05-19 11:17:43 -07:00
Carlos Alcaide ab668d74eb feat: 🎸 Delete key-value row and other tweaks
Delete the recently introduced key-value class for rows, for they
key-value lookalike, when editing we want to use 3 colums on the table
rather than 2. Some style's tweaks to simplify
2021-05-19 11:16:30 -07:00
Susmitha Girumala 7876de8e2d feat: Add windows electron maker in desktop 2021-05-19 13:06:32 -04:00
Randall Morey 06f00ac8f6 Merge branch 'main' into new-table-style 2021-05-17 16:13:00 -04:00
Susmitha Girumala 97ee639d62 fix: Fix improper use of url.protocol 2021-05-17 15:28:45 -04:00
Susmitha Girumala 314aa62828 feat: Update windows platform specific menu options in deskto 2021-05-17 10:20:21 -04:00
Susmitha Girumala bcf27ae048 feat: Download windows cli archive in desktop 2021-05-17 09:23:30 -04:00
Carlos Alcaide ce9503fc5b feat: 🎸 Refactor table component with PR suggestions
Add shrink style to cells and deprecate expand. Add Key/value style for
table rows. Refactor storybook table page with all the changes. Refactor
styles for new feature and simplification.
2021-05-14 15:40:18 -07:00
Randall Morey dead74c825 feat: 🎸 add OIDC account_claim_maps modeling 2021-05-14 12:05:09 -04:00
Randall Morey 24e37d3faa feat: 🎸 add OIDC change state custom method 2021-05-13 14:10:37 -04:00
Carlos Alcaide 385ea34c98 Swap @style='expand' to boolean @expand='true' 2021-05-13 09:41:13 -07:00
Randall Morey e91612b313 feat: 🎸 implement basic OIDC auth method attributes modeling 2021-05-12 17:12:59 -04:00
Carlos Alcaide 8feeaa47dc feat: 🎸 Refactor rose table component
We refactor the component adding a condensed variant of the table,
refactor the hidden feature. We also provide different alignments for a
cell. Updated tests and storybook page
2021-05-11 15:08:01 -07:00
Susmitha Girumala ec9d953bad test: Add Rose::Table::Row::Cell tests 2021-05-11 15:08:01 -07:00
Susmitha Girumala 84051e4b72 chore: Refactor Rose::Table::Row visually-hidden to hidden 2021-05-11 15:08:01 -07:00
Susmitha Girumala 6b93032a78 style: Update Rose::Table storybook 2021-05-11 15:08:01 -07:00
Susmitha Girumala a909ba67df chore: Minor cleanup in Rose::Table::Row template 2021-05-11 15:08:01 -07:00
Susmitha Girumala cf40b53fd0 chore: Format newly added files in rose 2021-05-11 15:08:01 -07:00
Susmitha Girumala 3425dc89be chore: Setup Rose::Table cell alignment config 2021-05-11 15:08:01 -07:00
Susmitha Girumala 8e1e2b6b36 feat: Add Rose::Table::Row::Cell for cells to expand/shrink 2021-05-11 15:08:01 -07:00
Susmitha Girumala 4ace04de7a feat: Add condensed style to Rose::Table 2021-05-11 15:08:01 -07:00
172 changed files with 4721 additions and 2228 deletions

No files matched your search

-17
View File
@@ -19,23 +19,6 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
- run: yarn run compliance:licenses
audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: cache
with:
path: '**/node_modules'
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- run: yarn install
if: steps.cache.outputs.cache-hit != 'true'
- run: yarn run compliance:audit
lint:
name: Lint
runs-on: ubuntu-latest
+10 -1
View File
@@ -122,11 +122,20 @@ yarn install
yarn build:ui:desktop
```
In windows, UI is generated using docker to provide a stable UI across platforms.
```cmd
yarn install
docker-compose -f docker-compose-embedding.yml run build-desktop-production
yarn 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: * (*)).
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn 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`.
EXE archived desktop UI is available at asset location under `zip` folder.
### Connect Boundary UI to Boundary local instance
+5 -1
View File
@@ -248,7 +248,11 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
// Normalize field-specific errors, if any.
const errors = fieldErrors.map((error) => ({
detail: error.description,
source: { pointer: `/data/attributes/${error.name}` },
// Ember Data cannot handle errors on nested attributes, so these are
// flattened (e.g. `attributes.foobar` becomes `attributes_foobar`).
// To capture these errors, add read-only fields on the model
// corresponding to the underscored field name.
source: { pointer: `/data/attributes/${error.name.replace('.', '_')}` },
}));
// Return a list of JSON API errors rooted under the `errors` key.
return {
@@ -0,0 +1,15 @@
// app/initializers/fragment-serializer.js
import FragmentAuthMethodAttributesAccountClaimMapSerializer from '../serializers/fragment-auth-method-attributes-account-claim-map';
export function initialize(application) {
application.register(
'serializer:fragment-auth-method-attributes-account-claim-map',
FragmentAuthMethodAttributesAccountClaimMapSerializer
);
}
// Fragment serializers must be manually registered
export default {
name: 'fragment-auth-method-attributes-account-claim-map',
initialize: initialize,
};
@@ -0,0 +1,12 @@
// app/initializers/fragment-serializer.js
import FragmentStringSerializer from '../serializers/fragment-string';
export function initialize(application) {
application.register('serializer:fragment-string', FragmentStringSerializer);
}
// Fragment serializers must be manually registered
export default {
name: 'fragment-string',
initialize: initialize,
};
+5
View File
@@ -17,6 +17,11 @@ export default class AccountModel extends GeneratedAccountModel {
*/
@equal('type', 'password') isPassword;
/**
* @type {boolean}
*/
@equal('type', 'oidc') isOIDC;
/**
* Convenience for getting username in account.
* @type {string}
+98
View File
@@ -1,9 +1,61 @@
import GeneratedAuthMethodModel from '../generated/models/auth-method';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { equal } from '@ember/object/computed';
/**
* Enum options per auth method type and field.
*/
export const options = {
oidc: {
signing_algorithms: [
'RS256',
'RS384',
'RS512',
'ES256',
'ES384',
'ES512',
'PS256',
'PS384',
'PS512',
'EdDSA',
],
state: ['inactive', 'active-private', 'active-public'],
account_claim_maps: {
to: ['sub', 'name', 'email'],
},
},
};
export default class AuthMethodModel extends GeneratedAuthMethodModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_state;
@attr('string', { readOnly: true }) attributes_issuer;
@attr('string', { readOnly: true }) attributes_client_id;
@attr('string', { readOnly: true }) attributes_client_secret;
@attr('string', { readOnly: true }) attributes_max_age;
@attr('string', { readOnly: true }) attributes_api_url_prefix;
@attr('string', { readOnly: true })
attributes_disable_discovered_config_validation;
@attr('string', { readOnly: true }) attributes_dry_run;
@attr('string', { readOnly: true }) attributes_account_claim_maps;
@attr('string', { readOnly: true }) attributes_claims_scopes;
@attr('string', { readOnly: true }) attributes_signing_algorithms;
@attr('string', { readOnly: true }) attributes_allowed_audiences;
@attr('string', { readOnly: true }) attributes_idp_ca_certs;
// =attributes
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentAuthMethodAttributesModel}
*/
@fragment('fragment-auth-method-attributes', { defaultValue: {} }) attributes;
/**
* @type {boolean}
*/
@@ -22,4 +74,50 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel {
get isOIDC() {
return this.type === 'oidc';
}
/**
* @type {boolean}
*/
get isInactive() {
return this.attributes.state === 'inactive';
}
/**
* @type {boolean}
*/
get isPrivate() {
return this.attributes.state === 'active-private';
}
/**
* @type {boolean}
*/
get isPublic() {
return this.attributes.state === 'active-public';
}
// =methods
/**
* Change the active and visibility state of an OIDC auth method
* given its ID.
* For OIDC auth methods only.
* @param {[string]} state
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
changeState(state, options = { adapterOptions: {} }) {
const defaultAdapterOptions = {
method: 'change-state',
state,
};
return this.save({
...options,
adapterOptions: {
...defaultAdapterOptions,
...options.adapterOptions,
},
});
}
}
@@ -2,8 +2,16 @@ import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentAccountAttributesModel extends Fragment {
// =attributes (password)
@attr('string', {
for: 'password',
description: 'The account login name',
})
login_name;
// =attributes (OIDC)
@attr('string', { for: 'oidc' }) subject;
@attr('string', { for: 'oidc' }) issuer;
@attr('string', { for: 'oidc', readOnly: true }) email;
@attr('string', { for: 'oidc', readOnly: true }) full_name;
}
@@ -0,0 +1,9 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentAuthMethodAttributesAccountClaimMapModel extends Fragment {
// =attributes (OIDC)
@attr('string') from;
@attr('string') to;
}
@@ -0,0 +1,40 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
export default class FragmentAuthMethodAttributesModel extends Fragment {
// =attributes (OIDC)
@attr('string', { readOnly: true }) state;
@attr('string') issuer;
@attr('string') client_id;
@attr('string') client_secret;
@attr('string', { readOnly: true }) client_secret_hmac;
@attr('number') max_age;
@attr('string') api_url_prefix;
@attr('string', { readOnly: true }) callback_url;
@attr('boolean') disable_discovered_config_validation;
@attr('boolean') dry_run;
@fragmentArray('fragment-auth-method-attributes-account-claim-map', {
emptyArrayIfMissing: true,
})
account_claim_maps;
@fragmentArray('fragment-string', {
emptyArrayIfMissing: true,
})
claims_scopes;
@fragmentArray('fragment-string', {
emptyArrayIfMissing: true,
})
signing_algorithms;
@fragmentArray('fragment-string', {
emptyArrayIfMissing: true,
})
allowed_audiences;
@fragmentArray('fragment-string', { emptyArrayIfMissing: true }) idp_ca_certs;
}
+23
View File
@@ -20,13 +20,36 @@ export default class AccountSerializer extends ApplicationSerializer {
* @return {object}
*/
serialize(snapshot) {
switch (snapshot.record.type) {
case 'password':
return this.serializePassword(...arguments);
}
}
/**
* Password serialization omits `attributes` and handles password-related
* custom methods.
* @param {Snapshot} snapshot
* @return {object}
*/
serializePassword(snapshot) {
const password = snapshot?.adapterOptions?.password;
let serialized = super.serialize(...arguments);
// Only login_name is serialized into password attributes.
const { login_name } = serialized.attributes;
if (login_name) {
serialized.attributes = { login_name };
} else {
serialized.attributes = {};
}
// New record case
if (password && snapshot?.record?.isNew)
serialized.attributes.password = password;
// Set password custom method
if (snapshot?.adapterOptions?.method === 'set-password') {
serialized = this.serializeForSetPassword(snapshot, password);
}
// Change password custom method
if (snapshot?.adapterOptions?.method === 'change-password') {
const { currentPassword, newPassword } = snapshot?.adapterOptions;
serialized = this.serializeForChangePassword(
+70 -3
View File
@@ -1,6 +1,64 @@
import ApplicationSerializer from './application';
import { copy } from 'ember-copy';
export default class AuthMethodSerializer extends ApplicationSerializer {
// =methods
/**
* Delegates to a type-specific serialization, or default.
* @override
* @param {Snapshot} snapshot
* @return {object}
*/
serialize(snapshot) {
switch (snapshot.record.type) {
case 'oidc':
return this.serializeOIDC(...arguments);
default:
return this.serializeDefault(...arguments);
}
}
/**
* Default serialization omits `attributes`.
* @return {object}
*/
serializeDefault() {
let serialized = super.serialize(...arguments);
delete serialized.attributes;
return serialized;
}
/**
* If `adapterOptions.state` is set, the serialization should
* include **only state** and version. Normally, this is not serialized.
* @param {Snapshot} snapshot
* @return {object}
*/
serializeOIDC(snapshot) {
let serialized = super.serialize(...arguments);
const state = snapshot?.adapterOptions?.state;
if (state) {
serialized = this.serializeOIDCWithState(snapshot, state);
} else {
delete serialized.attributes.state;
}
return serialized;
}
/**
* Returns a payload containing only state and version.
* @param {Snapshot} snapshot
* @param {string} state
* @return {object}
*/
serializeOIDCWithState(snapshot, state) {
return {
version: snapshot.attr('version'),
attributes: { state },
};
}
/**
* Sorts the array in order of "is_primary" such that the primary method
* appears first. The "natural" order of auth methods places primary first.
@@ -23,10 +81,19 @@ export default class AuthMethodSerializer extends ApplicationSerializer {
*
* TODO: generalize this so that all zero values are reified.
* @override
* @return {object}
* @see FragmentScope
* @param {Model} typeClass
* @param {Object} hash
* @return {Object}
*/
normalize() {
const normalized = super.normalize(...arguments);
normalize(typeClass, hash, ...rest) {
let normalizedHash = copy(hash, true);
// switch (normalizedHash.type) {
// case 'oidc':
// normalizedHash = this.normalizeOIDC(normalizedHash);
// break;
// }
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
if (!normalized.data.attributes.is_primary) {
normalized.data.attributes.is_primary = false;
}
@@ -0,0 +1,26 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentAccountAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
*
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Client secret cannot be unset. If it's falsy, it must be removed.
if (key === 'client_secret' && !json[key]) delete json[key];
return value;
}
}
@@ -0,0 +1,30 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentHostSetSerializer extends JSONSerializer {
// =methods
/**
* Serializes an account claim to string.
* @param {Snapshot} snapshot
* @return {string}
*/
serialize(snapshot) {
const from = snapshot.attr('from');
const to = snapshot.attr('to');
return `${from}=${to}`;
}
/**
* Normalizes account claim strings into JSON API objects.
* @param {Model} typeClass
* @param {string} value
* @return {object}
*/
normalize(typeClass, value) {
const normalizedHash = {
from: value.split('=')[0],
to: value.split('=')[1],
};
return super.normalize(typeClass, normalizedHash);
}
}
@@ -0,0 +1,26 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentAuthMethodAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
*
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Client secret cannot be unset. If it's falsy, it must be removed.
if (key === 'client_secret' && !json[key]) delete json[key];
return value;
}
}
@@ -0,0 +1,24 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentStringSerializer extends JSONSerializer {
// =methods
/**
* Serializes a to string.
* @param {Snapshot} snapshot
* @return {string}
*/
serialize(snapshot) {
return snapshot.attr('value');
}
/**
* Normalizes from strings.
* @param {Model} typeClass
* @param {string} value
* @return {object}
*/
normalize(typeClass, value) {
return super.normalize(typeClass, { value });
}
}
-17
View File
@@ -37,21 +37,4 @@ export default class HostSetSerializer extends ApplicationSerializer {
host_ids: hostIDs,
};
}
/**
* Converts string arrays to object arrays for use with `fragment-string`.
* E.g. `"foo"` -> `{value: "foo"}`
* @override
* @param {Model} modelClass
* @param {Object} resourceHash
* @return {Object}
*/
normalize(modelClass, resourceHash) {
// TODO: can fragment string normalization be handled generically?
// TODO: host_ids might actually be attributes.host_ids
if (resourceHash.host_ids) {
resourceHash.host_ids = resourceHash.host_ids.map((value) => ({ value }));
}
return super.normalize(modelClass, resourceHash);
}
}
+3 -2
View File
@@ -76,8 +76,9 @@ export default class StorageService extends Service {
const inMemory = get(config, 'storage.memory');
// Get the global context object (window) via service lookup,
// because just reaching for a global is icky
const globalContext = getOwner(this).lookup('service:-document')
.documentElement.parentNode.defaultView;
const globalContext =
getOwner(this).lookup('service:-document').documentElement.parentNode
.defaultView;
// If memory storage enabled, return the memory storage instance,
// otherwise return localStorage.
return inMemory ? this.#memoryStorage : globalContext.localStorage;
@@ -0,0 +1,4 @@
export {
default,
initialize,
} from 'api/initializers/fragment-auth-method-attributes-account-claim-map-serializer';
@@ -0,0 +1 @@
export { default, initialize } from 'api/initializers/fragment-string';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-auth-method-attributes-account-claim-map';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-auth-method-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-account-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-auth-method-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-string';
+2 -1
View File
@@ -106,8 +106,9 @@ export default function () {
}
);
// Authenticate/deauthenticate routes
// Authenticate + auth methods custom routes
this.post('/auth-methods/:id_method', authHandler);
// Deauthenticate route
this.post('/scopes/:id_method', deauthHandler);
// IAM : Users
@@ -11,4 +11,29 @@ export default Factory.extend({
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => datatype.boolean(),
attributes: function () {
let attrs;
switch (this.type) {
case 'oidc':
attrs = {
api_url_prefix: 'http://127.0.0.1:9200',
callback_url:
'http://127.0.0.1:9200/v1/auth-methods/oidc:authenticate:callback',
client_id: 'YTwdJBB55LpKnuXb9n65',
client_secret_hmac: 'GY7blb4ynM5TEPNi_qnphJ9aDWhW8FpmN3D-eP_L7Zk',
issuer: 'http://127.0.0.1:57070',
max_age: 1800,
disable_discovered_config_validation: false,
state: 'active-public',
account_claim_maps: ['oid=sub', 'full_name=name'],
claims_scopes: ['profile', 'email'],
signing_algorithms: ['RS256', 'RS384'],
allowed_audiences: ['www.alice.com', 'www.alice.com/admin'],
idp_ca_certs: ['certificate-1234', 'certificate-5678'],
};
break;
}
return attrs;
},
});
+26 -6
View File
@@ -107,14 +107,34 @@ const commandHandlers = {
},
};
// Handles all custom methods on /auth-methods/:id route
export function authHandler({ scopes, authMethods }, request) {
const payload = JSON.parse(request.requestBody);
const [, id] = request.params.id_method.match(/(?<id>.[^:]*)/);
const { command } = payload;
const [, id, method] = request.params.id_method.match(
/(?<id>.[^:]*):(?<method>(.*))/
);
const authMethod = authMethods.find(id);
const scope = scopes.find(authMethod.scopeId);
const scopeAttrs = this.serialize(scopes.find(scope.id));
return commandHandlers[authMethod.type][command](payload, scopeAttrs);
if (method === 'authenticate') {
const payload = JSON.parse(request.requestBody);
const { command } = payload;
const scope = scopes.find(authMethod.scopeId);
const scopeAttrs = this.serialize(scopes.find(scope.id));
return commandHandlers[authMethod.type][command](payload, scopeAttrs);
}
// TODO: this handler doesn't really belong here, but we already route
// POST requests on existing auth methods to this route handler file under the
// assumption it would be for authentication. While authentication should
// still occur here, we should handle other custom methods in the
// mirage config and route here only for auth.
if (method === 'change-state') {
const attrs = this.normalizedRequestAttrs();
return authMethod.update({
attributes: {
state: attrs.attributes.state,
},
});
}
}
export function deauthHandler() {
+22
View File
@@ -60,6 +60,28 @@ export default function initializeMockIPC(server) {
session_id: newSession.id,
};
}
/**
* Check for browsers running on mac OS
*/
isMacOS() {
return Boolean(window.navigator.userAgent.match(/(macintosh)/i));
}
/**
* Check for browsers running on windows OS
*/
isWindowsOS() {
return Boolean(window.navigator.userAgent.match(/(windows)/i));
}
/**
* Do nothing when attempting to minimize, toggle fullscreen,
* and close a browser window
*/
minimizeWindow() {}
closeWindow() {}
toggleFullScreenWindow() {}
}
/**
+2 -2
View File
@@ -28,7 +28,7 @@
"ember-cli-htmlbars": "^5.3.1",
"ember-cli-mirage": "^1.1.8",
"ember-data": "^3.20.0",
"ember-data-model-fragments": "5.0.0-beta.0",
"ember-data-model-fragments": "5.0.0-beta.2",
"ember-fetch": "^8.0.1"
},
"devDependencies": {
@@ -38,7 +38,7 @@
"@glimmer/tracking": "^1.0.3",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^1.4.0",
"doctoc": "^2.0.0",
"ember-auto-import": "^1.10.1",
"ember-cli": "~3.24.0",
"ember-cli-dependency-checker": "^3.2.0",
@@ -0,0 +1,35 @@
import Application from '@ember/application';
import { initialize } from 'dummy/initializers/fragment-auth-method-attributes-account-claim-map-serializer';
import { module, test } from 'qunit';
import Resolver from 'ember-resolver';
import { run } from '@ember/runloop';
module(
'Unit | Initializer | fragment-auth-method-attributes-account-claim-map-serializer',
function (hooks) {
hooks.beforeEach(function () {
this.TestApplication = class TestApplication extends Application {};
this.TestApplication.initializer({
name: 'initializer under test',
initialize,
});
this.application = this.TestApplication.create({
autoboot: false,
Resolver,
});
});
hooks.afterEach(function () {
run(this.application, 'destroy');
});
// TODO: Replace this with your real tests.
test('it works', async function (assert) {
await this.application.boot();
assert.ok(true);
});
}
);
@@ -0,0 +1,32 @@
import Application from '@ember/application';
import { initialize } from 'dummy/initializers/fragment-string';
import { module, test } from 'qunit';
import Resolver from 'ember-resolver';
import { run } from '@ember/runloop';
module('Unit | Initializer | fragment-string', function (hooks) {
hooks.beforeEach(function () {
this.TestApplication = class TestApplication extends Application {};
this.TestApplication.initializer({
name: 'initializer under test',
initialize,
});
this.application = this.TestApplication.create({
autoboot: false,
Resolver,
});
});
hooks.afterEach(function () {
run(this.application, 'destroy');
});
// TODO: Replace this with your real tests.
test('it works', async function (assert) {
await this.application.boot();
assert.ok(true);
});
});
@@ -1,13 +1,49 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
module('Unit | Model | auth method', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('auth-method', {});
assert.ok(model);
});
test('it has a `changeState` method that targets a specific POST API endpoint and serialization', async function (assert) {
assert.expect(1);
this.server.post(
'/v1/auth-methods/123abc:change-state',
(schema, request) => {
const body = JSON.parse(request.requestBody);
assert.deepEqual(body, {
attributes: {
state: 'foobar',
},
version: 1,
});
return { id: '123abc' };
}
);
const store = this.owner.lookup('service:store');
store.push({
data: {
id: '123abc',
type: 'auth-method',
attributes: {
type: 'oidc',
state: 'foobar',
version: 1,
scope: {
scope_id: 'o_1',
type: 'scope',
},
},
},
});
const model = store.peekRecord('auth-method', '123abc');
await model.changeState('foobar');
});
});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Model | auth token', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('auth-token', {});
@@ -0,0 +1,18 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Model | fragment auth method attributes account claim map',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord(
'fragment-auth-method-attributes-account-claim-map',
{}
);
assert.ok(model);
});
}
);
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Model | fragment auth method attributes', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('fragment-auth-method-attributes', {});
assert.ok(model);
});
});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Model | fragment host set', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('fragment-host-set', {});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Model | fragment principal', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('fragment-principal', {});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Model | fragment string', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('fragment-string', {});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Model | host', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('host', {});
@@ -1,10 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
module('Unit | Serializer | auth method', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('auth-method');
@@ -12,13 +13,94 @@ module('Unit | Serializer | auth method', function (hooks) {
assert.ok(serializer);
});
test('it serializes records', function (assert) {
test('it serializes password records', function (assert) {
assert.expect(2);
let store = this.owner.lookup('service:store');
let record = store.createRecord('auth-method', {});
let record = store.createRecord('auth-method', { type: 'password' });
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
assert.notOk(
serializedRecord.attributes,
'Password should not have attributes'
);
});
test('it serializes OIDC records without state', function (assert) {
assert.expect(1);
let store = this.owner.lookup('service:store');
let record = store.createRecord('auth-method', {
type: 'oidc',
name: 'OIDC Auth Method',
attributes: {
state: 'foo',
account_claim_maps: [{ from: 'foo', to: 'bar' }],
claims_scopes: [{ value: 'profile' }, { value: 'email' }],
signing_algorithms: [{ value: 'RS256' }, { value: 'RS384' }],
allowed_audiences: [
{ value: 'www.alice.com' },
{ value: 'www.alice.com/admin' },
],
idp_ca_certs: [
{ value: 'certificate-1234' },
{ value: 'certificate-5678' },
],
api_url_prefix: 'protocol://host:port/foo',
client_id: 'id123',
client_secret: 'secret456',
disable_discovered_config_validation: true,
dry_run: true,
issuer: 'http://www.example.net',
max_age: 500,
},
});
let serializedRecord = record.serialize();
assert.deepEqual(serializedRecord, {
type: 'oidc',
name: 'OIDC Auth Method',
description: null,
attributes: {
account_claim_maps: ['foo=bar'],
claims_scopes: ['profile', 'email'],
signing_algorithms: ['RS256', 'RS384'],
allowed_audiences: ['www.alice.com', 'www.alice.com/admin'],
idp_ca_certs: ['certificate-1234', 'certificate-5678'],
api_url_prefix: 'protocol://host:port/foo',
client_id: 'id123',
client_secret: 'secret456',
disable_discovered_config_validation: true,
dry_run: true,
issuer: 'http://www.example.net',
max_age: 500,
},
});
});
test('it serializes OIDC records with only state and version when `adapterOptions.state` is passed', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('auth-method');
const record = store.createRecord('auth-method', {
type: 'oidc',
attributes: {
state: 'foo',
},
version: 1,
});
const snapshot = record._createSnapshot();
snapshot.adapterOptions = {
state: 'bar',
};
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
attributes: {
state: 'bar',
},
version: 1,
});
});
test('it sorts primary first in normalizeArrayResponse', function (assert) {
@@ -63,4 +145,22 @@ module('Unit | Serializer | auth method', function (hooks) {
'First normalized item is primary'
);
});
test('it normalizes OIDC records', async function (assert) {
const store = this.owner.lookup('service:store');
this.server.get('/v1/auth-methods/oidc123', () => ({
attributes: {
account_claim_maps: ['from=to', 'foo=bar'],
},
version: 1,
type: 'oidc',
id: 'oidc123',
}));
const record = await store.findRecord('auth-method', 'oidc123');
const { account_claim_maps } = record.attributes;
assert.equal(account_claim_maps.firstObject.from, 'from');
assert.equal(account_claim_maps.firstObject.to, 'to');
assert.equal(account_claim_maps.lastObject.from, 'foo');
assert.equal(account_claim_maps.lastObject.to, 'bar');
});
});
@@ -0,0 +1,23 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | fragment account attributes', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('fragment-account-attributes');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('fragment-account-attributes', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
});
@@ -0,0 +1,37 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | fragment auth method attributes', function (hooks) {
setupTest(hooks);
test('it serializes empty strings to null', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('user', {
name: '',
description: '',
version: null,
});
let serializedRecord = record.serialize();
assert.deepEqual(serializedRecord, {
name: null,
description: null,
});
});
test('it does not serialize client_secret if present but falsy', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('user', {
name: '',
description: '',
version: null,
client_secret: false,
});
let serializedRecord = record.serialize();
assert.deepEqual(serializedRecord, {
name: null,
description: null,
});
});
});
@@ -0,0 +1,49 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | fragment string', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('fragment-string');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('fragment-string', {
value: 'string value',
});
let serializedRecord = record.serialize();
assert.equal(serializedRecord, 'string value');
});
test('it normalizes records', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('fragment-string');
const modelClass = store.createRecord('fragment-string').constructor;
const payload = 'string value';
const normalized = serializer.normalizeSingleResponse(
store,
modelClass,
payload
);
assert.deepEqual(normalized, {
included: [],
data: {
id: null,
type: 'fragment-string',
attributes: {
value: 'string value',
},
relationships: {},
},
});
});
});
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Serializer | host', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('host');
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Serializer | session', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('session');
+3 -4
View File
@@ -72,10 +72,9 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
*/
async restore(data) {
if (!data) return reject();
return this.validateToken(
data.attributes.token,
data.attributes.id
).then(() => this.normalizeData(data));
return this.validateToken(data.attributes.token, data.attributes.id).then(
() => this.normalizeData(data)
);
}
/**
+1 -1
View File
@@ -33,7 +33,7 @@
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"core": "*",
"doctoc": "^1.4.0",
"doctoc": "^2.0.0",
"ember-auto-import": "^1.10.1",
"ember-cli": "~3.24.0",
"ember-cli-dependency-checker": "^3.2.0",
+19
View File
@@ -7,6 +7,25 @@ helpers.
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
- [Add core to an App](#add-core-to-an-app)
- [Installation](#installation)
- [I18n](#i18n)
- [Notifications](#notifications)
- [Confirmations](#confirmations)
- [App and Company Names](#app-and-company-names)
- [Documentation URLs](#documentation-urls)
- [Loading Helper](#loading-helper)
- [Scope Service](#scope-service)
- [Yarn Commands](#yarn-commands)
- [Linting](#linting)
- [Formatting](#formatting)
- [Running tests](#running-tests)
- [Running the dummy application](#running-the-dummy-application)
- [Contributing](#contributing)
- [Building ToC](#building-toc)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
- [Add core to an App](#add-core-to-an-app)
- [Installation](#installation)
- [Yarn Commands](#yarn-commands)
+2 -2
View File
@@ -76,8 +76,8 @@ export default class OIDCAuthenticator extends BaseOIDCAuthenticator {
* @param {string} token
*/
addTokenToAuthorization(token) {
const adapterPrototype = this.store.adapterFor('application').constructor
.prototype;
const adapterPrototype =
this.store.adapterFor('application').constructor.prototype;
const headers = adapterPrototype?.headers;
if (!headers) adapterPrototype.headers = {};
adapterPrototype.headers.Authorization = null;
+2 -2
View File
@@ -91,8 +91,8 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
* @param {string} token
*/
addTokenToAuthorization(token) {
const adapterPrototype = this.store.adapterFor('application').constructor
.prototype;
const adapterPrototype =
this.store.adapterFor('application').constructor.prototype;
const headers = adapterPrototype?.headers;
if (!headers) adapterPrototype.headers = {};
adapterPrototype.headers.Authorization = null;
+1 -1
View File
@@ -1 +1 @@
export { default, appName } from 'core/helpers/app-name';
export { default } from 'core/helpers/app-name';
+1 -1
View File
@@ -1 +1 @@
export { default, companyCopyright } from 'core/helpers/company-copyright';
export { default } from 'core/helpers/company-copyright';
+1 -1
View File
@@ -1 +1 @@
export { default, companyName } from 'core/helpers/company-name';
export { default } from 'core/helpers/company-name';
+1 -1
View File
@@ -1 +1 @@
export { default, docUrl } from 'core/helpers/doc-url';
export { default } from 'core/helpers/doc-url';
@@ -1,4 +1 @@
export {
default,
formatDateIsoHuman,
} from 'core/helpers/format-date-iso-human';
export { default } from 'core/helpers/format-date-iso-human';
+1 -1
View File
@@ -1 +1 @@
export { default, formatDateIso } from 'core/helpers/format-date-iso';
export { default } from 'core/helpers/format-date-iso';
+1 -1
View File
@@ -1 +1 @@
export { default, isLoading } from 'core/helpers/is-loading';
export { default } from 'core/helpers/is-loading';
+1 -1
View File
@@ -48,7 +48,7 @@
"@glimmer/tracking": "^1.0.3",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^1.4.0",
"doctoc": "^2.0.0",
"ember-auto-import": "^1.10.1",
"ember-cli": "~3.24.0",
"ember-cli-dependency-checker": "^3.2.0",
@@ -31,3 +31,4 @@ set-password: Set Password
change-password: Change Password
edit-form: Edit Form
change-origin: Change Origin URL
change-state: Change State
+2
View File
@@ -27,6 +27,8 @@ titles:
abandon-confirm: Discard unsaved changes?
toggle-color-theme: Toggle Color Theme
welcome-to-boundary: Welcome to Boundary
label: Label
value: Value
descriptions:
empty-set: There are no items to display yet. You may be able to add items or try back later.
origin-initialization: To get started, please enter your origin URL
+57
View File
@@ -51,3 +51,60 @@ origin:
description: The Boundary URL to which to connect (e.g. protocol://host:port)
proxy:
label: Proxy
issuer:
label: Issuer
help: The issuer URL. Boundary expects only the schema, host, and port and will strip off ".well-known/openid-configuration" if present. This will be used for configuration discovery as well as for validation of the "iss" claim.
client_id:
label: Client ID
help: An OAuth 2.0 Client Identifier valid at the Authorization Server.
client_secret:
label: Client Secret
help: The client secret is set-only and may not be unset.
client_secret_hmac:
label: Client Secret HMAC
max_age:
label: Maximum Age
help: The OIDC "max_age" parameter sent to the Authorization Server indicating a maximum acceptable time in seconds since the user's last authentication before requiring the user to reauthenticate. 0 indicates an immediate need to reauthenticate.
api_url_prefix:
label: API URL Prefix
help: The prefix that should be used for any URLs needed during the authentication flow. This includes the callback URL, the token retrieval URL, and the redirection URL used by the OIDC Authorization Server.
disable_discovered_config_validation:
label: Disable discovered config validation
help: >
If the Authorization Server's discovered configuration contains values that do not match the configuration set on this auth method, this can be set to force the local configuration to override the discovered values. This is useful in the case of slow-changing or incorrect values announced by the Authorization Server. NOTE: For safety, not providing this value will be treated the same as "false"; that is, every time the method is updated or the state is changed, this value must be set to "true" or it will be disabled.
idp_ca_certs:
label: Certificates
help: Optional PEM-encoded X.509 CA certificates that can be used as trust anchors when connecting to an OIDC provider.
certificate:
label: Certificate
allowed_audiences:
label: Allowed Audiences
help: Allowed audience claims for this auth method.
claim:
label: Claim
claims_scopes:
label: Claims Scopes
help: Optional claims scopes that will be requested during authentication.
claims_scope:
label: Claims Scope
account_claim_maps:
label: Account Claim Maps
help: >
Optional claim maps from custom claims to the standard claims of sub, name and email. These maps are represented as key=value where the key equals the from_claim and the value equals the to_claim. For example "oid=sub".
account_claim_map:
label: Account Claim Map
from_claim:
label: From Claim
to_claim:
label: To Claim
signing_algorithms:
label: Signing Algorithms
help: The signing algorithms allowed for the auth method.
algorithm:
label: Algorithm
subject:
label: Subject
email:
label: Email
full_name:
label: Full Name
@@ -10,6 +10,7 @@ account:
description: Set a password for the account.
types:
password: Password
oidc: OIDC
actions:
create: Create Account
delete: Delete Account
@@ -30,6 +31,13 @@ auth-method:
pending:
title: Authentication Pending
description: Complete authentication in the newly-opened window.
titles:
new-signing-algorithm: New Signing Algorithm
new-allowed-audience: New Allowed Audience
new-claims-scope: New Claims Scope
new-from-claim: New From Claim
new-to-claim: New To Claim
new-certificate: New Certificate
types:
password: Password
oidc: OIDC
@@ -8,3 +8,6 @@ connection:
success:
description: "Local proxy address ({protocol}): {address}:{port}"
primary: Primary
inactive: Inactive
active-private: Private
active-public: Public
+1
View File
@@ -13,6 +13,7 @@ Boundary UIs.
- [Linting](#linting)
- [Formatting](#formatting)
- [Building Components](#building-components)
- [Using Components](#using-components)
- [Running tests](#running-tests)
- [Running the dummy application](#running-the-dummy-application)
- [Contributing](#contributing)
@@ -8,7 +8,7 @@
disabled={{@disabled}}
@type="checkbox"
@checked={{if @checked true false}}
aria-describedby="{{if @error (concat 'errors-' this.id)}}" />
aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" />
<label class="rose-form-checkbox-label" for={{this.id}}>
<span class="rose-form-checkbox-label-text">{{@label}}</span>
@@ -17,6 +17,12 @@
{{/if}}
</label>
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
{{yield (hash
errors=(component 'rose/form/errors' id=(concat 'errors-' this.id))
)}}
@@ -26,6 +26,7 @@ Renders a form input checkbox.
@name={{name}}
@label={{label}}
@description={{description}}
@helperText={{helperText}}
@checked={{checked}}
@error={{error}}
@disabled={{disabled}}
@@ -33,7 +34,8 @@ Renders a form input checkbox.
`,
context: {
label: text('text', 'Checkbox 🛎'),
description: text('description', 'This is an optional description paragraph.'),
description: text('description', 'Short description in label'),
helperText: text('helperText', 'This is an optional help text.'),
name: text('name', 'checkbox-1'),
checked: boolean('checked', false),
error: boolean('error', false),
@@ -30,5 +30,6 @@
cancel=this.handleCancel
enableEdit=this.enableEdit
)
isEditable=this.isEditable
)}}
</form>
@@ -1,16 +1,13 @@
<div class="rose-form-select">
<Rose::Form::Label @for={{this.id}} @error={{@error}}>
{{@label}}
</Rose::Form::Label>
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
{{#if @contextual}}
{{yield (hash
label=(component 'rose/form/label'
for=this.id
error=@error
)
helperText=(component 'rose/form/helper-text'
id=(concat 'helper-text-' this.id)
error=@error
)
field=(component 'rose/form/select/select'
id=this.id
name=@name
@@ -20,7 +17,35 @@
error=@error
aria-describedby=(concat (if @helperText (concat 'helper-text-' this.id)) (if @error (concat ' errors-' this.id)))
)
errors=(component 'rose/form/errors' id=(concat 'errors-' this.id))
errors=(component 'rose/form/errors'
id=(concat 'errors-' this.id)
)
)}}
{{else}}
<div class="rose-form-select">
</div>
<Rose::Form::Label @for={{this.id}} @error={{@error}}>
{{@label}}
</Rose::Form::Label>
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
{{yield (hash
field=(component 'rose/form/select/select'
id=this.id
name=@name
value=@value
onChange=@onChange
disabled=@disabled
error=@error
aria-describedby=(concat (if @helperText (concat 'helper-text-' this.id)) (if @error (concat ' errors-' this.id)))
)
errors=(component 'rose/form/errors' id=(concat 'errors-' this.id))
)}}
</div>
{{/if}}
@@ -112,3 +112,28 @@ Renders a form select element.
}
}}</Story>
</Preview>
<Preview>
<Story name="Fully Contextual Select">{{
template: hbs`
<Rose::Form::Select @value="value-3" @error={{true}} @contextual={{true}} as |field|>
<hr />
<field.label>Label</field.label>
<hr />
<field.helperText>Help</field.helperText>
<hr />
<field.field as |select|>
<select.option @value="value-1">Value 1</select.option>
<select.option @value="value-2">Value 2</select.option>
<select.option @value="value-3">Value 3</select.option>
</field.field>
<hr />
<field.errors as |errors|>
<errors.message>An error occurred.</errors.message>
</field.errors>
<hr />
</Rose::Form::Select>
`,
context: {},
}}</Story>
</Preview>
@@ -1,26 +1,50 @@
<div class="rose-form-input">
<Rose::Form::Label @for={{this.id}} @error={{@error}}>
{{@label}}
</Rose::Form::Label>
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
<Rose::Form::Textarea::Textarea
...attributes
@id={{this.id}}
@name={{@name}}
@value={{@value}}
@disabled={{@disabled}}
@error={{@error}}
@aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" />
{{#if @contextual}}
{{yield (hash
errors=(component 'rose/form/errors' id=(concat 'errors-' this.id))
label=(component 'rose/form/label'
for=this.id
error=@error
)
helperText=(component 'rose/form/helper-text'
id=(concat 'helper-text-' this.id)
error=@error
)
field=(component 'rose/form/textarea/textarea'
id=this.id
name=@name
value=@value
disabled=@disabled
error=@error
aria-describedby=(concat (if @helperText (concat 'helper-text-' this.id)) (if @error (concat ' errors-' this.id)))
)
errors=(component 'rose/form/errors'
id=(concat 'errors-' this.id)
)
)}}
{{else}}
<div class="rose-form-input">
</div>
<Rose::Form::Label @for={{this.id}} @error={{@error}}>
{{@label}}
</Rose::Form::Label>
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
<Rose::Form::Textarea::Textarea
...attributes
@id={{this.id}}
@name={{@name}}
@value={{@value}}
@disabled={{@disabled}}
@error={{@error}}
@aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" />
{{yield (hash
errors=(component 'rose/form/errors' id=(concat 'errors-' this.id))
)}}
</div>
{{/if}}
@@ -70,3 +70,24 @@ Renders a simple form textarea.
}
}}</Story>
</Preview>
<Preview>
<Story name="Fully Contextual Textarea">{{
template: hbs`
<Rose::Form::Textarea @value="Text" @error={{true}} @contextual={{true}} as |field|>
<hr />
<field.label>Label</field.label>
<hr />
<field.helperText>Help</field.helperText>
<hr />
<field.field />
<hr />
<field.errors as |errors|>
<errors.message>An error occurred.</errors.message>
</field.errors>
<hr />
</Rose::Form::Textarea>
`,
context: {},
}}</Story>
</Preview>
@@ -1,4 +1,4 @@
<table ...attributes class="rose-table">
<table ...attributes class="rose-table {{if @style (concat "rose-table-" @style)}}">
{{#if @caption}}
<caption>{{@caption}}</caption>
{{/if}}
@@ -1,27 +1,42 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { boolean, select } from '@storybook/addon-knobs';
<Meta title="Rose|Table" component="Table" />
# Table
A table component to display data in structure of rows and columns. It supports `header`, `body` and `footer` optional definitions. Each definition allows for a table `row` to be defined with either a `cell` or `headerCell` table element.
A table component to display data in structure of rows and columns. It supports `header`, `body` and `footer` optional definitions. Each definition allows for a table `row` to be defined with either a `cell` or `headerCell` table element. Rows can be visually hidden, and cells can be shrinked to its content with, and center/right cell alignment is supported.
```htmlbars
<Rose::Table as |table|>
Cell shrink should be applied to all the cells in a row. Otherwise the bigger cell with shrink applied will determinate the width of the row.
```html
<Rose::Table @style={{style}} as |table|>
<table.header as |header|>
<header.row as |row|>
<header.row @hidden={{hideHeaderRow}} as |row|>
<row.headerCell>Address</row.headerCell>
<row.headerCell>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
<row.headerCell @alignCenter={{true}}>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-1, host-set-2</row.cell>
<row.cell>optional name</row.cell>
<row.cell @shrink={{true}}>optional name</row.cell>
<row.cell @alignCenter={{true}}>description</row.cell>
</body.row>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-2</row.cell>
<row.cell @shrink={{true}} @alignCenter={{true}}>optional name</row.cell>
<row.cell @alignRight={{true}}>description</row.cell>
</body.row>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-1</row.cell>
<row.cell @shrink={{true}}>optional name</row.cell>
<row.cell>description</row.cell>
</body.row>
</table.body>
@@ -36,22 +51,34 @@ A table component to display data in structure of rows and columns. It supports
<Preview>
<Story name="Basic">{{
template: hbs`
<Rose::Table as |table|>
<Rose::Table @style={{style}} as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>Address</row.headerCell>
<row.headerCell>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
<header.row @hidden={{hideHeaderRow}} as |row|>
<row.headerCell @shrink={{cellShrink}} @alignCenter={{cellAlignmentCenter}} @alignRight={{cellAlignmentRight}}>Address</row.headerCell>
<row.headerCell @alignCenter={{cellAlignmentCenter}} @alignRight={{cellAlignmentRight}}>Host Set</row.headerCell>
<row.headerCell @shrink={{cellShrink}} @alignCenter={{cellAlignmentCenter}} @alignRight={{cellAlignmentRight}}>Name</row.headerCell>
<row.headerCell @alignCenter={{cellAlignmentCenter}} @alignRight={{cellAlignmentRight}}>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell @shrink={{cellShrink}}>1.1.1.1</row.cell>
<row.cell>host-set-1, host-set-2</row.cell>
<row.cell>optional name</row.cell>
<row.cell @shrink={{cellShrink}}>optional name</row.cell>
<row.cell>description</row.cell>
</body.row>
<body.row as |row|>
<row.cell @shrink={{cellShrink}} @alignCenter={{cellAlignmentCenter}}>1.1.1.1</row.cell>
<row.cell @alignCenter={{cellAlignmentCenter}}>host-set-2</row.cell>
<row.cell @shrink={{cellShrink}} @alignCenter={{cellAlignmentCenter}}>optional name</row.cell>
<row.cell @alignCenter={{cellAlignmentCenter}}>description</row.cell>
</body.row>
<body.row as |row|>
<row.cell @shrink={{cellShrink}} @alignRight={{cellAlignmentRight}}>1.1.1.1</row.cell>
<row.cell @alignRight={{cellAlignmentRight}}>host-set-1</row.cell>
<row.cell @shrink={{cellShrink}} @alignRight={{cellAlignmentRight}}>optional name</row.cell>
<row.cell @alignRight={{cellAlignmentRight}}>description</row.cell>
</body.row>
</table.body>
<table.footer as |footer|>
<footer.row as |row|>
@@ -60,6 +87,102 @@ A table component to display data in structure of rows and columns. It supports
</table.footer>
</Rose::Table>
`,
context: {}
context: {
hideHeaderRow: boolean('hideHeaderRow', false),
cellShrink: boolean('cellShrink', false),
style: select(
'style',
{
'default': '',
'condensed': 'condensed'
}
),
cellAlignmentCenter: boolean('cellAlignmentCenter', false),
cellAlignmentRight: boolean('cellAlignmentRight', false),
},
}}</Story>
</Preview>
<Preview>
<Story name="Basic Key/Value Table">{{
template: hbs`
<Rose::Table @style={{style}} as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell @shrink={{true}}>Key</row.headerCell>
<row.headerCell colspan="2">Value</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Input @contextual={{true}} as |input|>
<input.field />
</Rose::Form::Input>
</row.cell>
<row.cell @shrink={{true}} @alignRight={{true}}>
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Select
@name="Test select"
as |field|
>
<field.field as |select|>
<select.option>Choose an option</select.option>
<select.option @value="value-1">Value 1</select.option>
<select.option @value="value-2">Value 2</select.option>
<select.option @value="value-3">Value 3</select.option>
</field.field>
</Rose::Form::Select>
</row.cell>
<row.cell @shrink={{true}} @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Input @contextual={{true}} as |input|>
<input.field />
</Rose::Form::Input>
</row.cell>
<row.cell @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Textarea
@name="field"
readonly={{false}}
/>
</row.cell>
<row.cell @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
</table.body>
<table.footer @hidden={{true}} as |footer|>
<footer.row as |row|>
<row.cell colspan="3">Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
`,
context: {
style: select(
'style',
{
'condensed': 'condensed',
'default': ''
}
)
}
}}</Story>
</Preview>
@@ -0,0 +1 @@
{{yield}}
@@ -0,0 +1,28 @@
import Component from '@ember/component';
import layout from './index';
import { computed } from '@ember/object';
export default Component.extend({
layout,
tagName: 'td',
classNameBindings: ['cellTypeStyle', 'cellShrink', 'cellAlignmentStyle'],
cellTypeStyle: computed(function () {
return this.tagName.match('th')
? `rose-table-header-cell`
: 'rose-table-cell';
}),
cellShrink: computed(function () {
if (this.shrink) {
return 'rose-table-cell-shrink';
}
}),
cellAlignmentStyle: computed(function () {
if (this.alignRight) {
return 'align-right';
}
if (this.alignCenter) {
return 'align-center';
}
}),
});
@@ -1,12 +1,6 @@
<tr ...attributes class='rose-table-row'>
<tr ...attributes class='rose-table-row {{if @hidden (concat "rose-table-row-visually-hidden")}} {{if @style (concat "rose-table-row-" @style)}}'>
{{yield (hash
cell=(component 'rose/anonymous'
tagName='td'
class='rose-table-cell'
)
headerCell=(component 'rose/anonymous'
tagName='th'
class='rose-table-header-cell'
)
cell=(component 'rose/table/row/cell')
headerCell=(component 'rose/table/row/cell' tagName='th')
)}}
</tr>
@@ -1,5 +1,6 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-dropdown-button {
@include type.type(s);
@@ -11,7 +12,7 @@
padding: 0 sizing.rems(s);
text-decoration: none;
text-align: left;
$line-height: 36 / 14;
$line-height: math.div(36, 14);
line-height: $line-height;
--background-color: transparent;
@@ -1,12 +1,13 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
$checkmark: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231563ff' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'></path></svg>");
.rose-dropdown-item {
@include type.type(s);
min-width: (sizing.rems(m)) * 20;
$line-height: 36 / 14;
$line-height: math.div(36, 14);
line-height: $line-height;
--color: var(--stark);
@@ -1,5 +1,6 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-dropdown-link {
@include type.type(s);
@@ -11,7 +12,7 @@
padding: 0 sizing.rems(s);
text-decoration: none;
text-align: left;
$line-height: 36 / 14;
$line-height: math.div(36, 14);
line-height: $line-height;
--background-color: transparent;
@@ -1,5 +1,6 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-dropdown-section {
.rose-dropdown-section-title {
@@ -7,7 +8,7 @@
text-transform: uppercase;
padding: 0 sizing.rems(s);
margin: 0;
$line-height: 36 / 14;
$line-height: math.div(36, 14);
line-height: $line-height;
color: var(--ui-gray);
}
@@ -4,14 +4,17 @@
.rose-form-checkbox {
@include type.type(m);
position: relative;
margin-bottom: sizing.rems(xs);
color: var(--ui-gray);
margin-bottom: sizing.rems(m);
.rose-form-errors {
margin-top: sizing.rems(xs);
margin-bottom: sizing.rems(xs);
}
.rose-form-helper-text {
padding-left: sizing.rems(l);
}
.rose-form-checkbox-label {
// label styles
@include type.type(s, semibold);
@@ -23,6 +26,7 @@
}
.rose-form-checkbox-label-description {
margin-bottom: 0;
margin-top: sizing.rems(xs);
}
@@ -54,6 +58,10 @@
}
}
.rose-form-helper-text {
margin-top: sizing.rems(xs);
}
.rose-form-checkbox-field {
// mostly this just visually hides the native checkbox
position: absolute;
@@ -2,6 +2,8 @@
@use '../../utilities/type';
.rose-form-fieldset {
margin-bottom: sizing.rems(m);
.rose-form-fieldset-title {
@include type.type(l, bold);
}
@@ -1,11 +1,12 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-header-nav-link {
@include type.type(s, semibold);
// target line height is 16, which is not a standard line height
$lineHeight: 16 / 14;
$lineHeight: math.div(16, 14);
line-height: $lineHeight;
text-decoration: none;
@@ -1,6 +1,7 @@
@use '../../variables/sizing';
@use '../../variables/typography';
@use '../../utilities/type';
@use 'sass:math';
.rose-list-key-value {
margin-bottom: sizing.rems(m);
@@ -10,9 +11,9 @@
display: flex;
border-bottom: 1px solid var(--ui-gray-subtler-3);
@include type.type(s);
$lineHeight: 36 / 14;
$lineHeight: math.div(36, 14);
line-height: $lineHeight;
padding: (sizing.rems(s) / 2) 0;
padding: math.div(sizing.rems(s), 2) 0;
.rose-list-key-value-item-cell {
display: flex;
@@ -1,5 +1,6 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-nav-sidebar {
$threePX: sizing.rems(xxxs) + sizing.rems(xxxxs);
@@ -19,7 +20,7 @@
display: none;
color: var(--ui-gray);
text-transform: uppercase;
$lineHeight: 20 / 12;
$lineHeight: math.div(20, 12);
line-height: $lineHeight;
padding: 0 sizing.rems(m);
margin-bottom: $threePX;
@@ -30,7 +31,7 @@
@include type.type(s);
display: block;
color: var(--stark);
$lineHeight: 36 / 14;
$lineHeight: math.div(36, 14);
line-height: $lineHeight;
text-decoration: none;
padding: 0 sizing.rems(m);
@@ -1,5 +1,6 @@
@use '../../variables/sizing';
@use '../../utilities/type';
@use 'sass:math';
.rose-table {
@include type.type(s, normal);
@@ -10,9 +11,30 @@
text-align: left;
}
// This styles are add to stop the input to apply extra margin (default on forms)
.rose-form-input,
.rose-form-input-field,
.rose-form-select,
.rose-form-select-field {
margin: 0;
width: 100%;
}
// Fix margin issue with textarea elements inside tables
textarea {
display: block;
}
.rose-table-cell,
.rose-table-header-cell {
vertical-align: middle;
&.align-right {
text-align: right;
}
&.align-center {
text-align: center;
}
}
.rose-table-header-cell {
@@ -35,7 +57,27 @@
}
}
.rose-table-cell-shrink {
width: 1%;
white-space: nowrap;
}
[class*='rose-button'] {
margin-bottom: 0;
}
&.rose-table-condensed {
.rose-table-cell {
@include type.type(s);
$lineHeight: math.div(36, 14);
line-height: $lineHeight;
padding: math.div(sizing.rems(s), 2) sizing.rems(s);
}
}
.rose-table-row-visually-hidden {
position: absolute;
top: -9999rem;
left: -9999rem;
}
}
@@ -1,5 +1,6 @@
@use 'sass:map';
@use 'sizing';
@use 'sass:math';
/**
* SASS variables defining typography.
@@ -46,15 +47,15 @@ $font-sizes: (
// Line height expressed as ratio.
$line-heights: (
xl: 36 / 28,
l: 24 / 20,
m: 20 / 16,
s: 18 / 14,
xs: 16 / 12,
h1: 48 / 28,
h2: 36 / 20,
h3: 24 / 16,
button: 24 / 13,
xl: math.div(36, 28),
l: math.div(24, 20),
m: math.div(20, 16),
s: math.div(18, 14),
xs: math.div(16, 12),
h1: math.div(48, 28),
h2: math.div(36, 20),
h3: math.div(24, 16),
button: math.div(24, 13),
);
/**
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/layout/tabs';
-1
View File
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/tab';
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/table/row/cell';
+3 -3
View File
@@ -43,15 +43,15 @@
"@ember/test-helpers": "^2.1.4",
"@glimmer/component": "^1.0.3",
"@glimmer/tracking": "^1.0.3",
"@hashicorp/structure-icons": "^1.8.1",
"@hashicorp/structure-icons": "^1.9.0-0",
"@storybook/addon-actions": "^6.0.26",
"@storybook/addon-docs": "^6.0.26",
"@storybook/addon-knobs": "^6.0.26",
"@storybook/ember": "^6.0.26",
"@storybook/ember-cli-storybook": "^0.2.1",
"@storybook/ember-cli-storybook": "^0.4.0",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^1.4.0",
"doctoc": "^2.0.0",
"ember-auto-import": "^1.10.1",
"ember-cli": "~3.24.0",
"ember-cli-dependency-checker": "^3.2.0",
+191 -88
View File
@@ -1,93 +1,196 @@
<Rose::Header as |header|>
<header.brand @logo="logo" @text="Product Name" />
<h2>Condensed Table</h2>
<Rose::Table @style="condensed" as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell >Address</row.headerCell>
<row.headerCell>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-1, host-set-2</row.cell>
<row.cell>optional name</row.cell>
<row.cell>description</row.cell>
</body.row>
</table.body>
<table.footer as |footer|>
<footer.row as |row|>
<row.cell>Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
<header.nav as |nav|>
<nav.dropdown @text="org-name" @icon="user-organization" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about" @style="danger">Menu item - danger</dropdown.link>
<dropdown.separator />
<dropdown.button>Button menu item</dropdown.button>
</nav.dropdown>
<h2>Condensed Table with Shrink Cells</h2>
<Rose::Table @style="condensed" as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>Address</row.headerCell>
<row.headerCell @shrink={{true}}>Host Set</row.headerCell>
<row.headerCell @shrink={{true}}>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell @shrink={{true}}>host-set-1, host-set-2</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
</row.cell>
<row.cell>description</row.cell>
</body.row>
</table.body>
<table.footer @hidden={{true}} as |footer|>
<footer.row as |row|>
<row.cell>Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
<nav.link @route="index">Section</nav.link>
<nav.link @route="about">Section</nav.link>
</header.nav>
<h2>Condensed Table with Right and Center aligned cells</h2>
<Rose::Table @style="condensed" as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>Address</row.headerCell>
<row.headerCell @alignCenter={{true}}>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell @alignCenter={{true}}>host-set-1, host-set-2</row.cell>
<row.cell @alignRight={{true}}>optional name</row.cell>
<row.cell @alignCenter={{true}}>description</row.cell>
</body.row>
</table.body>
<table.footer as |footer|>
<footer.row as |row|>
<row.cell>Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
<header.utilities as |utilities|>
<utilities.dropdown
@text="user@example.net"
@iconOnly={{true}}
@icon="user-square-fill" as |dropdown|
>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about" @style="danger">Menu item - danger</dropdown.link>
<dropdown.button>Button - default</dropdown.button>
<dropdown.separator />
<dropdown.button @style="danger">Button - danger</dropdown.button>
</utilities.dropdown>
</header.utilities>
</Rose::Header>
<h2>Condensed Table Key/Value Lookalike (edit mode)</h2>
<Rose::Table @style="condensed" as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell @shrink={{true}}>Key</row.headerCell>
<row.headerCell colspan="2">Value</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Input @contextual={{true}} as |input|>
<input.field />
</Rose::Form::Input>
</row.cell>
<row.cell @shrink={{true}} @alignRight={{true}}>
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Select
@name="Test select"
as |field|
>
<field.field as |select|>
<select.option>Choose an option</select.option>
<select.option @value="value-1">Value 1</select.option>
<select.option @value="value-2">Value 2</select.option>
<select.option @value="value-3">Value 3</select.option>
</field.field>
</Rose::Form::Select>
</row.cell>
<row.cell @shrink={{true}} @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Input @contextual={{true}} as |input|>
<input.field />
</Rose::Form::Input>
</row.cell>
<row.cell @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
<body.row as |row|>
<row.cell>Key</row.cell>
<row.cell>
<Rose::Form::Textarea
@name="field"
readonly={{false}}
/>
</row.cell>
<row.cell @alignRight={{true}}>
<Rose::Button @style="secondary">Add</Rose::Button>
</row.cell>
</body.row>
</table.body>
<table.footer @hidden={{true}} as |footer|>
<footer.row as |row|>
<row.cell colspan="3">Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
<Rose::Dropdown @text="Dropdown" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about" @style="danger">Menu item - danger</dropdown.link>
<dropdown.separator></dropdown.separator>
<h2>Condensed Table With Visually Hidden Header & Footer</h2>
<Rose::Table @style="condensed" as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell >Address</row.headerCell>
<row.headerCell>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-1, host-set-2</row.cell>
<row.cell>optional name</row.cell>
<row.cell>description</row.cell>
</body.row>
</table.body>
<table.footer as |footer|>
<footer.row @hidden={{true}} as |row|>
<row.cell>Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
<dropdown.button>Button menu item</dropdown.button>
<dropdown.button @style="danger">Button - danger</dropdown.button>
<dropdown.separator />
<dropdown.section @title="Dropdown Section Title">
<dropdown.link @route="index">Menu item</dropdown.link>
</dropdown.section>
</Rose::Dropdown>
<Rose::Dropdown @showCaret={{false}} @icon="more-horizontal" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</Rose::Dropdown>
<Rose::Form as |form|>
<form.radioGroup tagName="ul" @name="color" @selectedValue="red" as |radioGroup|>
<Rose::Dropdown @text="Color" as |dropdown|>
<ul>
<li>
<dropdown.item>
<radioGroup.radio
@label="Red"
@value="red"
@icon="user-organization" />
</dropdown.item>
</li>
<li>
<dropdown.item>
<radioGroup.radio
@label="Green"
@value="green"
/>
</dropdown.item>
</li>
<li>
<dropdown.item>
<radioGroup.radio
@label="Blue"
@value="blue"
/>
</dropdown.item>
</li>
</ul>
</Rose::Dropdown>
</form.radioGroup>
</Rose::Form>
<Rose::Dialog::Cover>
<:header>
<h1>Authentication Pending</h1>
</:header>
<:body>
<p>Complete authentication in the newly-opened window.</p>
<p>Don't see the new window? <a href="#">Try again.</a></p>
<p><a href="#">Cancel</a></p>
</:body>
</Rose::Dialog::Cover>
<h2>Table</h2>
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>Address</row.headerCell>
<row.headerCell>Host Set</row.headerCell>
<row.headerCell>Name</row.headerCell>
<row.headerCell>Description</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.cell>1.1.1.1</row.cell>
<row.cell>host-set-1, host-set-2</row.cell>
<row.cell>optional name</row.cell>
<row.cell>description</row.cell>
</body.row>
</table.body>
<table.footer as |footer|>
<footer.row as |row|>
<row.cell>Footer</row.cell>
</footer.row>
</table.footer>
</Rose::Table>
@@ -9,9 +9,9 @@ module('Integration | Component | rose/form/checkbox', function (hooks) {
test('it renders', async function (assert) {
assert.expect(3);
await render(hbs`<Rose::Form::Checkbox @label="Label" />`);
assert.equal(await find('label').textContent.trim(), 'Label');
assert.ok(await find('input'));
assert.notOk(await find('.rose-form-checkbox-label-description'));
assert.equal(find('label').textContent.trim(), 'Label');
assert.ok(find('input'));
assert.notOk(find('.rose-form-checkbox-label-description'));
});
test('it renders optional description paragraph', async function (assert) {
@@ -20,14 +20,25 @@ module('Integration | Component | rose/form/checkbox', function (hooks) {
hbs`<Rose::Form::Checkbox @label="Label" @description="Hello world" />`
);
assert.equal(
await find('.rose-form-checkbox-label-text').textContent.trim(),
find('.rose-form-checkbox-label-text').textContent.trim(),
'Label'
);
assert.equal(
await find('.rose-form-checkbox-label-description').textContent.trim(),
find('.rose-form-checkbox-label-description').textContent.trim(),
'Hello world'
);
assert.ok(find('input'));
});
test('it renders optional helper text', async function (assert) {
assert.expect(1);
await render(
hbs`<Rose::Form::Checkbox @label="Label" @helperText="Hello world" />`
);
assert.equal(
find('.rose-form-helper-text').textContent.trim(),
'Hello world'
);
assert.ok(await find('input'));
});
test('it displays optional errors', async function (assert) {
@@ -49,26 +60,26 @@ module('Integration | Component | rose/form/checkbox', function (hooks) {
test('it is not checked by default', async function (assert) {
await render(hbs`<Rose::Form::Checkbox />`);
assert.equal(await find('input').checked, false);
assert.equal(find('input').checked, false);
});
test('it is not disabled by default', async function (assert) {
await render(hbs`<Rose::Form::Checkbox />`);
assert.equal(await find('input').disabled, false);
assert.equal(find('input').disabled, false);
});
test('it is checked when @checked={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Checkbox @checked={{true}} />`);
assert.equal(await find('input').checked, true);
assert.equal(find('input').checked, true);
});
test('it marks error when @error={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Checkbox @error={{true}} />`);
assert.ok(await find('.error'));
assert.ok(find('.error'));
});
test('it is disabled when @disabled={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Checkbox @disabled={{true}} />`);
assert.equal(await find('input').disabled, true);
assert.equal(find('input').disabled, true);
});
});
@@ -144,4 +144,28 @@ module('Integration | Component | rose/form/select', function (hooks) {
`);
await fillIn('[name="my-select"]', 'value-1');
});
test('it supports a fully contextual usage', async function (assert) {
assert.expect(5);
await render(hbs`
<Rose::Form::Select @value="value-1" @error={{true}} @contextual={{true}} as |field|>
<field.label>Label</field.label>
<field.helperText>Help</field.helperText>
<field.field as |select|>
<select.option @value="value-1">value-1</select.option>
</field.field>
<field.errors as |errors|>
<errors.message>An error occurred.</errors.message>
</field.errors>
</Rose::Form::Select>
`);
assert.notOk(
find('.rose-form-input'),
'The form field wrapper element is not present in contextual mode'
);
assert.ok(find('.rose-form-label.error'));
assert.ok(find('.rose-form-helper-text.error'));
assert.ok(find('.rose-form-select-field.error'));
assert.ok(find('.rose-form-error-message'));
});
});
@@ -57,4 +57,26 @@ module('Integration | Component | rose/form/textarea', function (hooks) {
await render(hbs`<Rose::Form::Textarea @error={{true}} />`);
assert.ok(await find('.error'));
});
test('it supports a fully contextual usage', async function (assert) {
assert.expect(5);
await render(hbs`
<Rose::Form::Textarea @value="Text" @error={{true}} @contextual={{true}} as |field|>
<field.label>Label</field.label>
<field.helperText>Help</field.helperText>
<field.field />
<field.errors as |errors|>
<errors.message>An error occurred.</errors.message>
</field.errors>
</Rose::Form::Textarea>
`);
assert.notOk(
find('.rose-form-input'),
'The form field wrapper element is not present in contextual mode'
);
assert.ok(find('.rose-form-label.error'));
assert.ok(find('.rose-form-helper-text.error'));
assert.ok(find('.rose-form-input-field.error'));
assert.ok(find('.rose-form-error-message'));
});
});
@@ -43,4 +43,9 @@ module('Integration | Component | rose/table', function (hooks) {
</Rose::Table>`);
assert.ok(find('tfoot'));
});
test('it supports a style class', async function (assert) {
await render(hbs`<Rose::Table @style="condensed" />`);
assert.ok(find('.rose-table-condensed'));
});
});
@@ -7,17 +7,20 @@ module('Integration | Component | rose/table/row', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row />`);
assert.ok(find('tr'));
assert.ok(find('.rose-table-row'));
});
test('it renders with attributes', async function (assert) {
assert.expect(1);
await render(hbs`<Rose::Table::Row id="row"/>`);
assert.ok(find('#row'));
});
test('it renders a cell', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row as |row|>
<row.cell />
</Rose::Table::Row>`);
@@ -26,10 +29,17 @@ module('Integration | Component | rose/table/row', function (hooks) {
});
test('it renders a header cell', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row as |row|>
<row.headerCell />
</Rose::Table::Row>`);
assert.ok(find('th'));
assert.ok(find('.rose-table-header-cell'));
});
test('it can be hidden', async function (assert) {
assert.expect(1);
await render(hbs`<Rose::Table::Row @hidden=true />`);
assert.ok(find('.rose-table-row-visually-hidden'));
});
});
@@ -0,0 +1,76 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/table/row/cell', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row::Cell>
content
</Rose::Table::Row::Cell>`);
assert.ok(find('td'));
assert.equal(find('.rose-table-cell').textContent.trim(), 'content');
});
test('it renders with attributes', async function (assert) {
assert.expect(1);
await render(hbs`<Rose::Table::Row::Cell id="cell"/>`);
assert.ok(find('#cell'));
});
test('it renders as header cell', async function (assert) {
assert.expect(4);
await render(hbs`<Rose::Table::Row::Cell @tagName='th'/>`);
assert.ok(find('th'));
assert.ok(find('.rose-table-header-cell'));
assert.notOk(find('td'));
assert.notOk(find('.rose-table-cell'));
});
test('it adds shrink style class', async function (assert) {
assert.expect(1);
await render(hbs`<Rose::Table::Row::Cell @shrink={{true}} />`);
assert.ok(find('.rose-table-cell-shrink'));
});
test('it does not add expand style class in default cells', async function (assert) {
assert.expect(1);
await render(hbs`<Rose::Table::Row::Cell />`);
assert.notOk(find('.rose-table-cell-shrink'));
});
test('it renders with align center in a header cell', async function (assert) {
assert.expect(2);
await render(
hbs`<Rose::Table::Row::Cell @tagName='th' @alignCenter=true />`
);
assert.ok(find('.rose-table-header-cell'));
assert.ok(find('.align-center'));
});
test('it renders with align center in a body', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row::Cell @alignCenter=true />`);
assert.ok(find('.rose-table-cell'));
assert.ok(find('.align-center'));
});
test('it renders with align right in a header cell', async function (assert) {
assert.expect(2);
await render(
hbs`<Rose::Table::Row::Cell @tagName='th' @alignRight=true />`
);
assert.ok(find('.rose-table-header-cell'));
assert.ok(find('.align-right'));
});
test('it renders with align right', async function (assert) {
assert.expect(2);
await render(hbs`<Rose::Table::Row::Cell @alignRight=true />`);
assert.ok(find('.rose-table-cell'));
assert.ok(find('.align-right'));
});
});
Regular → Executable
+11 -2
View File
@@ -6,24 +6,33 @@ version: '3.8'
services:
node: &node
image: docker.mirror.hashicorp.services/node:12
image: docker.mirror.hashicorp.services/node:14
volumes:
- './:/src'
- 'icu_node_modules:/src/node_modules'
- 'icu_addons_api_node_modules:/src/addons/api/node_modules'
- 'icu_addons_auth_node_modules:/src/addons/auth/node_modules'
- 'icu_addons_core_node_modules:/src/addons/core/node_modules'
- 'icu_addons_rose_node_modules:/src/addons/rose/node_modules'
- 'icu_ui_admin_node_modules:/src/ui/admin/node_modules'
- 'icu_ui_desktop_node_modules:/src/ui/desktop/node_modules'
working_dir: /src
build:
build-admin:
<<: *node
entrypoint: bash
command: '-c "yarn install && yarn build:ui:admin && chmod -R 777 ."'
build-desktop-production:
<<: *node
entrypoint: bash
command: '-c "yarn install --frozen-lockfile --non-interactive && yarn build:ui:desktop:production && chmod -R 777 ."'
volumes:
icu_node_modules:
icu_addons_api_node_modules:
icu_addons_auth_node_modules:
icu_addons_core_node_modules:
icu_addons_rose_node_modules:
icu_ui_admin_node_modules:
icu_ui_desktop_node_modules:
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff. Show more