Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7413afb11d | ||
|
|
e4ccf21ed8 | ||
|
|
3a96aadb99 | ||
|
|
4f044db204 | ||
|
|
d202c550cb | ||
|
|
173fa8a8e6 | ||
|
|
a2e6a6ba44 | ||
|
|
bfa3d8fd3a | ||
|
|
233cc2073b | ||
|
|
ed513ebd14 | ||
|
|
6ceddce774 | ||
|
|
84fe290aeb | ||
|
|
0e38cfe54a | ||
|
|
7d70493a66 | ||
|
|
d61867ab10 | ||
|
|
e8e83d979b | ||
|
|
559764a5c1 | ||
|
|
bb494e83f8 | ||
|
|
d5a183abbf | ||
|
|
7f32b871b9 | ||
|
|
0a5eb3ae98 | ||
|
|
7a2ebdd3a1 | ||
|
|
3b6e968bf8 | ||
|
|
56487cdfad | ||
|
|
15b0baba34 | ||
|
|
96795e450e | ||
|
|
43cc25b0a5 | ||
|
|
f9ac2a93e5 | ||
|
|
378fcc53c2 | ||
|
|
a73c57848d | ||
|
|
86ebba9394 | ||
|
|
0ef4b2244b | ||
|
|
3b01e84f1e | ||
|
|
000c22bd08 | ||
|
|
7e2493dd82 | ||
|
|
aea193c78d | ||
|
|
04287e72c2 | ||
|
|
a535991174 | ||
|
|
9c642b5065 | ||
|
|
4bcf061234 | ||
|
|
09d5d1077d | ||
|
|
1a3dc72d57 | ||
|
|
25c0125cd8 | ||
|
|
4daa09ca7b | ||
|
|
14f9635af4 | ||
|
|
4ba3af3272 | ||
|
|
db4964c985 | ||
|
|
1f328e54ee | ||
|
|
295d236b35 | ||
|
|
a83e6af6de | ||
|
|
d5959f4a94 | ||
|
|
48cc8596dc | ||
|
|
33e6515749 | ||
|
|
28963a75cc | ||
|
|
6c1e9bc944 | ||
|
|
1e421225fa | ||
|
|
50ba2eb73a | ||
|
|
1f20e2c40c | ||
|
|
f0926cba87 | ||
|
|
3278d229e8 | ||
|
|
d92654776c | ||
|
|
34c8ad1d6f | ||
|
|
1208fea0a4 | ||
|
|
790cec2931 | ||
|
|
c7ee9cde17 | ||
|
|
1143fb02cc | ||
|
|
3fcff99024 | ||
|
|
2b5afa0e2c | ||
|
|
b8c4feb634 | ||
|
|
1b5e1a7736 | ||
|
|
27afafa077 | ||
|
|
73fef6b907 | ||
|
|
2e2ff60c8f | ||
|
|
4abf4e5550 | ||
|
|
8934e44558 | ||
|
|
c3561a1063 | ||
|
|
2f50e6a980 | ||
|
|
ff0971ca64 | ||
|
|
3f3ac19628 | ||
|
|
b946917ad8 | ||
|
|
bfdaacc2cd | ||
|
|
e47c3bd14e | ||
|
|
88d47688fa | ||
|
|
62b097170c | ||
|
|
32de6a6ed5 | ||
|
|
1e8c8a3ba0 | ||
|
|
0a57865270 | ||
|
|
b209ff7b2a | ||
|
|
a6a33f6b56 | ||
|
|
445d789344 | ||
|
|
939dd16409 | ||
|
|
b0a2348776 | ||
|
|
3bfb6fe5ee | ||
|
|
2fd646c2c9 | ||
|
|
5659df3336 | ||
|
|
9f66314a9e | ||
|
|
6ddf551d75 | ||
|
|
6272b1a164 | ||
|
|
43cd689da0 | ||
|
|
66601240fc | ||
|
|
11daac57c2 | ||
|
|
9b07969491 | ||
|
|
8e3362fb12 | ||
|
|
d9bd8fa193 | ||
|
|
3da7bfc7cc | ||
|
|
d82cbec7c3 | ||
|
|
3f9530a970 | ||
|
|
804a879182 | ||
|
|
170fc74b7f | ||
|
|
2ac8818cc6 | ||
|
|
99851fce59 | ||
|
|
93af7241f5 | ||
|
|
503a7b9eb8 | ||
|
|
ffcc8a25d6 | ||
|
|
eeb8610973 | ||
|
|
75bb34f738 | ||
|
|
242c3f6370 | ||
|
|
3d3d5ec4d2 | ||
|
|
f34af386c4 | ||
|
|
9b625a642f | ||
|
|
1ed0d24fa2 | ||
|
|
b12b303b97 | ||
|
|
5e591ce5bb | ||
|
|
23de0695d0 | ||
|
|
8d4cb1bfea | ||
|
|
d67f6dab2f | ||
|
|
8ee0e0b92d | ||
|
|
be9b68eeff | ||
|
|
1e20f7a149 | ||
|
|
b413e9d8dd | ||
|
|
fd3c9eb876 | ||
|
|
5a76f2791a | ||
|
|
ca2508635e | ||
|
|
baff01ad48 | ||
|
|
3f04c209f3 | ||
|
|
dbf2a8e43f | ||
|
|
db2a40b58f | ||
|
|
f14346d3a4 | ||
|
|
c2b4c0ad8b | ||
|
|
2bb9db6a67 | ||
|
|
cf9166fe4a | ||
|
|
50c7bfc659 | ||
|
|
ecda08164a | ||
|
|
2dfda037fe | ||
|
|
9979fe8464 | ||
|
|
49f6330a5d | ||
|
|
9049bb2087 | ||
|
|
82135a4b20 | ||
|
|
e737cb448c | ||
|
|
dd37718c62 | ||
|
|
e6f0166ff0 | ||
|
|
606a48ca49 | ||
|
|
d1e46cb9b1 | ||
|
|
11ceb8b5ca | ||
|
|
3854d32e92 | ||
|
|
bbb703c671 | ||
|
|
f507edae03 | ||
|
|
9231963ac1 | ||
|
|
9bfb136247 | ||
|
|
4af4ee7cc0 | ||
|
|
12a9f72c68 | ||
|
|
bb88672aff | ||
|
|
25e004976e | ||
|
|
60993e455a | ||
|
|
40ee317f47 | ||
|
|
530cd0298a | ||
|
|
374a13cd07 | ||
|
|
6f5da333e0 | ||
|
|
d121bcf204 | ||
|
|
2acd555966 | ||
|
|
820843c51d | ||
|
|
243f165644 | ||
|
|
5036c62740 | ||
|
|
3ffe70cf5d | ||
|
|
c5b0061428 | ||
|
|
fefae2b73d | ||
|
|
196d01aae2 | ||
|
|
96707ebe15 | ||
|
|
79f5db0441 | ||
|
|
d4fee0af34 | ||
|
|
a8c65e73f5 | ||
|
|
13df776c18 | ||
|
|
12588a17ab | ||
|
|
7b2c8b7d58 | ||
|
|
873965d7b7 | ||
|
|
f02f488edd | ||
|
|
a2ee17e918 | ||
|
|
bf8620d75c | ||
|
|
d0eb41e03b | ||
|
|
6e932f8054 | ||
|
|
76291896c3 | ||
|
|
c803adccc1 | ||
|
|
f7854b642c | ||
|
|
a7a6bdedf9 | ||
|
|
4bcd0e7d06 | ||
|
|
895a25218e | ||
|
|
8356e7d47a | ||
|
|
680ae9980b | ||
|
|
e106454bb5 | ||
|
|
aa227a0db8 | ||
|
|
8aca872c66 | ||
|
|
269c56bf83 | ||
|
|
a89b27c813 | ||
|
|
2804b4322b | ||
|
|
66d16d84d0 | ||
|
|
f420d01fb8 | ||
|
|
4bd80a716e | ||
|
|
8098beb450 | ||
|
|
a6672ecef4 | ||
|
|
79b6737aaa | ||
|
|
31afafff4e | ||
|
|
d4a4828b67 | ||
|
|
44fa206619 | ||
|
|
bc10b00874 | ||
|
|
bbd73b36d1 | ||
|
|
3a52b40214 | ||
|
|
c41855ccc4 | ||
|
|
c013b178d7 | ||
|
|
683fffaa0e | ||
|
|
31f67e568c | ||
|
|
9c15d02080 | ||
|
|
c14be070f7 | ||
|
|
7b1410d832 | ||
|
|
06cf112b61 | ||
|
|
95e3410540 | ||
|
|
77d7187116 | ||
|
|
28d18e389c | ||
|
|
d95ae373ab | ||
|
|
a784eeecc2 | ||
|
|
84962c9ed3 | ||
|
|
cfd2e13832 | ||
|
|
880f8f6b39 | ||
|
|
c5b1cc1498 | ||
|
|
343f85f8e7 | ||
|
|
654bc18422 | ||
|
|
81b59af3b4 | ||
|
|
e50d0ac00b | ||
|
|
9fe89c9b18 | ||
|
|
8c20d29806 | ||
|
|
10d93992d4 | ||
|
|
0552d1db5a | ||
|
|
07919409d9 | ||
|
|
d691c1fa11 | ||
|
|
b632df901b | ||
|
|
ecb67ea344 | ||
|
|
4c2e8f7a76 | ||
|
|
22c4581d4d | ||
|
|
34c668b4d9 | ||
|
|
9437edbf8a | ||
|
|
5890b1b2e8 | ||
|
|
5ea26dc7ea | ||
|
|
6afde1fb18 | ||
|
|
708a0836cc | ||
|
|
3fc51ee54a | ||
|
|
e85a75305f | ||
|
|
9f469aec38 | ||
|
|
dd7907da8c | ||
|
|
634c522eaf | ||
|
|
57a924703e | ||
|
|
89285dcfba | ||
|
|
05c14d7bbb | ||
|
|
8c4228016c | ||
|
|
8d00730653 | ||
|
|
38ed272fc2 | ||
|
|
d0a11d6054 | ||
|
|
8022234e84 | ||
|
|
b58bde0461 | ||
|
|
48c7538172 | ||
|
|
cb938d0e33 | ||
|
|
f2544684ec | ||
|
|
131ce44ecc | ||
|
|
b91f8c1661 | ||
|
|
3943a740bc | ||
|
|
d88a4ac103 | ||
|
|
afeaf3a671 | ||
|
|
1a968a7d24 | ||
|
|
ac5f7fa1c9 | ||
|
|
c1cd762da4 | ||
|
|
5f9230c458 | ||
|
|
2381b3372c | ||
|
|
4a07478f1e | ||
|
|
5c4ceb7f0f | ||
|
|
e940ec98d4 | ||
|
|
b7e0f42d47 | ||
|
|
1b300c676b | ||
|
|
a7cc1b9ba2 | ||
|
|
c99449d8e5 | ||
|
|
2038a463db | ||
|
|
4f0e2794ec | ||
|
|
89891a401a | ||
|
|
d572d5157d | ||
|
|
6ffb4a2350 | ||
|
|
1e1ea1c49a | ||
|
|
fb4238b035 | ||
|
|
3d6d7c99c2 | ||
|
|
5bb7d41903 | ||
|
|
47c453e979 | ||
|
|
e16bfdfb03 | ||
|
|
3138aecc37 | ||
|
|
b19583c08d | ||
|
|
3d8a70d676 | ||
|
|
7e6158b847 | ||
|
|
23e82b352e | ||
|
|
2ccd08a8d4 | ||
|
|
6159378a52 | ||
|
|
65039ce820 | ||
|
|
fde633331c | ||
|
|
597b945aca | ||
|
|
d23684c42f | ||
|
|
cb3bcf1b9e | ||
|
|
c8c5c2e7ef | ||
|
|
8886f14996 | ||
|
|
e8c7e81828 | ||
|
|
1f47ca8ea9 | ||
|
|
f61cad986f | ||
|
|
6256bfc3cf | ||
|
|
3133997e78 | ||
|
|
ed9324a0de | ||
|
|
ab668d74eb | ||
|
|
7876de8e2d | ||
|
|
06f00ac8f6 | ||
|
|
97ee639d62 | ||
|
|
314aa62828 | ||
|
|
bcf27ae048 | ||
|
|
ce9503fc5b | ||
|
|
dead74c825 | ||
|
|
24e37d3faa | ||
|
|
385ea34c98 | ||
|
|
e91612b313 | ||
|
|
8feeaa47dc | ||
|
|
ec9d953bad | ||
|
|
84051e4b72 | ||
|
|
6b93032a78 | ||
|
|
a909ba67df | ||
|
|
cf40b53fd0 | ||
|
|
3425dc89be | ||
|
|
8e1e2b6b36 | ||
|
|
4ace04de7a |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+15
@@ -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,
|
||||
};
|
||||
@@ -17,19 +17,19 @@ export default class AccountModel extends GeneratedAccountModel {
|
||||
*/
|
||||
@equal('type', 'password') isPassword;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@equal('type', 'oidc') isOIDC;
|
||||
|
||||
/**
|
||||
* Convenience for getting username in account.
|
||||
* @type {string}
|
||||
*/
|
||||
@computed('type', 'attributes.login_name')
|
||||
get username() {
|
||||
let username = '';
|
||||
switch (this.type) {
|
||||
case 'password':
|
||||
username = this.attributes.login_name;
|
||||
break;
|
||||
}
|
||||
return username;
|
||||
@computed('attributes.{email,full_name,login_name}')
|
||||
get accountName() {
|
||||
const { email, full_name, login_name } = this.attributes;
|
||||
return email || full_name || login_name;
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,13 +1,95 @@
|
||||
import GeneratedSessionModel from '../generated/models/session';
|
||||
import { computed } from '@ember/object';
|
||||
import { equal } from '@ember/object/computed';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { A } from '@ember/array';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class SessionCredential {
|
||||
// =classes
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static Library = class Library {
|
||||
id;
|
||||
name;
|
||||
description;
|
||||
type;
|
||||
|
||||
constructor(id, name, description, type) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static SecretItem = class SecretItem {
|
||||
key;
|
||||
value;
|
||||
|
||||
constructor(key, value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
// =attributes
|
||||
rawCredential;
|
||||
library;
|
||||
#payloadSecret;
|
||||
|
||||
/**
|
||||
* The incoming payload may contain a JSON object under the `decoded` key only
|
||||
* if it can be JSON-decoded. Some credential stores may not provide JSON,
|
||||
* however, in which case we may have only the raw base64-encoded secret
|
||||
* string. In the former case, we convert the key/value pairs of the JSON to
|
||||
* an array of `SecretItem` instances. In the latter case, we return an
|
||||
* array containing a single `SecretItem` instance with key `secret` and a
|
||||
* string value equal to the base64-decoded raw secret.
|
||||
* The credential secret as an array of key/value objects.
|
||||
* @type {SessionCredential.SecretItem[]}
|
||||
*/
|
||||
get secrets() {
|
||||
if (this.#payloadSecret?.decoded) {
|
||||
const secretJSON = this.#payloadSecret.decoded;
|
||||
return Object.keys(secretJSON).map(
|
||||
(key) => new SessionCredential.SecretItem(key, secretJSON[key])
|
||||
);
|
||||
} else {
|
||||
// decode from base64
|
||||
const decodedString = atob(this.#payloadSecret.raw);
|
||||
return [new SessionCredential.SecretItem('secret', decodedString)];
|
||||
}
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
constructor(cred) {
|
||||
const { id, name, description, type } = cred.credential_library;
|
||||
this.library = new SessionCredential.Library(id, name, description, type);
|
||||
this.#payloadSecret = cred.secret;
|
||||
this.rawCredential = cred;
|
||||
}
|
||||
}
|
||||
|
||||
export default class SessionModel extends GeneratedSessionModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', { readOnly: true }) proxy_address;
|
||||
@attr('number', { readOnly: true }) proxy_port;
|
||||
// These transient attributes are used for values that come from the CLI
|
||||
// `connect` command as triggered by Desktop. These attributes represent
|
||||
// transient read-only values.
|
||||
@tracked acknowledged;
|
||||
@tracked started_desktop_client;
|
||||
@tracked proxy_address;
|
||||
@tracked proxy_port;
|
||||
credentials = A();
|
||||
|
||||
@equal('status', 'active') isActive;
|
||||
@equal('status', 'pending') isPending;
|
||||
@@ -41,6 +123,13 @@ export default class SessionModel extends GeneratedSessionModel {
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addCredential(cred) {
|
||||
this.credentials.pushObject(new SessionCredential(cred));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the session via the `cancel` method.
|
||||
* See serializer and adapter for more information.
|
||||
|
||||
@@ -20,13 +20,55 @@ export default class AccountSerializer extends ApplicationSerializer {
|
||||
* @return {object}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
switch (snapshot.record.type) {
|
||||
case 'password':
|
||||
return this.serializePassword(...arguments);
|
||||
case 'oidc':
|
||||
return this.serializeOIDC(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OIDC serialization limits the `attributes` posted on create and omits
|
||||
* `attributes` entirely on update.
|
||||
*/
|
||||
serializeOIDC(snapshot) {
|
||||
const { isNew } = snapshot?.record;
|
||||
let serialized = super.serialize(...arguments);
|
||||
delete serialized.attributes;
|
||||
if (isNew) {
|
||||
serialized.attributes = {};
|
||||
const { issuer, subject } = snapshot.attr('attributes').record;
|
||||
if (issuer) serialized.attributes.issuer = issuer;
|
||||
if (subject) serialized.attributes.subject = subject;
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+4
@@ -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';
|
||||
@@ -13,7 +13,7 @@ export default function () {
|
||||
// make this `/api`, for example, if your API is namespaced
|
||||
this.namespace = config.api.namespace;
|
||||
// delay for each request, automatically set to 0 during testing
|
||||
this.timing = 350;
|
||||
this.timing = 1;
|
||||
|
||||
// Allow any configured API host to passthrough, which is useful in
|
||||
// development for testing against a locally running backend.
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
import factory from '../generated/factories/account';
|
||||
import { random, internet } from 'faker';
|
||||
|
||||
export default factory.extend();
|
||||
export default factory.extend({
|
||||
attributes() {
|
||||
switch (this.type) {
|
||||
case 'password':
|
||||
return { login_name: random.words() };
|
||||
case 'oidc':
|
||||
return {
|
||||
issuer: internet.ip(),
|
||||
subject: 'sub',
|
||||
email: internet.email(),
|
||||
full_name: random.words(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -14,9 +14,9 @@ export default factory.extend({
|
||||
*/
|
||||
withAccountsAndUsers: trait({
|
||||
afterCreate(authMethod, server) {
|
||||
const { scope } = authMethod;
|
||||
const { scope, type } = authMethod;
|
||||
server.createList('user', 5, { scope }).map((user) => {
|
||||
const { id } = server.create('account', { scope, authMethod });
|
||||
const { id } = server.create('account', { scope, type, authMethod });
|
||||
user.update({ accountIds: [id] });
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date, datatype } from 'faker';
|
||||
import { date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedAccountFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
type: 'password',
|
||||
name: () => random.words(),
|
||||
description: () => random.words(),
|
||||
// name: () => random.words(),
|
||||
// description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => datatype.number(),
|
||||
attributes: () => ({ login_name: random.words() }),
|
||||
});
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
@@ -53,13 +53,93 @@ export default function initializeMockIPC(server) {
|
||||
type,
|
||||
created_time: new Date().toISOString(),
|
||||
});
|
||||
const decoded = {
|
||||
key1: 'value 1',
|
||||
key2: true,
|
||||
key3: 'test',
|
||||
};
|
||||
const raw = btoa(JSON.stringify(decoded));
|
||||
return {
|
||||
address: window.location.hostname,
|
||||
port: datatype.number(),
|
||||
protocol: type,
|
||||
session_id: newSession.id,
|
||||
credentials: [
|
||||
{
|
||||
credential_library: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Library Name',
|
||||
description: 'Library Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_library: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Library Name',
|
||||
description: 'Library Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_library: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Library Name',
|
||||
description: 'Library Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_library: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Library Name',
|
||||
description: 'Library Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
+35
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -10,15 +10,14 @@ module('Unit | Model | account', function (hooks) {
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it has an username attribute for password account', function (assert) {
|
||||
test('it has an accountName attribute for password account', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('account', {
|
||||
name: 'name',
|
||||
type: 'password',
|
||||
});
|
||||
assert.notOk(model.username);
|
||||
model.attributes.login_name = 'username';
|
||||
assert.equal(model.username, 'username');
|
||||
assert.notOk(model.accountName);
|
||||
model.attributes.login_name = 'foobar';
|
||||
assert.equal(model.accountName, 'foobar');
|
||||
});
|
||||
});
|
||||
@@ -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', {});
|
||||
|
||||
+18
@@ -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', {});
|
||||
|
||||
@@ -4,7 +4,75 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | account', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes normally', function (assert) {
|
||||
test('it serializes OIDC-type with only issuer and subject in `attributes` on create', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
const record = store.createRecord('account', {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
full_name: 'full_name',
|
||||
email: 'email',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes OIDC-type without `attributes` on update', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'account',
|
||||
attributes: {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
full_name: 'full_name',
|
||||
email: 'email',
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('account', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.notOk(serializedRecord.attributes);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes password-type normally', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
@@ -19,7 +87,6 @@ module('Unit | Serializer | account', function (hooks) {
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'password',
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 @@
|
||||
export { default, appName } from 'core/helpers/app-name';
|
||||
export { default } from 'core/helpers/app-name';
|
||||
@@ -1 +1 @@
|
||||
export { default, companyCopyright } from 'core/helpers/company-copyright';
|
||||
export { default } from 'core/helpers/company-copyright';
|
||||
@@ -1 +1 @@
|
||||
export { default, companyName } from 'core/helpers/company-name';
|
||||
export { default } from 'core/helpers/company-name';
|
||||
@@ -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 @@
|
||||
export { default, formatDateIso } from 'core/helpers/format-date-iso';
|
||||
export { default } from 'core/helpers/format-date-iso';
|
||||
@@ -1 +1 @@
|
||||
export { default, isLoading } from 'core/helpers/is-loading';
|
||||
export { default } from 'core/helpers/is-loading';
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5832 1.67511L5.68872 7.36908L10.6547 13.3821L13.4227 11.0961L17.298 15.5239L13.8483 18.5432L15.3301 20.2363L18.7798 17.217L20.2352 18.8797L16.7874 21.8973L18.2693 23.5904L23.4101 19.091L15.1582 9.66276L17.5492 7.68814L12.5832 1.67511Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.06114 10.6154C3.9843 10.3626 2.37549 8.59326 2.37549 6.44818C2.37549 4.12959 4.25508 2.25 6.57366 2.25C7.31363 2.25 8.00889 2.44144 8.6126 2.7775L10.4051 1.26126C9.33393 0.468662 8.00853 0 6.57366 0C3.01243 0 0.125488 2.88695 0.125488 6.44818C0.125488 10.0094 3.01243 12.8964 6.57366 12.8964C7.02078 12.8964 7.45727 12.8508 7.87875 12.7642L6.06114 10.6154Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4577 1.67511L5.56323 7.36908L10.5292 13.3821L13.2972 11.0961L17.1724 15.5238L13.7226 18.5433L15.2044 20.2364L18.6543 17.2169L20.1097 18.8797L16.662 21.8973L18.1438 23.5904L23.2846 19.091L15.0328 9.66276L17.4237 7.68814L12.4577 1.67511Z" fill="black"/>
|
||||
<mask id="path-2-inside-1" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.766 4.35302L5.62911 4.255L5.53249 11.2901L9.12206 11.3394L9.20978 17.2227L4.62573 17.2911L4.6593 19.5409L9.24333 19.4725L9.27627 21.682L4.69506 21.7503L4.7286 24L11.5596 23.8982L11.3728 11.3703L12.6694 11.3882L12.766 4.35302Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.766 4.35302L5.62911 4.255L5.53249 11.2901L9.12206 11.3394L9.20978 17.2227L4.62573 17.2911L4.6593 19.5409L9.24333 19.4725L9.27627 21.682L4.69506 21.7503L4.7286 24L11.5596 23.8982L11.3728 11.3703L12.6694 11.3882L12.766 4.35302Z" fill="black"/>
|
||||
<path d="M5.62911 4.255L5.64971 2.75515L4.14985 2.73455L4.12925 4.23441L5.62911 4.255ZM12.766 4.35302L14.2659 4.37362L14.2865 2.87376L12.7866 2.85316L12.766 4.35302ZM5.53249 11.2901L4.03264 11.2695L4.01204 12.7694L5.5119 12.79L5.53249 11.2901ZM9.12206 11.3394L10.6219 11.3171L10.6002 9.85959L9.14265 9.83958L9.12206 11.3394ZM9.20978 17.2227L9.23216 18.7226L10.732 18.7002L10.7096 17.2004L9.20978 17.2227ZM4.62573 17.2911L4.60335 15.7913L3.10352 15.8137L3.1259 17.3135L4.62573 17.2911ZM4.6593 19.5409L3.15947 19.5633L3.18185 21.0631L4.68168 21.0407L4.6593 19.5409ZM9.24333 19.4725L10.7432 19.4501L10.7208 17.9503L9.22095 17.9727L9.24333 19.4725ZM9.27627 21.682L9.29863 23.1818L10.7985 23.1594L10.7761 21.6596L9.27627 21.682ZM4.69506 21.7503L4.6727 20.2504L3.17286 20.2728L3.19522 21.7726L4.69506 21.7503ZM4.7286 24L3.22877 24.0224L3.25113 25.5222L4.75096 25.4998L4.7286 24ZM11.5596 23.8982L11.5819 25.398L13.0818 25.3756L13.0594 23.8758L11.5596 23.8982ZM11.3728 11.3703L11.3934 9.87048L9.84992 9.84929L9.87293 11.3927L11.3728 11.3703ZM12.6694 11.3882L12.6488 12.888L14.1487 12.9086L14.1693 11.4087L12.6694 11.3882ZM5.60851 5.75486L12.7454 5.85288L12.7866 2.85316L5.64971 2.75515L5.60851 5.75486ZM7.03235 11.3107L7.12897 4.2756L4.12925 4.23441L4.03264 11.2695L7.03235 11.3107ZM9.14265 9.83958L5.55309 9.79028L5.5119 12.79L9.10146 12.8393L9.14265 9.83958ZM7.62222 11.3618L7.70995 17.2451L10.7096 17.2004L10.6219 11.3171L7.62222 11.3618ZM9.1874 15.7229L4.60335 15.7913L4.64811 18.791L9.23216 18.7226L9.1874 15.7229ZM3.1259 17.3135L3.15947 19.5633L6.15913 19.5185L6.12557 17.2688L3.1259 17.3135ZM4.68168 21.0407L9.2657 20.9723L9.22095 17.9727L4.63692 18.0411L4.68168 21.0407ZM7.74349 19.4949L7.77644 21.7043L10.7761 21.6596L10.7432 19.4501L7.74349 19.4949ZM9.25391 20.1821L4.6727 20.2504L4.71742 23.2501L9.29863 23.1818L9.25391 20.1821ZM3.19522 21.7726L3.22877 24.0224L6.22843 23.9777L6.19489 21.7279L3.19522 21.7726ZM4.75096 25.4998L11.5819 25.398L11.5372 22.3983L4.70624 22.5002L4.75096 25.4998ZM13.0594 23.8758L12.8726 11.348L9.87293 11.3927L10.0597 23.9205L13.0594 23.8758ZM12.69 9.88829L11.3934 9.87048L11.3522 12.8702L12.6488 12.888L12.69 9.88829ZM11.2662 4.33242L11.1696 11.3676L14.1693 11.4087L14.2659 4.37362L11.2662 4.33242Z" fill="black" mask="url(#path-2-inside-1)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2796 1.26121C9.20838 0.46864 7.88301 0 6.44818 0C2.88695 0 0 2.88695 0 6.44818C0 9.00798 1.4916 11.2194 3.65306 12.2607L3.68943 9.61275C2.80735 8.84314 2.25 7.71075 2.25 6.44818C2.25 4.12959 4.12959 2.25 6.44818 2.25C7.18811 2.25 7.88333 2.44143 8.48703 2.77746L10.2796 1.26121Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -31,3 +31,5 @@ set-password: Set Password
|
||||
change-password: Change Password
|
||||
edit-form: Edit Form
|
||||
change-origin: Change Origin URL
|
||||
change-state: Change State
|
||||
close: Close
|
||||
@@ -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
|
||||
|
||||
@@ -51,3 +51,67 @@ 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
|
||||
key:
|
||||
label: Key
|
||||
value:
|
||||
label: Value
|
||||
callback_url:
|
||||
label: Callback URL
|
||||
help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration.
|
||||
@@ -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
|
||||
@@ -139,6 +147,17 @@ session:
|
||||
description: No active sessions available to display yet.
|
||||
actions:
|
||||
connect: Connect
|
||||
credential:
|
||||
title: Credential
|
||||
title_plural: Credentials
|
||||
secret:
|
||||
label: Secret
|
||||
credential_library:
|
||||
title: Credential Library
|
||||
actions:
|
||||
raw-api:
|
||||
show: View raw API output
|
||||
hide: Hide raw API output
|
||||
org:
|
||||
title: Org
|
||||
title_plural: Orgs
|
||||
@@ -225,6 +244,12 @@ target:
|
||||
none:
|
||||
title: No Targets Available
|
||||
description: No Targets to display yet. Contact your Boundary admin if you don't see a Target you expect access to.
|
||||
connection-success:
|
||||
title: Successfully Connected
|
||||
connection-details:
|
||||
title: Target Connection Details
|
||||
proxy_url:
|
||||
title: Proxy URL
|
||||
actions:
|
||||
create: New Target
|
||||
delete: Delete Target
|
||||
@@ -242,3 +267,4 @@ target:
|
||||
add:
|
||||
title: Add Host Sets
|
||||
description: Select host sets to assign to this target.
|
||||
secret: ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
|
||||
@@ -4,7 +4,7 @@ success: Success
|
||||
warning: Warning
|
||||
error: Error
|
||||
copied: Copied
|
||||
connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
inactive: Inactive
|
||||
active-private: Private
|
||||
active-public: Public
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<details
|
||||
...attributes
|
||||
class="rose-dropdown {{if @dropdownRight "rose-dropdown-right"}}"
|
||||
class="rose-dropdown {{if @dropdownRight "rose-dropdown-right"}} {{if @style (concat "rose-dropdown-" @style)}}"
|
||||
{{on-click-outside this.close}}
|
||||
>
|
||||
|
||||
@@ -22,14 +22,28 @@
|
||||
|
||||
</summary>
|
||||
|
||||
<div class="rose-dropdown-content" {{on-click-inside this.close}}>
|
||||
{{yield (hash
|
||||
link=(component "rose/dropdown/link" style=this.style)
|
||||
button=(component "rose/dropdown/button" style=this.style)
|
||||
item=(component "rose/anonymous" class="rose-dropdown-item")
|
||||
separator=(component "rose/anonymous" tagName="hr" class="rose-dropdown-separator")
|
||||
section=(component "rose/dropdown/section" style=this.style)
|
||||
)}}
|
||||
</div>
|
||||
{{#if @ignoreClickInside}}
|
||||
<div class="rose-dropdown-content">
|
||||
{{yield (hash
|
||||
link=(component "rose/dropdown/link" style=this.style)
|
||||
button=(component "rose/dropdown/button" style=this.style)
|
||||
item=(component "rose/anonymous" class="rose-dropdown-item")
|
||||
separator=(component "rose/anonymous" tagName="hr" class="rose-dropdown-separator")
|
||||
section=(component "rose/dropdown/section" style=this.style)
|
||||
key-value=(component "rose/dropdown/key-value")
|
||||
)}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="rose-dropdown-content" {{on-click-inside this.close}}>
|
||||
{{yield (hash
|
||||
link=(component "rose/dropdown/link" style=this.style)
|
||||
button=(component "rose/dropdown/button" style=this.style)
|
||||
item=(component "rose/anonymous" class="rose-dropdown-item")
|
||||
separator=(component "rose/anonymous" tagName="hr" class="rose-dropdown-separator")
|
||||
section=(component "rose/dropdown/section" style=this.style)
|
||||
key-value=(component "rose/dropdown/key-value")
|
||||
)}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</details>
|
||||
@@ -63,6 +63,24 @@ A simple dropdown component with support for links and buttons.
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
<Preview>
|
||||
<Story name="Dropdown with Key Value Items" height="13rem">{{
|
||||
template: hbs`
|
||||
<Rose::Dropdown @text="View details" @showCaret={{showCaret}} @ignoreClickInside={{true}} as |dropdown|>
|
||||
<dropdown.key-value>
|
||||
<:key>Name</:key>
|
||||
<:value>Key/Value</:value>
|
||||
</dropdown.key-value>
|
||||
<dropdown.separator />
|
||||
<dropdown.key-value>
|
||||
<:key>Description</:key>
|
||||
<:value>Lorem ipsum dolor sit amet consectetur adipisicing elit. Assumenda illo ratione libero ducimus magnam, porro sed provident repellat mollitia a, repellendus placeat. Magni consequuntur cumque, debitis obcaecati dolores quasi natus?</:value>
|
||||
</dropdown.key-value>
|
||||
</Rose::Dropdown>
|
||||
`,
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
|
||||
```html
|
||||
<Rose::Dropdown @text="Choose One…" as |dropdown|>
|
||||
@@ -125,3 +143,14 @@ form field.
|
||||
</form.radioGroup>
|
||||
</Rose::Form>
|
||||
```
|
||||
|
||||
Closing dropdown after clicking inside it's content can be ignored using `ignoreClickInside`. This is useful when dropdown contains keyvalue items that users need to select/copy/interact with.
|
||||
|
||||
```html
|
||||
<Rose::Dropdown @text="View details" @showCaret={{true}} @ignoreClickInside={{true}} as |dropdown|>
|
||||
<dropdown.key-value>
|
||||
<:key>ID</:key>
|
||||
<:value>cl_nbv9375hLHs</:value>
|
||||
</dropdown.key-value>
|
||||
</Rose::Dropdown>
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
<dl class='rose-dropdown-key-value rose-dropdown-item'>
|
||||
<dt class='rose-dropdown-key'>
|
||||
{{yield to='key'}}
|
||||
</dt>
|
||||
|
||||
<dd class='rose-dropdown-value'>
|
||||
{{yield to='value'}}
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -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;
|
||||
|
||||
@@ -5,6 +5,7 @@ $xs: sizing.rems(xs); // 8
|
||||
$xxs: sizing.rems(xxs); // 4
|
||||
$chevron: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231b1f26' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z'></path></svg>");
|
||||
$chevronWhite: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%23ffffff' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z'></path></svg>");
|
||||
$chevronGrey: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%23626873' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z'></path></svg>");
|
||||
|
||||
.rose-dropdown {
|
||||
--background-image-light: #{$chevron};
|
||||
@@ -121,4 +122,26 @@ $chevronWhite: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%23f
|
||||
border: none;
|
||||
margin: $xxs 0;
|
||||
}
|
||||
|
||||
// Styles for condensed verion. Some are overrides
|
||||
&.rose-dropdown-condensed {
|
||||
--background-image: #{$chevronGrey};
|
||||
border: sizing.rems(xxxxs) solid var(--ui-border);
|
||||
border-radius: sizing.rems(xxs);
|
||||
|
||||
.rose-dropdown-trigger {
|
||||
color: var(--ui-gray-starker-1);
|
||||
padding: 0 sizing.rems(s); // To clear the inherit padding.
|
||||
|
||||
&.show-caret {
|
||||
padding-right: sizing.rems(l) + sizing.rems(xs); // 32
|
||||
background-position: right sizing.rems(xs) center;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-dropdown-content {
|
||||
border: none;
|
||||
border: sizing.rems(xxxxs) solid var(--ui-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
@import 'link';
|
||||
@import 'item';
|
||||
@import 'section';
|
||||
@import 'key-value';
|
||||
@@ -1,13 +1,20 @@
|
||||
@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: $line-height;
|
||||
$paddingY: math.div(
|
||||
sizing.rems(xl) - (sizing.rems(m) + sizing.rems(xxxs)),
|
||||
2
|
||||
);
|
||||
$paddingX: sizing.rems(s);
|
||||
padding: $paddingY $paddingX;
|
||||
//$line-height: math.div(36, 14);
|
||||
//line-height: $line-height;
|
||||
|
||||
--color: var(--stark);
|
||||
--background-color: transparent;
|
||||
@@ -15,26 +22,22 @@ $checkmark: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231563
|
||||
color: var(--color);
|
||||
background-color: var(--background-color);
|
||||
|
||||
&:hover {
|
||||
--background-color: var(--ui-gray-subtler-5);
|
||||
}
|
||||
|
||||
.rose-form-radio {
|
||||
color: currentColor;
|
||||
margin-bottom: 0;
|
||||
|
||||
.rose-form-radio-label {
|
||||
display: block;
|
||||
margin: -#{$paddingY} -#{$paddingX};
|
||||
padding-top: sizing.rems(xs) + sizing.rems(xxxxs);
|
||||
padding-bottom: sizing.rems(xs) + sizing.rems(xxxxs);
|
||||
padding-left: sizing.rems(s);
|
||||
padding-right: sizing.rems(xl);
|
||||
font-weight: normal;
|
||||
|
||||
// &::before,
|
||||
// &::after {
|
||||
// display: none;
|
||||
// }
|
||||
&:hover {
|
||||
background-color: var(--ui-gray-subtler-5);
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: sizing.rems(xxs) + sizing.rems(xxxs);
|
||||
@@ -56,11 +59,6 @@ $checkmark: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231563
|
||||
~ .rose-form-radio-label {
|
||||
color: var(--action);
|
||||
background-color: var(--action-subtler-1h);
|
||||
|
||||
// background-image: #{$checkmark};
|
||||
// background-repeat: no-repeat;
|
||||
// background-position: right sizing.rems(m) center;
|
||||
// background-size: sizing.rems(m);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
@use '../../variables/sizing';
|
||||
|
||||
.rose-dropdown-key-value {
|
||||
.rose-dropdown-key {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.rose-dropdown-value {
|
||||
color: var(--ui-gray);
|
||||
padding: sizing.rems(xxs) sizing.rems(s) 0 0;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Loaded 100 of 215 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user