Merge pull request #7177 from hashicorp/ui-staging

ui: UI Release Merge (ui-staging merge)
pull/7191/head
Kenia 2020-01-30 16:08:35 -05:00 committed by GitHub
commit bf492d2678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 770 additions and 67 deletions

View File

@ -35,15 +35,18 @@ start-consul: deps
start-api: deps
yarn run start:api
test: deps
test: deps test-node
yarn run test
test-view: deps
test-view: deps test-node
yarn run test:view
test-parallel: deps
yarn run test:parallel
test-node:
yarn run test:node
lint: deps
yarn run lint:hbs && yarn run lint:js

View File

@ -56,7 +56,33 @@ export default Component.extend({
return getSplitters(get(this, 'chain.Nodes'));
}),
routes: computed('chain.Nodes', function() {
return getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
const routes = getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
// if we have no routes with a PathPrefix of '/' or one with no definition at all
// then add our own 'default catch all'
if (
!routes.find(item => get(item, 'Definition.Match.HTTP.PathPrefix') === '/') &&
!routes.find(item => typeof item.Definition === 'undefined')
) {
let nextNode = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
const splitterID = `splitter:${this.chain.ServiceName}`;
if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
nextNode = splitterID;
}
routes.push({
Default: true,
ID: `route:${this.chain.ServiceName}`,
Name: this.chain.ServiceName,
Definition: {
Match: {
HTTP: {
PathPrefix: '/',
},
},
},
NextNode: nextNode,
});
}
return routes;
}),
resolvers: computed('chain.{Nodes,Targets}', function() {
return getResolvers(
@ -73,7 +99,7 @@ export default Component.extend({
switch (item.Type) {
case 'splitter':
item.Splits.forEach(splitter => {
graph.addLink(`splitter:${item.Name}`, splitter.NextNode);
graph.addLink(item.ID, splitter.NextNode);
});
break;
case 'router':

View File

@ -7,6 +7,7 @@ export default FormComponent.extend({
datacenterRepo: service('repository/dc/component'),
type: 'policy',
name: 'policy',
allowServiceIdentity: true,
classNames: ['policy-form'],
isScoped: false,

View File

@ -12,6 +12,7 @@ export default ChildSelectorComponent.extend({
datacenterRepo: service('repository/dc/component'),
name: 'policy',
type: 'policy',
allowServiceIdentity: true,
classNames: ['policy-selector'],
init: function() {
this._super(...arguments);

View File

@ -4,7 +4,7 @@ export function initialize(container) {
if (env('CONSUL_UI_DISABLE_REALTIME')) {
return;
}
['node', 'coordinate', 'session', 'service', 'proxy', 'discovery-chain']
['node', 'coordinate', 'session', 'service', 'proxy', 'discovery-chain', 'intention']
.concat(env('CONSUL_NSPACES_ENABLED') ? ['nspace/enabled'] : [])
.map(function(item) {
// create repositories that return a promise resolving to an EventSource
@ -70,6 +70,12 @@ export function initialize(container) {
proxyRepo: 'repository/proxy/event-source',
},
},
{
route: 'dc/intentions/index',
services: {
repo: 'repository/intention/event-source',
},
},
{
service: 'form',
services: {

View File

@ -65,6 +65,9 @@
padding: 10px;
padding-left: 36px;
}
%menu-panel .is-active {
position: relative;
}
%menu-panel .is-active > *::after {
position: absolute;
top: 50%;

View File

@ -10,6 +10,9 @@ html.template-node.template-list .unhealthy h2 {
html.template-node.template-show #meta-data table tr {
cursor: default;
}
html.template-node.template-show #services table tbody td em {
display: inline-block;
}
.healthy > div > ul > li {
padding-top: 16px;
}

View File

@ -1,3 +1,5 @@
{{head-layout}}
{{title 'Consul' separator=' - '}}
{{#if (not loading)}}
{{outlet}}
{{else}}

View File

@ -3,8 +3,9 @@
{{#yield-slot name='template'}}
{{else}}
<header>
Policy or service identity?
Policy{{if allowServiceIdentity ' or service identity?' ''}}
</header>
{{#if allowServiceIdentity}}
<p>
A Service Identity is default policy with a configurable service name. This saves you some time and effort you're using Consul for Connect features.
</p>
@ -13,10 +14,13 @@
{{#each templates as |template|}}
<label>
<span>{{template.name}}</span>
<input data-test-radiobutton={{concat 'template_' template.template}} type="radio" name="{{name}}[template]" value={{template.template}} checked={{eq item.template template.template}} onchange={{action (changeset-set item 'template') value='target.value'}}/>
<input data-test-radiobutton={{concat 'template_' template.template}} type="radio" name={{concat name '[template]'}} value={{template.template}} checked={{eq item.template template.template}} onchange={{action (changeset-set item 'template') value='target.value'}}/>
</label>
{{/each}}
</div>
{{else}}
<input type="hidden" name={{concat name '[template]'}} value="" />
{{/if}}
{{/yield-slot}}
<label class="type-text{{if (and item.error.Name (not item.isPristine)) ' has-error'}}">
<span>Name</span>

View File

@ -17,7 +17,7 @@
<h2>New Policy</h2>
{{/block-slot}}
{{#block-slot name='body'}}
{{policy-form form=form dc=dc}}
{{policy-form form=form dc=dc allowServiceIdentity=allowServiceIdentity}}
{{/block-slot}}
{{#block-slot name='actions' as |close|}}
<button type="submit" {{action 'save' item items (queue (action close) (action 'reset'))}} disabled={{if (or item.isSaving item.isPristine item.isInvalid) 'disabled'}}>

View File

@ -23,7 +23,29 @@
{{#each item.Children as |child|}}
<li onclick={{onclick}} id={{concat 'resolver:' child.ID}}>
<a name="">
{{#if child.Failover}}
{{#if child.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
{{child.Name}}
</dd>
</dl>
{{#if child.Failover}}
<dl class="failover">
<dt data-tooltip={{concat child.Failover.Type ' failover'}}>{{concat child.Failover.Type ' failover'}}</dt>
<dd>
<ol>
{{#each child.Failover.Targets as |target|}}
<li>
<span>{{target}}</span>
</li>
{{/each}}
</ol>
</dd>
</dl>
{{/if}}
{{else if child.Failover}}
{{child.Name}}
<dl class="failover">
<dt data-tooltip={{concat child.Failover.Type ' failover'}}>{{concat child.Failover.Type ' failover'}}</dt>
<dd>
@ -36,13 +58,6 @@
</ol>
</dd>
</dl>
{{else if child.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
{{child.Name}}
</dd>
</dl>
{{else}}
{{child.Name}}
{{/if}}

View File

@ -1,3 +1,12 @@
{{#if isAuthorized }}
{{#if create }}
{{title 'New Policy'}}
{{else}}
{{title 'Edit Policy'}}
{{/if}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'policy ' (if (or isAuthorized isEnabled) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/policies/notifications'}}

View File

@ -1,3 +1,8 @@
{{#if isAuthorized }}
{{title 'Policies'}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'policy ' (if (not isAuthorized) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/policies/notifications'}}

View File

@ -1,3 +1,12 @@
{{#if isAuthorized }}
{{#if item.ID}}
{{title 'Edit Role'}}
{{else}}
{{title 'New Role'}}
{{/if}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'role ' (if (or isAuthorized isEnabled) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/roles/notifications'}}

View File

@ -1,3 +1,8 @@
{{#if isAuthorized }}
{{title 'Roles'}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'role ' (if (not isAuthorized) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/roles/notifications'}}

View File

@ -1,3 +1,12 @@
{{#if isAuthorized }}
{{#if create}}
{{title 'New Token'}}
{{else}}
{{title 'Edit Token'}}
{{/if}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'token ' (if (or isAuthorized isEnabled) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/acls/tokens/notifications'}}

View File

@ -1,3 +1,8 @@
{{#if isAuthorized }}
{{title 'Tokens'}}
{{else}}
{{title 'Access Controls'}}
{{/if}}
{{#app-view class=(concat 'token ' (if (and isEnabled (not isAuthorized)) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
{{#block-slot name='notification' as |status type subject|}}
{{partial 'dc/acls/tokens/notifications'}}

View File

@ -1,3 +1,9 @@
{{#if item.ID }}
{{title 'Edit Intention'}}
{{else}}
{{title 'New Intention'}}
{{/if}}
{{#app-view class="intention edit" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/intentions/notifications'}}

View File

@ -1,3 +1,4 @@
{{title 'Intentions'}}
{{#app-view class="intention list"}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/intentions/notifications'}}

View File

@ -1,3 +1,8 @@
{{#if create }}
{{title 'New Key/Value'}}
{{else}}
{{title 'Edit Key/Value'}}
{{/if}}
{{#app-view class="kv edit" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/kv/notifications'}}

View File

@ -1,3 +1,4 @@
{{title 'Key/Value'}}
{{#app-view class="kv list" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/kv/notifications'}}

View File

@ -1,3 +1,4 @@
{{title 'Nodes'}}
{{#app-view class="node list"}}
{{#block-slot name='header'}}
<h1>

View File

@ -1,3 +1,4 @@
{{title item.Node}}
{{#app-view class="node show"}}
{{#block-slot name='notification' as |status type|}}
{{!TODO: Move sessions to its own folder within nodes }}

View File

@ -30,7 +30,7 @@
<p>
By adding policies to this namespaces, you will apply them to all tokens created within this namespace.
</p>
{{policy-selector dc=dc nspace='default' items=item.ACLs.PolicyDefaults}}
{{policy-selector dc=dc nspace='default' allowServiceIdentity=false items=item.ACLs.PolicyDefaults}}
</fieldset>
{{/if}}
<div>

View File

@ -1,3 +1,8 @@
{{#if create }}
{{title 'New Namespace'}}
{{else}}
{{title 'Edit Namespace'}}
{{/if}}
{{#app-view class="nspace edit" loading=isLoading}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/nspaces/notifications'}}

View File

@ -1,3 +1,4 @@
{{title 'Namespaces'}}
{{#app-view class="nspace list" loading=isLoading}}
{{#block-slot name='notification' as |status type subject|}}
{{partial 'dc/nspaces/notifications'}}

View File

@ -1,3 +1,4 @@
{{title 'Services'}}
{{#app-view class="service list"}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/services/notifications'}}

View File

@ -1,3 +1,4 @@
{{title item.ID}}
{{#app-view class="instance show"}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/services/notifications'}}

View File

@ -1,3 +1,4 @@
{{title item.Service.Service}}
{{#app-view class="service show"}}
{{#block-slot name='notification' as |status type|}}
{{partial 'dc/services/notifications'}}

View File

@ -0,0 +1 @@
<title>{{model.title}}</title>

View File

@ -1,3 +1,4 @@
{{title "Settings"}}
{{#hashicorp-consul id="wrapper" permissions=permissions dcs=dcs dc=dc nspaces=nspaces nspace=nspace}}
{{#app-view class="settings show"}}
{{#block-slot name='header'}}
@ -27,7 +28,7 @@
{{#if (not (env 'CONSUL_UI_DISABLE_REALTIME'))}}
<fieldset>
<h2>Blocking Queries</h2>
<p>Keep catalog info up-to-date without refreshing the page. Any changes made to services and nodes would be reflected in real time.</p>
<p>Keep catalog info up-to-date without refreshing the page. Any changes made to services, nodes and intentions would be reflected in real time.</p>
<div class="type-toggle">
<label>
<input type="checkbox" name="client[blocking]" checked={{if item.client.blocking 'checked'}} onchange={{action 'change'}} />

View File

@ -38,6 +38,14 @@ export const getSplitters = function(nodes) {
return getNodesByType(nodes, 'splitter').map(function(item) {
// Splitters need IDs adding so we can find them in the DOM later
item.ID = `splitter:${item.Name}`;
// splitters have a service.nspace as a name
// do the reverse dance to ensure we don't mess up any
// serivice names with dots in them
const temp = item.Name.split('.');
temp.reverse();
temp.shift();
temp.reverse();
item.Name = temp.join('.');
return item;
});
};
@ -53,45 +61,74 @@ export const getRoutes = function(nodes, uid) {
};
export const getResolvers = function(dc, nspace = 'default', targets = {}, nodes = {}) {
const resolvers = {};
Object.values(targets).forEach(target => {
const node = nodes[`resolver:${target.ID}`];
const resolver = findResolver(resolvers, target.Service, nspace, dc);
// We use this to figure out whether this target is a redirect target
const alternate = getAlternateServices([target.ID], `service.${nspace}.${dc}`);
let failovers;
// Figure out the failover type
if (typeof node.Resolver.Failover !== 'undefined') {
failovers = getAlternateServices(node.Resolver.Failover.Targets, target.ID);
}
switch (true) {
// This target is a redirect
case alternate.Type !== 'Service':
resolver.Children.push({
Redirect: true,
ID: target.ID,
Name: target[alternate.Type],
});
break;
// This target is a Subset
case typeof target.ServiceSubset !== 'undefined':
resolver.Children.push({
// make all our resolver nodes
Object.values(nodes)
.filter(item => item.Type === 'resolver')
.forEach(function(item) {
const parts = item.Name.split('.');
let subset;
// this will leave behind the service.name.nspace.dc even if the service name contains a dot
if (parts.length > 3) {
subset = parts.shift();
}
parts.reverse();
// slice off from dc.nspace onwards leaving the potentially dot containing service name
// const nodeDc =
parts.shift();
// const nodeNspace =
parts.shift();
// if it does contain a dot put it back to the correct order
parts.reverse();
const service = parts.join('.');
const resolver = findResolver(resolvers, service, nspace, dc);
let failovers;
if (typeof item.Resolver.Failover !== 'undefined') {
// figure out what type of failover this is
failovers = getAlternateServices(item.Resolver.Failover.Targets, item.Name);
}
if (subset) {
const child = {
Subset: true,
ID: target.ID,
Name: target.ServiceSubset,
Filter: target.Subset.Filter,
...(typeof failovers !== 'undefined'
? {
Failover: failovers,
}
: {}),
});
break;
// This target is just normal service that might have failovers
default:
ID: item.Name,
Name: subset,
};
if (typeof failovers !== 'undefined') {
child.Failover = failovers;
}
resolver.Children.push(child);
} else {
if (typeof failovers !== 'undefined') {
resolver.Failover = failovers;
}
}
});
Object.values(targets).forEach(target => {
// Failovers don't have a specific node
if (typeof nodes[`resolver:${target.ID}`] !== 'undefined') {
// We use this to figure out whether this target is a redirect target
const alternate = getAlternateServices([target.ID], `service.${nspace}.${dc}`);
// as Failovers don't make it here, we know anything that has alternateServices
// must be a redirect
if (alternate.Type !== 'Service') {
// find the already created resolver
const resolver = findResolver(resolvers, target.Service, nspace, dc);
// and add the redirect as a child, redirects are always children
const child = {
Redirect: true,
ID: target.ID,
Name: target[alternate.Type],
};
// redirects can then also have failovers
// so it this one does, figure out what type they are and add them
// to the redirect
if (typeof nodes[`resolver:${target.ID}`].Resolver.Failover !== 'undefined') {
child.Failover = getAlternateServices(
nodes[`resolver:${target.ID}`].Resolver.Failover.Targets,
target.ID
);
}
resolver.Children.push(child);
}
}
});
return Object.values(resolvers);

View File

@ -0,0 +1,39 @@
const test = require('tape');
const getEnvironment = require('../../config/environment.js');
test(
'config has the correct environment settings',
function(t) {
[
{
environment: 'production',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: '{{.ACLsEnabled}}',
CONSUL_NSPACES_ENABLED: '{{ if .NamespacesEnabled }}{{.NamespacesEnabled}}{{ else }}false{{ end }}',
},
{
environment: 'test',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: true,
},
{
environment: 'staging',
CONSUL_BINARY_TYPE: 'oss',
CONSUL_ACLS_ENABLED: true,
CONSUL_NSPACES_ENABLED: true,
}
].forEach(
function(item) {
const env = getEnvironment(item.environment);
Object.keys(item).forEach(
function(key) {
t.equal(env[key], item[key], `Expect ${key} to equal ${item[key]} in the ${item.environment} environment`);
}
);
}
);
t.end();
}
);

View File

@ -24,6 +24,7 @@
"test": "ember test --test-port=${EMBER_TEST_PORT:-7357}",
"test:parallel": "EMBER_EXAM_PARALLEL=true ember exam --split=4 --parallel",
"test:view": "ember test --server --test-port=${EMBER_TEST_PORT:-7357}",
"test:node": "tape ./node-tests/**/*.js",
"test:coverage": "COVERAGE=true ember test --test-port=${EMBER_TEST_PORT:-7357}",
"test:view:coverage": "COVERAGE=true ember test --server --test-port=${EMBER_TEST_PORT:-7357}",
"steps:list": "node ./lib/commands/bin/list.js"
@ -86,6 +87,7 @@
"ember-load-initializers": "^2.0.0",
"ember-math-helpers": "^2.4.0",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-page-title": "^5.1.0",
"ember-power-select": "^3.0.3",
"ember-power-select-with-create": "^0.6.0",
"ember-qunit": "^4.4.1",
@ -107,6 +109,7 @@
"node-sass": "^4.9.3",
"prettier": "^1.10.2",
"qunit-dom": "^0.9.0",
"tape": "^4.13.0",
"text-encoding": "^0.7.0"
},
"engines": {

View File

@ -0,0 +1,19 @@
@setupApplicationTest
Feature: dc / acls / policies / as many / nspaces: As many for nspaces
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 nspace model from yaml
---
Name: key
ACLs:
PolicyDefaults: ~
RoleDefaults: ~
---
When I visit the nspace page for yaml
---
dc: datacenter
namespace: key
---
Then the url should be /datacenter/namespaces/key
And I click policies.create
And I don't see the "#policies [data-test-radiobutton=template_service-identity]" element

View File

@ -0,0 +1,14 @@
@setupApplicationTest
Feature: dc / acls / policies / create
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the policy page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/acls/policies/create
And the title should be "New Policy - Consul"
@ignore
Scenario: Test we can create a ACLs Policy
Then ok

View File

@ -10,6 +10,7 @@ Feature: dc / acls / policies / index: ACL Policy List
---
Then the url should be /dc-1/acls/policies
Then I see 3 policy models
And the title should be "Policies - Consul"
Scenario: Searching the policies
Given 1 datacenter model with the value "dc-1"
And 3 policy models from yaml

View File

@ -15,6 +15,7 @@ Feature: dc / acls / policies / update: ACL Policy Update
---
Then the url should be /datacenter/acls/policies/policy-id
Then I see 3 token models
And the title should be "Edit Policy - Consul"
Scenario: Update to [Name], [Rules], [Description]
Then I fill in the policy form with yaml
---

View File

@ -0,0 +1,14 @@
@setupApplicationTest
Feature: dc / acls / roles / create
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the role page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/acls/roles/create
And the title should be "New Role - Consul"
@ignore
Scenario: Test we can create a ACLs role
Then ok

View File

@ -10,6 +10,7 @@ Feature: dc / acls / roles / index: ACL Roles List
---
Then the url should be /dc-1/acls/roles
Then I see 3 role models
And the title should be "Roles - Consul"
Scenario: Searching the roles
Given 1 datacenter model with the value "dc-1"
And 3 role models from yaml

View File

@ -14,6 +14,7 @@ Feature: dc / acls / roles / update: ACL Role Update
---
Then the url should be /datacenter/acls/roles/role-id
Then I see 3 token models
And the title should be "Edit Role - Consul"
Scenario: Update to [Name], [Rules], [Description]
Then I fill in the role form with yaml
---

View File

@ -0,0 +1,14 @@
@setupApplicationTest
Feature: dc / acls / tokens / create
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the token page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/acls/tokens/create
And the title should be "New Token - Consul"
@ignore
Scenario: Test we can create a ACLs Token
Then ok

View File

@ -9,6 +9,7 @@ Feature: dc / acls / tokens / index: ACL Token List
dc: dc-1
---
Then the url should be /dc-1/acls/tokens
And the title should be "Tokens - Consul"
Then I see 3 token models
Scenario: Searching the tokens
Given 1 datacenter model with the value "dc-1"

View File

@ -13,6 +13,7 @@ Feature: dc / acls / tokens / update: ACL Token Update
token: key
---
Then the url should be /datacenter/acls/tokens/key
And the title should be "Edit Token - Consul"
Scenario: Update to [Name]
Then I fill in with yaml
---

View File

@ -19,6 +19,7 @@ Feature: dc / intentions / create: Intention Create
dc: datacenter
---
Then the url should be /datacenter/intentions/create
And the title should be "New Intention - Consul"
# Set source
And I click "[data-test-source-element] .ember-power-select-trigger"
And I type "web" into ".ember-power-select-search-input"
@ -39,5 +40,6 @@ Feature: dc / intentions / create: Intention Create
Action: deny
---
Then the url should be /datacenter/intentions
And the title should be "Intentions - Consul"
And "[data-notification]" has the "notification-create" class
And "[data-notification]" has the "success" class

View File

@ -12,6 +12,7 @@ Feature: dc / intentions / update: Intention Update
intention: intention-id
---
Then the url should be /datacenter/intentions/intention-id
And the title should be "Edit Intention - Consul"
Scenario: Update to [Description], [Action]
Then I fill in with yaml
---
@ -25,6 +26,7 @@ Feature: dc / intentions / update: Intention Update
Action: [Action]
---
Then the url should be /datacenter/intentions
And the title should be "Intentions - Consul"
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
Where:

View File

@ -0,0 +1,14 @@
@setupApplicationTest
Feature: dc / kvs / create
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the kv page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/kv/create
And the title should be "New Key/Value - Consul"
@ignore
Scenario: Test we can create a KV
Then ok

View File

@ -13,6 +13,7 @@ Feature: dc / kvs / update: KV Update
kv: "[Name]"
---
Then the url should be /datacenter/kv/[EncodedName]/edit
And the title should be "Edit Key/Value - Consul"
# Turn the Code Editor off so we can fill the value easier
And I click "[name=json]"
Then I fill in with yaml
@ -52,6 +53,7 @@ Feature: dc / kvs / update: KV Update
And I submit
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with the body " "
Then the url should be /datacenter/kv
And the title should be "Key/Value - Consul"
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
Scenario: Update to a key change value to ''

View File

@ -5,7 +5,7 @@ Feature: dc / list-blocking
I want to see changes if I change consul externally
Background:
Given 1 datacenter model with the value "dc-1"
Scenario: Viewing the listing pages
Scenario: Viewing the listing pages for [Page]
Given 3 [Model] models
And a network latency of 100
When I visit the [Page] page for yaml
@ -25,8 +25,9 @@ Feature: dc / list-blocking
| Page | Model | Url |
| services | service | services |
| nodes | node | nodes |
| intentions | intention | intentions |
------------------------------------------------
Scenario: Viewing detail pages with a listing
Scenario: Viewing detail pages with a listing for [Page]
Given 3 [Model] models
And a network latency of 100
When I visit the [Page] page for yaml

View File

@ -14,6 +14,7 @@ Feature: dc / nodes / index
dc: dc-1
---
Then the url should be /dc-1/nodes
And the title should be "Nodes - Consul"
Then I see 3 node models
Scenario: Seeing the leader in unhealthy listing
Given 3 node models from yaml

View File

@ -75,6 +75,7 @@ Feature: dc / nodes / show: Show node
node: node-0
---
Then the url should be /dc1/nodes/node-0
And the title should be "node-0 - Consul"
And the url "/v1/internal/ui/node/node-0" responds with a 404 status
And pause until I see the text "no longer exists" in "[data-notification]"
@ignore

View File

@ -0,0 +1,14 @@
@setupApplicationTest
Feature: dc / acls / nspaces / create
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the nspace page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/namespaces/create
And the title should be "New Namespace - Consul"
@ignore
Scenario: Test we can create a Namespace
Then ok

View File

@ -15,6 +15,7 @@ Feature: dc / nspaces / index: Nspaces List
dc: dc-1
---
Then the url should be /dc-1/namespaces
And the title should be "Namespaces - Consul"
Scenario:
Then I see 3 nspace models
Scenario: Searching the nspaces

View File

@ -14,6 +14,7 @@ Feature: dc / nspaces / update: Nspace Update
namespace: namespace
---
Then the url should be /datacenter/namespaces/namespace
And the title should be "Edit Namespace - Consul"
Scenario: Update to [Description]
Then I fill in with yaml
---

View File

@ -26,6 +26,7 @@ Feature: dc / services / index: List Services
dc: dc-1
---
Then the url should be /dc-1/services
And the title should be "Services - Consul"
Then I see 6 service models
And I see externalSource on the services like yaml
---

View File

@ -81,6 +81,7 @@ Feature: dc / services / instances / show: Show Service Instance
When I click metaData on the tabs
And I see metaDataIsSelected on the tabs
And I see 3 of the metaData object
And the title should be "service-0-with-id - Consul"
Scenario: A Service instance warns when deregistered whilst blocking
Given settings from yaml

View File

@ -16,6 +16,8 @@ Feature: dc / services / show: Show Service
service: service-0
---
Then I see externalSource like "consul"
And the title should be "service-0 - Consul"
Scenario: Given a service with an 'unsupported' external source, there is no logo
Given 1 datacenter model with the value "dc1"
And 1 node models

View File

@ -0,0 +1,9 @@
@setupApplicationTest
@notNamespaceable
Feature: settings / show: Show Settings Page
Scenario:
Given 1 datacenter model with the value "datacenter"
When I visit the settings page
Then the url should be /setting
And the title should be "Settings - Consul"

View File

@ -0,0 +1,10 @@
import steps from '../../../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ConsulUi Tests</title>
<title>Consul</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "head"}}

View File

@ -109,6 +109,8 @@ export default {
nspaces: create(
nspaces(visitable, deletable, creatable, clickable, attribute, collection, text, freetextFilter)
),
nspace: create(nspace(visitable, submitable, deletable, cancelable)),
nspace: create(
nspace(visitable, submitable, deletable, cancelable, policySelector, roleSelector)
),
settings: create(settings(visitable, submitable)),
};

View File

@ -1,8 +1,17 @@
export default function(visitable, submitable, deletable, cancelable) {
export default function(
visitable,
submitable,
deletable,
cancelable,
policySelector,
roleSelector
) {
return {
visit: visitable(['/:dc/namespaces/:namespace', '/:dc/namespaces/create']),
...submitable({}, 'form > div'),
...cancelable({}, 'form > div'),
...deletable({}, 'form > div'),
policies: policySelector(),
roles: roleSelector(),
};
}

View File

@ -1,5 +1,5 @@
export default function(visitable, submitable) {
return submitable({
visit: visitable('/settings'),
visit: visitable('/setting'),
});
}

View File

@ -40,6 +40,9 @@ export default function(scenario, assert, pauseUntil, find, currentURL, clipboar
.dom(document.querySelector(selector))
.hasClass(cls, `Expected [class] to contain ${cls} on ${selector}`);
})
.then([`I don't see the "$selector" element`], function(selector) {
assert.equal(document.querySelector(selector), null, `Expected not to see ${selector}`);
})
.then(['"$selector" doesn\'t have the "$class" class'], function(selector, cls) {
assert.ok(
!document.querySelector(selector).classList.contains(cls),
@ -66,5 +69,8 @@ export default function(scenario, assert, pauseUntil, find, currentURL, clipboar
}
const current = currentURL() || '';
assert.equal(current, url, `Expected the url to be ${url} was ${current}`);
})
.then(['the title should be "$title"'], function(title) {
assert.equal(document.title, title, `Expected the document.title to equal "${title}"`);
});
}

View File

@ -92,4 +92,108 @@ module('Unit | Utility | components/discovery-chain/get-resolvers', function() {
})
);
});
test('it finds subsets with failovers correctly', function(assert) {
return Promise.resolve({
Chain: {
ServiceName: 'service-name',
Namespace: 'default',
Datacenter: 'dc-1',
Protocol: 'http',
StartNode: '',
Nodes: {
'resolver:v2.dc-failover.default.dc-1': {
Type: 'resolver',
Name: 'v2.dc-failover.default.dc-1',
Resolver: {
Target: 'v2.dc-failover.defauilt.dc-1',
Failover: {
Targets: ['v2.dc-failover.default.dc-5', 'v2.dc-failover.default.dc-6'],
},
},
},
},
Targets: {
'v2.dc-failover.default.dc-1': {
ID: 'v2.dc-failover.default.dc-1',
Service: 'dc-failover',
Namespace: 'default',
Datacenter: 'dc-1',
Subset: {
Filter: '',
},
},
'v2.dc-failover.default.dc-6': {
ID: 'v2.dc-failover.default.dc-6',
Service: 'dc-failover',
Namespace: 'default',
Datacenter: 'dc-6',
Subset: {
Filter: '',
},
},
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const expected = {
ID: 'dc-failover.default.dc-1',
Name: 'dc-failover',
Children: [
{
Subset: true,
ID: 'v2.dc-failover.default.dc-1',
Name: 'v2',
Failover: {
Type: 'Datacenter',
Targets: ['dc-5', 'dc-6'],
},
},
],
};
assert.deepEqual(actual[0], expected);
});
});
test('it finds services with failovers correctly', function(assert) {
return Promise.resolve({
Chain: {
ServiceName: 'service-name',
Namespace: 'default',
Datacenter: 'dc-1',
Protocol: 'http',
StartNode: '',
Nodes: {
'resolver:dc-failover.default.dc-1': {
Type: 'resolver',
Name: 'dc-failover.default.dc-1',
Resolver: {
Target: 'dc-failover.defauilt.dc-1',
Failover: {
Targets: ['dc-failover.default.dc-5', 'dc-failover.default.dc-6'],
},
},
},
},
Targets: {
'dc-failover.default.dc-1': {
ID: 'dc-failover.default.dc-1',
Service: 'dc-failover',
Namespace: 'default',
Datacenter: 'dc-1',
},
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const expected = {
ID: 'dc-failover.default.dc-1',
Name: 'dc-failover',
Children: [],
Failover: {
Type: 'Datacenter',
Targets: ['dc-5', 'dc-6'],
},
};
assert.deepEqual(actual[0], expected);
});
});
});

View File

@ -4082,6 +4082,18 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-equal@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
dependencies:
is-arguments "^1.0.4"
is-date-object "^1.0.1"
is-regex "^1.0.4"
object-is "^1.0.1"
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@ -4135,6 +4147,11 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
defined@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
del@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7"
@ -4263,6 +4280,13 @@ dot-prop@^4.1.0:
dependencies:
is-obj "^1.0.0"
dotignore@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905"
integrity sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==
dependencies:
minimatch "^3.0.4"
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -4428,7 +4452,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0:
resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.10.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.10.0, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
version "6.18.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957"
integrity sha512-7ceC8joNYxY2wES16iIBlbPSxwKDBhYwC8drU3ZEvuPDMwVv1KzxCNu1fvxyFEBWhwaRNTUxSCsEVoTd9nosGA==
@ -4539,6 +4563,14 @@ ember-cli-get-component-path-option@^1.0.0:
resolved "https://registry.yarnpkg.com/ember-cli-get-component-path-option/-/ember-cli-get-component-path-option-1.0.0.tgz#0d7b595559e2f9050abed804f1d8eff1b08bc771"
integrity sha1-DXtZVVni+QUKvtgE8djv8bCLx3E=
ember-cli-head@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/ember-cli-head/-/ember-cli-head-0.4.1.tgz#28b7ee86439746640834b232a3b34ab1329f3cf3"
integrity sha512-MIgshw5nGil7Q/TU4SDRCsgsiA3wPC9WqOig/g1LlHTNXjR4vH7s/ddG7GTfK5Kt4ZQHJEUDXpd/lIbdBkIQ/Q==
dependencies:
ember-cli-babel "^6.11.0"
ember-cli-htmlbars "^2.0.3"
ember-cli-htmlbars-inline-precompile@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars-inline-precompile/-/ember-cli-htmlbars-inline-precompile-2.1.0.tgz#61b91ff1879d44ae504cadb46fb1f2604995ae08"
@ -4561,7 +4593,7 @@ ember-cli-htmlbars-inline-precompile@^3.0.0:
heimdalljs-logger "^0.1.9"
silent-error "^1.1.0"
ember-cli-htmlbars@^2.0.1:
ember-cli-htmlbars@^2.0.1, ember-cli-htmlbars@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-2.0.5.tgz#b5a105429a6bce4f7c9c97b667e3b8926e31397f"
integrity sha512-3f3PAxdnQ/fhQa8XP/3z4RLRgLHxV8j4Ln75aHbRdemOCjBa048KxL9l+acRLhCulbGQCMnLiIUIC89PAzLrcA==
@ -4904,6 +4936,13 @@ ember-computed-style@^0.3.0:
ember-compatibility-helpers "^1.2.0"
ember-maybe-import-regenerator "^0.1.5"
ember-copy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ember-copy/-/ember-copy-1.0.0.tgz#426554ba6cf65920f31d24d0a3ca2cb1be16e4aa"
integrity sha512-aiZNAvOmdemHdvZNn0b5b/0d9g3JFpcOsrDgfhYEbfd7SzE0b69YiaVK2y3wjqfjuuiA54vOllGN4pjSzECNSw==
dependencies:
ember-cli-babel "^6.6.0"
ember-data@~3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.12.0.tgz#ce20c41163ce50124d12a4370641fd9b4a21c3e2"
@ -5037,6 +5076,16 @@ ember-native-dom-helpers@^0.5.3:
broccoli-funnel "^1.1.0"
ember-cli-babel "^6.6.0"
ember-page-title@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/ember-page-title/-/ember-page-title-5.1.0.tgz#baf9fc00f95faf638f8493740a1f80daaa5007e9"
integrity sha512-Ou2kwvwlQdDxre20WUMDym54e+5r9g0lTINFiBZUavoBCOUYdBP711LbmmUIIlYblZTOb/TWadNEQZpd2DkIAg==
dependencies:
ember-cli-babel "^7.7.3"
ember-cli-head "^0.4.0"
ember-cli-htmlbars "^3.0.1"
ember-copy "^1.0.0"
ember-power-select-with-create@^0.6.0:
version "0.6.2"
resolved "https://registry.yarnpkg.com/ember-power-select-with-create/-/ember-power-select-with-create-0.6.2.tgz#05faf361c435f5c2c61c24e687aebddefd569897"
@ -5337,6 +5386,23 @@ es-abstract@^1.13.0, es-abstract@^1.5.1:
string.prototype.trimleft "^2.0.0"
string.prototype.trimright "^2.0.0"
es-abstract@^1.17.0-next.1:
version "1.17.4"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.1.5"
is-regex "^1.0.5"
object-inspect "^1.7.0"
object-keys "^1.1.1"
object.assign "^4.1.0"
string.prototype.trimleft "^2.1.1"
string.prototype.trimright "^2.1.1"
es-to-primitive@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
@ -5346,6 +5412,15 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@ -5984,6 +6059,13 @@ follow-redirects@^1.0.0:
dependencies:
debug "^3.0.0"
for-each@~0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
dependencies:
is-callable "^1.1.3"
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -6165,7 +6247,7 @@ fstream@^1.0.0, fstream@^1.0.12:
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.0.2, function-bind@^1.1.1:
function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
@ -6312,7 +6394,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.2, glob@^7.1.4, glob@~7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3:
glob@^7.1.3, glob@~7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -6517,6 +6599,11 @@ has-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
has-to-string-tag-x@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
@ -6560,7 +6647,7 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.1, has@^1.0.3:
has@^1.0.1, has@^1.0.3, has@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
@ -6853,7 +6940,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -6962,6 +7049,11 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -6984,6 +7076,11 @@ is-buffer@~2.0.3:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
is-callable@^1.1.3, is-callable@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
@ -7156,6 +7253,13 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"
is-regex@^1.0.5, is-regex@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
dependencies:
has "^1.0.3"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
@ -8303,7 +8407,7 @@ minimist@0.0.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
@ -8794,6 +8898,16 @@ object-inspect@^1.6.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
object-inspect@^1.7.0, object-inspect@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
object-is@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4"
integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@ -9761,6 +9875,14 @@ regexp-tree@^0.1.6:
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f"
integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw==
regexp.prototype.flags@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@ -9965,6 +10087,13 @@ resolve@^1.10.0, resolve@^1.3.3:
dependencies:
path-parse "^1.0.6"
resolve@~1.14.2:
version "1.14.2"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2"
integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==
dependencies:
path-parse "^1.0.6"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
@ -9980,6 +10109,13 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
resumer@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759"
integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=
dependencies:
through "~2.3.4"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -10718,6 +10854,15 @@ string-width@^3.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string.prototype.trim@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782"
integrity sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
function-bind "^1.1.1"
string.prototype.trimleft@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz#68b6aa8e162c6a80e76e3a8a0c2e747186e271ff"
@ -10726,6 +10871,14 @@ string.prototype.trimleft@^2.0.0:
define-properties "^1.1.2"
function-bind "^1.0.2"
string.prototype.trimleft@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==
dependencies:
define-properties "^1.1.3"
function-bind "^1.1.1"
string.prototype.trimright@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz#ab4a56d802a01fbe7293e11e84f24dc8164661dd"
@ -10734,6 +10887,14 @@ string.prototype.trimright@^2.0.0:
define-properties "^1.1.2"
function-bind "^1.0.2"
string.prototype.trimright@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9"
integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==
dependencies:
define-properties "^1.1.3"
function-bind "^1.1.1"
string_decoder@0.10, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@ -10908,6 +11069,27 @@ tapable@^1.0.0, tapable@^1.1.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tape@^4.13.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/tape/-/tape-4.13.0.tgz#e2f581ff5f12a7cbd787e9f83c76c2851782fce2"
integrity sha512-J/hvA+GJnuWJ0Sj8Z0dmu3JgMNU+MmusvkCT7+SN4/2TklW18FNCp/UuHIEhPZwHfy4sXfKYgC7kypKg4umbOw==
dependencies:
deep-equal "~1.1.1"
defined "~1.0.0"
dotignore "~0.1.2"
for-each "~0.3.3"
function-bind "~1.1.1"
glob "~7.1.6"
has "~1.0.3"
inherits "~2.0.4"
is-regex "~1.0.5"
minimist "~1.2.0"
object-inspect "~1.7.0"
resolve "~1.14.2"
resumer "~0.0.0"
string.prototype.trim "~1.2.1"
through "~2.3.8"
tar@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
@ -11045,7 +11227,7 @@ through2@^3.0.1:
dependencies:
readable-stream "2 || 3"
through@^2.3.6:
through@^2.3.6, through@~2.3.4, through@~2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=