Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca56c37fb0 | ||
|
|
5e2d9e9e2b | ||
|
|
d0ca5cc041 | ||
|
|
310539e2de | ||
|
|
9da86f8411 | ||
|
|
76e721bd91 | ||
|
|
a19aa2ff0c | ||
|
|
cdecf570e2 | ||
|
|
c04c8f4f0a | ||
|
|
8fb33b8ff5 | ||
|
|
e89f31a93b | ||
|
|
518bf19318 | ||
|
|
d35823fae6 | ||
|
|
0231dabbe6 | ||
|
|
263d2b8d95 | ||
|
|
98537f708c | ||
|
|
5596d55035 | ||
|
|
a8f1780731 | ||
|
|
8e6988c179 | ||
|
|
ea293d947f | ||
|
|
d23afb1c71 | ||
|
|
b8ec749d03 | ||
|
|
0c5c9387b5 | ||
|
|
bbbd5cea65 | ||
|
|
66888a4c95 | ||
|
|
ca35246a7b | ||
|
|
16fe712f4e | ||
|
|
296b5fbecd | ||
|
|
4240bbee56 | ||
|
|
c3a9ecb0ec | ||
|
|
e40ff1f63c | ||
|
|
87479b8e3c | ||
|
|
264f81a6f3 | ||
|
|
772c8b38c0 | ||
|
|
03aa4f32f1 | ||
|
|
0b145fac47 | ||
|
|
9bd7193833 | ||
|
|
f3a4017e5f | ||
|
|
43eca819bf | ||
|
|
9aa3f8f5ad | ||
|
|
8cceb4090f | ||
|
|
dc7ebec668 | ||
|
|
cffa419dd6 | ||
|
|
83a938fada | ||
|
|
61c80518de | ||
|
|
d1119efd11 | ||
|
|
a9cb6f35f9 | ||
|
|
6f85419675 | ||
|
|
0cb90353f0 | ||
|
|
cd9f05962c | ||
|
|
9d342cf517 | ||
|
|
88fc063ab9 | ||
|
|
d3e707d740 | ||
|
|
fc4af516a2 | ||
|
|
edb8836a2c | ||
|
|
b29f877e35 | ||
|
|
e299740951 | ||
|
|
03fbc19c2c | ||
|
|
e42647e6a2 | ||
|
|
a03a3811d4 | ||
|
|
d4ef4bc58c | ||
|
|
3d7166d059 | ||
|
|
025ae1bfe2 | ||
|
|
0bc4cd6e21 | ||
|
|
4a6555317a | ||
|
|
be7e3bb56e | ||
|
|
ed0c774714 | ||
|
|
2bdb008f65 | ||
|
|
8109d7789c | ||
|
|
3eb9f07fe0 | ||
|
|
a07cd9e1a1 | ||
|
|
bbbb1620f6 | ||
|
|
1d71e3bec8 | ||
|
|
63d302d716 | ||
|
|
341baa76fc | ||
|
|
cc6f0aa1b4 | ||
|
|
1fb98f8ed3 | ||
|
|
08543e9868 | ||
|
|
0fbcf8e16e | ||
|
|
7ff569779c | ||
|
|
8f2deadc49 | ||
|
|
89b7d0345a | ||
|
|
ad3b9ed684 | ||
|
|
a7a84d64b9 | ||
|
|
7fd89bb946 | ||
|
|
dbfc21ace3 | ||
|
|
7bd8d87ad2 | ||
|
|
f5b80f93d2 | ||
|
|
1e5d52410e | ||
|
|
0dedbfb411 | ||
|
|
992df6b34b | ||
|
|
af7e539f3d | ||
|
|
83f9ab5c61 | ||
|
|
a9c06638c7 | ||
|
|
0637d805d3 | ||
|
|
f089200dc0 | ||
|
|
2726538c41 | ||
|
|
b6291bd3c7 | ||
|
|
0e89ecabdc | ||
|
|
debf6fbe06 | ||
|
|
517ba08f55 | ||
|
|
7f13365336 | ||
|
|
8ebdd995e3 | ||
|
|
e73cd828a5 | ||
|
|
d2a42f7468 | ||
|
|
b1614dc65d | ||
|
|
9931b8d3ca | ||
|
|
b7e5477b3d | ||
|
|
027371567b | ||
|
|
95612186d8 | ||
|
|
f7dd5ebe55 | ||
|
|
a6be658aa5 | ||
|
|
123476a3a8 | ||
|
|
d44528fc2c | ||
|
|
b2c85b9805 | ||
|
|
03ce6be3ee | ||
|
|
a35c4e0ffd | ||
|
|
710bcccfd5 | ||
|
|
8b9d945efb | ||
|
|
aa2114062b | ||
|
|
90055ea142 | ||
|
|
e1aa0c6bcb | ||
|
|
af337626f5 | ||
|
|
dad2d456d0 | ||
|
|
9dac6fc446 | ||
|
|
00348e4c55 | ||
|
|
b0dd9bf803 | ||
|
|
2e23bbea3e | ||
|
|
7e7bad050f | ||
|
|
afadb1d83a | ||
|
|
52c9fb5570 | ||
|
|
2dd3e13cd3 | ||
|
|
74fa0876fb | ||
|
|
c531553c07 | ||
|
|
0ad6d027e6 | ||
|
|
2eb7084643 | ||
|
|
06d162ae89 | ||
|
|
c43ced0380 | ||
|
|
28a4a7304e | ||
|
|
e74fd0202e | ||
|
|
0823c1cb2d | ||
|
|
d034631e4d | ||
|
|
c5af15f4c1 | ||
|
|
fce79d46d7 | ||
|
|
c45357bf25 | ||
|
|
d96d7425f8 | ||
|
|
2000c10006 | ||
|
|
a348e4830a | ||
|
|
7c496b5b90 | ||
|
|
5d6da1e52d | ||
|
|
2eb1d01622 | ||
|
|
f012d10dcb | ||
|
|
a96689e2cc | ||
|
|
c5f72c53d8 | ||
|
|
699d5063d9 | ||
|
|
08deda59aa | ||
|
|
f8cce58d49 | ||
|
|
f69477e556 | ||
|
|
7f5a792af4 | ||
|
|
dd96423ead | ||
|
|
a1117529e5 | ||
|
|
44510ea8d4 | ||
|
|
9c93ff6c13 | ||
|
|
59818fa24d | ||
|
|
98e14a642f | ||
|
|
45fc58fe75 | ||
|
|
6dad1cd276 | ||
|
|
2212c7dd9e | ||
|
|
98bc86497a | ||
|
|
f447fb11ef | ||
|
|
e091730ffb | ||
|
|
3e479fc02a | ||
|
|
ce2be6b4b3 | ||
|
|
816b50eb1b | ||
|
|
9067558082 | ||
|
|
d6129a7dfc | ||
|
|
3b4bb24eb7 | ||
|
|
2217482329 | ||
|
|
32bdf2acdc | ||
|
|
03187e2e2e | ||
|
|
cf72af4004 | ||
|
|
e4a0150ee7 | ||
|
|
1a46a6b183 | ||
|
|
cf377ad3dc | ||
|
|
c82a8be1ed | ||
|
|
b9c5ed0de3 | ||
|
|
885ce5ebf5 | ||
|
|
9f7d528582 | ||
|
|
8544d61e9c | ||
|
|
a1b3e5382b | ||
|
|
1fb41665e5 | ||
|
|
bd50c4f78c | ||
|
|
0f73cd5faf | ||
|
|
96de8f262f | ||
|
|
4a871134a5 | ||
|
|
9bcb402fa2 | ||
|
|
bb1580ab6e | ||
|
|
75f45a134d | ||
|
|
b272f85e68 | ||
|
|
fdb46df9f2 | ||
|
|
f39ac156cb | ||
|
|
986cde5941 | ||
|
|
c94f54c90f | ||
|
|
4c648ad4be | ||
|
|
cd10c30ca8 | ||
|
|
d964411f70 | ||
|
|
9d68eb495e | ||
|
|
ef80dd87c1 | ||
|
|
a59e012f52 | ||
|
|
580a2f4159 | ||
|
|
b0d999cf3c | ||
|
|
97b82818cf | ||
|
|
4f0f8fce26 | ||
|
|
2b7563ee7b | ||
|
|
d8f43531e1 | ||
|
|
0404fe308f | ||
|
|
d3bdab9014 | ||
|
|
4b41c8dc7a | ||
|
|
12913e70ce | ||
|
|
7630f454fd | ||
|
|
9a2e5d23b9 | ||
|
|
a0f371bfc5 | ||
|
|
9100fe9a34 | ||
|
|
87b76a91c4 | ||
|
|
aa4a487e97 | ||
|
|
94cfca561c | ||
|
|
58f46e9334 | ||
|
|
5519cf8557 | ||
|
|
7413afb11d | ||
|
|
e4ccf21ed8 | ||
|
|
3a96aadb99 | ||
|
|
4f044db204 | ||
|
|
d202c550cb | ||
|
|
173fa8a8e6 | ||
|
|
a2e6a6ba44 | ||
|
|
bfa3d8fd3a | ||
|
|
233cc2073b | ||
|
|
ed513ebd14 | ||
|
|
371b89b5ad | ||
|
|
07d2667d14 | ||
|
|
39d19cab06 | ||
|
|
d7659bd9de | ||
|
|
e855697601 | ||
|
|
e5c4ac650e | ||
|
|
688b6652d1 | ||
|
|
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 | ||
|
|
5a76f2791a | ||
|
|
ca2508635e | ||
|
|
baff01ad48 | ||
|
|
3f04c209f3 | ||
|
|
dbf2a8e43f | ||
|
|
db2a40b58f | ||
|
|
f14346d3a4 | ||
|
|
c2b4c0ad8b | ||
|
|
2bb9db6a67 | ||
|
|
cf9166fe4a | ||
|
|
50c7bfc659 | ||
|
|
ecda08164a | ||
|
|
2dfda037fe | ||
|
|
49f6330a5d | ||
|
|
e737cb448c |
No files matched your search
@@ -22,6 +22,7 @@ testem.log
|
||||
yarn-error.log
|
||||
storybook-static
|
||||
**/.storybook/*.html
|
||||
.watchman-cookie*
|
||||
|
||||
# ember-try
|
||||
.node_modules.ember-try
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import BaseModel from '../../models/base';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
/**
|
||||
* A credential library is a resource that can generate and retrieve
|
||||
* credentials.
|
||||
*/
|
||||
export default class GeneratedCredentialLibraryModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource, to help differentiate schemas',
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('string', {
|
||||
description: 'The owning credential store ID.',
|
||||
})
|
||||
credential_store_id;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional name for identification purposes',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional user-set description for identification purposes',
|
||||
})
|
||||
description;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was created\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
created_time;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was last updated\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
updated_time;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import BaseModel from '../../models/base';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
/**
|
||||
* A credential store is a resource that can retrieve, store, and potentially
|
||||
* generate credentials.
|
||||
*/
|
||||
export default class GeneratedCredentialStoreModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource, to help differentiate schemas',
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional name for identification purposes',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
description: 'Optional user-set description for identification purposes',
|
||||
})
|
||||
description;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was created\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
created_time;
|
||||
|
||||
@attr('date', {
|
||||
description: 'The time this resource was last updated\nOutput only.',
|
||||
readOnly: true,
|
||||
})
|
||||
updated_time;
|
||||
|
||||
@attr('number', {
|
||||
description: 'Current version number of this resource.',
|
||||
})
|
||||
version;
|
||||
}
|
||||
@@ -26,15 +26,10 @@ export default class AccountModel extends GeneratedAccountModel {
|
||||
* 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
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import GeneratedCredentialLibraryModel from '../generated/models/credential-library';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
/**
|
||||
* Enum options for credential library.
|
||||
*/
|
||||
export const options = {
|
||||
http_method: ['GET', 'POST'],
|
||||
};
|
||||
|
||||
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
|
||||
// =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_http_method;
|
||||
@attr('string', { readOnly: true }) attributes_http_request_body;
|
||||
@attr('string', { readOnly: true }) attributes_path;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Attributes of this resource, if any, represented as a JSON fragment.
|
||||
* @type {FragmentCredentialLibraryAttributesModel}
|
||||
*/
|
||||
@fragment('fragment-credential-library-attributes', { defaultValue: {} })
|
||||
attributes;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import GeneratedCredentialStoreModel from '../generated/models/credential-store';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
import { equal } from '@ember/object/computed';
|
||||
|
||||
export default class CredentialStoreModel extends GeneratedCredentialStoreModel {
|
||||
// =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_address;
|
||||
@attr('string', { readOnly: true }) attributes_namespace;
|
||||
@attr('string', { readOnly: true }) attributes_ca_cert;
|
||||
@attr('string', { readOnly: true }) attributes_tls_server_name;
|
||||
@attr('string', { readOnly: true }) attributes_tls_skip_verify;
|
||||
@attr('string', { readOnly: true }) attributes_token;
|
||||
@attr('string', { readOnly: true }) attributes_client_certificate;
|
||||
@attr('string', { readOnly: true }) attributes_client_certificate_key;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Attributes of this resource, if any, represented as a JSON fragment.
|
||||
* @type {FragmentAuthMethodAttributesModel}
|
||||
*/
|
||||
@fragment('fragment-credential-store-attributes', { defaultValue: {} })
|
||||
attributes;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@equal('type', 'vault') isVault;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { options } from './credential-library';
|
||||
|
||||
export default class FragmentCredentialLibraryAttributesModel extends Fragment {
|
||||
//= attributes (vault)
|
||||
|
||||
@attr('string', {
|
||||
description:
|
||||
'The HTTP method the library uses when requesting credentials from Vault.',
|
||||
defaultValue: () => options.http_method[0],
|
||||
})
|
||||
http_method;
|
||||
|
||||
@attr('string', {
|
||||
description:
|
||||
'The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.',
|
||||
})
|
||||
http_request_body;
|
||||
|
||||
@attr('string', {
|
||||
description: 'The path in Vault to request credentials from.',
|
||||
})
|
||||
path;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentCredentialStoreAttributesModel extends Fragment {
|
||||
// =attributes (vault)
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description:
|
||||
"The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
|
||||
})
|
||||
address;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description: 'The namespace within Vault to use.',
|
||||
})
|
||||
namespace;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description:
|
||||
"A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
|
||||
})
|
||||
ca_cert;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description:
|
||||
'Name to use as the SNI host when connecting to Vault via TLS.',
|
||||
})
|
||||
tls_server_name;
|
||||
|
||||
@attr('boolean', {
|
||||
for: 'vault',
|
||||
description: 'Whether or not to skip TLS verification.',
|
||||
})
|
||||
tls_skip_verify;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description: 'A token used for accessing Vault.',
|
||||
})
|
||||
token;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description: 'The Vault token hmac.',
|
||||
readOnly: true,
|
||||
})
|
||||
token_hmac;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description:
|
||||
'A PEM-encoded client certificate to use for TLS authentication to the Vault server.',
|
||||
})
|
||||
client_certificate;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description:
|
||||
"A PEM-encoded private key matching the client certificate from 'client_certificate'.",
|
||||
})
|
||||
client_certificate_key;
|
||||
|
||||
@attr('string', {
|
||||
for: 'vault',
|
||||
description: 'The Vault client certificate key hmac.',
|
||||
readOnly: true,
|
||||
})
|
||||
client_certificate_key_hmac;
|
||||
}
|
||||
@@ -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,6 +20,16 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
})
|
||||
host_sets;
|
||||
|
||||
/**
|
||||
* Credential library ids are read only and can be
|
||||
* persisted via a dedicated call to `addCredentialLibraries()`.
|
||||
*/
|
||||
@fragmentArray('fragment-string', {
|
||||
readOnly: true,
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
application_credential_library_ids;
|
||||
|
||||
/**
|
||||
* An array of resolved host set and host catalog instances. Model instances
|
||||
* must already be loaded into the store (this method will not load unloaded
|
||||
@@ -36,6 +46,24 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
.filter((hostSetRef) => hostSetRef.model !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of resolved credential library instances. Model instances
|
||||
* must already be loaded into the store (this method will not load unloaded
|
||||
* instances). Unresolvable instances are excluded from the array.
|
||||
* @type {[CredentialLibraryModel]}
|
||||
*/
|
||||
@computed('application_credential_library_ids.[]', 'store')
|
||||
get credentialLibraries() {
|
||||
return this.application_credential_library_ids
|
||||
.map((credential_library_fragment) =>
|
||||
this.store.peekRecord(
|
||||
'credential-library',
|
||||
credential_library_fragment.value
|
||||
)
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sessions associated with this target (but only already loaded sessions).
|
||||
* @type {SessionModel[]}
|
||||
@@ -122,4 +150,66 @@ export default class TargetModel extends GeneratedTargetModel {
|
||||
removeHostSet(hostSetID, options) {
|
||||
return this.removeHostSets([hostSetID], options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds credential libraries via the `add-credential-libraries` method.
|
||||
* See serializer and adapter for more information.
|
||||
* @param {[string]} credentialLibraryIDs
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
addCredentialLibraries(
|
||||
credentialLibraryIDs,
|
||||
options = { adapterOptions: {} }
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'add-credential-libraries',
|
||||
credentialLibraryIDs,
|
||||
};
|
||||
// There is no "deep merge" in ES.
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete credential libraries via the `remove-credential-libraries` method.
|
||||
* See serializer and adapter for more information.
|
||||
* @param {[string]} credentialLibraryIDs
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
removeCredentialLibraries(
|
||||
credentialLibraryIDs,
|
||||
options = { adapterOptions: {} }
|
||||
) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'remove-credential-libraries',
|
||||
credentialLibraryIDs,
|
||||
};
|
||||
// There is no "deep merge" in ES.
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single credential library set via the `remove-credential-libraries` method.
|
||||
* @param {number} credentialLibraryID
|
||||
* @param {object} options
|
||||
* @return {Promise}
|
||||
*/
|
||||
removeCredentialLibrary(credentialLibraryID, options) {
|
||||
return this.removeCredentialLibraries([credentialLibraryID], options);
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,28 @@ export default class AccountSerializer extends ApplicationSerializer {
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default class CredentialLibrarySerializer extends ApplicationSerializer {}
|
||||
@@ -0,0 +1,3 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default class CredentialStoreSerializer extends ApplicationSerializer {}
|
||||
@@ -0,0 +1,27 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentCredentialLibraryAttributesSerializer extends JSONSerializer {
|
||||
/**
|
||||
* If an attribute is annotated as readOnly in the model, don't serialize it.
|
||||
* Otherwise delegate to default attribute serializer.
|
||||
* Only serialize http_request_body when `http_method` is set to `POST`.
|
||||
* @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];
|
||||
// Do not serialize http_request_body unless http_method is POST
|
||||
if (key === 'http_request_body' && !json['http_method']?.match(/post/i))
|
||||
delete json[key];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentCredentialStoreAttributesSerializer 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];
|
||||
// Token cannot be unset. If it's falsy, it must be removed.
|
||||
if (key === 'token' && !json[key]) delete json[key];
|
||||
// Client certificate key cannot be unset when certificate is set.
|
||||
// If it's falsy, it must be removed.
|
||||
if (
|
||||
key === 'client_certificate_key' &&
|
||||
!json[key] &&
|
||||
json['client_certificate']
|
||||
)
|
||||
delete json[key];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,11 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
/**
|
||||
* If `adapterOptions.hostSets` is set to an array of host set models,
|
||||
* the resulting target serialization should include **only host sets**
|
||||
* and the version. Normally, host sets are not serialized.
|
||||
* and the version.
|
||||
* If `adapterOptions.credentialLibraries` is set to an array of
|
||||
* credential library models, the resulting target serialization should
|
||||
* include **only credential libraries** and the version.
|
||||
* Normally, neither host sets or credential libraries are not serialized.
|
||||
* @override
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
@@ -16,6 +20,12 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
const hostSetIDs = snapshot?.adapterOptions?.hostSetIDs;
|
||||
if (hostSetIDs)
|
||||
serialized = this.serializeWithHostSets(snapshot, hostSetIDs);
|
||||
const credentialLibraryIDs = snapshot?.adapterOptions?.credentialLibraryIDs;
|
||||
if (credentialLibraryIDs)
|
||||
serialized = this.serializeWithCredentialLibraries(
|
||||
snapshot,
|
||||
credentialLibraryIDs
|
||||
);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@@ -32,4 +42,18 @@ export default class TargetSerializer extends ApplicationSerializer {
|
||||
host_set_ids: hostSetIDs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a payload containing only the credential_library_ids array using IDs
|
||||
* passed into the function (rather than existing credential libraries on the model).
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {[string]} credentialLibraryIDs
|
||||
* @return {object}
|
||||
*/
|
||||
serializeWithCredentialLibraries(snapshot, credentialLibraryIDs) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
application_credential_library_ids: credentialLibraryIDs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/credential-library';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/credential-store';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-credential-library-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-credential-store-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/credential-library';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/credential-store';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-credential-library-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-credential-store-attributes';
|
||||
@@ -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.
|
||||
@@ -366,10 +366,10 @@ export default function () {
|
||||
const target = targets.find(id);
|
||||
const updatedAttrs = {
|
||||
version: attrs.version,
|
||||
hostSetIds: target.hostSetIds,
|
||||
};
|
||||
// If adding host sets, push them into the array
|
||||
if (method === 'add-host-sets') {
|
||||
updatedAttrs.hostSetIds = target.hostSetIds;
|
||||
attrs.hostSetIds.forEach((id) => {
|
||||
if (!updatedAttrs.hostSetIds.includes(id)) {
|
||||
updatedAttrs.hostSetIds.push(id);
|
||||
@@ -378,10 +378,28 @@ export default function () {
|
||||
}
|
||||
// If deleting host sets, filter them out of the array
|
||||
if (method === 'remove-host-sets') {
|
||||
updatedAttrs.hostSetIds = target.hostSetIds;
|
||||
updatedAttrs.hostSetIds = updatedAttrs.hostSetIds.filter((id) => {
|
||||
return !attrs.hostSetIds.includes(id);
|
||||
});
|
||||
}
|
||||
// If adding credential libraries, push them into the array
|
||||
if (method === 'add-credential-libraries') {
|
||||
updatedAttrs.credentialLibraryIds = target.credentialLibraryIds;
|
||||
attrs.applicationCredentialLibraryIds.forEach((id) => {
|
||||
if (!updatedAttrs.credentialLibraryIds.includes(id)) {
|
||||
updatedAttrs.credentialLibraryIds.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
// If deleting credential libraries, filter them out of the array
|
||||
if (method === 'remove-credential-libraries') {
|
||||
updatedAttrs.credentialLibraryIds = target.credentialLibraryIds;
|
||||
updatedAttrs.credentialLibraryIds =
|
||||
updatedAttrs.credentialLibraryIds.filter((id) => {
|
||||
return !attrs.applicationCredentialLibraryIds.includes(id);
|
||||
});
|
||||
}
|
||||
return target.update(updatedAttrs);
|
||||
}
|
||||
);
|
||||
@@ -456,6 +474,41 @@ export default function () {
|
||||
return new Response(200);
|
||||
});
|
||||
|
||||
// credential-stores
|
||||
|
||||
this.get(
|
||||
'/credential-stores',
|
||||
({ credentialStores }, { queryParams: { scope_id: scopeId } }) =>
|
||||
credentialStores.where({ scopeId })
|
||||
);
|
||||
|
||||
this.get('/credential-stores/:id');
|
||||
this.post('/credential-stores');
|
||||
this.patch('/credential-stores/:id');
|
||||
this.del('/credential-stores/:id');
|
||||
|
||||
// credential-libraries
|
||||
|
||||
this.get(
|
||||
'/credential-libraries',
|
||||
(
|
||||
{ credentialLibraries },
|
||||
{ queryParams: { credential_store_id: credentialStoreId } }
|
||||
) => credentialLibraries.where({ credentialStoreId })
|
||||
);
|
||||
this.get('/credential-libraries/:id');
|
||||
this.post(
|
||||
'/credential-libraries',
|
||||
function ({ credentialStores, credentialLibraries }) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const credentialStore = credentialStores.find(attrs.credentialStoreId);
|
||||
attrs.scopeId = credentialStore.scope.id;
|
||||
return credentialLibraries.create(attrs);
|
||||
}
|
||||
);
|
||||
this.del('/credential-libraries/:id');
|
||||
this.patch('/credential-libraries/:id');
|
||||
|
||||
/* Uncomment the following line and the Response import above
|
||||
* Then change the response code to simulate error responses.
|
||||
* this.get('/scopes', () => new Response(505));
|
||||
|
||||
@@ -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] });
|
||||
});
|
||||
},
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import factory from '../generated/factories/credential-library';
|
||||
import { random, system } from 'faker';
|
||||
|
||||
const types = ['vault'];
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `credential-library-id-${i}`,
|
||||
type: (i) => types[i % types.length],
|
||||
|
||||
attributes() {
|
||||
switch (this.type) {
|
||||
case 'vault':
|
||||
return {
|
||||
http_method: 'GET',
|
||||
http_request_body: random.word(),
|
||||
path: system.directoryPath(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import factory from '../generated/factories/credential-store';
|
||||
import { random, internet, datatype } from 'faker';
|
||||
import { trait } from 'ember-cli-mirage';
|
||||
|
||||
const types = ['vault'];
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `credential-store-id-${i}`,
|
||||
type: (i) => types[i % types.length],
|
||||
|
||||
attributes() {
|
||||
switch (this.type) {
|
||||
case 'vault':
|
||||
return {
|
||||
address: internet.ip(),
|
||||
namespace: random.word(),
|
||||
ca_cert: 'ca-cert-123',
|
||||
tls_server_name: internet.domainName(),
|
||||
tls_skip_verify: datatype.boolean(),
|
||||
token_hmac: 'token-hmac-abcde0987654321',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key_hmac: 'client-cert-key-hmac-abcde0987654321',
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
withAssociations: trait({
|
||||
afterCreate(credentialStore, server) {
|
||||
const { scope } = credentialStore;
|
||||
server.createList('credential-library', 2, {
|
||||
scope,
|
||||
credentialStore,
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -7,7 +7,7 @@ const hostSetChance = 0.3;
|
||||
|
||||
export default factory.extend({
|
||||
/**
|
||||
* -1 means "unlimited" and we want to generate these on occassion.
|
||||
* -1 means "unlimited" and we want to generate these on occasion.
|
||||
*/
|
||||
session_connection_limit: () => random.arrayElement([-1, datatype.number()]),
|
||||
|
||||
@@ -25,11 +25,11 @@ export default factory.extend({
|
||||
},
|
||||
|
||||
/**
|
||||
* Randomly selects existing host sets to assign to target.
|
||||
* Randomly selects existing host sets and credential libraries to assign to target.
|
||||
*/
|
||||
withRandomHostSets: trait({
|
||||
withAssociations: trait({
|
||||
afterCreate(target, server) {
|
||||
let randomlySelectedHostSets;
|
||||
let randomlySelectedHostSets, randomlySelectedCredentialLibraries;
|
||||
randomlySelectedHostSets = server.schema.hostSets
|
||||
// BLERG: fun fact, for no reason at all, the element passed
|
||||
// into a where function is not a full model instance, as you might
|
||||
@@ -38,7 +38,17 @@ export default factory.extend({
|
||||
// the result set of `where` _is a collection of model instances_.
|
||||
.where((hostSet) => hostSet.scopeId === target.scope.id)
|
||||
.models.filter(() => randomBoolean(hostSetChance));
|
||||
target.update({ hostSets: randomlySelectedHostSets });
|
||||
|
||||
randomlySelectedCredentialLibraries = server.schema.credentialLibraries
|
||||
.where(
|
||||
(credentialLibrary) => credentialLibrary.scopeId === target.scope.id
|
||||
)
|
||||
.models.filter(() => randomBoolean());
|
||||
|
||||
target.update({
|
||||
hostSets: randomlySelectedHostSets,
|
||||
credentialLibraries: randomlySelectedCredentialLibraries,
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -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() }),
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedCredentialLibraryModelFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
name: () => random.words(),
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedCredentialStoreModelFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
name: () => random.words(),
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
scope: belongsTo(),
|
||||
credentialStore: belongsTo(),
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
scope: belongsTo(),
|
||||
});
|
||||
@@ -3,4 +3,5 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
||||
export default Model.extend({
|
||||
scope: belongsTo(),
|
||||
hostSets: hasMany(),
|
||||
credentialLibraries: hasMany(),
|
||||
});
|
||||
@@ -47,7 +47,8 @@ export default function (server) {
|
||||
// Other resources
|
||||
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
|
||||
server.createList('host-catalog', 2, { scope }, 'withChildren');
|
||||
server.createList('target', 2, { scope }, 'withRandomHostSets');
|
||||
server.createList('session', 4, { scope }, 'withAssociations');
|
||||
server.createList('credential-store', 3, { scope }, 'withAssociations');
|
||||
server.createList('target', 2, { scope }, 'withAssociations');
|
||||
});
|
||||
}
|
||||
@@ -53,11 +53,69 @@ 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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'credential-library',
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
json.credential_store_id = model.credentialStoreId;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'credential-store',
|
||||
|
||||
_hashForModel(/* model */) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
// in a real API, the following fields are set-only and returned in
|
||||
// associated HMAC fields
|
||||
delete json.attributes?.token;
|
||||
delete json.attributes?.client_certificate_key;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
@@ -23,6 +23,9 @@ export default ApplicationSerializer.extend({
|
||||
return { id: host_set_id, host_catalog_id };
|
||||
});
|
||||
}
|
||||
if (model.credentialLibraryIds?.length) {
|
||||
json.application_credential_library_ids = model.credentialLibraryIds;
|
||||
}
|
||||
return json;
|
||||
},
|
||||
});
|
||||
@@ -29,20 +29,20 @@
|
||||
"ember-cli-mirage": "^1.1.8",
|
||||
"ember-data": "^3.20.0",
|
||||
"ember-data-model-fragments": "5.0.0-beta.2",
|
||||
"ember-fetch": "^8.0.1"
|
||||
"ember-fetch": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.1.4",
|
||||
"@ember/test-helpers": "^2.2.8",
|
||||
"@glimmer/component": "^1.0.3",
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli": "~3.27.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.0.2",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-terser": "^4.0.1",
|
||||
"ember-cli-update": "^0.54.6",
|
||||
@@ -59,8 +59,8 @@
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^2.15.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.1.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | credential library', 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('credential-library', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | credential store', 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('credential-store', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Model | fragment credential library attributes',
|
||||
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-credential-library-attributes',
|
||||
{}
|
||||
);
|
||||
assert.ok(model);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | fragment credential store attributes', 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-credential-store-attributes', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -223,4 +223,162 @@ module('Unit | Model | target', function (hooks) {
|
||||
'Target has two associated sessions loaded in the store'
|
||||
);
|
||||
});
|
||||
|
||||
test('it has a `credentialLibraries` array of resolved model instances (if those instances are already in the store)', function (assert) {
|
||||
assert.expect(4);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'target',
|
||||
attributes: {
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const target = store.peekRecord('target', '123abc');
|
||||
assert.equal(
|
||||
target.application_credential_library_ids.length,
|
||||
2,
|
||||
'Target has two entires in application_credential_library_ids'
|
||||
);
|
||||
assert.equal(
|
||||
target.credentialLibraries.length,
|
||||
0,
|
||||
'Target has no resolved credentialLibraries because they are not loaded yet'
|
||||
);
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'credential-library',
|
||||
attributes: {},
|
||||
},
|
||||
});
|
||||
store.push({
|
||||
data: {
|
||||
id: '2',
|
||||
type: 'credential-library',
|
||||
attributes: {},
|
||||
},
|
||||
});
|
||||
// Since `credentialLibraries` is computed on `application_credential_library_ids`,
|
||||
// not the store itself, it's necessary to do this assignment to kick-off the
|
||||
// computed update.
|
||||
/* eslint-disable no-self-assign */
|
||||
target.application_credential_library_ids =
|
||||
target.application_credential_library_ids;
|
||||
/* eslint-enable no-self-assign */
|
||||
assert.equal(
|
||||
target.application_credential_library_ids.length,
|
||||
2,
|
||||
'Target has two entires in application_credential_library_ids'
|
||||
);
|
||||
assert.equal(
|
||||
target.credentialLibraries.length,
|
||||
2,
|
||||
'Target has two resolved credentialLibraries'
|
||||
);
|
||||
});
|
||||
|
||||
test('it has an `addCredentialLibraries` method that targets a specific POST API endpoint and serialization', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post(
|
||||
'/v1/targets/123abc:add-credential-libraries',
|
||||
(schema, request) => {
|
||||
const body = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(body, {
|
||||
application_credential_library_ids: ['123_abc', 'foobar'],
|
||||
version: 1,
|
||||
});
|
||||
return { id: '123abc' };
|
||||
}
|
||||
);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'target',
|
||||
attributes: {
|
||||
name: 'Target',
|
||||
description: 'Description',
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
version: 1,
|
||||
scope: {
|
||||
scope_id: 'o_1',
|
||||
type: 'scope',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const model = store.peekRecord('target', '123abc');
|
||||
await model.addCredentialLibraries(['123_abc', 'foobar']);
|
||||
});
|
||||
|
||||
test('it has a `removeCredentialLibraries` method that targets a specific POST API endpoint and serialization', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post(
|
||||
'/v1/targets/123abc:remove-credential-libraries',
|
||||
(schema, request) => {
|
||||
const body = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(body, {
|
||||
application_credential_library_ids: ['1', '2'],
|
||||
version: 1,
|
||||
});
|
||||
return { id: '123abc' };
|
||||
}
|
||||
);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'target',
|
||||
attributes: {
|
||||
name: 'Target',
|
||||
description: 'Description',
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
version: 1,
|
||||
scope: {
|
||||
scope_id: 'o_1',
|
||||
type: 'scope',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const model = store.peekRecord('target', '123abc');
|
||||
await model.removeCredentialLibraries(['1', '2']);
|
||||
});
|
||||
|
||||
test('it has a `removeCredentialLibrary` method that deletes a single credential library using `removeCredentialLibraries` method', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post(
|
||||
'/v1/targets/123abc:remove-credential-libraries',
|
||||
(schema, request) => {
|
||||
const body = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(body, {
|
||||
application_credential_library_ids: ['2'],
|
||||
version: 1,
|
||||
});
|
||||
return { id: '123abc' };
|
||||
}
|
||||
);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'target',
|
||||
attributes: {
|
||||
name: 'Target',
|
||||
description: 'Description',
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
version: 1,
|
||||
scope: {
|
||||
scope_id: 'o_1',
|
||||
type: 'scope',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const model = store.peekRecord('target', '123abc');
|
||||
await model.removeCredentialLibrary('2');
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -203,6 +203,7 @@ module('Unit | Serializer | application', function (hooks) {
|
||||
name: 'Target 1',
|
||||
scope: { scope_id: 'o_123' },
|
||||
host_sets: [],
|
||||
application_credential_library_ids: [],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | credential library', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes correctly on create', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-library');
|
||||
const record = store.createRecord('credential-library', {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes correctly on update', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-library');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'credential-library',
|
||||
attributes: {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('credential-library', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | credential store', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes vault-type correctly on create', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-store');
|
||||
const record = store.createRecord('credential-store', {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
token: 'foo',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
attributes: {
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
token: 'foo',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes vault-type correctly on update', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'credential-store',
|
||||
attributes: {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
token: 'foo',
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('credential-store', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
token: 'foo',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Serializer | fragment credential library 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(
|
||||
'fragment-credential-library-attributes',
|
||||
{
|
||||
http_method: '',
|
||||
path: null,
|
||||
}
|
||||
);
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(serializedRecord, {
|
||||
http_method: null,
|
||||
path: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('it does not serialize http_request_body unless http_method is set to POST', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord(
|
||||
'fragment-credential-library-attributes',
|
||||
{
|
||||
http_method: 'GET',
|
||||
http_request_body: 'body',
|
||||
}
|
||||
);
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(
|
||||
serializedRecord,
|
||||
{
|
||||
http_method: 'GET',
|
||||
path: null,
|
||||
},
|
||||
'http_request_body attribute is not expected'
|
||||
);
|
||||
|
||||
record.http_method = 'POST';
|
||||
serializedRecord = record.serialize();
|
||||
assert.deepEqual(
|
||||
serializedRecord,
|
||||
{
|
||||
http_method: 'POST',
|
||||
http_request_body: 'body',
|
||||
path: null,
|
||||
},
|
||||
'http_request_body attribute is expected'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,117 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Serializer | fragment credential store 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(
|
||||
'fragment-credential-store-attributes',
|
||||
{
|
||||
address: '',
|
||||
ca_cert: '',
|
||||
client_certificate: '',
|
||||
client_certificate_key: '',
|
||||
namespace: '',
|
||||
tls_server_name: '',
|
||||
tls_skip_verify: false,
|
||||
}
|
||||
);
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(serializedRecord, {
|
||||
address: null,
|
||||
ca_cert: null,
|
||||
client_certificate: null,
|
||||
client_certificate_key: null,
|
||||
namespace: null,
|
||||
tls_server_name: null,
|
||||
tls_skip_verify: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('it does not serialize token if present but falsy', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord(
|
||||
'fragment-credential-store-attributes',
|
||||
{
|
||||
token: null,
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
}
|
||||
);
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(
|
||||
serializedRecord,
|
||||
{
|
||||
address: 'http://localhost:5000',
|
||||
ca_cert: 'ca-cert-123',
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
},
|
||||
'token attribute is not expected'
|
||||
);
|
||||
});
|
||||
|
||||
test('it does not serialize client certificate key if present but falsy when client certificate is present', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord(
|
||||
'fragment-credential-store-attributes',
|
||||
{
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: '',
|
||||
address: 'http://localhost:5000',
|
||||
token: 'foo',
|
||||
ca_cert: 'ca-cert-123',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
}
|
||||
);
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(
|
||||
serializedRecord,
|
||||
{
|
||||
client_certificate: 'client-cert-123',
|
||||
address: 'http://localhost:5000',
|
||||
token: 'foo',
|
||||
ca_cert: 'ca-cert-123',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
},
|
||||
'client certificate key attribute is not expected'
|
||||
);
|
||||
|
||||
record.client_certificate_key = 'client-cert-key-456';
|
||||
serializedRecord = record.serialize();
|
||||
assert.deepEqual(
|
||||
serializedRecord,
|
||||
{
|
||||
client_certificate: 'client-cert-123',
|
||||
client_certificate_key: 'client-cert-key-456',
|
||||
address: 'http://localhost:5000',
|
||||
token: 'foo',
|
||||
ca_cert: 'ca-cert-123',
|
||||
namespace: 'foobar',
|
||||
tls_server_name: 'yes',
|
||||
tls_skip_verify: false,
|
||||
},
|
||||
'client certificate key attribute is expected'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -4,7 +4,7 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | target', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes targets normally, without host sets', function (assert) {
|
||||
test('it serializes targets normally, without host sets or credential libraries', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('target');
|
||||
@@ -15,6 +15,7 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
{ host_set_id: '1', host_catalog_id: '2' },
|
||||
{ host_set_id: '3', host_catalog_id: '4' },
|
||||
],
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
scope: {
|
||||
scope_id: 'org_1',
|
||||
type: 'org',
|
||||
@@ -63,6 +64,27 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes only credential libraries and version when an `adapterOptions.credentialLibraryIDs` array is passed', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('target');
|
||||
const record = store.createRecord('target', {
|
||||
name: 'User',
|
||||
description: 'Description',
|
||||
application_credential_library_ids: [{ value: '1' }, { value: '2' }],
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {
|
||||
credentialLibraryIDs: ['4', '5'],
|
||||
};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
application_credential_library_ids: ['4', '5'],
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes records with array fields', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
@@ -75,6 +97,7 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
{ host_set_id: '1', host_catalog_id: '2' },
|
||||
{ host_set_id: '3', host_catalog_id: '4' },
|
||||
],
|
||||
application_credential_library_ids: ['1'],
|
||||
};
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
@@ -92,13 +115,14 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
{ host_set_id: '1', host_catalog_id: '2' },
|
||||
{ host_set_id: '3', host_catalog_id: '4' },
|
||||
],
|
||||
application_credential_library_ids: [{ value: '1' }],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it normalizes missing host_sets to empty array', function (assert) {
|
||||
test('it normalizes missing host_sets and credential libraries to empty array', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('target');
|
||||
@@ -122,6 +146,7 @@ module('Unit | Serializer | target', function (hooks) {
|
||||
name: 'Target 1',
|
||||
scope: { scope_id: 'o_123' },
|
||||
host_sets: [],
|
||||
application_credential_library_ids: [],
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.1.4",
|
||||
"@ember/test-helpers": "^2.2.8",
|
||||
"@glimmer/component": "^1.0.3",
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
@@ -35,9 +35,9 @@
|
||||
"core": "*",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli": "~3.27.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.0.2",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-terser": "^4.0.1",
|
||||
"ember-cli-update": "^0.54.6",
|
||||
@@ -53,8 +53,8 @@
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^2.15.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.1.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
|
||||
@@ -43,16 +43,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.1.4",
|
||||
"@ember/test-helpers": "^2.2.8",
|
||||
"@glimmer/component": "^1.0.3",
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli": "~3.27.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.0.2",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-terser": "^4.0.1",
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
@@ -68,8 +68,8 @@
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^2.15.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.1.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
|
||||
@@ -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,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.18544 1.11673L4.58911 4.91271L7.89977 8.92139L9.74509 7.3974L12.3286 10.3492L10.0287 12.3622L11.0166 13.4909L13.3165 11.4779L14.2867 12.5865L11.9883 14.5982L12.9762 15.7269L16.4034 12.7273L10.9021 6.44183L12.4961 5.12541L9.18544 1.11673Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.39097 2.9997L4.63303 2.93436L4.56862 7.62444L6.96166 7.65731L7.02014 11.5795L3.96411 11.6251L3.98649 13.1249L7.04251 13.0793L7.06447 14.5523L4.01033 14.5979L4.03269 16.0977L8.58667 16.0298L8.46213 7.67792L9.32656 7.68979L9.39097 2.9997Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.73342 0.840799C7.01928 0.312425 6.13571 0 5.17915 0C2.805 0 0.880371 1.92463 0.880371 4.29878C0.880371 6.00532 1.87477 7.47961 3.31575 8.1738L3.33999 6.4085C2.75194 5.89543 2.38037 5.1405 2.38037 4.29878C2.38037 2.75306 3.63343 1.5 5.17915 1.5C5.67244 1.5 6.13592 1.62762 6.53838 1.85163L7.73342 0.840799Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -32,3 +32,5 @@ change-password: Change Password
|
||||
edit-form: Edit Form
|
||||
change-origin: Change Origin URL
|
||||
change-state: Change State
|
||||
close: Close
|
||||
toggle-secret: Toggle secret visibility
|
||||
@@ -108,3 +108,10 @@ 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.
|
||||
@@ -147,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
|
||||
@@ -233,20 +244,105 @@ 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
|
||||
add-host-sets: Add Host Sets
|
||||
add-host-sets: Add Host Sources
|
||||
add-credential-libraries: Add Credential Sources
|
||||
types:
|
||||
tcp: TCP
|
||||
host-set:
|
||||
title: Host Source
|
||||
title_plural: Host Sources
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Host Sets
|
||||
description: No hosts sets in this target.
|
||||
title: Welcome to Host Sources
|
||||
description: No hosts sources in this target.
|
||||
none:
|
||||
title: No Host Sets Available
|
||||
description: No hosts sets available to add to target.
|
||||
title: No Host Sources Available
|
||||
description: No hosts sources available to add to target.
|
||||
add:
|
||||
title: Add Host Sets
|
||||
description: Select host sets to assign to this target.
|
||||
title: Add Host Sources
|
||||
description: Select host sources to assign to this target.
|
||||
credential-library:
|
||||
title: Credential Source
|
||||
title_plural: Credential Sources
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Credential Sources
|
||||
description: No credential sources in this target.
|
||||
none:
|
||||
title: No Credential Sources Available
|
||||
description: No credential sources available to add to this target.
|
||||
add:
|
||||
title: Add Credential Sources
|
||||
description: Select credential sources to assign to this target.
|
||||
credential-store:
|
||||
title: Credential Store
|
||||
title_plural: Credential Stores
|
||||
description: A credential store is a collection of credentials and credential libraries.
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Credential Stores
|
||||
actions:
|
||||
delete: Delete Credential Store
|
||||
types:
|
||||
vault: Vault
|
||||
form:
|
||||
address:
|
||||
label: Address
|
||||
help: The address of the Vault server. This should be a complete URL such as https://127.0.0.1:8200.
|
||||
namespace:
|
||||
label: Namespace
|
||||
help: A Vault namespace. Requires Vault Enterprise.
|
||||
ca_cert:
|
||||
label: CA Certificate
|
||||
help: A PEM-encoded CA certificate to verify the Vault server's TLS certificate.
|
||||
tls_server_name:
|
||||
label: TLS Server Name
|
||||
help: Name to use as the SNI host when connecting to Vault via TLS.
|
||||
tls_skip_verify:
|
||||
label: TLS Skip Verify
|
||||
help: Disable verification of TLS certificates. Using this option is highly discouraged as it decreases the security of data transmissions to and from the Vault server.
|
||||
token:
|
||||
label: Token
|
||||
help: A token used for accessing Vault.
|
||||
token_hmac:
|
||||
label: Token HMAC
|
||||
client_certificate:
|
||||
label: Client Certificate
|
||||
help: A PEM-encoded client certificate to use for TLS authentication to the Vault server.
|
||||
client_certificate_key:
|
||||
label: Client Certificate Key
|
||||
help: A PEM-encoded private key matching the client certificate from Client Certificate.
|
||||
client_certificate_key_hmac:
|
||||
label: Client Certificate Key HMAC
|
||||
credential-library:
|
||||
title: Credential Library
|
||||
title_plural: Credential Libraries
|
||||
description: A credential library is a resource that provides credentials.
|
||||
actions:
|
||||
create: New Credential Library
|
||||
delete: Delete Credential Library
|
||||
messages:
|
||||
none:
|
||||
title: No Credential Libraries Available
|
||||
description: No Credential Libraries available in this credential store.
|
||||
form:
|
||||
vault_path:
|
||||
label: Vault Path
|
||||
help: The path the library uses when requesting credentials from Vault.
|
||||
http_method:
|
||||
label: HTTP Method
|
||||
help: The HTTP method the library uses when requesting credentials from Vault.
|
||||
http_request_body:
|
||||
label: HTTP Method POST Request Body
|
||||
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
|
||||
types:
|
||||
vault: Vault
|
||||
@@ -4,9 +4,6 @@ success: Success
|
||||
warning: Warning
|
||||
error: Error
|
||||
copied: Copied
|
||||
connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
inactive: Inactive
|
||||
active-private: Private
|
||||
|
||||
@@ -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>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="rose-form-checkbox{{if @error " error"}}">
|
||||
<div class="rose-form-checkbox{{if @error " error"}} {{if @inline " inline"}}">
|
||||
|
||||
<Input
|
||||
...attributes
|
||||
|
||||
@@ -68,3 +68,61 @@ Renders a form input checkbox.
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Checkbox
|
||||
@name="boolean-value"
|
||||
@label="Yes?"
|
||||
@checked={{false}}
|
||||
@error={{false}}
|
||||
@disabled={{false}}
|
||||
@inline={{false}}
|
||||
/>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Checkbox with inline style" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Checkbox
|
||||
@name='checkbox-1'
|
||||
@label='Checkbox1 🛎'
|
||||
@description={{description}}
|
||||
@helperText={{helperText}}
|
||||
@checked={{checked}}
|
||||
@error={{error}}
|
||||
@disabled={{disabled}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@name='checkbox-2'
|
||||
@label='Checkbox2 🛎'
|
||||
@description={{description}}
|
||||
@helperText={{helperText}}
|
||||
@checked={{checked}}
|
||||
@error={{error}}
|
||||
@disabled={{disabled}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@name='checkbox-3'
|
||||
@label='Checkbox3 🛎'
|
||||
@description={{description}}
|
||||
@helperText={{helperText}}
|
||||
@checked={{checked}}
|
||||
@error={{error}}
|
||||
@disabled={{disabled}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
description: text('description', 'Short description in label'),
|
||||
helperText: text('helperText', 'This is an optional help text.'),
|
||||
checked: boolean('checked', false),
|
||||
error: boolean('error', false),
|
||||
disabled: boolean('disabled', false),
|
||||
inline: boolean('inline', false)
|
||||
}
|
||||
}}</Story>
|
||||
|
||||
</Preview>
|
||||
@@ -20,6 +20,7 @@
|
||||
input=(component 'rose/form/input' disabled=this.disabled)
|
||||
select=(component 'rose/form/select' disabled=this.disabled)
|
||||
textarea=(component 'rose/form/textarea' disabled=this.disabled)
|
||||
fieldset=(component 'rose/form/fieldset')
|
||||
actions=(component
|
||||
(if this.showEditToggleButton
|
||||
'rose/form/actions/edit-toggle'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{{yield (hash
|
||||
radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed)
|
||||
)}}
|
||||
radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed inline=@inline)
|
||||
)}}
|
||||
@@ -15,6 +15,7 @@ Renders a form radiofield group.
|
||||
@name={{name}}
|
||||
@selectedValue="disagree"
|
||||
@changed={{fn}}
|
||||
@inline={{false}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.radio
|
||||
@@ -39,6 +40,7 @@ Renders a form radiofield group.
|
||||
@name={{name}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@changed={{changed}}
|
||||
@inline={{inline}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.radio
|
||||
@@ -59,6 +61,7 @@ Renders a form radiofield group.
|
||||
context: {
|
||||
name: text('name', 'mustagree'),
|
||||
disabled: boolean('disabled', false),
|
||||
inline: boolean('inline', true),
|
||||
selectedValue: text('selectedValue', 'disagree'),
|
||||
firstFieldLabel: text('first label', 'Agree'),
|
||||
firstFieldValue: text('first value', 'agree'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="rose-form-radio{{if @error " error"}}">
|
||||
<div class="rose-form-radio{{if @error " error"}} {{if @inline "inline"}}">
|
||||
|
||||
<RadioButton
|
||||
...attributes
|
||||
|
||||
@@ -54,3 +54,71 @@ Renders a form radiofield.
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Radio::Radio
|
||||
@label="Yes"
|
||||
@error={{false}}
|
||||
@value="yes-agree"
|
||||
@selectedValue="yes-agree"
|
||||
@disabled={{false}}
|
||||
@changed={{fn}}
|
||||
@inline={{true}}
|
||||
/>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Radio with inline style" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Radio::Radio
|
||||
@label='Radiofield1'
|
||||
@error={{error}}
|
||||
@value={{value}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
<Rose::Form::Radio::Radio
|
||||
@label='Radiofield2'
|
||||
@error={{error}}
|
||||
@value={{value}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
<Rose::Form::Radio::Radio
|
||||
@label='Radiofield3'
|
||||
@error={{error}}
|
||||
@value={{value}}
|
||||
@selectedValue={{selectedValue}}
|
||||
@disabled={{disabled}}
|
||||
@icon={{icon}}
|
||||
@inline={{inline}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
error: boolean('error', false),
|
||||
value: text('value', 'value and selectedValue must match to select radiofield'),
|
||||
selectedValue: text(
|
||||
'selectedValue',
|
||||
'value and selectedValue must match to select radiofield'
|
||||
),
|
||||
disabled: boolean('disabled', false),
|
||||
icon: select(
|
||||
'icon',
|
||||
{
|
||||
'none': null,
|
||||
'user-organization': 'user-organization',
|
||||
'copy-action': 'copy-action',
|
||||
'unfold-more': 'unfold-more',
|
||||
'unfold-less': 'unfold-less',
|
||||
},
|
||||
null
|
||||
),
|
||||
inline: boolean('inline', false)
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -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';
|
||||
@@ -7,8 +7,14 @@ $checkmark: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231563
|
||||
.rose-dropdown-item {
|
||||
@include type.type(s);
|
||||
min-width: (sizing.rems(m)) * 20;
|
||||
$line-height: math.div(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;
|
||||
@@ -16,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);
|
||||
@@ -57,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;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-form-checkbox {
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
margin-right: sizing.rems(s);
|
||||
}
|
||||
@include type.type(m);
|
||||
position: relative;
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
.rose-form-fieldset {
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
|
||||
.rose-form-fieldset-title {
|
||||
@include type.type(l, bold);
|
||||
@include type.type(s, semibold);
|
||||
}
|
||||
|
||||
.rose-form-fieldset-description {
|
||||
|
||||
@@ -21,8 +21,13 @@ $radio-svg: (
|
||||
);
|
||||
|
||||
$data-svg-prefix: 'data:image/svg+xml;utf-8,';
|
||||
|
||||
.rose-form-radio {
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
.rose-form-radio-label {
|
||||
margin-right: sizing.rems(s);
|
||||
}
|
||||
}
|
||||
@include type.type(m);
|
||||
position: relative;
|
||||
margin-bottom: sizing.rems(xs);
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
.rose-table-row {
|
||||
border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
|
||||
text-align: left;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// This styles are add to stop the input to apply extra margin (default on forms)
|
||||
@@ -39,6 +43,9 @@
|
||||
|
||||
.rose-table-header-cell {
|
||||
@include type.type(s, semibold);
|
||||
}
|
||||
|
||||
.rose-table-header .rose-table-header-cell {
|
||||
color: var(--ui-gray);
|
||||
}
|
||||
|
||||
@@ -67,11 +74,27 @@
|
||||
}
|
||||
|
||||
&.rose-table-condensed {
|
||||
.rose-table-cell {
|
||||
.rose-table-cell,
|
||||
.rose-table-header-cell {
|
||||
@include type.type(s);
|
||||
$lineHeight: math.div(36, 14);
|
||||
line-height: $lineHeight;
|
||||
padding: math.div(sizing.rems(s), 2) sizing.rems(s);
|
||||
padding: sizing.rems(xxs) + sizing.rems(xxxxs) sizing.rems(s);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
.rose-table-header {
|
||||
border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
|
||||
.rose-table-header-cell {
|
||||
$lineHeight: math.div(16, 14);
|
||||
line-height: $lineHeight;
|
||||
padding: sizing.rems(xs) sizing.rems(s);
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/dropdown/key-value';
|
||||
+10
-10
@@ -40,22 +40,22 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/test-helpers": "^2.1.4",
|
||||
"@ember/test-helpers": "^2.2.8",
|
||||
"@glimmer/component": "^1.0.3",
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"@hashicorp/structure-icons": "^1.9.0-0",
|
||||
"@storybook/addon-actions": "^6.0.26",
|
||||
"@storybook/addon-docs": "^6.0.26",
|
||||
"@storybook/addon-knobs": "^6.0.26",
|
||||
"@storybook/ember": "^6.0.26",
|
||||
"@storybook/addon-actions": "^6.3.1",
|
||||
"@storybook/addon-docs": "^6.3.1",
|
||||
"@storybook/addon-knobs": "^6.3.0",
|
||||
"@storybook/ember": "^6.3.1",
|
||||
"@storybook/ember-cli-storybook": "^0.4.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli": "~3.27.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-inject-live-reload": "^2.0.2",
|
||||
"ember-cli-inject-live-reload": "^2.1.0",
|
||||
"ember-cli-sass": "^10.0.1",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-terser": "^4.0.1",
|
||||
@@ -73,8 +73,8 @@
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"ember-template-lint": "^2.15.0",
|
||||
"ember-try": "^1.4.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-ember": "^10.1.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
@@ -83,7 +83,7 @@
|
||||
"prettier": "^2.2.1",
|
||||
"qunit": "^2.13.0",
|
||||
"qunit-dom": "^1.6.0",
|
||||
"sass": "^1.26.5",
|
||||
"sass": "^1.36.0",
|
||||
"sass-lint": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,196 +1,82 @@
|
||||
<h2>Condensed Table</h2>
|
||||
<Rose::Table @style="condensed" as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell >Address</row.headerCell>
|
||||
<row.headerCell>Host Set</row.headerCell>
|
||||
<row.headerCell>Name</row.headerCell>
|
||||
<row.headerCell>Description</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>1.1.1.1</row.cell>
|
||||
<row.cell>host-set-1, host-set-2</row.cell>
|
||||
<row.cell>optional name</row.cell>
|
||||
<row.cell>description</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer as |footer|>
|
||||
<footer.row as |row|>
|
||||
<row.cell>Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
<h2>KeyValue Dropdown</h2>
|
||||
<Rose::Dropdown
|
||||
@text="Credential library details"
|
||||
@showCaret={{true}}
|
||||
@ignoreClickInside={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.key-value>
|
||||
<:key>ID</:key>
|
||||
<:value>cl_nbv9375hLHs</:value>
|
||||
</dropdown.key-value>
|
||||
|
||||
<h2>Condensed Table with Shrink Cells</h2>
|
||||
<Rose::Table @style="condensed" as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>Address</row.headerCell>
|
||||
<row.headerCell @shrink={{true}}>Host Set</row.headerCell>
|
||||
<row.headerCell @shrink={{true}}>Name</row.headerCell>
|
||||
<row.headerCell>Description</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>1.1.1.1</row.cell>
|
||||
<row.cell @shrink={{true}}>host-set-1, host-set-2</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
|
||||
</row.cell>
|
||||
<row.cell>description</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer @hidden={{true}} as |footer|>
|
||||
<footer.row as |row|>
|
||||
<row.cell>Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
<dropdown.separator />
|
||||
|
||||
<h2>Condensed Table with Right and Center aligned cells</h2>
|
||||
<Rose::Table @style="condensed" as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>Address</row.headerCell>
|
||||
<row.headerCell @alignCenter={{true}}>Host Set</row.headerCell>
|
||||
<row.headerCell>Name</row.headerCell>
|
||||
<row.headerCell>Description</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>1.1.1.1</row.cell>
|
||||
<row.cell @alignCenter={{true}}>host-set-1, host-set-2</row.cell>
|
||||
<row.cell @alignRight={{true}}>optional name</row.cell>
|
||||
<row.cell @alignCenter={{true}}>description</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer as |footer|>
|
||||
<footer.row as |row|>
|
||||
<row.cell>Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
<dropdown.key-value>
|
||||
<:key>Name</:key>
|
||||
<:value>Key/Value</:value>
|
||||
</dropdown.key-value>
|
||||
|
||||
<h2>Condensed Table Key/Value Lookalike (edit mode)</h2>
|
||||
<Rose::Table @style="condensed" as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell @shrink={{true}}>Key</row.headerCell>
|
||||
<row.headerCell colspan="2">Value</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>Key</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<input.field />
|
||||
</Rose::Form::Input>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}} @alignRight={{true}}>
|
||||
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
<body.row as |row|>
|
||||
<row.cell>Key</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Form::Select
|
||||
@name="Test select"
|
||||
as |field|
|
||||
>
|
||||
<field.field as |select|>
|
||||
<select.option>Choose an option</select.option>
|
||||
<select.option @value="value-1">Value 1</select.option>
|
||||
<select.option @value="value-2">Value 2</select.option>
|
||||
<select.option @value="value-3">Value 3</select.option>
|
||||
</field.field>
|
||||
</Rose::Form::Select>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}} @alignRight={{true}}>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
<body.row as |row|>
|
||||
<row.cell>Key</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<input.field />
|
||||
</Rose::Form::Input>
|
||||
</row.cell>
|
||||
<row.cell @alignRight={{true}}>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
<body.row as |row|>
|
||||
<row.cell>Key</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Form::Textarea
|
||||
@name="field"
|
||||
readonly={{false}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell @alignRight={{true}}>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer @hidden={{true}} as |footer|>
|
||||
<footer.row as |row|>
|
||||
<row.cell colspan="3">Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
<dropdown.separator />
|
||||
|
||||
<h2>Condensed Table With Visually Hidden Header & Footer</h2>
|
||||
<Rose::Table @style="condensed" as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row @hidden={{true}} as |row|>
|
||||
<row.headerCell >Address</row.headerCell>
|
||||
<row.headerCell>Host Set</row.headerCell>
|
||||
<row.headerCell>Name</row.headerCell>
|
||||
<row.headerCell>Description</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>1.1.1.1</row.cell>
|
||||
<row.cell>host-set-1, host-set-2</row.cell>
|
||||
<row.cell>optional name</row.cell>
|
||||
<row.cell>description</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer as |footer|>
|
||||
<footer.row @hidden={{true}} as |row|>
|
||||
<row.cell>Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
<dropdown.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>
|
||||
|
||||
<h2>Table</h2>
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>Address</row.headerCell>
|
||||
<row.headerCell>Host Set</row.headerCell>
|
||||
<row.headerCell>Name</row.headerCell>
|
||||
<row.headerCell>Description</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
<body.row as |row|>
|
||||
<row.cell>1.1.1.1</row.cell>
|
||||
<row.cell>host-set-1, host-set-2</row.cell>
|
||||
<row.cell>optional name</row.cell>
|
||||
<row.cell>description</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
<table.footer as |footer|>
|
||||
<footer.row as |row|>
|
||||
<row.cell>Footer</row.cell>
|
||||
</footer.row>
|
||||
</table.footer>
|
||||
</Rose::Table>
|
||||
</Rose::Dropdown>
|
||||
|
||||
<h2>Condensed dropdown</h2>
|
||||
<Rose::Dropdown
|
||||
@style="condensed"
|
||||
@text="Credential library details"
|
||||
@showCaret={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.section @title="ID">
|
||||
<dropdown.item>cl_nbv9375hLHs</dropdown.item>
|
||||
</dropdown.section>
|
||||
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title="Name">
|
||||
<dropdown.item>Key/Value</dropdown.item>
|
||||
</dropdown.section>
|
||||
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title="Description">
|
||||
<dropdown.item>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet sem sodales neque ultrices sollicitudin vel sed justo. Vivamus vitae sollicitudin turpis.</dropdown.item>
|
||||
</dropdown.section>
|
||||
</Rose::Dropdown>
|
||||
|
||||
<h2>Normal Dropdown</h2>
|
||||
<Rose::Dropdown
|
||||
@text="Credential library details"
|
||||
@showCaret={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.section @title="ID">
|
||||
<dropdown.item>cl_nbv9375hLHs</dropdown.item>
|
||||
</dropdown.section>
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title="Name">
|
||||
<dropdown.item>Key/Value</dropdown.item>
|
||||
</dropdown.section>
|
||||
<dropdown.separator />
|
||||
|
||||
<dropdown.section @title="Description">
|
||||
<dropdown.item>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet sem sodales neque ultrices sollicitudin vel sed justo. Vivamus vitae sollicitudin turpis.</dropdown.item>
|
||||
</dropdown.section>
|
||||
</Rose::Dropdown>
|
||||
|
||||
<h2>Normal Dropdown</h2>
|
||||
<Rose::Dropdown
|
||||
@text="Credential library details"
|
||||
@showCaret={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.link @route="about">Menu item</dropdown.link>
|
||||
<dropdown.link @route="about">Menu item 2</dropdown.link>
|
||||
</Rose::Dropdown>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | rose/dropdown/key-value', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`
|
||||
<Rose::Dropdown::KeyValue>
|
||||
<:key>key</:key>
|
||||
<:value>value</:value>
|
||||
</Rose::Dropdown::KeyValue>
|
||||
`);
|
||||
|
||||
assert.equal(find('.rose-dropdown-key').textContent.trim(), 'key');
|
||||
assert.equal(find('.rose-dropdown-value').textContent.trim(), 'value');
|
||||
});
|
||||
});
|
||||
+8
-3
@@ -52,7 +52,7 @@
|
||||
"request": "~2.68.0",
|
||||
"tunnel-agent": "~0.6.0",
|
||||
"cryptiles": "~4.1.2",
|
||||
"prismjs": "~1.23.0",
|
||||
"prismjs": "^1.24.0",
|
||||
"npm-user-validate": "^1.0.1",
|
||||
"caniuse-lite": "^1.0.30001157",
|
||||
"immer": "^8.0.1",
|
||||
@@ -65,12 +65,17 @@
|
||||
"merge": "^2.1.1",
|
||||
"xmlhttprequest-ssl": "^1.6.2",
|
||||
"underscore": "^1.12.1",
|
||||
"hosted-git-info": "3.0.8",
|
||||
"hosted-git-info": "^3.0.8",
|
||||
"trim": "~1.0.1",
|
||||
"browserslist": "^4.16.5",
|
||||
"ws": "^7.4.6",
|
||||
"css-what": "^5.0.1",
|
||||
"trim-newlines": "^3.0.1"
|
||||
"trim-newlines": "^3.0.1",
|
||||
"clean-css": "^4.1.11",
|
||||
"mem": "^4.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"normalize-url": "^4.5.1",
|
||||
"glob-parent": "^5.1.2"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{{#if @model.type}}
|
||||
{{component
|
||||
(concat 'form/account/' @model.type)
|
||||
model=@model
|
||||
submit=@submit
|
||||
cancel=@cancel
|
||||
}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,127 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.attributes.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.email}}
|
||||
@label={{t 'form.email.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.full_name}}
|
||||
@label={{t 'form.full_name.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
@error={{@model.errors.description}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<form.input
|
||||
@name='issuer'
|
||||
@type='text'
|
||||
@value={{@model.attributes.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
@error={{@model.errors.issuer}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.issuer}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.issuer as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='subject'
|
||||
@type='text'
|
||||
@value={{@model.attributes.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
@error={{@model.errors.subject}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.subject}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.subject as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
{{/if}}
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
|
||||
</Rose::Form>
|
||||
@@ -180,7 +180,11 @@
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.signing_algorithms' i)
|
||||
@@ -215,7 +219,11 @@
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@@ -273,7 +281,11 @@
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.allowed_audiences' i)
|
||||
@@ -297,7 +309,11 @@
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@@ -355,7 +371,11 @@
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.claims_scopes' i)
|
||||
@@ -379,7 +399,11 @@
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@@ -456,7 +480,11 @@
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.account_claim_maps' i)
|
||||
@@ -496,7 +524,11 @@
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @addAccountClaimMapItem this.newFromClaim this.newToClaim)
|
||||
@@ -549,7 +581,11 @@
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.idp_ca_certs' i)
|
||||
@@ -572,7 +608,11 @@
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
@disabled={{if
|
||||
@model.isNew
|
||||
false
|
||||
(if form.isEditable false true)
|
||||
}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@@ -629,6 +669,16 @@
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.attributes.callback_url}}
|
||||
@label={{t 'form.callback_url.label'}}
|
||||
@helperText={{t 'form.callback_url.help'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@error={{@model.errors.name}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@error={{@model.errors.description}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.input
|
||||
@name='vault_path'
|
||||
@type='text'
|
||||
@value={{@model.attributes.path}}
|
||||
@label={{t 'resources.credential-library.form.vault_path.label'}}
|
||||
@error={{@model.errors.attributes_path}}
|
||||
@helperText={{t 'resources.credential-library.form.vault_path.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_path}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_path as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.fieldset>
|
||||
<:title>{{t 'resources.credential-library.form.http_method.label'}}</:title>
|
||||
<:description>{{t
|
||||
'resources.credential-library.form.http_method.help'
|
||||
}}</:description>
|
||||
<:body>
|
||||
<form.radioGroup
|
||||
@name='http_method'
|
||||
@selectedValue={{@model.attributes.http_method}}
|
||||
@changed={{fn (mut @model.attributes.http_method)}}
|
||||
@inline={{true}}
|
||||
disabled={{@model.isSaving}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each this.httpMethodOptions as |httpMethod|}}
|
||||
<radioGroup.radio @label={{httpMethod}} @value={{httpMethod}} />
|
||||
{{/each}}
|
||||
</form.radioGroup>
|
||||
</:body>
|
||||
</form.fieldset>
|
||||
|
||||
{{#if this.isHttpRequestBodyAllowed}}
|
||||
<form.textarea
|
||||
@name='http_request_body'
|
||||
@type='text'
|
||||
@value={{@model.attributes.http_request_body}}
|
||||
@label={{t 'resources.credential-library.form.http_request_body.label'}}
|
||||
@error={{@model.errors.attributes_http_request_body}}
|
||||
@helperText={{t
|
||||
'resources.credential-library.form.http_request_body.help'
|
||||
}}
|
||||
@disabled={{this.cannotSaveHttpRequestBody}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_http_request_body}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_http_request_body as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
{{/if}}
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,20 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { options } from 'api/models/credential-library';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default class FormCredentialLibraryIndexComponent extends Component {
|
||||
// =properties
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
httpMethodOptions = options.http_method;
|
||||
|
||||
/**
|
||||
* Only allow HTTP request body field if http_method is set to POST.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@computed('args.model.attributes.http_method')
|
||||
get isHttpRequestBodyAllowed() {
|
||||
return this.args.model.attributes.http_method?.match(/post/i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@error={{@model.errors.description}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.input
|
||||
@name='address'
|
||||
@type='text'
|
||||
@value={{@model.attributes.address}}
|
||||
@label={{t 'resources.credential-store.form.address.label'}}
|
||||
@error={{@model.errors.attributes_address}}
|
||||
@helperText={{t 'resources.credential-store.form.address.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_address}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_address as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='token'
|
||||
@type='text'
|
||||
@value={{@model.attributes.token}}
|
||||
@label={{t 'resources.credential-store.form.token.label'}}
|
||||
@error={{@model.errors.attributes_token}}
|
||||
@helperText={{t 'resources.credential-store.form.token.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_token}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_token as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.attributes.token_hmac}}
|
||||
@label={{t 'resources.credential-store.form.token_hmac.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<form.input
|
||||
@name='namespace'
|
||||
@type='text'
|
||||
@value={{@model.attributes.namespace}}
|
||||
@label={{t 'resources.credential-store.form.namespace.label'}}
|
||||
@error={{@model.errors.attributes_namespace}}
|
||||
@helperText={{t 'resources.credential-store.form.namespace.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_namespace}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_namespace as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='tls_server_name'
|
||||
@type='text'
|
||||
@value={{@model.attributes.tls_server_name}}
|
||||
@label={{t 'resources.credential-store.form.tls_server_name.label'}}
|
||||
@error={{@model.errors.attributes_tls_server_name}}
|
||||
@helperText={{t 'resources.credential-store.form.tls_server_name.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_tls_server_name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_tls_server_name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.checkbox
|
||||
@label={{t 'resources.credential-store.form.tls_skip_verify.label'}}
|
||||
@helperText={{t 'resources.credential-store.form.tls_skip_verify.help'}}
|
||||
@checked={{@model.attributes.tls_skip_verify}}
|
||||
onChange={{fn
|
||||
(mut @model.attributes.tls_skip_verify)
|
||||
(if @model.attributes.tls_skip_verify false true)
|
||||
}}
|
||||
@error={{@model.errors.attributes_tls_skip_verify}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_tls_skip_verify}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_tls_skip_verify as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.checkbox>
|
||||
|
||||
<form.textarea
|
||||
@name='client_certificate'
|
||||
@type='text'
|
||||
@value={{@model.attributes.client_certificate}}
|
||||
@label={{t 'resources.credential-store.form.client_certificate.label'}}
|
||||
@error={{@model.errors.attributes_client_certificate}}
|
||||
@helperText={{t 'resources.credential-store.form.client_certificate.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_client_certificate}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_client_certificate as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.textarea
|
||||
@name='client_certificate_key'
|
||||
@type='text'
|
||||
@value={{@model.attributes.client_certificate_key}}
|
||||
@label={{t 'resources.credential-store.form.client_certificate_key.label'}}
|
||||
@error={{@model.errors.attributes_client_certificate_key}}
|
||||
@helperText={{t
|
||||
'resources.credential-store.form.client_certificate_key.help'
|
||||
}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_client_certificate_key}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_client_certificate_key as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.attributes.client_certificate_key_hmac}}
|
||||
@label={{t
|
||||
'resources.credential-store.form.client_certificate_key_hmac.label'
|
||||
}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<form.textarea
|
||||
@name='ca_cert'
|
||||
@type='text'
|
||||
@value={{@model.attributes.ca_cert}}
|
||||
@label={{t 'resources.credential-store.form.ca_cert.label'}}
|
||||
@error={{@model.errors.attributes_ca_cert}}
|
||||
@helperText={{t 'resources.credential-store.form.ca_cert.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes_ca_cert}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes_ca_cert as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,63 @@
|
||||
{{#if this.hasAvailableCredentialLibraries}}
|
||||
<Rose::Form
|
||||
class='full-width'
|
||||
@onSubmit={{fn this.submit @submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
as |form|
|
||||
>
|
||||
<form.actions
|
||||
@submitText={{t 'resources.target.actions.add-credential-libraries'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each this.filteredCredentialLibraries as |credentialLibrary|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
<form.checkbox
|
||||
@label={{credentialLibrary.id}}
|
||||
@description={{credentialLibrary.description}}
|
||||
onChange={{fn this.toggleCredentialLibrary credentialLibrary}}
|
||||
/>
|
||||
</row.headerCell>
|
||||
<row.cell>{{credentialLibrary.name}}</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Badge>{{t
|
||||
(concat
|
||||
'resources.credential-library.types.' credentialLibrary.type
|
||||
)
|
||||
}}</Rose::Badge>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</Rose::Form>
|
||||
{{/if}}
|
||||
|
||||
{{#unless this.hasAvailableCredentialLibraries}}
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Message
|
||||
@title={{t 'resources.target.credential-library.messages.none.title'}}
|
||||
as |message|
|
||||
>
|
||||
<message.description>
|
||||
{{t 'resources.target.credential-library.messages.none.description'}}
|
||||
</message.description>
|
||||
<message.link @route='scopes.scope.targets.target.credential-libraries'>
|
||||
<Rose::Icon @name='chevron-left' />
|
||||
{{t 'actions.back'}}
|
||||
</message.link>
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
{{/unless}}
|
||||
@@ -0,0 +1,65 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { computed, action } from '@ember/object';
|
||||
import { A } from '@ember/array';
|
||||
|
||||
export default class FormTargetAddCredentialLibrariesIndexComponent extends Component {
|
||||
// =properties
|
||||
|
||||
/**
|
||||
* Array of selected credential library IDs.
|
||||
* @type {EmberArray}
|
||||
*/
|
||||
selectedCredentialLibraryIDs = A();
|
||||
|
||||
/**
|
||||
* Checks for unassigned credential libraries.
|
||||
* @param {[CredentialLibraryModel]} filteredCredentialLibraries
|
||||
* @type {boolean}
|
||||
*/
|
||||
@computed('filteredCredentialLibraries.length')
|
||||
get hasAvailableCredentialLibraries() {
|
||||
return this.filteredCredentialLibraries.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out credential libraries not already added to the target.
|
||||
* @type {[CredentialLibraryModel]}
|
||||
*/
|
||||
@computed(
|
||||
'args.{credentialLibraries.[],model.application_credential_library_ids.[]}'
|
||||
)
|
||||
get filteredCredentialLibraries() {
|
||||
// Get IDs for credential libraries already added to the current target
|
||||
const currentCredentialLibraryIDs =
|
||||
this.args.model.application_credential_library_ids.map(
|
||||
(credentialLibrary) => credentialLibrary.value
|
||||
);
|
||||
const notAddedCredentialLibraries = this.args.credentialLibraries.filter(
|
||||
({ id }) => !currentCredentialLibraryIDs.includes(id)
|
||||
);
|
||||
return notAddedCredentialLibraries;
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Add/Remove credential library to current selection
|
||||
* @param CredentialLibraryModel
|
||||
*/
|
||||
@action
|
||||
toggleCredentialLibrary(credentialLibrary) {
|
||||
if (!this.selectedCredentialLibraryIDs.includes(credentialLibrary.id)) {
|
||||
this.selectedCredentialLibraryIDs.addObject(credentialLibrary.id);
|
||||
} else {
|
||||
this.selectedCredentialLibraryIDs.removeObject(credentialLibrary.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit selected credential library ids
|
||||
*/
|
||||
@action
|
||||
submit(fn) {
|
||||
fn(this.selectedCredentialLibraryIDs);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
|
||||
<form.actions
|
||||
@submitText={{t 'actions.add-host-sets'}}
|
||||
@submitText={{t 'resources.target.actions.add-host-sets'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.username.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'resources.auth-method.title'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
@@ -31,7 +32,12 @@
|
||||
onChange={{fn this.toggleAccount account}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell>{{account.username}}</row.cell>
|
||||
<row.cell>{{account.accountName}}</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.auth-method.types.' account.type)}}
|
||||
</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<code>{{account.auth_method_id}}</code>
|
||||
</row.cell>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
// = attributes
|
||||
/**
|
||||
* Translated breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.intl.t('resources.credential-store.title_plural');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreController extends Controller {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* A credential store breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.model.displayName;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Translated credential libraries breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.intl.t('resources.credential-library.title_plural');
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Translated new credential libraries breadcrumb
|
||||
* @type {string}
|
||||
*/
|
||||
get breadCrumb() {
|
||||
return this.intl.t('titles.new');
|
||||
}
|
||||
}
|
||||
Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user