Merge branch 'main' into chore-argument-less-helper-paren-less-invocation

This commit is contained in:
Randall Morey authored and GitHub committed 2022-02-23 10:09:43 -05:00
commit 9a91cc2ea8
67 files changed
+2031 -482

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
import ApplicationAdapter from './application';
export default class HostCatalogAdapter extends ApplicationAdapter {
/**
* Overrides the default `buildURL` such that on _create_, URLs for records of
* type `plugin` get an extra query parameter `?plugin_name`. All other
* request types behave as normal.
*
* @override
* @param {string} modelName
* @param {string} id
* @return {string} url
* @return {string} requestType
*/
buildURL(modelName, id, snapshot, requestType) {
let url = super.buildURL(...arguments);
if (requestType === 'createRecord') {
const pluginName = snapshot.attr('plugin')?.name;
if (pluginName) url = `${url}?plugin_name=${pluginName}`;
}
return url;
}
}
@@ -39,11 +39,6 @@ export default class GeneratedHostCatalogModel extends BaseModel {
})
disabled;
@attr({
description: 'Attributes specific to the catalog type',
})
attributes;
@attr('number', {
description: 'Current version number of this resource.',
})
@@ -1,9 +0,0 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentHostAttributesModel extends Fragment {
@attr('string', {
description: 'The address (DNS or IP name) used to reach the host',
})
address;
}
+1 -1
View File
@@ -26,7 +26,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {boolean}
*/
get isUnknown() {
return this.isPlugin && !types.includes(this.plugin.name);
return this.isPlugin && !types.includes(this.plugin?.name);
}
/**
+12 -1
View File
@@ -1,5 +1,6 @@
import GeneratedHostSetModel from '../generated/models/host-set';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { types } from './host-catalog';
export default class HostSetModel extends GeneratedHostSetModel {
// =attributes
@@ -20,6 +21,14 @@ export default class HostSetModel extends GeneratedHostSetModel {
return this.type === 'plugin';
}
/**
* True if the host set is an unknown type.
* @type {boolean}
*/
get isUnknown() {
return this.isPlugin && !types.includes(this.plugin?.name);
}
/**
* Returns if a host-set plugin is AWS or not
* @type {boolean}
@@ -41,7 +50,9 @@ export default class HostSetModel extends GeneratedHostSetModel {
* @type {string}
*/
get compositeType() {
return this.plugin ? this.plugin.name : this.type;
if (this.isUnknown) return 'unknown';
if (this.isPlugin) return this.plugin.name;
return 'static';
}
/**
+29 -6
View File
@@ -1,12 +1,35 @@
import GeneratedHostModel from '../generated/models/host';
import { fragment } from 'ember-data-model-fragments/attributes';
import { attr } from '@ember-data/model';
export default class HostModel extends GeneratedHostModel {
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentHostAttributesModel}
*/
@fragment('fragment-host-attributes', { defaultValue: {} }) attributes;
// =attributes
// Static specific fields
@attr('string', {
description: 'The address (DNS or IP name) used to reach the host',
isNestedAttribute: true,
})
address;
// Plugin specific fields
@attr({
readOnly: true,
emptyArrayIfMissing: true,
})
ip_addresses;
@attr('string', {
description: '',
readOnly: true,
})
external_id;
// Aws specific
@attr({
readOnly: true,
emptyArrayIfMissing: true,
})
dns_names;
/**
* True if the host is static.
+9 -6
View File
@@ -234,11 +234,13 @@ export default class ApplicationSerializer extends RESTSerializer {
*/
normalizeNestedAttributes(typeClass, hash) {
typeClass.attributes.forEach((attribute) => {
const { isNestedAttribute } = attribute.options;
const attributeValue = hash.attributes?.[attribute.name];
if (isNestedAttribute && attributeValue) {
hash[attribute.name] = attributeValue;
}
const {
name,
options: { isNestedAttribute },
} = attribute;
const value = hash.attributes?.[name];
const isUndefined = value === undefined;
if (isNestedAttribute && !isUndefined) hash[name] = value;
});
return hash;
}
@@ -261,7 +263,8 @@ export default class ApplicationSerializer extends RESTSerializer {
name,
options: { isNestedSecret },
} = attribute;
if (isNestedSecret) hash[name] = null;
const value = hash.secrets?.[name];
if (isNestedSecret) hash[name] = value || null;
});
return hash;
}
+16
View File
@@ -1,4 +1,5 @@
import ApplicationSerializer from './application';
import { copy } from 'ember-copy';
const fieldByType = {
aws: ['preferred_endpoints', 'filters'],
@@ -69,4 +70,19 @@ export default class HostSetSerializer extends ApplicationSerializer {
}
return value;
}
/**
* Temporarily converts `filters` to an array if it is a string. This is a
* quirk of the API.
*
* TODO: remove once API consistently returns arrays.
*/
normalize(typeClass, hash, ...rest) {
const normalizedHash = copy(hash, true);
if (typeof normalizedHash?.filters === 'string') {
normalizedHash.filters = [normalizedHash.filters];
}
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
return normalized;
}
}
+1
View File
@@ -0,0 +1 @@
export { default } from 'api/adapters/host-catalog';
@@ -1 +0,0 @@
export { default } from 'api/models/fragment-host-attributes';
+13 -1
View File
@@ -270,7 +270,19 @@ export default function () {
return hostCatalogs.where({ scopeId });
}
);
this.post('/host-catalogs');
this.post(
'/host-catalogs',
function ({ hostCatalogs }, { queryParams: { plugin_name } }) {
const attrs = this.normalizedRequestAttrs();
if (plugin_name) {
attrs.type = 'plugin';
attrs.plugin = {
name: plugin_name,
};
}
return hostCatalogs.create(attrs);
}
);
this.get('/host-catalogs/:id');
this.patch('/host-catalogs/:id');
this.del('/host-catalogs/:id');
+17 -6
View File
@@ -47,19 +47,30 @@ export default factory.extend({
case 'aws':
return {
disable_credential_rotation: datatype.boolean(),
tenant_id: datatype.hexaDecimal(6),
client_id: datatype.hexaDecimal(6),
subscription_id: datatype.hexaDecimal(8),
secret_id: datatype.hexaDecimal(6),
secret_value: datatype.string(12),
region: address.state(),
};
case 'azure':
return {
disable_credential_rotation: datatype.boolean(),
region: address.state(),
tenant_id: datatype.hexaDecimal(6),
client_id: datatype.hexaDecimal(6),
subscription_id: datatype.hexaDecimal(8),
};
}
},
secrets() {
switch (this.plugin?.name) {
case 'aws':
return {
access_key_id: datatype.hexaDecimal(8),
secret_access_key: datatype.string(12),
};
case 'azure':
return {
secret_id: datatype.hexaDecimal(6),
secret_value: datatype.string(12),
};
}
},
withChildren: trait({
+1 -1
View File
@@ -43,7 +43,7 @@ export default factory.extend({
// Azure specific
filter() {
if (this.plugin?.name === 'azure') {
return `${database.column}=${database.collation}`;
return `${database.column()}=${database.collation()}`;
}
},
});
+23
View File
@@ -1,5 +1,6 @@
import factory from '../generated/factories/host';
import permissions from '../helpers/permissions';
import { internet, datatype } from 'faker';
export default factory.extend({
id: (i) => `host-id-${i}`,
@@ -18,4 +19,26 @@ export default factory.extend({
return this.hostCatalog.plugin;
}
},
ip_addresses() {
const addressesAmount = Math.floor(Math.random() * 6) + 1;
let result = [];
for (let i = 0; i < addressesAmount; ++i) {
result.push(internet.ip());
}
return result;
},
external_id() {
return datatype.uuid();
},
// Only aws plugins have dns_names
dns_names() {
if (this.plugin?.name === 'aws') {
const dnsNamesAmount = Math.floor(Math.random() * 6) + 1;
let result = [];
for (let i = 0; i < dnsNamesAmount; i++) {
result.push(internet.domainName());
}
return result;
}
},
});
@@ -1,5 +1,5 @@
import { Factory } from 'ember-cli-mirage';
import { random, date, internet, datatype } from 'faker';
import { random, date, datatype, internet } from 'faker';
/**
* GeneratedHostModelFactory
+2 -2
View File
@@ -36,12 +36,12 @@
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
"@babel/core": "^7.16.5",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-auto-import": "^1.12.0",
"ember-cli": "~4.1.0",
"ember-cli-dependency-checker": "^3.2.0",
@@ -0,0 +1,48 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Adapter | host catalog', function (hooks) {
setupTest(hooks);
test('it generates correct createRecord URLs for static host catalogs', function (assert) {
assert.expect(1);
const scopeID = 'o_1';
const mockSnapshot = {
adapterOptions: {
scopeID,
},
attr() {
return null;
},
};
const adapter = this.owner.lookup('adapter:host-catalog');
const createRecordURL = adapter.buildURL(
'host-catalog',
null,
mockSnapshot,
'createRecord'
);
assert.equal(createRecordURL, '/v1/host-catalogs');
});
test('it generates correct createRecord URLs for dynamic host catalogs with extra query parameter ?plugin_name', function (assert) {
assert.expect(1);
const scopeID = 'o_1';
const mockSnapshot = {
adapterOptions: {
scopeID,
},
attr() {
return { name: 'aws' };
},
};
const adapter = this.owner.lookup('adapter:host-catalog');
const createRecordURL = adapter.buildURL(
'host-catalog',
null,
mockSnapshot,
'createRecord'
);
assert.equal(createRecordURL, '/v1/host-catalogs?plugin_name=aws');
});
});
@@ -183,15 +183,19 @@ module('Unit | Model | host set', function (hooks) {
test('get compositeType return expected values', async function (assert) {
assert.expect(3);
const store = this.owner.lookup('service:store');
const modelPlugin = store.createRecord('host-set', {
const modelPlugin1 = store.createRecord('host-set', {
type: 'plugin',
plugin: { name: 'aws' },
});
const modelPlugin2 = store.createRecord('host-set', {
type: 'plugin',
plugin: { name: 'Test name' },
});
const modelStatic = store.createRecord('host-set', {
type: 'static',
});
assert.equal(typeof modelPlugin.compositeType, 'string');
assert.equal(modelPlugin.compositeType, 'Test name');
assert.equal(modelPlugin1.compositeType, 'aws');
assert.equal(modelPlugin2.compositeType, 'unknown');
assert.equal(modelStatic.compositeType, 'static');
});
@@ -4,14 +4,6 @@ import { setupTest } from 'ember-qunit';
module('Unit | Serializer | host catalog', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {});
@@ -171,4 +163,159 @@ module('Unit | Serializer | host catalog', function (hooks) {
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it normalizes an static host catalog as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
const hostCatalog = store.createRecord('host-catalog').constructor;
const payload = {
id: '1',
name: 'Host catalog test',
description: 'Test description',
authorized_actions: ['no-op'],
type: 'static',
};
const normalized = serializer.normalizeSingleResponse(
store,
hostCatalog,
payload
);
assert.deepEqual(normalized, {
data: {
id: '1',
type: 'host-catalog',
attributes: {
name: 'Host catalog test',
description: 'Test description',
authorized_actions: ['no-op'],
type: 'static',
access_key_id: null,
secret_access_key: null,
secret_id: null,
secret_value: null,
},
relationships: {},
},
included: [],
});
});
test('it normalizes an aws plugin type host catalog as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
const hostCatalog = store.createRecord('host-catalog').constructor;
const payload = {
id: '1',
name: 'Host catalog test',
description: 'Test description',
authorized_actions: ['no-op'],
type: 'plugin',
plugin: {
id: 'plugin-id-5',
name: 'aws',
description: 'aws host catalog',
},
attributes: {
disable_credential_rotation: false,
region: 'Illinois',
},
secrets: {
access_key_id: '0xF3B0a6f8',
secret_access_key: 'zq{2:IVc8@W^',
},
};
const normalized = serializer.normalizeSingleResponse(
store,
hostCatalog,
payload
);
assert.deepEqual(normalized, {
data: {
id: '1',
type: 'host-catalog',
attributes: {
name: 'Host catalog test',
type: 'plugin',
description: 'Test description',
authorized_actions: ['no-op'],
plugin: {
id: 'plugin-id-5',
name: 'aws',
description: 'aws host catalog',
},
disable_credential_rotation: false,
region: 'Illinois',
access_key_id: '0xF3B0a6f8',
secret_access_key: 'zq{2:IVc8@W^',
secret_id: null,
secret_value: null,
},
relationships: {},
},
included: [],
});
});
test('it normalizes an azure plugin type host catalog as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
const hostCatalog = store.createRecord('host-catalog').constructor;
const payload = {
id: '1',
name: 'Host catalog test',
description: 'Test description',
authorized_actions: ['no-op', 'read', 'update', 'delete'],
type: 'plugin',
plugin: {
id: 'plugin-id-6',
name: 'azure',
description: 'azure host catalog',
},
attributes: {
disable_credential_rotation: true,
tenant_id: 'tenant',
client_id: 'client',
subscription_id: 'subscription',
},
secrets: {
secret_id: '0xF3B0a6f8',
secret_value: 'zq{2:IVc8@W^',
},
};
const normalized = serializer.normalizeSingleResponse(
store,
hostCatalog,
payload
);
assert.deepEqual(normalized, {
data: {
id: '1',
type: 'host-catalog',
attributes: {
name: 'Host catalog test',
type: 'plugin',
description: 'Test description',
authorized_actions: ['no-op', 'read', 'update', 'delete'],
plugin: {
id: 'plugin-id-6',
name: 'azure',
description: 'azure host catalog',
},
disable_credential_rotation: true,
secret_id: '0xF3B0a6f8',
secret_value: 'zq{2:IVc8@W^',
client_id: 'client',
tenant_id: 'tenant',
subscription_id: 'subscription',
access_key_id: null,
secret_access_key: null,
},
relationships: {},
},
included: [],
});
});
});
+84 -11
View File
@@ -4,19 +4,92 @@ import { setupTest } from 'ember-qunit';
module('Unit | Serializer | host', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('host');
assert.ok(serializer);
test('it serializes host with type static as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('host', {
name: 'Host 1',
compositeType: 'static',
description: 'Description',
host_catalog_id: '123',
version: 1,
address: '188e:68a9:b342:c05c:2595:2f46:499c:759f',
});
assert.deepEqual(record.serialize(), {
name: 'Host 1',
description: 'Description',
type: 'static',
host_catalog_id: '123',
version: 1,
attributes: {
address: '188e:68a9:b342:c05c:2595:2f46:499c:759f',
},
});
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('host', {});
test('it normalizes type static record as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host');
const host = store.createRecord('host').constructor;
const payload = {
id: '1',
name: 'host test',
type: 'static',
authorized_actions: ['no-op', 'read'],
address: '10.0.0.1',
};
const normalized = serializer.normalizeSingleResponse(store, host, payload);
assert.deepEqual(normalized, {
included: [],
data: {
id: '1',
type: 'host',
attributes: {
name: 'host test',
type: 'static',
authorized_actions: ['no-op', 'read'],
address: '10.0.0.1',
dns_names: [],
ip_addresses: [],
},
relationships: {},
},
});
});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
test('it normalizes type plugin (aws) record as expected', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host');
const host = store.createRecord('host').constructor;
const payload = {
id: '1',
name: 'host test',
type: 'plugin',
plugin: {
name: 'aws',
},
authorized_actions: ['no-op', 'read'],
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
dns_names: ['test.example.internal', 'test.example.external'],
};
const normalized = serializer.normalizeSingleResponse(store, host, payload);
assert.deepEqual(normalized, {
included: [],
data: {
id: '1',
type: 'host',
attributes: {
name: 'host test',
type: 'plugin',
authorized_actions: ['no-op', 'read'],
ip_addresses: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
dns_names: ['test.example.internal', 'test.example.external'],
plugin: { name: 'aws' },
},
relationships: {},
},
});
});
});
+2 -2
View File
@@ -28,13 +28,13 @@
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"core": "*",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-auto-import": "^1.12.0",
"ember-cli": "~4.1.0",
"ember-cli-dependency-checker": "^3.2.0",
+2 -2
View File
@@ -42,12 +42,12 @@
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-auto-import": "^1.12.0",
"ember-cli": "~4.1.0",
"ember-cli-dependency-checker": "^3.2.0",
@@ -36,3 +36,4 @@ toggle-secret: Toggle secret visibility
minimize: Minimize
fullscreen: Fullscreen
clear-all-filters: Clear all filters
learn-more: Learn more
+5
View File
@@ -30,12 +30,17 @@ titles:
label: Label
value: Value
no-results: No Results
choose-a-provider: Choose a provider
provider: Provider
ip-addresses: Latest IP Addresses
descriptions:
empty-set: There are no items to display yet. You may be able to add items or try back later.
origin-initialization: To get started, please enter your origin URL
create-but-not-list: You do not have permission to list {resource}, but you can create new instances.
neither-list-nor-create: You do not have permission to list or create {resource}.
no-results: We found no matching records.
choose-a-provider: There are several built-in plugins for the most common providers. You can choose from on of the included plugins or use a custom plugin.
provider: This is the provider for this host catalog. The format of the host set filters are specific to each provider.
questions:
delete-confirm: Are you sure you want to delete this resource?
remove-confirm: Are you sure you want to remove this association?
+19
View File
@@ -118,3 +118,22 @@ callback_url:
filter:
label: Filter
help: A boolean expression defining a filter run against the provided information.
tenant_id:
label: Tenant ID
help: The tenant (directory) ID for your Azure Active Directory application.
subscription_id:
label: Subscription ID
help: The subscription ID for the subscription with read access.
access_key_id:
label: Access Key ID
help: The access key ID found on the Security Credentials page in AWS.
secret_access_key:
label: Secret Access Key
help: The secret access key found on the Security Credentials page in AWS.
region:
label: Region
secret:
label: Client secret value
help: The client secret value for the client you want Boundary to use
disable_credential_rotation:
label: Disable credential rotation
+17 -11
View File
@@ -78,16 +78,25 @@ host-catalog:
title: Host Catalog
title_plural: Host Catalogs
description: A host catalog is a collection of hosts and host sets.
type: Type
messages:
welcome:
title: Welcome to Host Catalogs
actions:
delete: Delete Host Catalog
help:
static: Provide a list of hosts and host sets with static addresses
dynamic: Automate discovery of target hosts and services on Azure or AWS
types:
static: Static
unknown: Unknown
unknown: Plugin
aws: AWS
azure: Azure
dynamic: Dynamic
form:
azure_client_id:
label: Client ID
help: The client ID for the client you want Boundary to use.
host-set:
title: Host Set
title_plural: Host Sets
@@ -98,6 +107,13 @@ host-set:
delete: Delete Host Set
create-and-add-host: Create and Add Host
add-hosts: Add Existing Host
form:
filter:
label: Filter
help: Create a filter to select resources using values such as tag name or tag value. Filter format is specific to each provider
preferred_endpoints:
label: Preferred Endpoints
help: Selects addresses of hosts, in cidr:<valid IPv4/6 CIDR> or dns:<globbed name> format
messages:
welcome:
title: Welcome to Host Sets
@@ -112,11 +128,6 @@ host-set:
add-none:
title: No Hosts Available
description: No hosts available for selection.
types:
static: Static
unknown: Unknown
aws: AWS
azure: Azure
host:
title: Host
title_plural: Hosts
@@ -134,11 +145,6 @@ host:
add: Add Host
create: New Host
delete: Delete Host
types:
static: Static
unknown: Unknown
aws: AWS
azure: Azure
session:
title: Session
title_plural: Sessions
@@ -30,6 +30,10 @@
border-radius: sizing.rems(xxxs);
box-shadow: inset 0px 3px 1px rgba(var(--stark-components), var(--opacity-4));
&::placeholder {
color: var(--ui-gray-subtler-1);
}
&.error {
color: var(--failure);
border-color: currentColor;
@@ -32,12 +32,12 @@ $radio-svg: (
);
.rose-form-radio-card {
$cardWideWidth: $size-m * 11;
$cardWideWidth: $size-m * 10;
$cardTileWidth: $size-m * 7.25;
$borderWidth: $size-xxxs * 2;
&.wide {
width: ($size-m) * 23;
width: ($size-m) * 20;
}
&.tile {
@@ -61,7 +61,7 @@ $radio-svg: (
display: inline-flex;
align-items: center;
justify-content: center;
margin: $size-m $size-xs $size-xl 0;
margin: $size-m $size-xs $size-m 0;
.radio-card-overlay {
position: absolute;
+4 -4
View File
@@ -41,7 +41,7 @@
"devDependencies": {
"@babel/core": "^7.16.5",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"@hashicorp/flight-icons": "^2.0.1",
@@ -55,13 +55,13 @@
"@storybook/components": "^6.3.12",
"@storybook/core-events": "^6.3.0",
"@storybook/ember": "^6.3.8",
"@storybook/ember-cli-storybook": "^0.4.0",
"@storybook/ember-cli-storybook": "^0.5.0",
"@storybook/theming": "^6.3.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-ember-modules-api-polyfill": "^2.12.0",
"babel-plugin-ember-modules-api-polyfill": "^3.5.0",
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-auto-import": "^1.12.0",
"ember-cli": "~4.1.0",
"ember-cli-dependency-checker": "^3.2.0",
+1 -1
View File
@@ -40,7 +40,7 @@
},
"devDependencies": {
"cz-conventional-changelog": "^3.1.0",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"git-cz": "^4.3.1",
"license-checker": "^25.0.1"
},
@@ -0,0 +1,171 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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}}
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.fieldset>
<:title>{{t 'form.type.label'}}</:title>
<:body>
<form.radioGroup
@name='types'
@selectedValue={{@model.type}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.static'}}
@value='static'
@helperText={{t 'resources.host-catalog.help.static'}}
@layout='wide'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.dynamic'}}
@value='plugin'
@helperText={{t 'resources.host-catalog.help.dynamic'}}
@layout='wide'
/>
</form.radioGroup>
</:body>
</form.fieldset>
<Rose::Separator />
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
</form.fieldset>
<form.fieldset>
<:title>{{t 'titles.choose-a-provider'}}</:title>
<:description>{{t 'descriptions.choose-a-provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value='aws'
@layout='tile'
@icon='flight-icons/svg/aws-color-16'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value='azure'
@layout='tile'
@icon='flight-icons/svg/azure-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{else}}
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:description>{{t 'descriptions.provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value='aws'
@layout='tile'
@icon='flight-icons/svg/aws-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{/if}}
<form.input
@name='region'
@type='region'
@value={{@model.region}}
@label={{t 'form.region.label'}}
readonly={{false}}
/>
<form.input
@name='access_key_id'
@type='access_key_id'
@value={{@model.access_key_id}}
@label={{t 'form.access_key_id.label'}}
@helperText={{t 'form.access_key_id.help'}}
@linkText={{t 'actions.learn-more'}}
readonly={{false}}
/>
<form.input
@name='secret_access_key'
@type='secret_access_key'
@value={{@model.secret_access_key}}
@label={{t 'form.secret_access_key.label'}}
@helperText={{t 'form.secret_access_key.help'}}
@linkText={{t 'actions.learn-more'}}
readonly={{false}}
/>
<form.checkbox
@name='disable_credential_rotation'
@checked={{@model.disable_credential_rotation}}
@label={{t 'form.disable_credential_rotation.label'}}
@onChange={{fn this.toggleDisableCredentialRotation @model}}
/>
&nbsp;
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,11 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class FormHostCatalogAwsComponent extends Component {
// =actions
@action
toggleDisableCredentialRotation(model) {
model.disable_credential_rotation = !model.disable_credential_rotation;
}
}
@@ -0,0 +1,180 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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}}
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.fieldset>
<:title>{{t 'form.type.label'}}</:title>
<:body>
<form.radioGroup
@name='types'
@selectedValue={{@model.type}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.static'}}
@value='static'
@helperText={{t 'resources.host-catalog.help.static'}}
@layout='wide'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.dynamic'}}
@value='plugin'
@helperText={{t 'resources.host-catalog.help.dynamic'}}
@layout='wide'
/>
</form.radioGroup>
</:body>
</form.fieldset>
<Rose::Separator />
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
</form.fieldset>
<form.fieldset>
<:title>{{t 'titles.choose-a-provider'}}</:title>
<:description>{{t 'descriptions.choose-a-provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value='aws'
@layout='tile'
@icon='flight-icons/svg/aws-color-16'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value='azure'
@layout='tile'
@icon='flight-icons/svg/azure-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{else}}
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:description>{{t 'descriptions.provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value='azure'
@layout='tile'
@icon='flight-icons/svg/azure-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{/if}}
<form.input
@name='tenant_id'
@type='tenant_id'
@value={{@model.tenant_id}}
@label={{t 'form.tenant_id.label'}}
@helperText={{t 'form.tenant_id.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url @doc}}
readonly={{false}}
/>
<form.input
@name='subscription_id'
@type='subscription_id'
@value={{@model.subscription_id}}
@label={{t 'form.subscription_id.label'}}
@helperText={{t 'form.subscription_id.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url @doc}}
readonly={{false}}
/>
<form.input
@name='client_id'
@type='client_id'
@value={{@model.client_id}}
@label={{t 'resources.host-catalog.form.azure_client_id.label'}}
@helperText={{t 'resources.host-catalog.form.azure_client_id.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url @doc}}
readonly={{false}}
/>
<form.input
@name='secret_value'
@type='secret_value'
@value={{@model.secret_value}}
@label={{t 'form.secret.label'}}
@helperText={{t 'form.secret.help'}}
@linkText={{t 'actions.learn-more'}}
@link={{doc-url @doc}}
readonly={{false}}
/>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -1,56 +1,8 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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 (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
{{component
(concat 'form/host-catalog/' @model.compositeType)
model=@model
submit=@submit
cancel=@cancel
changeType=@changeType
doc=@doc
}}
@@ -0,0 +1,115 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<form.input
@name='name'
@type='name'
@value={{@model.name}}
@label={{t 'form.name.label'}}
@helperText={{t 'form.name.help'}}
@error={{@model.errors.name}}
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'}}
@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.fieldset>
<:title>{{t 'form.type.label'}}</:title>
<:body>
<form.radioGroup
@name='types'
@selectedValue={{@model.type}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.static'}}
@value='static'
@helperText={{t 'resources.host-catalog.help.static'}}
@layout='wide'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.dynamic'}}
@value='plugin'
@helperText={{t 'resources.host-catalog.help.dynamic'}}
@layout='wide'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{#if @model.isPlugin}}
<Rose::Separator />
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
</form.fieldset>
<form.fieldset>
<:title>{{t 'titles.choose-a-provider'}}</:title>
<:description>{{t 'descriptions.choose-a-provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@changed={{@changeType}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value='aws'
@layout='tile'
/>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value='azure'
@layout='tile'
/>
</form.radioGroup>
</:body>
</form.fieldset>
{{/if}}
{{/if}}
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,247 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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>
<form.input
@value={{@model.type}}
@label={{t 'form.type.label'}}
readonly={{true}}
@disabled={{true}}
/>
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:description>{{t 'descriptions.provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value={{@model.compositeType}}
@layout='tile'
@icon='flight-icons/svg/aws-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
<Rose::Form::Fieldset>
<:title>{{t 'resources.host-set.form.preferred_endpoints.label'}}</:title>
<:description>{{t
'resources.host-set.form.preferred_endpoints.help'
}}</:description>
<:body>
{{#if @model.errors.preferred_endpoints}}
<Rose::Form::Errors as |errors|>
{{#each @model.errors.preferred_endpoints as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</Rose::Form::Errors>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.preferred_endpoints as |endpoint i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@value={{endpoint.value}}
@error={{@model.errors.preferred_endpoints}}
@contextual={{true}}
placeholder='cidr:2001:4860:4860::8888/32'
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'preferred_endpoints' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@value={{@model.preferred_endpoint}}
placeholder='cidr:2001:4860:4860::8888/32'
/>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn
@addStringItem
'preferred_endpoints'
@model.preferred_endpoint
)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
<Rose::Form::Fieldset>
<:title>{{t 'resources.host-set.form.filter.label'}}</:title>
<:description>{{t 'resources.host-set.form.filter.help'}}</:description>
<:body>
{{#if @model.errors.filters}}
<Rose::Form::Errors as |errors|>
{{#each @model.errors.filters as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</Rose::Form::Errors>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.filters as |filter i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@value={{filter.value}}
@error={{@model.errors.filters}}
@contextual={{true}}
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'filters' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<body.row as |row|>
<row.cell>
<form.input @type='text' @value={{@model.filter}} />
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @addStringItem 'filters' @model.filter)}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,194 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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>
<form.input
@value={{@model.type}}
@label={{t 'form.type.label'}}
readonly={{true}}
@disabled={{true}}
/>
<form.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:description>{{t 'descriptions.provider'}}</:description>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value={{@model.compositeType}}
@layout='tile'
@icon='flight-icons/svg/azure-color-16'
/>
</form.radioGroup>
</:body>
</form.fieldset>
<Rose::Form::Fieldset>
<:title>{{t 'resources.host-set.form.preferred_endpoints.label'}}</:title>
<:description>{{t
'resources.host-set.form.preferred_endpoints.help'
}}</:description>
<:body>
{{#if @model.errors.preferred_endpoints}}
<Rose::Form::Errors as |errors|>
{{#each @model.errors.preferred_endpoints as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</Rose::Form::Errors>
{{/if}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row @hidden={{true}} as |row|>
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
<row.headerCell />
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.preferred_endpoints as |endpoint i|}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@value={{endpoint.value}}
@error={{@model.errors.preferred_endpoints}}
@contextual={{true}}
placeholder='cidr:2001:4860:4860::8888/32'
as |field|
>
<field.field />
</form.input>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='warning'
@iconOnly='flight-icons/svg/trash-16'
title={{t 'actions.remove'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on 'click' (fn @removeItemByIndex 'preferred_endpoints' i)}}
>
{{t 'actions.remove'}}
</Rose::Button>
</row.cell>
</body.row>
{{/each}}
<body.row as |row|>
<row.cell>
<form.input
@type='text'
@value={{@model.preferred_endpoint}}
placeholder='cidr:2001:4860:4860::8888/32'
/>
</row.cell>
<row.cell @shrink={{true}}>
<Rose::Button
@style='secondary'
title={{t 'actions.add'}}
@disabled={{if
@model.isNew
false
(if form.isEditable false true)
}}
{{on
'click'
(fn
@addStringItem
'preferred_endpoints'
@model.preferred_endpoint
)
}}
>
{{t 'actions.add'}}
</Rose::Button>
</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
</Rose::Form::Fieldset>
<form.textarea
@name='filter'
@type='filter'
@value={{@model.filter}}
@label={{t 'resources.host-set.form.filter.label'}}
@helperText={{t 'resources.host-set.form.filter.help'}}
@linkText={{t 'form. tiltes.learn-more'}}
@error={{@model.errors.description}}
disabled={{@model.isSaving}}
readonly={{false}}
as |field|
>
{{#if @model.errors.filter}}
<field.errors as |errors|>
{{#each @model.errors.filter as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.textarea>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -1,63 +1,8 @@
<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'}}
@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 (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
{{component
(concat 'form/host-set/' @model.compositeType)
model=@model
submit=@submit
cancel=@cancel
removeItemByIndex=@removeItemByIndex
addStringItem=@addStringItem
}}
@@ -0,0 +1,57 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<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 (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,97 @@
<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'}}
@error={{@model.errors.name}}
@helperText={{t 'form.name.help'}}
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.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.aws'}}
@value={{@model.compositeType}}
@layout='tile'
@icon={{concat 'flight-icons/svg/' @model.compositeType '-color-16'}}
/>
</form.radioGroup>
</:body>
</form.fieldset>
<form.fieldset>
<:title>{{t 'titles.ip-addresses'}}</:title>
<:body>
{{#each @model.ip_addresses as |ip_address|}}
<form.input
@value={{ip_address}}
readonly={{true}}
@disabled={{true}}
/>
{{/each}}
</:body>
</form.fieldset>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -0,0 +1,97 @@
<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'}}
@error={{@model.errors.name}}
@helperText={{t 'form.name.help'}}
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.fieldset>
<:title>{{t 'titles.provider'}}</:title>
<:body>
<form.radioGroup
@name='provider'
@selectedValue={{@model.compositeType}}
@disabled={{true}}
as |radioGroup|
>
<radioGroup.card
@label={{t 'resources.host-catalog.types.azure'}}
@value={{@model.compositeType}}
@layout='tile'
@icon={{concat 'flight-icons/svg/' @model.compositeType '-color-16'}}
/>
</form.radioGroup>
</:body>
</form.fieldset>
<form.fieldset>
<:title>{{t 'titles.ip-addresses'}}</:title>
<:body>
{{#each @model.ip_addresses as |ip_address|}}
<form.input
@value={{ip_address}}
readonly={{true}}
@disabled={{true}}
/>
{{/each}}
</:body>
</form.fieldset>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
+18 -82
View File
@@ -1,82 +1,18 @@
<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'}}
@error={{@model.errors.name}}
@helperText={{t 'form.name.help'}}
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 'form.address.label'}}
@error={{@model.errors.name}}
@helperText={{t 'form.address.help'}}
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>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
{{#if @model.compositeType}}
{{component
(concat 'form/host/' @model.compositeType)
model=@model
submit=@submit
cancel=@cancel
}}
{{else}}
{{! create-and-add-host cancel route doesn't
have a model and this else part is required to prevent
erroring out here. This has been noted in the tech debt doc to further investigate }}
{{component
(concat 'form/host/' 'static')
model=@model
submit=@submit
cancel=@cancel
}}
{{/if}}
@@ -0,0 +1,82 @@
<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'}}
@error={{@model.errors.name}}
@helperText={{t 'form.name.help'}}
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.address}}
@label={{t 'form.address.label'}}
@error={{@model.errors.address}}
@helperText={{t 'form.address.help'}}
readonly={{false}}
as |field|
>
{{#if @model.errors.address}}
<field.errors as |errors|>
{{#each @model.errors.address as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
{{#if (can 'save model' @model)}}
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -34,9 +34,7 @@
</row.cell>
<row.cell>{{hostSet.name}}</row.cell>
<row.cell>
<Rose::Badge>{{t
(concat 'resources.host-set.types.' hostSet.type)
}}</Rose::Badge>
<HostCatalogTypeBadge @model={{hostSet}} />
</row.cell>
<row.cell>
<code>{{hostSet.host_catalog_id}}</code>
@@ -0,0 +1,15 @@
{{#if @model.isStatic}}
<Rose::Badge>
{{t (concat 'resources.host-catalog.types.' @model.compositeType)}}
</Rose::Badge>
{{else if @model.isUnknown}}
<Rose::Badge>
{{t (concat 'resources.host-catalog.types.' @model.compositeType)}}
</Rose::Badge>
{{else}}
<Rose::Badge
@icon={{concat 'flight-icons/svg/' @model.compositeType '-color-16'}}
>
{{t (concat 'resources.host-catalog.types.' @model.compositeType)}}
</Rose::Badge>
{{/if}}
@@ -0,0 +1,7 @@
import Controller from '@ember/controller';
export default class ScopesScopeHostCatalogsNewController extends Controller {
// =attributes
queryParams = ['type'];
}
@@ -1,4 +1,5 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetRoute extends Route {
// =methods
@@ -49,4 +50,30 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsHostSetRoute exte
}
);
}
/**
* Adds a string item to array `property` on the passed `filter`.
* @param {hostSetModel} hostSet
* @param {string} property
* @param {string} value
*/
@action
async addStringItem(hostSet, property, value) {
const array = hostSet.get(property);
array.addObject({ value });
}
/**
* Removes an item from array `property` at `index` on the
* passed `hostSet`.
* @param {hostSetModel} hostSet
* @param {string} property
* @param {number} index
*/
@action
async removeItemByIndex(hostSet, property, index) {
const array = hostSet.get(property);
array.removeAt(index);
}
}
@@ -1,18 +1,22 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeHostCatalogsHostCatalogHostSetsNewRoute extends Route {
// =methods
// =services
@service router;
// =methods
/**
* Creates a new unsaved host set in current host catalog scope.
* @return {HostSetModel}
*/
model() {
const { id: host_catalog_id } = this.modelFor(
const { id: host_catalog_id, compositeType } = this.modelFor(
'scopes.scope.host-catalogs.host-catalog'
);
return this.store.createRecord('host-set', {
type: 'static',
compositeType,
host_catalog_id,
});
}
@@ -47,4 +51,29 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsNewRoute extends
model: model,
});
}
/**
* Adds a string item to array `property` on the passed `filter`.
* @param {hostSetModel} hostSet
* @param {string} property
* @param {string} value
*/
@action
async addStringItem(hostSet, property, value) {
const array = hostSet.get(property);
array.addObject({ value });
}
/**
* Removes an item from array `property` at `index` on the
* passed `hostSet`.
* @param {hostSetModel} hostSet
* @param {string} property
* @param {number} index
*/
@action
async removeItemByIndex(hostSet, property, index) {
const array = hostSet.get(property);
array.removeAt(index);
}
}
@@ -1,17 +1,54 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeHostCatalogsNewRoute extends Route {
// =services
@service router;
// =attributes
queryParams = {
type: {
refreshModel: true,
},
};
// =methods
/**
* Creates a new unsaved host catalog belonging to the current scope.
* Also rollback/destroy any new, unsaved instances from this route before
* creating another, but reuse name/description if available.
* @return {HostCatalogModel}
*/
model() {
model({ type: compositeType = 'static' }) {
const scopeModel = this.modelFor('scopes.scope');
let name, description, disable_credential_rotation;
if (this.currentModel?.isNew) {
({ name, description } = this.currentModel);
this.currentModel.rollbackAttributes();
}
if (compositeType === 'azure') {
disable_credential_rotation = true;
}
return this.store.createRecord('host-catalog', {
type: 'static',
scopeModel,
compositeType,
name,
description,
disable_credential_rotation,
});
}
/**
* Update type of host catalog
* @param {string} type
*/
@action
changeType(type) {
if (type === 'plugin') type = 'aws';
this.router.replaceWith({ queryParams: { type } });
}
}
@@ -14,9 +14,9 @@
>
{{t 'resources.host-set.actions.add-hosts'}}
</dropdown.link>
<dropdown.separator />
{{/if}}
{{#if (can 'delete model' @model)}}
<dropdown.separator />
<dropdown.button
@style='danger'
@disabled={{@model.canSave}}
@@ -8,7 +8,9 @@
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
{{#if @model.isStatic}}
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
{{/if}}
</header.row>
</table.header>
<table.body as |body|>
@@ -24,7 +26,7 @@
<p>{{host.description}}</p>
</row.headerCell>
<row.cell>
<Rose::Badge>{{host.type}}</Rose::Badge>
<HostCatalogTypeBadge @model={{host}} />
</row.cell>
<row.cell>
<Copyable
@@ -2,4 +2,6 @@
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
@removeItemByIndex={{route-action 'removeItemByIndex' @model}}
@addStringItem={{route-action 'addStringItem' @model}}
/>
@@ -25,21 +25,7 @@
<p>{{hostSet.description}}</p>
</row.headerCell>
<row.cell>
{{#if hostSet.isStatic}}
<Rose::Badge>
{{t (concat 'resources.host-set.types.' hostSet.compositeType)}}
</Rose::Badge>
{{else}}
<Rose::Badge
@icon={{concat
'flight-icons/svg/'
hostSet.compositeType
'-color-16'
}}
>
{{t (concat 'resources.host-set.types.' hostSet.compositeType)}}
</Rose::Badge>
{{/if}}
<HostCatalogTypeBadge @model={{hostSet}} />
</row.cell>
<row.cell>
<Copyable
@@ -4,4 +4,6 @@
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
@removeItemByIndex={{route-action 'removeItemByIndex' @model}}
@addStringItem={{route-action 'addStringItem' @model}}
/>
@@ -27,21 +27,7 @@
<p>{{host.description}}</p>
</row.headerCell>
<row.cell>
{{#if host.isStatic}}
<Rose::Badge>
{{t (concat 'resources.host.types.' host.compositeType)}}
</Rose::Badge>
{{else}}
<Rose::Badge
@icon={{concat
'flight-icons/svg/'
host.compositeType
'-color-16'
}}
>
{{t (concat 'resources.host.types.' host.compositeType)}}
</Rose::Badge>
{{/if}}
<HostCatalogTypeBadge @model={{host}} />
</row.cell>
<row.cell>
<Copyable
@@ -53,7 +39,7 @@
</Copyable>
</row.cell>
<row.cell>
<code>{{host.attributes.address}}</code>
<code>{{host.address}}</code>
</row.cell>
</body.row>
{{/each}}
@@ -56,40 +56,7 @@
<p>{{hostCatalog.description}}</p>
</row.headerCell>
<row.cell>
{{#if hostCatalog.isStatic}}
<Rose::Badge>
{{t
(concat
'resources.host-catalog.types.'
hostCatalog.compositeType
)
}}
</Rose::Badge>
{{else if hostCatalog.isUnknown}}
<Rose::Badge>
{{t
(concat
'resources.host-catalog.types.'
hostCatalog.compositeType
)
}}
</Rose::Badge>
{{else}}
<Rose::Badge
@icon={{concat
'flight-icons/svg/'
hostCatalog.compositeType
'-color-16'
}}
>
{{t
(concat
'resources.host-catalog.types.'
hostCatalog.compositeType
)
}}
</Rose::Badge>
{{/if}}
<HostCatalogTypeBadge @model={{hostCatalog}} />
</row.cell>
<row.cell>
<Copyable
@@ -18,6 +18,8 @@
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
@changeType={{route-action 'changeType'}}
@doc='host-catalog.set-up-cloud-hosts'
/>
</page.body>
@@ -38,9 +38,7 @@
<p>{{hostSet.model.description}}</p>
</row.headerCell>
<row.cell>
<Rose::Badge>{{t
(concat 'resources.host-set.types.' hostSet.model.type)
}}</Rose::Badge>
<HostCatalogTypeBadge @model={{hostSet.model}} />
</row.cell>
<row.cell>
<Copyable
+1
View File
@@ -53,6 +53,7 @@ module.exports = function (environment) {
'group.add-members': '/groups/add-members',
'host-catalog': '/host-catalogs',
'host-catalog.new': '/host-catalogs/new',
'host-catalog.set-up-cloud-hosts': '/host-catalog-set-up-cloud-hosts',
'host-set': '/host-sets',
'host-set.new': '/host-sets/new',
'host-set.add-hosts': '/host-sets/add-hosts',
+2 -2
View File
@@ -38,7 +38,7 @@
"@babel/core": "^7.16.5",
"@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^2.0.2",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
@@ -46,7 +46,7 @@
"broccoli-asset-rev": "^3.0.0",
"clipboard": "^2.0.8",
"core": "*",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-a11y-testing": "^4.2.0",
"ember-auto-import": "^1.12.0",
"ember-breadcrumbs": "^0.2.3",
@@ -31,6 +31,8 @@ module('Acceptance | host-catalogs | create', function (hooks) {
hostCatalogs: null,
hostCatalog: null,
newHostCatalog: null,
newStaticHostCatalog: null,
newDynamicHostCatalog: null,
};
hooks.beforeEach(function () {
@@ -64,6 +66,8 @@ module('Acceptance | host-catalogs | create', function (hooks) {
urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`;
urls.newHostCatalog = `${urls.hostCatalogs}/new`;
urls.newStaticHostCatalog = `${urls.newHostCatalog}?type=static`;
urls.newDynamicHostCatalog = `${urls.newHostCatalog}?type=aws`;
// Generate resource couner
gethostCatalogCount = () =>
this.server.schema.hostCatalogs.all().models.length;
@@ -71,26 +75,49 @@ module('Acceptance | host-catalogs | create', function (hooks) {
authenticateSession({});
});
test('Users can create new host catalogs', async function (assert) {
test('Users can create new static host catalogs', async function (assert) {
assert.expect(1);
const count = gethostCatalogCount();
await visit(urls.newHostCatalog);
await visit(urls.newStaticHostCatalog);
await fillIn('[name="name"]', 'random string');
await fillIn('[name="description"]', 'random string');
await fillIn('[name="types"]', 'static');
await click('[type="submit"]');
assert.equal(gethostCatalogCount(), count + 1);
});
test('Users can cancel create new host catalogs', async function (assert) {
test('Users can create new dynamic host catalogs', async function (assert) {
assert.expect(1);
const count = gethostCatalogCount();
await visit(urls.newDynamicHostCatalog);
await fillIn('[name="name"]', 'random string');
await fillIn('[name="description"]', 'random string');
await fillIn('[name="types"]', 'aws');
await click('[type="submit"]');
assert.equal(gethostCatalogCount(), count + 1);
});
test('Users can cancel creation of new static host catalogs', async function (assert) {
assert.expect(2);
const count = gethostCatalogCount();
await visit(urls.newHostCatalog);
await visit(urls.newStaticHostCatalog);
await fillIn('[name="name"]', 'random string');
await click('.rose-form-actions [type="button"]');
assert.equal(currentURL(), urls.hostCatalogs);
assert.equal(gethostCatalogCount(), count);
});
test('Users can navigate to new host catalogs route with proper authorization', async function (assert) {
test('Users can cancel creation of new dynamic host catalogs', async function (assert) {
assert.expect(2);
const count = gethostCatalogCount();
await visit(urls.newDynamicHostCatalog);
await fillIn('[name="name"]', 'random string');
await click('.rose-form-actions [type="button"]');
assert.equal(currentURL(), urls.hostCatalogs);
assert.equal(gethostCatalogCount(), count);
});
test('Users can navigate to new static host catalogs route with proper authorization', async function (assert) {
assert.expect(2);
await visit(urls.hostCatalogs);
assert.ok(
@@ -101,7 +128,7 @@ module('Acceptance | host-catalogs | create', function (hooks) {
assert.ok(find(`[href="${urls.newHostCatalog}"]`));
});
test('Users cannot navigate to new host catalogs route without proper authorization', async function (assert) {
test('Users cannot navigate to new static host catalogs route without proper authorization', async function (assert) {
assert.expect(2);
instances.scopes.project.authorized_collection_actions['host-catalogs'] =
[];
@@ -111,10 +138,10 @@ module('Acceptance | host-catalogs | create', function (hooks) {
'host-catalogs'
].includes('create')
);
assert.notOk(find(`[href="${urls.newHostCatalog}"]`));
assert.notOk(find(`[href="${urls.newStaticHostCatalog}"]`));
});
test('saving a new host catalog with invalid fields displays error messages', async function (assert) {
test('saving a new static host catalog with invalid fields displays error messages', async function (assert) {
assert.expect(2);
this.server.post('/host-catalogs', () => {
return new Response(
@@ -135,7 +162,7 @@ module('Acceptance | host-catalogs | create', function (hooks) {
}
);
});
await visit(urls.newHostCatalog);
await visit(urls.newStaticHostCatalog);
await click('[type="submit"]');
assert.ok(
find('[role="alert"]').textContent.trim(),
@@ -0,0 +1,14 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/host-catalogs/new', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/host-catalogs/new'
);
assert.ok(controller);
});
});
+6
View File
@@ -2,6 +2,12 @@
Canonical reference for changes, improvements, and bugfixes for Boundary Desktop.
## v1.4.2 [CLI 0.7.5] (2022.02.17)
### New
- Support dynamic host catalogs. ([PR](https://github.com/hashicorp/boundary-ui/pull/981))
## v1.4.1 [CLI 0.7.3] (2021.12.16)
### New
@@ -26,7 +26,13 @@
<code>{{host.id}}</code>
</Copyable>
</row.cell>
<row.cell>{{host.attributes.address}}</row.cell>
<row.cell>
{{#if host.isStatic}}
{{host.address}}
{{else}}
{{t 'resources.host-catalog.types.dynamic'}}
{{/if}}
</row.cell>
<row.cell>
<Rose::Button
@style='secondary'
+1 -1
View File
@@ -1 +1 @@
0.7.3
0.7.5
+2 -2
View File
@@ -56,7 +56,7 @@
"@doyensec/electronegativity": "^1.9.1",
"@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^2.0.2",
"@ember/test-helpers": "^2.4.2",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"@playwright/test": "^1.15.0",
@@ -67,7 +67,7 @@
"core": "*",
"cross-env": "^7.0.3",
"cross-os": "^1.4.0",
"doctoc": "^2.0.1",
"doctoc": "^2.1.0",
"ember-a11y-testing": "^4.2.0",
"ember-auto-import": "^1.12.0",
"ember-autofocus-modifier": "^2.0.0",
+40 -102
View File
@@ -2943,10 +2943,10 @@
ember-cli-htmlbars "^5.7.1"
ember-destroyable-polyfill "^2.0.3"
"@ember/test-helpers@^2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.4.2.tgz#e5be97e316dc502d7da933461135fbcb9254726e"
integrity sha512-++k/dXZ0IeOQLSm0Yu5QuCRe9UoptuckMhaXHY37VLb1varLHRuP8908kigVNt9c5waiocaK4jPjDBkKYRLlfA==
"@ember/test-helpers@^2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.6.0.tgz#d687515c6ab49ba72717fc62046970ef4a72ea9c"
integrity sha512-N5sr3layWk60wB3maCy+/5hFHQRcTh8aqxcZTSs3Od9QkuHdWBtRgMGLP/35mXpJlgWuu3xqLpt6u3dGHc8gCg==
dependencies:
"@ember/test-waiters" "^3.0.0"
broccoli-debug "^0.6.5"
@@ -4888,15 +4888,15 @@
dependencies:
lodash "^4.17.15"
"@storybook/ember-cli-storybook@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@storybook/ember-cli-storybook/-/ember-cli-storybook-0.4.0.tgz#d0d0d326fa99a19602071e91d93b0bbd907493ec"
integrity sha512-sJyetdQRtM8tO3pjxaRt1Y1nuWRCRAp3mPLgqNS7+6Pv/bISsSgkg0bziqXq1DeDLLOZYe1kqYschaVifTy89A==
"@storybook/ember-cli-storybook@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@storybook/ember-cli-storybook/-/ember-cli-storybook-0.5.0.tgz#b97fc212e6920871f0d6ac4348d663c335ff61b4"
integrity sha512-TleCWq4SgtgeutdSQ2UWEEI5h37au9vX8U+LXN1dbnSBNnNpmdSa0PxXhFhJNZaMZsMq8XzAQowG+H0U7BxIeg==
dependencies:
broccoli-funnel "^2.0.2"
broccoli-merge-trees "^3.0.2"
broccoli-funnel "^3.0.8"
broccoli-merge-trees "^4.2.0"
cheerio "^1.0.0-rc.2"
ember-cli-addon-docs-yuidoc "^0.2.3"
ember-cli-addon-docs-yuidoc "^1.0.0"
ember-cli-babel "^7.23.0"
ember-cli-htmlbars "^5.3.1"
@@ -6416,11 +6416,6 @@ ansistyles@~0.1.3:
resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=
any-promise@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -6932,13 +6927,6 @@ babel-plugin-ember-data-packages-polyfill@^0.1.2:
dependencies:
"@ember-data/rfc395-data" "^0.0.4"
babel-plugin-ember-modules-api-polyfill@^2.12.0:
version "2.13.4"
resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.13.4.tgz#cf62bc9bfd808c48d810d5194f4329e9453bd603"
integrity sha512-uxQPkEQAzCYdwhZk16O9m1R4xtCRNy4oEUTBrccOPfzlIahRZJic/JeP/ZEL0BC6Mfq6r55eOg6gMF/zdFoCvA==
dependencies:
ember-rfc176-data "^0.3.13"
babel-plugin-ember-modules-api-polyfill@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-3.5.0.tgz#27b6087fac75661f779f32e60f94b14d0e9f6965"
@@ -10105,10 +10093,10 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
doctoc@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/doctoc/-/doctoc-2.0.1.tgz#d5aee2bce65a438ff8717d9e51df3d540caa3b78"
integrity sha512-JsxnSVZtLCThKehjFPBDhP1+ZLmdfXQynZH/0ABAwrnd1Zf3AV6LigC9oWJyaZ+c6RXCDnlGUNJ7I+1v8VaaRg==
doctoc@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctoc/-/doctoc-2.1.0.tgz#e7cbcd1f9e65519c295461423b2e7195d9e0d7ab"
integrity sha512-0darEVEuWKLyIlpGOzE5cILf/pgUu25qUs6YwCqLqfxb8+3b9Cl4iakA8vwYrBQOkJ5SwpHKEPVMu2KOMrTA7A==
dependencies:
"@textlint/markdown-to-ast" "~6.1.7"
anchor-markdown-header "~0.5.7"
@@ -10562,16 +10550,17 @@ ember-can@^3.1.0:
ember-cli-htmlbars "^4.2.1"
ember-inflector "^3.0.1"
ember-cli-addon-docs-yuidoc@^0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/ember-cli-addon-docs-yuidoc/-/ember-cli-addon-docs-yuidoc-0.2.4.tgz#dd8f5cf4982e8492b255fb677e9d771925e27dce"
integrity sha512-asyGxagV+VKK+edc5iOW8JI8Css/d8mEE7ndWKqYa8Ub4eFwVfI6SLr9gHZc8f7q9BK+M5a/RSWHSnTTL5/3Zw==
ember-cli-addon-docs-yuidoc@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ember-cli-addon-docs-yuidoc/-/ember-cli-addon-docs-yuidoc-1.0.0.tgz#0bc750c3950d77fbcce645855063013b295ee6be"
integrity sha512-q7tNVbD4niPgXEQ1Exfeh7Mfiwi3sbT5IG8vXgAumpJgATouTJrk3MGeZjInR7x6CV0AKDiExF/BdMwAshTmrg==
dependencies:
broccoli-caching-writer "^3.0.3"
ember-cli-babel "^7.7.3"
fs-extra "^5.0.0"
json-api-serializer "^1.11.0"
lodash "^4.17.5"
ember-cli-babel "^7.22.1"
ember-cli-htmlbars "^5.3.1"
fs-extra "^9.0.1"
json-api-serializer "^2.6.0"
lodash "^4.17.20"
yuidocjs "^0.10.2"
ember-cli-app-version@^5.0.0:
@@ -11480,7 +11469,7 @@ ember-resolver@^8.0.3:
ember-cli-version-checker "^5.1.2"
resolve "^1.20.0"
ember-rfc176-data@^0.3.13, ember-rfc176-data@^0.3.15, ember-rfc176-data@^0.3.17:
ember-rfc176-data@^0.3.15, ember-rfc176-data@^0.3.17:
version "0.3.17"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.17.tgz#d4fc6c33abd6ef7b3440c107a28e04417b49860a"
integrity sha512-EVzTTKqxv9FZbEh6Ktw56YyWRAA0MijKvl7H8C06wVF+8f/cRRz3dXxa4nkwjzyVwx4rzKGuIGq77hxJAQhWWw==
@@ -14413,7 +14402,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoek@2.x.x, hoek@4.x.x, hoek@6.x.x, hoek@~4.2.0:
hoek@2.x.x, hoek@6.x.x, hoek@~4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
@@ -15573,13 +15562,6 @@ isbinaryfile@^4.0.8:
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf"
integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==
isemail@3.x.x:
version "3.2.0"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c"
integrity sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==
dependencies:
punycode "2.x.x"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -16219,15 +16201,6 @@ jest@^27.4.7:
import-local "^3.0.2"
jest-cli "^27.4.7"
joi@^12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a"
integrity sha512-z0FNlV4NGgjQN1fdtHYXf5kmgludM65fG/JlXzU6+rwkt9U5UWuXVYnXa2FpK0u6+qBuCmrm5byPNuiiddAHvQ==
dependencies:
hoek "4.x.x"
isemail "3.x.x"
topo "2.x.x"
jpeg-js@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
@@ -16381,17 +16354,12 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-api-serializer@^1.11.0:
version "1.15.1"
resolved "https://registry.yarnpkg.com/json-api-serializer/-/json-api-serializer-1.15.1.tgz#241ea66dfc5c629ae46d8315c6ee4232b81bb6b7"
integrity sha512-dp7d/TLWudViXADFnmdiq80krYZ+zA0WeB1771O1Is8HWdeySZofhp/TPWHCzgFPmGkgwI7oTEgjgZ6Dcxr/2w==
json-api-serializer@^2.6.0:
version "2.6.6"
resolved "https://registry.yarnpkg.com/json-api-serializer/-/json-api-serializer-2.6.6.tgz#fa5d283b913e0ea25653f4d04f981dc44d66dd4f"
integrity sha512-l7/e2O5/0+GU38RTMowRUKxRlT8zFc1oryAYBxqK54sFZnvJOJI2bo4XbbxnkTtvSzwkyv5DoBUuFm0hvG3avg==
dependencies:
into-stream "^3.1.0"
joi "^12.0.0"
lodash "^4.5.1"
stream-to-array "^2.3.0"
through2 "^2.0.3"
unique-stream "^2.2.1"
setimmediate "^1.0.5"
json-buffer@3.0.0:
version "3.0.0"
@@ -17322,7 +17290,7 @@ lodash.without@~4.4.0:
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1, lodash@^4.7.0, lodash@~4.17.10:
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.1, lodash@^4.7.0, lodash@~4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -20295,16 +20263,16 @@ punycode@1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
pupa@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
@@ -21911,7 +21879,7 @@ set-value@^2.0.0, set-value@^2.0.1, set-value@^4.0.1:
is-plain-object "^2.0.4"
is-primitive "^3.0.1"
setimmediate@^1.0.4:
setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -22535,13 +22503,6 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
stream-to-array@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353"
integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=
dependencies:
any-promise "^1.1.0"
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@@ -23282,15 +23243,7 @@ throttleit@0.0.2:
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8=
through2-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
dependencies:
through2 "~2.0.0"
xtend "~4.0.0"
through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
through2@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
@@ -23471,13 +23424,6 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
topo@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
integrity sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=
dependencies:
hoek "4.x.x"
tough-cookie@^4.0.0, tough-cookie@~2.2.0, tough-cookie@~2.3.3:
version "2.3.4"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
@@ -23863,14 +23809,6 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
unique-stream@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
dependencies:
json-stable-stringify-without-jsonify "^1.0.1"
through2-filter "^3.0.0"
unique-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
@@ -24873,7 +24811,7 @@ xmlhttprequest-ssl@^1.6.2:
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6"
integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==