From a86ebba52d38334df72245d31d51cd71598aee92 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 21 Feb 2019 13:10:53 +0000 Subject: [PATCH] UI: Service Instances (#5326) This gives more prominence to 'Service Instances' as opposed to 'Services'. It also begins to surface Connect related 'nouns' such as 'Proxies' and 'Upstreams' and begins to interconnect them giving more visibility to operators. Various smaller changes: 1. Move healthcheck-status component to healthcheck-output 2. Create a new healthcheck-status component for showing the number of checks plus its icon 3. Create a new healthcheck-info component to group multiple statuses plus a different view if there are no checks 4. Componentize tag-list --- ui-v2/app/adapters/proxy.js | 20 ++++ ui-v2/app/components/healthcheck-info.js | 4 + ui-v2/app/components/healthcheck-list.js | 36 ++++++++ ui-v2/app/components/healthcheck-output.js | 5 + ui-v2/app/components/healthcheck-status.js | 11 ++- ui-v2/app/components/tab-nav.js | 1 + ui-v2/app/components/tag-list.js | 6 ++ ui-v2/app/controllers/dc/services/instance.js | 17 ++++ ui-v2/app/controllers/dc/services/show.js | 57 ++++++------ ui-v2/app/initializers/search.js | 3 +- ui-v2/app/models/proxy.js | 12 +++ ui-v2/app/router.js | 3 + ui-v2/app/routes/dc/services/instance.js | 29 ++++++ ui-v2/app/routes/dc/services/show.js | 1 + ui-v2/app/serializers/proxy.js | 6 ++ ui-v2/app/services/dom.js | 4 +- ui-v2/app/services/repository/proxy.js | 33 +++++++ ui-v2/app/services/repository/service.js | 39 ++++++-- .../styles/components/app-view/layout.scss | 9 ++ .../app/styles/components/app-view/skin.scss | 28 +++++- .../styles/components/breadcrumbs/skin.scss | 11 ++- .../app/styles/components/form-elements.scss | 2 +- .../styles/components/healthcheck-info.scss | 12 +++ .../index.scss | 0 .../components/healthcheck-info/layout.scss | 32 +++++++ .../components/healthcheck-info/skin.scss | 21 +++++ ...ck-status.scss => healthcheck-output.scss} | 20 ++-- .../components/healthcheck-output/index.scss | 2 + .../layout.scss | 14 +-- .../skin.scss | 20 ++-- ui-v2/app/styles/components/icons/index.scss | 7 +- ui-v2/app/styles/components/index.scss | 4 +- ui-v2/app/styles/components/pill.scss | 3 +- ui-v2/app/styles/components/table.scss | 43 ++++----- ui-v2/app/styles/components/table/layout.scss | 42 +-------- ui-v2/app/styles/components/tabs.scss | 2 +- ui-v2/app/styles/components/tabs/layout.scss | 3 + ui-v2/app/styles/components/tabs/skin.scss | 9 ++ .../styles/components/tabular-collection.scss | 27 ++++-- ui-v2/app/styles/components/tag-list.scss | 5 + .../app/styles/components/tag-list/index.scss | 2 + .../styles/components/tag-list/layout.scss | 10 ++ .../app/styles/components/tag-list/skin.scss | 0 ui-v2/app/styles/core/typography.scss | 6 +- ui-v2/app/styles/routes/dc/service/index.scss | 17 ---- ui-v2/app/styles/variables/custom-query.scss | 4 +- .../templates/components/healthcheck-info.hbs | 9 ++ .../templates/components/healthcheck-list.hbs | 5 + .../components/healthcheck-output.hbs | 25 +++++ .../components/healthcheck-status.hbs | 28 +----- ui-v2/app/templates/components/tag-list.hbs | 8 ++ .../app/templates/dc/nodes/-healthchecks.hbs | 6 +- ui-v2/app/templates/dc/nodes/-services.hbs | 2 +- .../app/templates/dc/services/-instances.hbs | 55 +++++++++++ .../app/templates/dc/services/-nodechecks.hbs | 8 ++ .../templates/dc/services/-servicechecks.hbs | 8 ++ ui-v2/app/templates/dc/services/-tags.hbs | 7 ++ .../app/templates/dc/services/-upstreams.hbs | 27 ++++++ ui-v2/app/templates/dc/services/index.hbs | 16 +--- ui-v2/app/templates/dc/services/instance.hbs | 72 +++++++++++++++ ui-v2/app/templates/dc/services/show.hbs | 91 +++++-------------- ui-v2/app/utils/computed/purify.js | 20 ++-- .../components/catalog-filter.feature | 25 ----- .../tests/acceptance/dc/services/show.feature | 16 +--- .../components/healthcheck-info-test.js | 22 +++++ .../components/healthcheck-list-test.js | 23 +++++ .../components/healthcheck-output-test.js | 34 +++++++ .../components/healthcheck-status-test.js | 18 +--- .../integration/components/tag-list-test.js | 33 +++++++ ui-v2/tests/pages/dc/services/show.js | 14 +-- ui-v2/tests/unit/adapters/proxy-test.js | 12 +++ .../controllers/dc/services/instance-test.js | 12 +++ ui-v2/tests/unit/models/proxy-test.js | 14 +++ .../unit/routes/dc/services/instance-test.js | 11 +++ ui-v2/tests/unit/serializers/proxy-test.js | 24 +++++ 75 files changed, 918 insertions(+), 369 deletions(-) create mode 100644 ui-v2/app/adapters/proxy.js create mode 100644 ui-v2/app/components/healthcheck-info.js create mode 100644 ui-v2/app/components/healthcheck-list.js create mode 100644 ui-v2/app/components/healthcheck-output.js create mode 100644 ui-v2/app/components/tag-list.js create mode 100644 ui-v2/app/controllers/dc/services/instance.js create mode 100644 ui-v2/app/models/proxy.js create mode 100644 ui-v2/app/routes/dc/services/instance.js create mode 100644 ui-v2/app/serializers/proxy.js create mode 100644 ui-v2/app/services/repository/proxy.js create mode 100644 ui-v2/app/styles/components/healthcheck-info.scss rename ui-v2/app/styles/components/{healthcheck-status => healthcheck-info}/index.scss (100%) create mode 100644 ui-v2/app/styles/components/healthcheck-info/layout.scss create mode 100644 ui-v2/app/styles/components/healthcheck-info/skin.scss rename ui-v2/app/styles/components/{healthcheck-status.scss => healthcheck-output.scss} (56%) create mode 100644 ui-v2/app/styles/components/healthcheck-output/index.scss rename ui-v2/app/styles/components/{healthcheck-status => healthcheck-output}/layout.scss (63%) rename ui-v2/app/styles/components/{healthcheck-status => healthcheck-output}/skin.scss (60%) create mode 100644 ui-v2/app/styles/components/tag-list.scss create mode 100644 ui-v2/app/styles/components/tag-list/index.scss create mode 100644 ui-v2/app/styles/components/tag-list/layout.scss create mode 100644 ui-v2/app/styles/components/tag-list/skin.scss create mode 100644 ui-v2/app/templates/components/healthcheck-info.hbs create mode 100644 ui-v2/app/templates/components/healthcheck-list.hbs create mode 100644 ui-v2/app/templates/components/healthcheck-output.hbs create mode 100644 ui-v2/app/templates/components/tag-list.hbs create mode 100644 ui-v2/app/templates/dc/services/-instances.hbs create mode 100644 ui-v2/app/templates/dc/services/-nodechecks.hbs create mode 100644 ui-v2/app/templates/dc/services/-servicechecks.hbs create mode 100644 ui-v2/app/templates/dc/services/-tags.hbs create mode 100644 ui-v2/app/templates/dc/services/-upstreams.hbs create mode 100644 ui-v2/app/templates/dc/services/instance.hbs create mode 100644 ui-v2/tests/integration/components/healthcheck-info-test.js create mode 100644 ui-v2/tests/integration/components/healthcheck-list-test.js create mode 100644 ui-v2/tests/integration/components/healthcheck-output-test.js create mode 100644 ui-v2/tests/integration/components/tag-list-test.js create mode 100644 ui-v2/tests/unit/adapters/proxy-test.js create mode 100644 ui-v2/tests/unit/controllers/dc/services/instance-test.js create mode 100644 ui-v2/tests/unit/models/proxy-test.js create mode 100644 ui-v2/tests/unit/routes/dc/services/instance-test.js create mode 100644 ui-v2/tests/unit/serializers/proxy-test.js diff --git a/ui-v2/app/adapters/proxy.js b/ui-v2/app/adapters/proxy.js new file mode 100644 index 000000000000..d4f4f41ea539 --- /dev/null +++ b/ui-v2/app/adapters/proxy.js @@ -0,0 +1,20 @@ +import Adapter from './application'; +import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/proxy'; +import { OK as HTTP_OK } from 'consul-ui/utils/http/status'; +export default Adapter.extend({ + urlForQuery: function(query, modelName) { + if (typeof query.id === 'undefined') { + throw new Error('You must specify an id'); + } + // https://www.consul.io/api/catalog.html#list-nodes-for-connect-capable-service + return this.appendURL('catalog/connect', [query.id], this.cleanQuery(query)); + }, + handleResponse: function(status, headers, payload, requestData) { + let response = payload; + if (status === HTTP_OK) { + const url = this.parseURL(requestData.url); + response = this.handleBatchResponse(url, response, PRIMARY_KEY, SLUG_KEY); + } + return this._super(status, headers, response, requestData); + }, +}); diff --git a/ui-v2/app/components/healthcheck-info.js b/ui-v2/app/components/healthcheck-info.js new file mode 100644 index 000000000000..abe1ccedb6d3 --- /dev/null +++ b/ui-v2/app/components/healthcheck-info.js @@ -0,0 +1,4 @@ +import Component from '@ember/component'; +export default Component.extend({ + tagName: '', +}); diff --git a/ui-v2/app/components/healthcheck-list.js b/ui-v2/app/components/healthcheck-list.js new file mode 100644 index 000000000000..092a1aadaf04 --- /dev/null +++ b/ui-v2/app/components/healthcheck-list.js @@ -0,0 +1,36 @@ +import Component from '@ember/component'; +import { get } from '@ember/object'; + +export default Component.extend({ + // TODO: Could potentially do this on attr change + actions: { + sortChecksByImportance: function(a, b) { + const statusA = get(a, 'Status'); + const statusB = get(b, 'Status'); + switch (statusA) { + case 'passing': + // a = passing + // unless b is also passing then a is less important + return statusB === 'passing' ? 0 : 1; + case 'critical': + // a = critical + // unless b is also critical then a is more important + return statusB === 'critical' ? 0 : -1; + case 'warning': + // a = warning + switch (statusB) { + // b is passing so a is more important + case 'passing': + return -1; + // b is critical so a is less important + case 'critical': + return 1; + // a and b are both warning, therefore equal + default: + return 0; + } + } + return 0; + }, + }, +}); diff --git a/ui-v2/app/components/healthcheck-output.js b/ui-v2/app/components/healthcheck-output.js new file mode 100644 index 000000000000..227501fc5c67 --- /dev/null +++ b/ui-v2/app/components/healthcheck-output.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + classNames: ['healthcheck-output'], +}); diff --git a/ui-v2/app/components/healthcheck-status.js b/ui-v2/app/components/healthcheck-status.js index 25a646d758c7..367cca6469f1 100644 --- a/ui-v2/app/components/healthcheck-status.js +++ b/ui-v2/app/components/healthcheck-status.js @@ -1,5 +1,12 @@ import Component from '@ember/component'; - +import { get, computed } from '@ember/object'; export default Component.extend({ - classNames: ['healthcheck-status'], + tagName: '', + count: computed('value', function() { + const value = get(this, 'value'); + if (Array.isArray(value)) { + return value.length; + } + return value; + }), }); diff --git a/ui-v2/app/components/tab-nav.js b/ui-v2/app/components/tab-nav.js index 142b50bf4d20..db166df6415e 100644 --- a/ui-v2/app/components/tab-nav.js +++ b/ui-v2/app/components/tab-nav.js @@ -3,4 +3,5 @@ import Component from '@ember/component'; export default Component.extend({ name: 'tab', tagName: 'nav', + classNames: ['tab-nav'], }); diff --git a/ui-v2/app/components/tag-list.js b/ui-v2/app/components/tag-list.js new file mode 100644 index 000000000000..1656e4a23c18 --- /dev/null +++ b/ui-v2/app/components/tag-list.js @@ -0,0 +1,6 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: 'dl', + classNames: ['tag-list'], +}); diff --git a/ui-v2/app/controllers/dc/services/instance.js b/ui-v2/app/controllers/dc/services/instance.js new file mode 100644 index 000000000000..a8934de52d1b --- /dev/null +++ b/ui-v2/app/controllers/dc/services/instance.js @@ -0,0 +1,17 @@ +import Controller from '@ember/controller'; +import { set } from '@ember/object'; + +export default Controller.extend({ + setProperties: function() { + this._super(...arguments); + // This method is called immediately after `Route::setupController`, and done here rather than there + // as this is a variable used purely for view level things, if the view was different we might not + // need this variable + set(this, 'selectedTab', 'service-checks'); + }, + actions: { + change: function(e) { + set(this, 'selectedTab', e.target.value); + }, + }, +}); diff --git a/ui-v2/app/controllers/dc/services/show.js b/ui-v2/app/controllers/dc/services/show.js index d4653888a56d..3f21a7b36bd6 100644 --- a/ui-v2/app/controllers/dc/services/show.js +++ b/ui-v2/app/controllers/dc/services/show.js @@ -1,38 +1,39 @@ import Controller from '@ember/controller'; -import { get, computed } from '@ember/object'; -import sumOfUnhealthy from 'consul-ui/utils/sumOfUnhealthy'; -import hasStatus from 'consul-ui/utils/hasStatus'; -import WithHealthFiltering from 'consul-ui/mixins/with-health-filtering'; +import { get, set, computed } from '@ember/object'; +import { inject as service } from '@ember/service'; import WithSearching from 'consul-ui/mixins/with-searching'; -export default Controller.extend(WithSearching, WithHealthFiltering, { +export default Controller.extend(WithSearching, { + dom: service('dom'), init: function() { this.searchParams = { - healthyServiceNode: 's', - unhealthyServiceNode: 's', + serviceInstance: 's', }; this._super(...arguments); }, - searchableHealthy: computed('healthy', function() { - return get(this, 'searchables.healthyServiceNode') - .add(get(this, 'healthy')) - .search(get(this, this.searchParams.healthyServiceNode)); - }), - searchableUnhealthy: computed('unhealthy', function() { - return get(this, 'searchables.unhealthyServiceNode') - .add(get(this, 'unhealthy')) - .search(get(this, this.searchParams.unhealthyServiceNode)); - }), - unhealthy: computed('filtered', function() { - return get(this, 'filtered').filter(function(item) { - return sumOfUnhealthy(item.Checks) > 0; - }); - }), - healthy: computed('filtered', function() { - return get(this, 'filtered').filter(function(item) { - return sumOfUnhealthy(item.Checks) === 0; - }); + setProperties: function() { + this._super(...arguments); + // This method is called immediately after `Route::setupController`, and done here rather than there + // as this is a variable used purely for view level things, if the view was different we might not + // need this variable + set(this, 'selectedTab', 'instances'); + }, + searchable: computed('items', function() { + return get(this, 'searchables.serviceInstance') + .add(get(this, 'items')) + .search(get(this, this.searchParams.serviceInstance)); }), - filter: function(item, { s = '', status = '' }) { - return hasStatus(get(item, 'Checks'), status); + actions: { + change: function(e) { + set(this, 'selectedTab', e.target.value); + // Ensure tabular-collections sizing is recalculated + // now it is visible in the DOM + get(this, 'dom') + .components('.tab-section input[type="radio"]:checked + div table') + .forEach(function(item) { + if (typeof item.didAppear === 'function') { + item.didAppear(); + } + }); + }, }, }); diff --git a/ui-v2/app/initializers/search.js b/ui-v2/app/initializers/search.js index 69875fdb114f..ebdd48c926f6 100644 --- a/ui-v2/app/initializers/search.js +++ b/ui-v2/app/initializers/search.js @@ -22,8 +22,7 @@ export function initialize(application) { kv: kv(filterable), healthyNode: node(filterable), unhealthyNode: node(filterable), - healthyServiceNode: serviceNode(filterable), - unhealthyServiceNode: serviceNode(filterable), + serviceInstance: serviceNode(filterable), nodeservice: nodeService(filterable), service: service(filterable), }; diff --git a/ui-v2/app/models/proxy.js b/ui-v2/app/models/proxy.js new file mode 100644 index 000000000000..9e0858219927 --- /dev/null +++ b/ui-v2/app/models/proxy.js @@ -0,0 +1,12 @@ +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; + +export const PRIMARY_KEY = 'uid'; +export const SLUG_KEY = 'ID'; +export default Model.extend({ + [PRIMARY_KEY]: attr('string'), + [SLUG_KEY]: attr('string'), + ServiceName: attr('string'), + ServiceID: attr('string'), + ServiceProxyDestination: attr('string'), +}); diff --git a/ui-v2/app/router.js b/ui-v2/app/router.js index 628a713c7aa2..764bc5f35802 100644 --- a/ui-v2/app/router.js +++ b/ui-v2/app/router.js @@ -18,6 +18,9 @@ export const routes = { show: { _options: { path: '/:name' }, }, + instance: { + _options: { path: '/:name/:id' }, + }, }, // Nodes represent a consul node nodes: { diff --git a/ui-v2/app/routes/dc/services/instance.js b/ui-v2/app/routes/dc/services/instance.js new file mode 100644 index 000000000000..da863ba41814 --- /dev/null +++ b/ui-v2/app/routes/dc/services/instance.js @@ -0,0 +1,29 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; +import { get } from '@ember/object'; + +export default Route.extend({ + repo: service('repository/service'), + proxyRepo: service('repository/proxy'), + model: function(params) { + const repo = get(this, 'repo'); + const proxyRepo = get(this, 'proxyRepo'); + const dc = this.modelFor('dc').dc.Name; + return hash({ + item: repo.findInstanceBySlug(params.id, params.name, dc), + }).then(function(model) { + return hash({ + proxy: + get(service, 'Kind') !== 'connect-proxy' + ? proxyRepo.findInstanceBySlug(params.id, params.name, dc) + : null, + ...model, + }); + }); + }, + setupController: function(controller, model) { + this._super(...arguments); + controller.setProperties(model); + }, +}); diff --git a/ui-v2/app/routes/dc/services/show.js b/ui-v2/app/routes/dc/services/show.js index bf5fa0d6579a..9376abdbc10a 100644 --- a/ui-v2/app/routes/dc/services/show.js +++ b/ui-v2/app/routes/dc/services/show.js @@ -19,6 +19,7 @@ export default Route.extend({ return { ...model, ...{ + // Nodes happen to be the ServiceInstances here items: model.item.Nodes, }, }; diff --git a/ui-v2/app/serializers/proxy.js b/ui-v2/app/serializers/proxy.js new file mode 100644 index 000000000000..7c3c5c42e08c --- /dev/null +++ b/ui-v2/app/serializers/proxy.js @@ -0,0 +1,6 @@ +import Serializer from './application'; +import { PRIMARY_KEY } from 'consul-ui/models/proxy'; + +export default Serializer.extend({ + primaryKey: PRIMARY_KEY, +}); diff --git a/ui-v2/app/services/dom.js b/ui-v2/app/services/dom.js index a3bda1c8c8a2..740406cb5982 100644 --- a/ui-v2/app/services/dom.js +++ b/ui-v2/app/services/dom.js @@ -70,7 +70,9 @@ export default Service.extend({ // with traditional/standard web components you wouldn't actually need this // method as you could just get to their methods from the dom element component: function(selector, context) { - // TODO: support passing a dom element, when we need to do that + if (typeof selector !== 'string') { + return $_(selector); + } return $_(this.element(selector, context)); }, components: function(selector, context) { diff --git a/ui-v2/app/services/repository/proxy.js b/ui-v2/app/services/repository/proxy.js new file mode 100644 index 000000000000..ce8c055d83f1 --- /dev/null +++ b/ui-v2/app/services/repository/proxy.js @@ -0,0 +1,33 @@ +import RepositoryService from 'consul-ui/services/repository'; +import { PRIMARY_KEY } from 'consul-ui/models/proxy'; +import { get } from '@ember/object'; +const modelName = 'proxy'; +export default RepositoryService.extend({ + getModelName: function() { + return modelName; + }, + getPrimaryKey: function() { + return PRIMARY_KEY; + }, + findAllBySlug: function(slug, dc, configuration = {}) { + const query = { + id: slug, + dc: dc, + }; + if (typeof configuration.cursor !== 'undefined') { + query.index = configuration.cursor; + } + return this.get('store').query(this.getModelName(), query); + }, + findInstanceBySlug: function(id, slug, dc, configuration) { + return this.findAllBySlug(slug, dc, configuration).then(function(items) { + if (get(items, 'length') > 0) { + const instance = items.findBy('ServiceProxyDestination', id); + if (instance) { + return instance; + } + } + return; + }); + }, +}); diff --git a/ui-v2/app/services/repository/service.js b/ui-v2/app/services/repository/service.js index 5654c3a61837..2da90a3c7265 100644 --- a/ui-v2/app/services/repository/service.js +++ b/ui-v2/app/services/repository/service.js @@ -7,16 +7,35 @@ export default RepositoryService.extend({ }, findBySlug: function(slug, dc) { return this._super(...arguments).then(function(item) { - const nodes = get(item, 'Nodes'); - const service = get(nodes, 'firstObject'); - const tags = nodes - .reduce(function(prev, item) { - return prev.concat(get(item, 'Service.Tags') || []); - }, []) - .uniq(); - set(service, 'Tags', tags); - set(service, 'Nodes', nodes); - return service; + const nodes = get(item, 'Nodes'); + const service = get(nodes, 'firstObject'); + const tags = nodes + .reduce(function(prev, item) { + return prev.concat(get(item, 'Service.Tags') || []); + }, []) + .uniq(); + set(service, 'Tags', tags); + set(service, 'Nodes', nodes); + return service; + }); + }, + findInstanceBySlug: function(id, slug, dc, configuration) { + return this.findBySlug(slug, dc, configuration).then(function(item) { + const i = item.Nodes.findIndex(function(item) { + return item.Service.ID === id; }); + if (i !== -1) { + const service = item.Nodes[i].Service; + service.Node = item.Nodes[i].Node; + service.ServiceChecks = item.Nodes[i].Checks.filter(function(item) { + return item.ServiceID != ''; + }); + service.NodeChecks = item.Nodes[i].Checks.filter(function(item) { + return item.ServiceID == ''; + }); + return service; + } + // TODO: probably need to throw a 404 here? + }); }, }); diff --git a/ui-v2/app/styles/components/app-view/layout.scss b/ui-v2/app/styles/components/app-view/layout.scss index 34827cc5bf55..f518b5d38d34 100644 --- a/ui-v2/app/styles/components/app-view/layout.scss +++ b/ui-v2/app/styles/components/app-view/layout.scss @@ -10,6 +10,15 @@ display: flex; align-items: flex-start; } +%app-view header dl { + float: left; + margin-top: 25px; + margin-right: 50px; + margin-bottom: 20px; +} +%app-view header dt { + font-weight: bold; +} /* units */ %app-view { margin-top: 50px; diff --git a/ui-v2/app/styles/components/app-view/skin.scss b/ui-v2/app/styles/components/app-view/skin.scss index e0269410ff2d..a117fa1258e8 100644 --- a/ui-v2/app/styles/components/app-view/skin.scss +++ b/ui-v2/app/styles/components/app-view/skin.scss @@ -1,10 +1,28 @@ -%app-view h2, -%app-view header > div:last-of-type { - border-bottom: $decor-border-100; +%app-view h2 { + border-bottom: $decor-border-200; +} +@media #{$--horizontal-selects} { + %app-view header h1 { + border-bottom: $decor-border-200; + } } -%app-view header > div:last-of-type, +@media #{$--lt-horizontal-selects} { + %app-view header > div > div:last-child { + border-bottom: $decor-border-200; + } +} +%app-view header > div > div:last-child, +%app-view header h1, %app-view h2 { - border-color: $keyline-light; + border-color: $gray-200; +} +// We know that any sibling navs might have a top border +// by default. As its squashed up to a h1, in this +// case hide its border to avoid double border +@media #{$--horizontal-selects} { + %app-view header h1 ~ nav { + border-top: 0 !important; + } } %app-content div > dl > dd { color: $gray-400; diff --git a/ui-v2/app/styles/components/breadcrumbs/skin.scss b/ui-v2/app/styles/components/breadcrumbs/skin.scss index 6bbe41f28847..fb86355e9244 100644 --- a/ui-v2/app/styles/components/breadcrumbs/skin.scss +++ b/ui-v2/app/styles/components/breadcrumbs/skin.scss @@ -1,9 +1,18 @@ -%breadcrumbs a { +%breadcrumbs li > * { @extend %with-chevron; } +%breadcrumbs li > strong::before { + color: $gray-300; +} +%breadcrumbs li > a::before { + color: rgba($color-action, 0.5); +} %breadcrumbs ol { list-style-type: none; } %breadcrumbs a { color: $color-action; } +%breadcrumbs strong { + color: $gray-400; +} diff --git a/ui-v2/app/styles/components/form-elements.scss b/ui-v2/app/styles/components/form-elements.scss index 942efbb8cba9..c0bd6fa6e0a6 100644 --- a/ui-v2/app/styles/components/form-elements.scss +++ b/ui-v2/app/styles/components/form-elements.scss @@ -24,7 +24,7 @@ form table, %app-content form dl { @extend %form-row; } -%app-content [role='radiogroup'] { +%app-content form:not(.filter-bar) [role='radiogroup'] { @extend %radio-group; } %radio-group label { diff --git a/ui-v2/app/styles/components/healthcheck-info.scss b/ui-v2/app/styles/components/healthcheck-info.scss new file mode 100644 index 000000000000..a249d32c4929 --- /dev/null +++ b/ui-v2/app/styles/components/healthcheck-info.scss @@ -0,0 +1,12 @@ +@import './healthcheck-info/index'; +@import './icons/index'; +tr dl { + @extend %healthcheck-info; +} +td span.zero { + @extend %with-no-healthchecks; + // TODO: Why isn't this is layout? + display: block; + text-indent: 20px; + color: $gray-400; +} diff --git a/ui-v2/app/styles/components/healthcheck-status/index.scss b/ui-v2/app/styles/components/healthcheck-info/index.scss similarity index 100% rename from ui-v2/app/styles/components/healthcheck-status/index.scss rename to ui-v2/app/styles/components/healthcheck-info/index.scss diff --git a/ui-v2/app/styles/components/healthcheck-info/layout.scss b/ui-v2/app/styles/components/healthcheck-info/layout.scss new file mode 100644 index 000000000000..0f084db303a5 --- /dev/null +++ b/ui-v2/app/styles/components/healthcheck-info/layout.scss @@ -0,0 +1,32 @@ +%healthcheck-info { + display: flex; + height: 100%; + float: left; +} +%healthcheck-info > * { + display: block; +} +%healthcheck-info dt.zero { + display: none; +} +%healthcheck-info dd.zero { + visibility: hidden; +} +%healthcheck-info dt { + text-indent: -9000px; +} +%healthcheck-info dt.warning { + overflow: visible; +} +%healthcheck-info dt.warning::before { + top: 7px; +} +%healthcheck-info dt.warning::after { + left: -2px; + top: -1px; +} +%healthcheck-info dd { + box-sizing: content-box; + margin-left: 22px; + padding-right: 10px; +} diff --git a/ui-v2/app/styles/components/healthcheck-info/skin.scss b/ui-v2/app/styles/components/healthcheck-info/skin.scss new file mode 100644 index 000000000000..9b22b05f01d7 --- /dev/null +++ b/ui-v2/app/styles/components/healthcheck-info/skin.scss @@ -0,0 +1,21 @@ +%healthcheck-info dt.passing { + @extend %with-passing; +} +%healthcheck-info dt.warning { + @extend %with-warning; +} +%healthcheck-info dt.critical { + @extend %with-critical; +} +%healthcheck-info dt.passing, +%healthcheck-info dt.passing + dd { + color: $color-success; +} +%healthcheck-info dt.warning, +%healthcheck-info dt.warning + dd { + color: $color-alert; +} +%healthcheck-info dt.critical, +%healthcheck-info dt.critical + dd { + color: $color-failure; +} diff --git a/ui-v2/app/styles/components/healthcheck-status.scss b/ui-v2/app/styles/components/healthcheck-output.scss similarity index 56% rename from ui-v2/app/styles/components/healthcheck-status.scss rename to ui-v2/app/styles/components/healthcheck-output.scss index 550b2c992aa7..96216d631597 100644 --- a/ui-v2/app/styles/components/healthcheck-status.scss +++ b/ui-v2/app/styles/components/healthcheck-output.scss @@ -1,32 +1,32 @@ -@import './healthcheck-status/index'; +@import './healthcheck-output/index'; @import './icons/index'; -.healthcheck-status { - @extend %healthcheck-status; +.healthcheck-output { + @extend %healthcheck-output; } -%healthcheck-status.passing { +%healthcheck-output.passing { @extend %with-passing; } -%healthcheck-status.warning { +%healthcheck-output.warning { @extend %with-warning; } -%healthcheck-status.critical { +%healthcheck-output.critical { @extend %with-critical; } -@media #{$--lt-spacious-healthcheck-status} { - .healthcheck-status button.copy-btn { +@media #{$--lt-spacious-healthcheck-output} { + .healthcheck-output button.copy-btn { margin-top: -11px; margin-right: -18px; padding: 0; width: 20px; visibility: hidden; } - %healthcheck-status { + %healthcheck-output { padding-left: 30px; padding-top: 10px; padding-bottom: 15px; padding-right: 13px; } - %healthcheck-status::before { + %healthcheck-output::before { width: 15px !important; height: 15px !important; left: 9px; diff --git a/ui-v2/app/styles/components/healthcheck-output/index.scss b/ui-v2/app/styles/components/healthcheck-output/index.scss new file mode 100644 index 000000000000..bc182521964a --- /dev/null +++ b/ui-v2/app/styles/components/healthcheck-output/index.scss @@ -0,0 +1,2 @@ +@import './skin'; +@import './layout'; diff --git a/ui-v2/app/styles/components/healthcheck-status/layout.scss b/ui-v2/app/styles/components/healthcheck-output/layout.scss similarity index 63% rename from ui-v2/app/styles/components/healthcheck-status/layout.scss rename to ui-v2/app/styles/components/healthcheck-output/layout.scss index ef40daf3757f..5f1a9403debb 100644 --- a/ui-v2/app/styles/components/healthcheck-status/layout.scss +++ b/ui-v2/app/styles/components/healthcheck-output/layout.scss @@ -1,4 +1,4 @@ -%healthcheck-status::before { +%healthcheck-output::before { background-size: 55%; width: 25px !important; height: 25px !important; @@ -6,25 +6,25 @@ top: 20px !important; margin-top: 0 !important; } -%healthcheck-status.warning::before { +%healthcheck-output.warning::before { background-size: 100%; } -%healthcheck-status { +%healthcheck-output { padding: 20px 24px; padding-bottom: 26px; padding-left: 57px; margin-bottom: 24px; position: relative; } -%healthcheck-status pre { +%healthcheck-output pre { padding: 12px; } -%healthcheck-status .with-feedback { +%healthcheck-output .with-feedback { float: right; } -%healthcheck-status dt { +%healthcheck-output dt { margin-bottom: 0.2em; } -%healthcheck-status dd:first-of-type { +%healthcheck-output dd:first-of-type { margin-bottom: 0.6em; } diff --git a/ui-v2/app/styles/components/healthcheck-status/skin.scss b/ui-v2/app/styles/components/healthcheck-output/skin.scss similarity index 60% rename from ui-v2/app/styles/components/healthcheck-status/skin.scss rename to ui-v2/app/styles/components/healthcheck-output/skin.scss index d0fd2cec13d4..9d26d4d66317 100644 --- a/ui-v2/app/styles/components/healthcheck-status/skin.scss +++ b/ui-v2/app/styles/components/healthcheck-output/skin.scss @@ -1,35 +1,35 @@ -%healthcheck-status { +%healthcheck-output { border-width: 1px; } -%healthcheck-status, -%healthcheck-status pre { +%healthcheck-output, +%healthcheck-output pre { border-radius: $decor-radius-100; } -%healthcheck-status dd:first-of-type { +%healthcheck-output dd:first-of-type { color: $gray-400; } -%healthcheck-status pre { +%healthcheck-output pre { background-color: $black; color: $white; } -%healthcheck-status.passing { +%healthcheck-output.passing { /* TODO: this should be a frame-gray */ // @extend %frame-green-500; color: $gray-900; border-color: $gray-200; border-style: solid; } -%healthcheck-status.warning { +%healthcheck-output.warning { @extend %frame-yellow-500; color: $gray-900; } -%healthcheck-status.critical { +%healthcheck-output.critical { @extend %frame-red-500; color: $gray-900; } -%healthcheck-status.passing::before { +%healthcheck-output.passing::before { background-color: $color-success !important; } -%healthcheck-status.critical::before { +%healthcheck-output.critical::before { background-color: $color-danger !important; } diff --git a/ui-v2/app/styles/components/icons/index.scss b/ui-v2/app/styles/components/icons/index.scss index acc1eb7d6fd0..4c61d11f3cf6 100644 --- a/ui-v2/app/styles/components/icons/index.scss +++ b/ui-v2/app/styles/components/icons/index.scss @@ -93,12 +93,11 @@ } %with-chevron::before { @extend %pseudo-icon; - background-image: url('data:image/svg+xml;charset=UTF-8,'); + content: '❮'; width: 6px; - height: 9px; + background-color: transparent; left: 0; - margin-top: -4px; - background-color: $color-transparent; + font-size: 0.7rem; } %with-folder::before { @extend %pseudo-icon; diff --git a/ui-v2/app/styles/components/index.scss b/ui-v2/app/styles/components/index.scss index f461f7bea1c8..edfda9d16efd 100644 --- a/ui-v2/app/styles/components/index.scss +++ b/ui-v2/app/styles/components/index.scss @@ -16,7 +16,9 @@ @import './app-view'; @import './product'; -@import './healthcheck-status'; +@import './tag-list'; +@import './healthcheck-output'; +@import './healthcheck-info'; @import './healthchecked-resource'; @import './freetext-filter'; @import './filter-bar'; diff --git a/ui-v2/app/styles/components/pill.scss b/ui-v2/app/styles/components/pill.scss index 4d8f0673a1e2..af1809c33957 100644 --- a/ui-v2/app/styles/components/pill.scss +++ b/ui-v2/app/styles/components/pill.scss @@ -1,4 +1,5 @@ @import './pill/index'; -td strong { +td strong, +%tag-list span { @extend %pill; } diff --git a/ui-v2/app/styles/components/table.scss b/ui-v2/app/styles/components/table.scss index 364c214b6532..749774ad1a08 100644 --- a/ui-v2/app/styles/components/table.scss +++ b/ui-v2/app/styles/components/table.scss @@ -1,41 +1,30 @@ @import './icons/index'; @import './table/index'; + +html.template-service.template-list td:first-child a span, +html.template-node.template-show #services td:first-child a span, +html.template-service.template-show #instances td:first-child a span { + @extend %with-external-source-icon; + float: left; + margin-right: 10px; + margin-top: 2px; +} +/* This nudges the th in for the external source icons */ +html.template-node.template-show #services th:first-child, +html.template-service.template-show #instances th:first-child, +html.template-service.template-list main th:first-child { + text-indent: 28px; +} + td.folder { @extend %with-folder; } -td dt.passing { - @extend %with-passing; -} -td dt.warning { - @extend %with-warning; -} -td dt.critical { - @extend %with-critical; -} -td span.zero { - @extend %with-no-healthchecks; - display: block; - text-indent: 20px; - color: $gray-400; -} table:not(.sessions) tr { cursor: pointer; } table:not(.sessions) td:first-child { padding: 0; } -td dt.passing, -td dt.passing + dd { - color: $color-success; -} -td dt.warning, -td dt.warning + dd { - color: $color-alert; -} -td dt.critical, -td dt.critical + dd { - color: $color-failure; -} /* Header Tooltips/Icon*/ th { overflow: visible; diff --git a/ui-v2/app/styles/components/table/layout.scss b/ui-v2/app/styles/components/table/layout.scss index 23301b423eac..2706e64dc5aa 100644 --- a/ui-v2/app/styles/components/table/layout.scss +++ b/ui-v2/app/styles/components/table/layout.scss @@ -31,7 +31,7 @@ table th { padding-bottom: 0.6em; } table td, -table td a { +table td:first-child a { padding: 0.9em 0; } table th, @@ -50,44 +50,6 @@ td:not(.actions) a { overflow: hidden; } -// TODO: this isn't specific to table -// these are the node health 3 column display -tr > * dl { - float: left; -} -td dl { - height: 100%; -} -td dl { - display: flex; -} -td dl > * { - display: block; -} -td dt.zero { - display: none; -} -td dd.zero { - visibility: hidden; -} -td dt { - text-indent: -9000px; -} -td dt.warning { - overflow: visible; -} -td dt.warning::before { - top: 7px; -} -td dt.warning::after { - left: -2px; - top: -1px; -} -td dd { - box-sizing: content-box; - margin-left: 22px; - padding-right: 10px; -} /* hide actions on narrow screens, you can always click in do everything from there */ @media #{$--lt-wide-table} { tr > .actions { @@ -96,6 +58,8 @@ td dd { } /* ideally these would be in route css files, but left here as they */ /* accomplish the same thing (hide non-essential columns for tables) */ +/* TODO: Move these to component/table.scss for the moment */ +/* Also mixed with things in component/tabular-collection.scss move those also */ @media #{$--lt-medium-table} { /* Policy > Datacenters */ html.template-policy.template-list tr > :nth-child(2) { diff --git a/ui-v2/app/styles/components/tabs.scss b/ui-v2/app/styles/components/tabs.scss index b0c08a7f8c5a..64a1b9138ef0 100644 --- a/ui-v2/app/styles/components/tabs.scss +++ b/ui-v2/app/styles/components/tabs.scss @@ -1,5 +1,5 @@ @import './tabs/index'; -main header nav:last-of-type:not(:first-of-type) { +.tab-nav { @extend %tab-nav; } .tab-section { diff --git a/ui-v2/app/styles/components/tabs/layout.scss b/ui-v2/app/styles/components/tabs/layout.scss index 9588b870ebf5..7b20b1aa1f7d 100644 --- a/ui-v2/app/styles/components/tabs/layout.scss +++ b/ui-v2/app/styles/components/tabs/layout.scss @@ -2,6 +2,9 @@ /* this keeps in-tab-section toolbars flush to the top, see Node Detail > Services */ margin-top: 0 !important; } +%tab-nav { + clear: both; +} @media #{$--horizontal-tabs} { %tab-nav ul { display: flex; diff --git a/ui-v2/app/styles/components/tabs/skin.scss b/ui-v2/app/styles/components/tabs/skin.scss index 81faad36953b..1538bcf0d853 100644 --- a/ui-v2/app/styles/components/tabs/skin.scss +++ b/ui-v2/app/styles/components/tabs/skin.scss @@ -1,3 +1,12 @@ +%tab-nav { + /* %frame-gray-something */ + border-bottom: $decor-border-100; + border-top: $decor-border-200; +} +%tab-nav { + /* %frame-gray-something */ + border-color: $gray-200; +} %tab-nav label { cursor: pointer; } diff --git a/ui-v2/app/styles/components/tabular-collection.scss b/ui-v2/app/styles/components/tabular-collection.scss index c9745e787f3f..53d6678ef64e 100644 --- a/ui-v2/app/styles/components/tabular-collection.scss +++ b/ui-v2/app/styles/components/tabular-collection.scss @@ -35,17 +35,16 @@ table.dom-recycling { /* using: */ /* calc(<100% divided by number of non-fixed width cells> - ) */ -html.template-service.template-list td:first-child a span, -html.template-node.template-show #services td:first-child a span { - @extend %with-external-source-icon; - float: left; - margin-right: 10px; - margin-top: 2px; -} /*TODO: trs only live in tables, get rid of table */ html.template-service.template-list main table tr { @extend %services-row; } +html.template-service.template-show #instances table tr { + @extend %instances-row; +} +html.template-instance.template-show #upstreams table tr { + @extend %upstreams-row; +} html.template-intention.template-list main table tr { @extend %intentions-row; } @@ -146,6 +145,12 @@ html.template-node.template-show main table.sessions tr { html.template-token.template-list main table tr td.me ~ td:nth-of-type(5) { display: none; } + html.template-service.template-show #instances tr > :nth-child(3) { + display: none; + } + %instances-row > * { + width: calc(100% / 4); + } } %kvs-row > *:first-child { @@ -155,7 +160,7 @@ html.template-node.template-show main table.sessions tr { @extend %table-actions; } %node-services-row > * { - width: 33%; + width: calc(100% / 3); } %policies-row > * { width: calc(33% - 20px); @@ -172,3 +177,9 @@ html.template-node.template-show main table.sessions tr { %services-row > * { width: auto; } +%instances-row > * { + width: calc(100% / 5); +} +%upstreams-row > * { + width: calc(100% / 3); +} diff --git a/ui-v2/app/styles/components/tag-list.scss b/ui-v2/app/styles/components/tag-list.scss new file mode 100644 index 000000000000..6bc2ea8e709d --- /dev/null +++ b/ui-v2/app/styles/components/tag-list.scss @@ -0,0 +1,5 @@ +@import './tag-list/index'; +.tag-list, +td.tags { + @extend %tag-list; +} diff --git a/ui-v2/app/styles/components/tag-list/index.scss b/ui-v2/app/styles/components/tag-list/index.scss new file mode 100644 index 000000000000..bc182521964a --- /dev/null +++ b/ui-v2/app/styles/components/tag-list/index.scss @@ -0,0 +1,2 @@ +@import './skin'; +@import './layout'; diff --git a/ui-v2/app/styles/components/tag-list/layout.scss b/ui-v2/app/styles/components/tag-list/layout.scss new file mode 100644 index 000000000000..2590f8b4c144 --- /dev/null +++ b/ui-v2/app/styles/components/tag-list/layout.scss @@ -0,0 +1,10 @@ +%tag-list dt { + display: none; +} +// TODO: Currently this is here to overwrite +// the default definition list layout used in edit pages +// ideally we'd be more specific with those to say +// only add padding to dl's in edit pages +%tag-list dd { + padding-left: 0; +} diff --git a/ui-v2/app/styles/components/tag-list/skin.scss b/ui-v2/app/styles/components/tag-list/skin.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ui-v2/app/styles/core/typography.scss b/ui-v2/app/styles/core/typography.scss index 2331c49c901c..bc945842a88f 100644 --- a/ui-v2/app/styles/core/typography.scss +++ b/ui-v2/app/styles/core/typography.scss @@ -36,10 +36,10 @@ h1, h2, %header-nav, %healthchecked-resource header span, -%healthcheck-status dt, +%healthcheck-output dt, %copy-button, %app-content div > dl > dt, -td a { +td:first-child a { font-weight: $typo-weight-semibold; } %form-element > span, @@ -51,7 +51,7 @@ caption { font-weight: $typo-weight-semibold !important; } th, -%breadcrumbs a, +%breadcrumbs li > *, %action-group-action, %tab-nav, %tooltip-bubble { diff --git a/ui-v2/app/styles/routes/dc/service/index.scss b/ui-v2/app/styles/routes/dc/service/index.scss index c38a957a9ddf..e69de29bb2d1 100644 --- a/ui-v2/app/styles/routes/dc/service/index.scss +++ b/ui-v2/app/styles/routes/dc/service/index.scss @@ -1,17 +0,0 @@ -@import '../../../components/pill/index'; -html.template-service.template-show main dl { - display: flex; - margin-bottom: 1.4em; -} -html.template-service.template-show main dt { - display: none; -} -// TODO: Generalize this, also see nodes/index -html.template-service.template-list td.tags span, -html.template-service.template-show main dd span { - @extend %pill; -} -html.template-node.template-show #services th:first-child, -html.template-service.template-list main th:first-child { - text-indent: 28px; -} diff --git a/ui-v2/app/styles/variables/custom-query.scss b/ui-v2/app/styles/variables/custom-query.scss index 56895ef26631..8e7160e678c3 100644 --- a/ui-v2/app/styles/variables/custom-query.scss +++ b/ui-v2/app/styles/variables/custom-query.scss @@ -26,8 +26,8 @@ $--lt-wide-footer: '(max-width: 420px)'; $--spacious-page-header: '(min-width: 850px)'; $--lt-spacious-page-header: '(max-width: 849px)'; -$--spacious-healthcheck-status: '(min-width: 421px)'; -$--lt-spacious-healthcheck-status: '(max-width: 420px)'; +$--spacious-healthcheck-output: '(min-width: 421px)'; +$--lt-spacious-healthcheck-output: '(max-width: 420px)'; $--wide-form: '(min-width: 421px)'; $--lt-wide-form: '(max-width: 420px)'; diff --git a/ui-v2/app/templates/components/healthcheck-info.hbs b/ui-v2/app/templates/components/healthcheck-info.hbs new file mode 100644 index 000000000000..13b62ac08c33 --- /dev/null +++ b/ui-v2/app/templates/components/healthcheck-info.hbs @@ -0,0 +1,9 @@ +{{#if (and (lt passing 1) (lt warning 1) (lt critical 1) )}} + 0 +{{else}} +
+ {{healthcheck-status width=passingWidth name='passing' value=passing}} + {{healthcheck-status width=warningWidth name='warning' value=warning}} + {{healthcheck-status width=criticalWidth name='critical' value=critical}} +
+{{/if}} diff --git a/ui-v2/app/templates/components/healthcheck-list.hbs b/ui-v2/app/templates/components/healthcheck-list.hbs new file mode 100644 index 000000000000..4b5774588e07 --- /dev/null +++ b/ui-v2/app/templates/components/healthcheck-list.hbs @@ -0,0 +1,5 @@ + diff --git a/ui-v2/app/templates/components/healthcheck-output.hbs b/ui-v2/app/templates/components/healthcheck-output.hbs new file mode 100644 index 000000000000..05a75e40a249 --- /dev/null +++ b/ui-v2/app/templates/components/healthcheck-output.hbs @@ -0,0 +1,25 @@ +{{#feedback-dialog type='inline'}} + {{#block-slot 'action' as |success error|}} + {{#copy-button success=(action success) error=(action error) clipboardText=output title='copy output to clipboard'}} + Copy Output + {{/copy-button}} + {{/block-slot}} + {{#block-slot 'success' as |transition|}} +

+ Copied IP Address! +

+ {{/block-slot}} + {{#block-slot 'error' as |transition|}} +

+ Sorry, something went wrong! +

+ {{/block-slot}} +{{/feedback-dialog}} +
+
{{name}}
+
{{notes}}
+
Output
+
+
{{output}}
+
+
\ No newline at end of file diff --git a/ui-v2/app/templates/components/healthcheck-status.hbs b/ui-v2/app/templates/components/healthcheck-status.hbs index 05a75e40a249..383f67386c56 100644 --- a/ui-v2/app/templates/components/healthcheck-status.hbs +++ b/ui-v2/app/templates/components/healthcheck-status.hbs @@ -1,25 +1,3 @@ -{{#feedback-dialog type='inline'}} - {{#block-slot 'action' as |success error|}} - {{#copy-button success=(action success) error=(action error) clipboardText=output title='copy output to clipboard'}} - Copy Output - {{/copy-button}} - {{/block-slot}} - {{#block-slot 'success' as |transition|}} -

- Copied IP Address! -

- {{/block-slot}} - {{#block-slot 'error' as |transition|}} -

- Sorry, something went wrong! -

- {{/block-slot}} -{{/feedback-dialog}} -
-
{{name}}
-
{{notes}}
-
Output
-
-
{{output}}
-
-
\ No newline at end of file +{{!-- we use concat here to avoid ember adding returns between words, which causes a layout issue--}} +
{{ concat 'Healthchecks ' (capitalize name) }}
+
{{format-number count}}
\ No newline at end of file diff --git a/ui-v2/app/templates/components/tag-list.hbs b/ui-v2/app/templates/components/tag-list.hbs new file mode 100644 index 000000000000..c51ea2a41883 --- /dev/null +++ b/ui-v2/app/templates/components/tag-list.hbs @@ -0,0 +1,8 @@ +{{#if (gt items.length 0)}} +
Tags
+
+ {{#each items as |item|}} + {{item}} + {{/each}} +
+{{/if}} diff --git a/ui-v2/app/templates/dc/nodes/-healthchecks.hbs b/ui-v2/app/templates/dc/nodes/-healthchecks.hbs index a956fad63133..19acae8c38e7 100644 --- a/ui-v2/app/templates/dc/nodes/-healthchecks.hbs +++ b/ui-v2/app/templates/dc/nodes/-healthchecks.hbs @@ -1,9 +1,5 @@ {{#if (gt item.Checks.length 0) }} - + {{healthcheck-list items=item.Checks}} {{else}}

This node has no health checks. diff --git a/ui-v2/app/templates/dc/nodes/-services.hbs b/ui-v2/app/templates/dc/nodes/-services.hbs index 856e51b172de..e692bb0d522e 100644 --- a/ui-v2/app/templates/dc/nodes/-services.hbs +++ b/ui-v2/app/templates/dc/nodes/-services.hbs @@ -19,7 +19,7 @@ - {{item.Service}}{{#if (not-eq item.ID item.Service) }}({{item.ID}}){{/if}} + {{item.Service}}{{#if (not-eq item.ID item.Service) }} ({{item.ID}}){{/if}} diff --git a/ui-v2/app/templates/dc/services/-instances.hbs b/ui-v2/app/templates/dc/services/-instances.hbs new file mode 100644 index 000000000000..7ef34c3400e8 --- /dev/null +++ b/ui-v2/app/templates/dc/services/-instances.hbs @@ -0,0 +1,55 @@ +{{#if (gt items.length 0) }} + +

+ {{freetext-filter searchable=searchable value=s placeholder="Search"}} +
+{{/if}} + {{#changeable-set dispatcher=searchable}} + {{#block-slot 'set' as |filtered|}} + {{#tabular-collection + data-test-instances + items=filtered as |item index| + }} + {{#block-slot 'header'}} + ID + Node + Address + Node Checks + Service Checks + {{/block-slot}} + {{#block-slot 'row'}} + + + + {{ or item.Service.ID item.Service.Service }} + + + + {{item.Node.Node}} + + + {{item.Service.Address}}:{{item.Service.Port}} + + + {{#with (reject-by 'ServiceID' '' item.Checks) as |checks|}} + {{healthcheck-info + passing=(filter-by 'Status' 'passing' checks) warning=(filter-by 'Status' 'warning' checks) critical=(filter-by 'Status' 'critical' checks) + }} + {{/with}} + + + {{#with (filter-by 'ServiceID' '' item.Checks) as |checks|}} + {{healthcheck-info + passing=(filter-by 'Status' 'passing' checks) warning=(filter-by 'Status' 'warning' checks) critical=(filter-by 'Status' 'critical' checks) + }} + {{/with}} + + {{/block-slot}} + {{/tabular-collection}} + {{/block-slot}} + {{#block-slot 'empty'}} +

+ There are no services. +

+ {{/block-slot}} + {{/changeable-set}} diff --git a/ui-v2/app/templates/dc/services/-nodechecks.hbs b/ui-v2/app/templates/dc/services/-nodechecks.hbs new file mode 100644 index 000000000000..487db34fc9e9 --- /dev/null +++ b/ui-v2/app/templates/dc/services/-nodechecks.hbs @@ -0,0 +1,8 @@ +{{#if (gt item.NodeChecks.length 0) }} + {{healthcheck-list items=item.NodeChecks}} +{{else}} +

+ This instance has no node health checks. +

+{{/if}} + diff --git a/ui-v2/app/templates/dc/services/-servicechecks.hbs b/ui-v2/app/templates/dc/services/-servicechecks.hbs new file mode 100644 index 000000000000..424772e70591 --- /dev/null +++ b/ui-v2/app/templates/dc/services/-servicechecks.hbs @@ -0,0 +1,8 @@ +{{#if (gt item.ServiceChecks.length 0) }} + {{healthcheck-list items=item.ServiceChecks}} +{{else}} +

+ This instance has no service health checks. +

+{{/if}} + diff --git a/ui-v2/app/templates/dc/services/-tags.hbs b/ui-v2/app/templates/dc/services/-tags.hbs new file mode 100644 index 000000000000..c0a3a0f7830a --- /dev/null +++ b/ui-v2/app/templates/dc/services/-tags.hbs @@ -0,0 +1,7 @@ +{{#if (gt item.Tags.length 0) }} +{{tag-list items=item.Tags}} +{{else}} +

+ There are no tags. +

+{{/if}} diff --git a/ui-v2/app/templates/dc/services/-upstreams.hbs b/ui-v2/app/templates/dc/services/-upstreams.hbs new file mode 100644 index 000000000000..f4ad6fcc8139 --- /dev/null +++ b/ui-v2/app/templates/dc/services/-upstreams.hbs @@ -0,0 +1,27 @@ +{{#if (gt item.Proxy.Upstreams.length 0) }} +{{#tabular-collection + data-test-upstreams + items=item.Proxy.Upstreams as |item index| +}} + {{#block-slot 'header'}} + Destination Name + Destination Type + Local Bind Port + {{/block-slot}} + {{#block-slot 'row'}} + + {{item.DestinationName}} + + + {{item.DestinationType}} + + + {{item.LocalBindPort}} + + {{/block-slot}} +{{/tabular-collection}} +{{else}} +

+ There are no upstreams. +

+{{/if}} diff --git a/ui-v2/app/templates/dc/services/index.hbs b/ui-v2/app/templates/dc/services/index.hbs index aee20a971833..7306ea10f314 100644 --- a/ui-v2/app/templates/dc/services/index.hbs +++ b/ui-v2/app/templates/dc/services/index.hbs @@ -35,18 +35,10 @@ - {{#if (and (lt item.ChecksPassing 1) (lt item.ChecksWarning 1) (lt item.ChecksCritical 1) )}} - 0 - {{else}} -
-
Healthchecks Passing
-
{{format-number item.ChecksPassing}}
-
Healthchecks Warning
-
{{format-number item.ChecksWarning}}
-
Healthchecks Critical
-
{{format-number item.ChecksCritical}}
-
- {{/if}} + {{healthcheck-info + passing=item.ChecksPassing warning=item.ChecksWarning critical=item.ChecksCritical + passingWidth=passingWidth warningWidth=warningWidth criticalWidth=criticalWidth + }} {{#if (gt item.Tags.length 0)}} diff --git a/ui-v2/app/templates/dc/services/instance.hbs b/ui-v2/app/templates/dc/services/instance.hbs new file mode 100644 index 000000000000..063855210426 --- /dev/null +++ b/ui-v2/app/templates/dc/services/instance.hbs @@ -0,0 +1,72 @@ +{{#app-view class="instance show"}} + {{#block-slot 'breadcrumbs'}} +
    +
  1. All Services
  2. +
  3. Service ({{item.Service}})
  4. +
  5. Instance
  6. +
+ {{/block-slot}} + {{#block-slot 'header'}} +

+ {{ item.ID }} +{{#with (service/external-source item) as |externalSource| }} + {{#with (css-var (concat '--' externalSource '-color-svg') 'none') as |bg| }} + {{#if (not-eq bg 'none') }} + Registered via {{externalSource}} + {{/if}} + {{/with}} +{{/with}} +

+
+
Service Name
+
{{item.Service}}
+
+
+
Node Name
+
{{item.Node.Node}}
+
+{{#if proxy}} +
+
Sidecar Proxy
+
{{proxy.ServiceID}}
+
+{{/if}} +{{#if (eq item.Kind 'connect-proxy')}} +
+
Dest. Service Instance
+
{{item.Proxy.DestinationServiceID}}
+
+
+
Local Service Address
+
{{item.Proxy.LocalServiceAddress}}:{{item.Proxy.LocalServicePort}}
+
+{{/if}} + {{/block-slot}} + {{#block-slot 'content'}} + {{tab-nav + items=(compact + (array + 'Service Checks' + 'Node Checks' +(if (eq item.Kind 'connect-proxy') 'Upstreams' '') + 'Tags' + ) + ) + selected=selectedTab + }} + {{#each + (compact + (array + (hash id=(slugify 'Service Checks') partial='dc/services/servicechecks') + (hash id=(slugify 'Node Checks') partial='dc/services/nodechecks') +(if (eq item.Kind 'connect-proxy') (hash id=(slugify 'Upstreams') partial='dc/services/upstreams') '') + (hash id=(slugify 'Tags') partial='dc/services/tags') + ) + ) as |panel| + }} + {{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}} + {{partial panel.partial}} + {{/tab-section}} + {{/each}} + {{/block-slot}} +{{/app-view}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/services/show.hbs b/ui-v2/app/templates/dc/services/show.hbs index 5a75f0055f9b..bd693d9b854b 100644 --- a/ui-v2/app/templates/dc/services/show.hbs +++ b/ui-v2/app/templates/dc/services/show.hbs @@ -15,76 +15,29 @@ {{/with}} {{/with}} - {{/block-slot}} - {{#block-slot 'toolbar'}} -{{#if (gt items.length 0) }} - {{catalog-filter searchable=(array searchableHealthy searchableUnhealthy) filters=healthFilters search=s status=filters.status onchange=(action 'filter')}} -{{/if}} + + {{tab-nav + items=(compact + (array + 'Instances' + 'Tags' + ) + ) + selected=selectedTab + }} {{/block-slot}} {{#block-slot 'content'}} -{{#if (gt item.Tags.length 0)}} -
-
Tags
-
- {{#each item.Tags as |item|}} - {{item}} - {{/each}} -
-
-{{/if}} -{{#if (gt unhealthy.length 0) }} -
-

Unhealthy Nodes

-
-
    - {{#changeable-set dispatcher=searchableUnhealthy}} - {{#block-slot 'set' as |unhealthy|}} - {{#each unhealthy as |item|}} - {{healthchecked-resource - tagName='li' - data-test-node=item.Node.Node - href=(href-to 'dc.nodes.show' item.Node.Node) - name=item.Node.Node - service=item.Service.ID - address=(concat (default item.Service.Address item.Node.Address) ':' item.Service.Port) - checks=item.Checks - }} - {{/each}} - {{/block-slot}} - {{#block-slot 'empty'}} -

    - There are no unhealthy nodes for that search. -

    - {{/block-slot}} - {{/changeable-set}} -
-
-
-{{/if}} -{{#if (gt healthy.length 0) }} -
-

Healthy Nodes

- {{#changeable-set dispatcher=searchableHealthy}} - {{#block-slot 'set' as |healthy|}} - {{#list-collection cellHeight=113 items=healthy as |item index|}} - {{healthchecked-resource - href=(href-to 'dc.nodes.show' item.Node.Node) - data-test-node=item.Node.Node - name=item.Node.Node - service=item.Service.ID - address=(concat (default item.Service.Address item.Node.Address) ':' item.Service.Port) - checks=item.Checks - status=item.Checks.[0].Status - }} - {{/list-collection}} - {{/block-slot}} - {{#block-slot 'empty'}} -

- There are no healthy nodes for that search. -

- {{/block-slot}} - {{/changeable-set}} -
-{{/if}} + {{#each + (compact + (array + (hash id=(slugify 'Instances') partial='dc/services/instances') + (hash id=(slugify 'Tags') partial='dc/services/tags') + ) + ) as |panel| + }} + {{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}} + {{partial panel.partial}} + {{/tab-section}} + {{/each}} {{/block-slot}} {{/app-view}} diff --git a/ui-v2/app/utils/computed/purify.js b/ui-v2/app/utils/computed/purify.js index 3c9eba3410a5..1008ed8c4766 100644 --- a/ui-v2/app/utils/computed/purify.js +++ b/ui-v2/app/utils/computed/purify.js @@ -1,4 +1,4 @@ -import { get } from '@ember/object'; +import { get, computed } from '@ember/object'; /** * Converts a conventional non-pure Ember `computed` function into a pure one @@ -8,20 +8,18 @@ import { get } from '@ember/object'; * @param {function} filter - Optional string filter function to pre-process the names of computed properties * @returns {function} - A pure `computed` function */ - -export default function(computed, filter) { +const _success = function(value) { + return value; +}; +const purify = function(computed, filter = args => args) { return function() { let args = [...arguments]; - let success = function(value) { - return value; - }; + let success = _success; // pop the user function off the end if (typeof args[args.length - 1] === 'function') { success = args.pop(); } - if (typeof filter === 'function') { - args = filter(args); - } + args = filter(args); // this is the 'conventional' `computed` const cb = function(name) { return success.apply( @@ -39,4 +37,6 @@ export default function(computed, filter) { // concat/push the user function back on return computed(...args.concat([cb])); }; -} +}; +export const subscribe = purify(computed); +export default purify; diff --git a/ui-v2/tests/acceptance/components/catalog-filter.feature b/ui-v2/tests/acceptance/components/catalog-filter.feature index ed11e247bd16..3b5dad1177ea 100644 --- a/ui-v2/tests/acceptance/components/catalog-filter.feature +++ b/ui-v2/tests/acceptance/components/catalog-filter.feature @@ -123,31 +123,6 @@ Feature: components / catalog-filter | Model | Page | Url | | service | node | /dc-1/nodes/node-0 | ------------------------------------------------- - Scenario: Filtering [Model] in [Page] - Given 1 datacenter model with the value "dc1" - And 2 [Model] models from yaml - --- - - ID: node-0 - --- - When I visit the [Page] page for yaml - --- - dc: dc1 - service: service-0 - --- - Then I fill in with yaml - --- - s: service-0-with-id - --- - And I see 1 [Model] model - Then I see id on the unhealthy like yaml - --- - - service-0-with-id - --- - Where: - ------------------------------------------------- - | Model | Page | Url | - | nodes | service | /dc-1/services/service-0 | - ------------------------------------------------- Scenario: Given 1 datacenter model with the value "dc-1" And 3 service models from yaml diff --git a/ui-v2/tests/acceptance/dc/services/show.feature b/ui-v2/tests/acceptance/dc/services/show.feature index 5fa48f0defa0..0c707f31b986 100644 --- a/ui-v2/tests/acceptance/dc/services/show.feature +++ b/ui-v2/tests/acceptance/dc/services/show.feature @@ -52,7 +52,7 @@ Feature: dc / services / show: Show Service Then I see the text "Tag1" in "[data-test-tags] span:nth-child(1)" Then I see the text "Tag2" in "[data-test-tags] span:nth-child(2)" Then I see the text "Tag3" in "[data-test-tags] span:nth-child(3)" - Scenario: Given various services the various ports on their nodes are displayed + Scenario: Given various services the various nodes on their instances are displayed Given 1 datacenter model with the value "dc1" And 3 node models And 1 service model from yaml @@ -83,21 +83,9 @@ Feature: dc / services / show: Show Service dc: dc1 service: service-0 --- - Then I see address on the healthy like yaml + Then I see address on the instances like yaml --- - "1.1.1.1:8080" - --- - Then I see address on the unhealthy like yaml - --- - "2.2.2.2:8000" - "3.3.3.3:8888" --- - Then I see id on the healthy like yaml - --- - - "passing-service-8080" - --- - Then I see id on the unhealthy like yaml - --- - - "service-8000" - - "service-8888" - --- diff --git a/ui-v2/tests/integration/components/healthcheck-info-test.js b/ui-v2/tests/integration/components/healthcheck-info-test.js new file mode 100644 index 000000000000..613a65065779 --- /dev/null +++ b/ui-v2/tests/integration/components/healthcheck-info-test.js @@ -0,0 +1,22 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('healthcheck-info', 'Integration | Component | healthcheck info', { + integration: true, +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{healthcheck-info}}`); + + assert.equal(this.$('dl').length, 1); + + // Template block usage: + this.render(hbs` + {{#healthcheck-info}} + {{/healthcheck-info}} + `); + assert.equal(this.$('dl').length, 1); +}); diff --git a/ui-v2/tests/integration/components/healthcheck-list-test.js b/ui-v2/tests/integration/components/healthcheck-list-test.js new file mode 100644 index 000000000000..a85c4866de8f --- /dev/null +++ b/ui-v2/tests/integration/components/healthcheck-list-test.js @@ -0,0 +1,23 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('healthcheck-list', 'Integration | Component | healthcheck list', { + integration: true, +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{healthcheck-list}}`); + + assert.equal(this.$('ul').length, 1); + + // Template block usage: + this.render(hbs` + {{#healthcheck-list}} + {{/healthcheck-list}} + `); + + assert.equal(this.$('ul').length, 1); +}); diff --git a/ui-v2/tests/integration/components/healthcheck-output-test.js b/ui-v2/tests/integration/components/healthcheck-output-test.js new file mode 100644 index 000000000000..b72e7412f9be --- /dev/null +++ b/ui-v2/tests/integration/components/healthcheck-output-test.js @@ -0,0 +1,34 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('healthcheck-output', 'Integration | Component | healthcheck output', { + integration: true, +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{healthcheck-output}}`); + + assert.notEqual( + this.$() + .text() + .trim() + .indexOf('Output'), + -1 + ); + + // Template block usage: + this.render(hbs` + {{#healthcheck-output}}{{/healthcheck-output}} + `); + + assert.notEqual( + this.$() + .text() + .trim() + .indexOf('Output'), + -1 + ); +}); diff --git a/ui-v2/tests/integration/components/healthcheck-status-test.js b/ui-v2/tests/integration/components/healthcheck-status-test.js index b19207e5a4cf..f4e9bd78ff22 100644 --- a/ui-v2/tests/integration/components/healthcheck-status-test.js +++ b/ui-v2/tests/integration/components/healthcheck-status-test.js @@ -10,25 +10,11 @@ test('it renders', function(assert) { // Handle any actions with this.on('myAction', function(val) { ... }); this.render(hbs`{{healthcheck-status}}`); - - assert.notEqual( - this.$() - .text() - .trim() - .indexOf('Output'), - -1 - ); + assert.equal(this.$('dt').length, 1); // Template block usage: this.render(hbs` {{#healthcheck-status}}{{/healthcheck-status}} `); - - assert.notEqual( - this.$() - .text() - .trim() - .indexOf('Output'), - -1 - ); + assert.equal(this.$('dt').length, 1); }); diff --git a/ui-v2/tests/integration/components/tag-list-test.js b/ui-v2/tests/integration/components/tag-list-test.js new file mode 100644 index 000000000000..6924c8562cd3 --- /dev/null +++ b/ui-v2/tests/integration/components/tag-list-test.js @@ -0,0 +1,33 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('tag-list', 'Integration | Component | tag list', { + integration: true, +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{tag-list}}`); + + assert.equal( + this.$() + .text() + .trim(), + '' + ); + + // Template block usage: + this.render(hbs` + {{#tag-list}} + {{/tag-list}} + `); + + assert.equal( + this.$() + .text() + .trim(), + '' + ); +}); diff --git a/ui-v2/tests/pages/dc/services/show.js b/ui-v2/tests/pages/dc/services/show.js index f50c60c834dd..5b5fb2e3813b 100644 --- a/ui-v2/tests/pages/dc/services/show.js +++ b/ui-v2/tests/pages/dc/services/show.js @@ -2,18 +2,8 @@ export default function(visitable, attribute, collection, text, filter) { return { visit: visitable('/:dc/services/:service'), externalSource: attribute('data-test-external-source', 'h1 span'), - nodes: collection('[data-test-node]', { - name: attribute('data-test-node'), - }), - healthy: collection('[data-test-healthy] [data-test-node]', { - name: attribute('data-test-node'), - address: text('header strong'), - id: text('header em'), - }), - unhealthy: collection('[data-test-unhealthy] [data-test-node]', { - name: attribute('data-test-node'), - address: text('header strong'), - id: text('header em'), + instances: collection('#instances [data-test-tabular-row]', { + address: text('[data-test-address]'), }), filter: filter, }; diff --git a/ui-v2/tests/unit/adapters/proxy-test.js b/ui-v2/tests/unit/adapters/proxy-test.js new file mode 100644 index 000000000000..13859457edeb --- /dev/null +++ b/ui-v2/tests/unit/adapters/proxy-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | proxy', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let adapter = this.owner.lookup('adapter:proxy'); + assert.ok(adapter); + }); +}); diff --git a/ui-v2/tests/unit/controllers/dc/services/instance-test.js b/ui-v2/tests/unit/controllers/dc/services/instance-test.js new file mode 100644 index 000000000000..2b0693934f04 --- /dev/null +++ b/ui-v2/tests/unit/controllers/dc/services/instance-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('controller:dc/services/instance', 'Unit | Controller | dc/services/instance', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + let controller = this.subject(); + assert.ok(controller); +}); diff --git a/ui-v2/tests/unit/models/proxy-test.js b/ui-v2/tests/unit/models/proxy-test.js new file mode 100644 index 000000000000..b37e80f56d29 --- /dev/null +++ b/ui-v2/tests/unit/models/proxy-test.js @@ -0,0 +1,14 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { run } from '@ember/runloop'; + +module('Unit | Model | proxy', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = run(() => store.createRecord('proxy', {})); + assert.ok(model); + }); +}); diff --git a/ui-v2/tests/unit/routes/dc/services/instance-test.js b/ui-v2/tests/unit/routes/dc/services/instance-test.js new file mode 100644 index 000000000000..122dc9ee163e --- /dev/null +++ b/ui-v2/tests/unit/routes/dc/services/instance-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:dc/services/instance', 'Unit | Route | dc/services/instance', { + // Specify the other units that are required for this test. + needs: ['service:repository/service', 'service:repository/proxy'], +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/ui-v2/tests/unit/serializers/proxy-test.js b/ui-v2/tests/unit/serializers/proxy-test.js new file mode 100644 index 000000000000..44090cfe02d1 --- /dev/null +++ b/ui-v2/tests/unit/serializers/proxy-test.js @@ -0,0 +1,24 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { run } from '@ember/runloop'; + +module('Unit | Serializer | proxy', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('proxy'); + + assert.ok(serializer); + }); + + test('it serializes records', function(assert) { + let store = this.owner.lookup('service:store'); + let record = run(() => store.createRecord('proxy', {})); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +});