From 355f034822fe34bfe8b6db86eb20ccb986591280 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 | 30 ++++-- .../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, 919 insertions(+), 370 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 0000000000..d4f4f41ea5 --- /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 0000000000..abe1ccedb6 --- /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 0000000000..092a1aadaf --- /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 0000000000..227501fc5c --- /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 25a646d758..367cca6469 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 142b50bf4d..db166df641 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 0000000000..1656e4a23c --- /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 0000000000..a8934de52d --- /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 d4653888a5..3f21a7b36b 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)); + 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)); }), - 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; - }); - }), - 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 69875fdb11..ebdd48c926 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 0000000000..9e08582199 --- /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 628a713c7a..764bc5f358 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 0000000000..da863ba418 --- /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 bf5fa0d657..9376abdbc1 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 0000000000..7c3c5c42e0 --- /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 a3bda1c8c8..740406cb59 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 0000000000..ce8c055d83 --- /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 5654c3a618..2da90a3c72 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 34827cc5bf..f518b5d38d 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 e0269410ff..a117fa1258 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 header > div:last-of-type, %app-view h2 { - border-color: $keyline-light; + border-bottom: $decor-border-200; +} +@media #{$--horizontal-selects} { + %app-view header h1 { + border-bottom: $decor-border-200; + } +} +@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: $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 6bbe41f288..fb86355e92 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 942efbb8cb..c0bd6fa6e0 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 0000000000..a249d32c49 --- /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 0000000000..0f084db303 --- /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 0000000000..9b22b05f01 --- /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 550b2c992a..96216d6315 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 0000000000..bc18252196 --- /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 ef40daf375..5f1a9403de 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 d0fd2cec13..9d26d4d663 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 acc1eb7d6f..4c61d11f3c 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 f461f7bea1..edfda9d16e 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 4d8f0673a1..af1809c339 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 364c214b65..749774ad1a 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 23301b423e..2706e64dc5 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 b0c08a7f8c..64a1b9138e 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 9588b870eb..7b20b1aa1f 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 81faad3695..1538bcf0d8 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 c9745e787f..53d6678ef6 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 0000000000..6bc2ea8e70 --- /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 0000000000..bc18252196 --- /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 0000000000..2590f8b4c1 --- /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 0000000000..e69de29bb2 diff --git a/ui-v2/app/styles/core/typography.scss b/ui-v2/app/styles/core/typography.scss index 2331c49c90..bc945842a8 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 c38a957a9d..e69de29bb2 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 56895ef266..8e7160e678 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 0000000000..13b62ac08c --- /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 0000000000..4b5774588e --- /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 0000000000..05a75e40a2 --- /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 05a75e40a2..383f67386c 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 0000000000..c51ea2a418 --- /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 a956fad631..19acae8c38 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 856e51b172..e692bb0d52 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 0000000000..7ef34c3400 --- /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 0000000000..487db34fc9 --- /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 0000000000..424772e705 --- /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 0000000000..c0a3a0f783 --- /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 0000000000..f4ad6fcc81 --- /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 aee20a9718..7306ea10f3 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 0000000000..0638552104 --- /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 5a75f0055f..bd693d9b85 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 3c9eba3410..1008ed8c47 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 ed11e247bd..3b5dad1177 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 5fa48f0def..0c707f31b9 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 0000000000..613a650657 --- /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 0000000000..a85c4866de --- /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 0000000000..b72e7412f9 --- /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 b19207e5a4..f4e9bd78ff 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 0000000000..6924c8562c --- /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 f50c60c834..5b5fb2e381 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 0000000000..13859457ed --- /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 0000000000..2b0693934f --- /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 0000000000..b37e80f56d --- /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 0000000000..122dc9ee16 --- /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 0000000000..44090cfe02 --- /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); + }); +});