UI: External Source markers (#4640)

1. Addition of external source icons for services marked as such.
2. New %with-tooltip css component (wip)
3. New 'no healthcheck' icon as external sources might not have
healthchecks, also minus icon on node cards in the service detail view
4. If a service doesn't have healthchecks, we use the [Services] tabs as the
default instead of the [Health Checks] tab in the Service detail page. 
5. `css-var` helper. The idea here is that it will eventually be
replaced with pure css custom properties instead of having to use JS. It
would be nice to be able to build the css variables into the JS at build
time (you'd probably still want to specify in config which variables you
wanted available in JS), but that's possible future work.

Lastly there is probably a tiny bit more testing edits here than usual,
I noticed that there was an area where the dynamic mocking wasn't
happening, it was just using the mocks from consul-api-double, the mocks
I was 'dynamically' setting happened to be the same as the ones in
consul-api-double. I've fixed this here also but it wasn't effecting
anything until actually made certain values dynamic.
This commit is contained in:
John Cowen 2018-09-12 20:23:39 +01:00 committed by GitHub
parent b1d83f98b0
commit b279f23372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 467 additions and 105 deletions

View File

@ -15,7 +15,12 @@ export default Controller.extend(WithFiltering, {
},
setProperties: function() {
this._super(...arguments);
set(this, 'selectedTab', 'health-checks');
// the default selected tab depends on whether you have any healthchecks or not
// so check the length here.
// 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', get(this.item, 'Checks.length') > 0 ? 'health-checks' : 'services');
},
filter: function(item, { s = '' }) {
const term = s.toLowerCase();

View File

@ -37,17 +37,17 @@ export default Controller.extend(WithHealthFiltering, {
item.hasStatus(status)
);
},
totalWidth: computed('{maxPassing,maxWarning,maxCritical}', function() {
maxWidth: computed('{maxPassing,maxWarning,maxCritical}', function() {
const PADDING = 32 * 3 + 13;
return ['maxPassing', 'maxWarning', 'maxCritical'].reduce((prev, item) => {
return prev + width(get(this, item));
}, PADDING);
}),
thWidth: computed('totalWidth', function() {
return widthDeclaration(get(this, 'totalWidth'));
totalWidth: computed('maxWidth', function() {
return widthDeclaration(get(this, 'maxWidth'));
}),
remainingWidth: computed('totalWidth', function() {
return htmlSafe(`width: calc(50% - ${Math.round(get(this, 'totalWidth') / 2)}px)`);
remainingWidth: computed('maxWidth', function() {
return htmlSafe(`width: calc(50% - ${Math.round(get(this, 'maxWidth') / 2)}px)`);
}),
maxPassing: computed('items', function() {
return max(get(this, 'items'), 'ChecksPassing');

View File

@ -0,0 +1,12 @@
import { helper } from '@ember/component/helper';
const cssVars = {
'--kubernetes-color-svg': `url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 21 20" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" stroke="%23FFF" fill="none"><path d="M10.21 1.002a1.241 1.241 0 0 0-.472.12L3.29 4.201a1.225 1.225 0 0 0-.667.83l-1.591 6.922a1.215 1.215 0 0 0 .238 1.035l4.463 5.55c.234.29.59.46.964.46l7.159-.002c.375 0 .73-.168.964-.459l4.462-5.55c.234-.292.322-.673.238-1.036l-1.593-6.921a1.225 1.225 0 0 0-.667-.83l-6.45-3.08a1.242 1.242 0 0 0-.598-.12z" fill="%23326CE5"/><path d="M10.275 3.357c-.213 0-.386.192-.386.429v.11c.005.136.035.24.052.367.033.27.06.492.043.7a.421.421 0 0 1-.125.2l-.01.163a4.965 4.965 0 0 0-3.22 1.548 6.47 6.47 0 0 1-.138-.099c-.07.01-.139.03-.23-.022-.172-.117-.33-.277-.52-.47-.087-.093-.15-.181-.254-.27L5.4 5.944a.46.46 0 0 0-.269-.101.372.372 0 0 0-.307.136c-.133.167-.09.422.094.57l.006.003.08.065c.11.08.21.122.32.187.231.142.422.26.574.403.06.063.07.175.078.223l.123.11a4.995 4.995 0 0 0-.787 3.483l-.162.047c-.042.055-.103.141-.166.167-.198.063-.422.086-.692.114-.126.01-.236.004-.37.03-.03.005-.07.016-.103.023l-.003.001-.006.002c-.228.055-.374.264-.327.47.047.206.27.331.498.282h.006c.003-.001.005-.003.008-.003l.1-.022c.131-.036.227-.088.346-.133.255-.092.467-.168.673-.198.086-.007.177.053.222.078l.168-.029a5.023 5.023 0 0 0 2.226 2.78l-.07.168c.025.065.053.154.034.218-.075.195-.203.4-.35.628-.07.106-.142.188-.206.309l-.05.104c-.099.212-.026.456.165.548.191.092.43-.005.532-.218h.001v-.001c.015-.03.036-.07.048-.098.055-.126.073-.233.111-.354.102-.257.159-.526.3-.694.038-.046.1-.063.166-.08l.087-.159a4.987 4.987 0 0 0 3.562.01l.083.148c.066.021.138.032.197.12.105.179.177.391.265.648.038.121.057.229.112.354.012.029.033.069.048.099.102.213.341.311.533.219.19-.092.264-.337.164-.549l-.05-.104c-.064-.12-.136-.202-.207-.307-.146-.23-.267-.419-.342-.613-.032-.1.005-.163.03-.228-.015-.017-.047-.111-.065-.156a5.023 5.023 0 0 0 2.225-2.8l.165.03c.058-.039.112-.088.216-.08.206.03.418.106.673.198.12.045.215.098.347.133.028.008.068.015.1.022l.007.002.006.001c.229.05.45-.076.498-.282.047-.206-.1-.415-.327-.47l-.112-.027c-.134-.025-.243-.019-.37-.03-.27-.027-.494-.05-.692-.113-.081-.031-.139-.128-.167-.167l-.156-.046a4.997 4.997 0 0 0-.804-3.474l.137-.123c.006-.069.001-.142.073-.218.151-.143.343-.261.574-.404.11-.064.21-.106.32-.187.025-.018.06-.047.086-.068.185-.148.227-.403.094-.57-.133-.166-.39-.182-.575-.034-.027.02-.062.048-.086.068-.104.09-.168.178-.255.27-.19.194-.348.355-.52.471-.075.044-.185.029-.235.026l-.146.104A5.059 5.059 0 0 0 10.7 5.328a9.325 9.325 0 0 1-.009-.172c-.05-.048-.11-.09-.126-.193-.017-.208.011-.43.044-.7.018-.126.047-.23.053-.367l-.001-.11c0-.237-.173-.429-.386-.429zM9.79 6.351l-.114 2.025-.009.004a.34.34 0 0 1-.54.26l-.003.002-1.66-1.177A3.976 3.976 0 0 1 9.79 6.351zm.968 0a4.01 4.01 0 0 1 2.313 1.115l-1.65 1.17-.006-.003a.34.34 0 0 1-.54-.26h-.003L10.76 6.35zm-3.896 1.87l1.516 1.357-.002.008a.34.34 0 0 1-.134.585l-.001.006-1.944.561a3.975 3.975 0 0 1 .565-2.516zm6.813.001a4.025 4.025 0 0 1 .582 2.51l-1.954-.563-.001-.008a.34.34 0 0 1-.134-.585v-.004l1.507-1.35zm-3.712 1.46h.62l.387.483-.139.602-.557.268-.56-.269-.138-.602.387-.482zm1.99 1.652a.339.339 0 0 1 .08.005l.002-.004 2.01.34a3.98 3.98 0 0 1-1.609 2.022l-.78-1.885.002-.003a.34.34 0 0 1 .296-.475zm-3.375.008a.34.34 0 0 1 .308.474l.005.007-.772 1.866a3.997 3.997 0 0 1-1.604-2.007l1.993-.339.003.005a.345.345 0 0 1 .067-.006zm1.683.817a.338.338 0 0 1 .312.179h.008l.982 1.775a3.991 3.991 0 0 1-2.57-.002l.979-1.772h.001a.34.34 0 0 1 .288-.18z" stroke-width=".25" fill="%23FFF"/></g></svg>')`,
'--terraform-color-svg': `url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 16 18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="%235C4EE5" d="M5.51 3.15l4.886 2.821v5.644L5.509 8.792z"/><path fill="%234040B2" d="M10.931 5.971v5.644l4.888-2.823V3.15z"/><path fill="%235C4EE5" d="M.086 0v5.642l4.887 2.823V2.82zM5.51 15.053l4.886 2.823v-5.644l-4.887-2.82z"/></g></svg>')`,
'--nomad-color-svg': `url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 16 18" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path fill="%231F9967" d="M11.569 6.871v2.965l-2.064 1.192-1.443-.894v7.74l.04.002 7.78-4.47V4.48h-.145z"/><path fill="%2325BA81" d="M7.997 0L.24 4.481l5.233 3.074 1.06-.645 2.57 1.435v-2.98l2.465-1.481v2.987l4.314-2.391v-.011z"/><path fill="%2325BA81" d="M7.02 9.54v2.976l-2.347 1.488V8.05l.89-.548L.287 4.48.24 4.48v8.926l7.821 4.467v-7.74z"/></g></svg>')`,
'--consul-color-svg': `url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M8.693 10.707a1.862 1.862 0 1 1-.006-3.724 1.862 1.862 0 0 1 .006 3.724" fill="%23961D59"/><path d="M12.336 9.776a.853.853 0 1 1 0-1.707.853.853 0 0 1 0 1.707M15.639 10.556a.853.853 0 1 1 .017-.07c-.01.022-.01.044-.017.07M14.863 8.356a.855.855 0 0 1-.925-1.279.855.855 0 0 1 1.559.255c.024.11.027.222.009.333a.821.821 0 0 1-.642.691M17.977 10.467a.849.849 0 1 1-1.67-.296.849.849 0 0 1 .982-.692c.433.073.74.465.709.905a.221.221 0 0 0-.016.076M17.286 8.368a.853.853 0 1 1-.279-1.684.853.853 0 0 1 .279 1.684M16.651 13.371a.853.853 0 1 1-1.492-.828.853.853 0 0 1 1.492.828M16.325 5.631a.853.853 0 1 1-.84-1.485.853.853 0 0 1 .84 1.485" fill="%23D62783"/><path d="M8.842 17.534c-4.798 0-8.687-3.855-8.687-8.612C.155 4.166 4.045.31 8.842.31a8.645 8.645 0 0 1 5.279 1.77l-1.056 1.372a6.987 6.987 0 0 0-7.297-.709 6.872 6.872 0 0 0 0 12.356 6.987 6.987 0 0 0 7.297-.709l1.056 1.374a8.66 8.66 0 0 1-5.279 1.77z" fill="%23D62783" fill-rule="nonzero"/></g></svg>')`,
};
export function cssVar(params, hash) {
return typeof cssVars[params[0]] !== 'undefined' ? cssVars[params[0]] : params[1];
}
export default helper(cssVar);

View File

@ -0,0 +1,16 @@
import { helper } from '@ember/component/helper';
import { get } from '@ember/object';
export function serviceExternalSource(params, hash) {
let source = get(params[0], 'ExternalSources.firstObject');
if (!source) {
source = get(params[0], 'Meta.external-source');
}
const prefix = typeof hash.prefix === 'undefined' ? '' : hash.prefix;
if (source) {
return `${prefix}${source}`;
}
return;
}
export default helper(serviceExternalSource);

View File

@ -14,6 +14,8 @@ export default Model.extend({
},
}),
Kind: attr('string'),
ExternalSources: attr(),
Meta: attr(),
Address: attr('string'),
Port: attr('number'),
EnableTagOverride: attr('boolean'),

View File

@ -29,6 +29,7 @@
@import 'components/confirmation-dialog';
@import 'components/feedback-dialog';
@import 'components/notice';
@import 'components/with-tooltip';
@import 'core/typography';
@import 'core/layout';

View File

@ -0,0 +1,3 @@
$consul-color-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M8.693 10.707a1.862 1.862 0 1 1-.006-3.724 1.862 1.862 0 0 1 .006 3.724" fill="%23961D59"/><path d="M12.336 9.776a.853.853 0 1 1 0-1.707.853.853 0 0 1 0 1.707M15.639 10.556a.853.853 0 1 1 .017-.07c-.01.022-.01.044-.017.07M14.863 8.356a.855.855 0 0 1-.925-1.279.855.855 0 0 1 1.559.255c.024.11.027.222.009.333a.821.821 0 0 1-.642.691M17.977 10.467a.849.849 0 1 1-1.67-.296.849.849 0 0 1 .982-.692c.433.073.74.465.709.905a.221.221 0 0 0-.016.076M17.286 8.368a.853.853 0 1 1-.279-1.684.853.853 0 0 1 .279 1.684M16.651 13.371a.853.853 0 1 1-1.492-.828.853.853 0 0 1 1.492.828M16.325 5.631a.853.853 0 1 1-.84-1.485.853.853 0 0 1 .84 1.485" fill="%23D62783"/><path d="M8.842 17.534c-4.798 0-8.687-3.855-8.687-8.612C.155 4.166 4.045.31 8.842.31a8.645 8.645 0 0 1 5.279 1.77l-1.056 1.372a6.987 6.987 0 0 0-7.297-.709 6.872 6.872 0 0 0 0 12.356 6.987 6.987 0 0 0 7.297-.709l1.056 1.374a8.66 8.66 0 0 1-5.279 1.77z" fill="%23D62783" fill-rule="nonzero"/></g></svg>');
$nomad-color-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 16 18" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path fill="%231F9967" d="M11.569 6.871v2.965l-2.064 1.192-1.443-.894v7.74l.04.002 7.78-4.47V4.48h-.145z"/><path fill="%2325BA81" d="M7.997 0L.24 4.481l5.233 3.074 1.06-.645 2.57 1.435v-2.98l2.465-1.481v2.987l4.314-2.391v-.011z"/><path fill="%2325BA81" d="M7.02 9.54v2.976l-2.347 1.488V8.05l.89-.548L.287 4.48.24 4.48v8.926l7.821 4.467v-7.74z"/></g></svg>');
$terraform-color-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 16 18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="%235C4EE5" d="M5.51 3.15l4.886 2.821v5.644L5.509 8.792z"/><path fill="%234040B2" d="M10.931 5.971v5.644l4.888-2.823V3.15z"/><path fill="%235C4EE5" d="M.086 0v5.642l4.887 2.823V2.82zM5.51 15.053l4.886 2.823v-5.644l-4.887-2.82z"/></g></svg>');

View File

@ -1,3 +1,4 @@
@import './decoration/index';
@import './color/index';
@import './typography/index';
@import './icons/index';

View File

@ -12,10 +12,6 @@
white-space: nowrap;
}
%healthchecked-resource,
%healthchecked-resource li {
border-color: $ui-gray-200;
}
%healthchecked-resource,
%healthchecked-resource header,
%healthchecked-resource li {
position: relative;
@ -76,3 +72,10 @@
.healthy .healthchecked-resource li:only-child strong {
display: none;
}
%healthchecked-resource ul:empty {
position: absolute;
top: 18px;
right: 20px;
width: 1em;
height: 1em;
}

View File

@ -1,3 +1,14 @@
%healthchecked-resource {
border: $decor-border-100;
box-shadow: 0 4px 8px 0 rgba($ui-black, 0.05);
}
%healthchecked-resource li {
border-top: $decor-border-100;
}
%healthchecked-resource,
%healthchecked-resource li {
border-color: $ui-gray-200;
}
%healthchecked-resource li.passing {
color: $ui-color-success;
}
@ -7,17 +18,16 @@
%healthchecked-resource li.critical {
color: $ui-color-failure;
}
%healthchecked-resource {
border: $decor-border-100;
box-shadow: 0 4px 8px 0 rgba($ui-black, 0.05);
}
%healthchecked-resource:hover,
%healthchecked-resource:focus {
box-shadow: 0 8px 10px 0 rgba($ui-black, 0.1);
}
%healthchecked-resource li {
border-top: $decor-border-100;
}
%healthchecked-resource {
border-radius: $radius-small;
}
%healthchecked-resource ul:empty {
@extend %with-no-healthchecks;
}
%healthchecked-resource ul:empty::before {
color: $ui-gray-400;
}

View File

@ -1,15 +1,35 @@
%pseudo-icon {
width: 1em;
height: 1em;
position: absolute;
top: 50%;
margin-top: -0.6em;
/*TODO: The old pseudo-icon was to specific */
/* make a temporary one with the -- prefix */
/* to make it more reusable temporarily */
%--pseudo-icon {
display: block;
content: '';
visibility: visible;
position: absolute;
top: 50%;
background-repeat: no-repeat;
background-position: center center;
}
%pseudo-icon-bg-img {
@extend %--pseudo-icon;
background-size: contain;
background-color: transparent;
}
%pseudo-icon-css {
@extend %--pseudo-icon;
width: 1em;
height: 1em;
margin-top: -0.6em;
background-color: currentColor;
visibility: visible;
}
%pseudo-icon {
@extend %pseudo-icon-css;
}
%with-external-source-icon {
background-repeat: no-repeat;
background-size: contain;
width: 18px;
height: 18px;
}
%with-dot {
content: '';
@ -135,6 +155,10 @@
@extend %pseudo-icon;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M4 5.064L1.064 8 0 6.936 2.936 4 0 1.064 1.064 0 4 2.936 6.936 0 8 1.064 5.064 4 8 6.936 6.936 8 4 5.064z" fill="%23FFF"/></svg>');
}
%with-minus {
@extend %pseudo-icon;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="9" height="2" viewBox="0 0 9 2" xmlns="http://www.w3.org/2000/svg"><path fill="%23FFF" fill-rule="nonzero" d="M0 0h8v2H0z"/></svg>');
}
%with-warning-icon-orange {
@extend %pseudo-icon;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="14" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M13.645 10.092c.24.409.365.88.365 1.37 0 1.392-1.027 2.527-2.294 2.538H2.322c-.824 0-1.592-.487-2.004-1.27a2.761 2.761 0 0 1 0-2.538l4.686-8.904C5.416.505 6.184.018 7.008.018c.824 0 1.592.487 2.004 1.27l4.633 8.804zm-5.989 1.264V9.607H6.344v1.749h1.312zm0-3.048v-4.37H6.344v4.37h1.312z" fill="%23fa8f37"/></svg>');
@ -183,3 +207,7 @@
@extend %with-cross;
border-radius: 20%;
}
%with-no-healthchecks::before {
@extend %with-minus;
border-radius: 20%;
}

View File

@ -8,6 +8,7 @@
}
.healthy > div {
width: calc(100% + 23px);
min-height: 500px;
}
.unhealthy > div {
margin-bottom: 20px;

View File

@ -30,6 +30,9 @@
display: flex;
align-items: flex-start;
}
%app-view h1 span {
@extend %with-external-source-icon;
}
%app-view {
margin-top: 50px;
}

View File

@ -11,6 +11,12 @@ td dt.warning {
td dt.critical {
@extend %with-critical;
}
td span.zero {
@extend %with-no-healthchecks;
display: block;
text-indent: 20px;
color: $ui-gray-400;
}
table:not(.sessions) tr {
cursor: pointer;
}

View File

@ -16,6 +16,17 @@ table tr > * {
tr > * dl {
float: left;
}
/* TODO: putting this here is less than ideal */
/* but this is another area where I am specifically */
/* targetting table-like things. This is now a prime */
/* area for a bit of refactoring/reorganizing */
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;
}
html.template-service.template-list main table tr {
@extend %services-row;
}
@ -34,7 +45,6 @@ html.template-node.template-show main table tr {
html.template-node.template-show main table.sessions tr {
@extend %node-sessions-row;
}
@media #{$--horizontal-session-list} {
%node-sessions-row > * {
// (100% / 7) - (300px / 6) - (120px / 6)

View File

@ -0,0 +1,9 @@
@import './with-tooltip/index';
%app-view h1 span {
@extend %with-tooltip;
}
%app-view h1 span {
text-indent: -9000px;
font-size: 0;
top: -9px;
}

View File

@ -0,0 +1,2 @@
@import './skin';
@import './layout';

View File

@ -0,0 +1,39 @@
%with-tooltip {
position: relative;
display: inline-flex;
justify-content: center;
align-items: center;
}
%with-tooltip::before,
%with-tooltip::after {
position: absolute;
}
%with-tooltip::before {
padding: 10px;
bottom: calc(100% + 5px);
text-align: center;
white-space: nowrap;
content: attr(data-tooltip);
// incase you are using text-indent to hide the
// text of the element %with-tooltip
text-indent: 0;
}
%with-tooltip::after {
content: '';
left: 50%;
margin-left: -5px;
top: -10px;
width: 10px;
height: 10px;
transform: rotate(45deg);
}
%with-tooltip::after,
%with-tooltip::before {
display: none;
}
%with-tooltip:hover::after,
%with-tooltip:hover::before,
%with-tooltip:focus::after,
%with-tooltip:focus::before {
display: block;
}

View File

@ -0,0 +1,9 @@
%with-tooltip::before,
%with-tooltip::after {
color: $ui-white;
background-color: $ui-gray-800;
}
%with-tooltip::before {
border-radius: $decor-radius-200;
box-shadow: 0 3px 1px 0 rgba($ui-black, 0.12);
}

View File

@ -48,7 +48,8 @@ td a {
th,
%breadcrumbs a,
%action-group a,
%tab-nav {
%tab-nav,
%with-tooltip::before {
font-weight: $typo-weight-medium;
}
main label a[rel*='help'],
@ -86,6 +87,7 @@ td {
font-size: $typo-size-600;
}
th,
%with-tooltip::before,
%healthchecked-resource strong,
%footer {
font-size: $typo-size-700;

View File

@ -1,12 +1,12 @@
<header class={{if service 'with-service' }}>
<header class={{if service 'with-service' }}>
<strong>{{address}}</strong>
<a href={{href}}>
<span>{{name}}</span>
<em>{{service}}</em>
</a>
</header>
<ul>
{{#if status }}
{{! its important to keep this <ul> with no whitespace so we can use :empty in css }}
<ul>{{#if status }}
<li class={{status}}>
<a href={{href}}>
<strong>{{status}}</strong>
@ -29,5 +29,4 @@
</a>
</li>
{{/if}}
{{/if}}
</ul>
{{/if}}</ul>

View File

@ -1,6 +1,11 @@
{{#if (gt item.Checks.length 0) }}
<ul data-test-node-healthchecks>
{{#each (sort-by (action 'sortChecksByImportance') item.Checks) as |check| }}
{{healthcheck-status data-test-node-healthcheck=check.Name tagName='li' name=check.Name class=check.Status status=check.Status notes=check.Notes output=check.Output}}
{{/each}}
</ul>
{{else}}
<p>
This node has no health checks.
</p>
{{/if}}

View File

@ -15,7 +15,10 @@
{{/block-slot}}
{{#block-slot 'row'}}
<td data-test-service-name="{{item.Service}}">
<a href={{href-to 'dc.services.show' item.Service }}>{{item.Service}}{{#if (not-eq item.ID item.Service) }} <em data-test-service-id="{{item.ID}}">({{item.ID}})</em>{{/if}}</a>
<a href={{href-to 'dc.services.show' item.Service}}>
<span data-test-external-source="{{service/external-source item}}" style="background-image: {{css-var (concat '--' (service/external-source item) '-color-svg') 'none'}}"></span>
{{item.Service}}{{#if (not-eq item.ID item.Service) }}<em data-test-service-id="{{item.ID}}">({{item.ID}})</em>{{/if}}
</a>
</td>
<td data-test-service-port="{{item.Port}}" class="port">
{{item.Port}}

View File

@ -10,13 +10,18 @@
{{/block-slot}}
{{#block-slot 'header'}}
<h1>
{{ item.Node }}
{{ item.Node }}
</h1>
{{tab-nav
items=(compact
(array 'Health Checks' 'Services' (if tomography 'Round Trip Time' '') 'Lock Sessions')
(array
'Health Checks'
'Services'
(if tomography 'Round Trip Time' '')
'Lock Sessions'
)
)
selected=(if selectedTab selectedTab 'health-checks')
selected=selectedTab
}}
{{/block-slot}}
{{#block-slot 'actions'}}
@ -49,7 +54,7 @@
)
) as |panel|
}}
{{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab 'health-checks') panel.id) onchange=(action "change")}}
{{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}}
{{partial panel.partial}}
{{/tab-section}}
{{/each}}

View File

@ -22,14 +22,21 @@
}}
{{#block-slot 'header'}}
<th style={{remainingWidth}}>Service</th>
<th style={{thWidth}}>Node Health</th>
<th style={{totalWidth}}>Node Health</th>
<th style={{remainingWidth}}>Tags</th>
{{/block-slot}}
{{#block-slot 'row'}}
<td data-test-service="{{item.Name}}" style={{remainingWidth}}>
<a href={{href-to 'dc.services.show' item.Name}}>{{item.Name}}</a>
<a href={{href-to 'dc.services.show' item.Name}}>
<span data-test-external-source="{{service/external-source item}}" style="background-image: {{css-var (concat '--' (service/external-source item) '-color-svg') 'none'}}"></span>
{{item.Name}}
</a>
</td>
<td>
<td style={{totalWidth}}>
{{#if (and (lt item.ChecksPassing 1) (lt item.ChecksWarning 1) (lt item.ChecksCritical 1) )}}
<span title="No Healthchecks" class="zero">0</span>
{{else}}
<dl>
<dt title="Passing" class="passing{{if (lt item.ChecksPassing 1) ' zero'}}">Healthchecks Passing</dt>
<dd title="Passing" class={{if (lt item.ChecksPassing 1) 'zero'}} style={{passingWidth}}>{{format_number item.ChecksPassing}}</dd>
@ -38,6 +45,7 @@
<dt title="Critical" class="critical{{if (lt item.ChecksCritical 1) ' zero'}}">Healthchecks Critical</dt>
<dd title="Critical" class={{if (lt item.ChecksCritical 1) 'zero'}} style={{criticalWidth}}>{{format_number item.ChecksCritical}}</dd>
</dl>
{{/if}}
</td>
<td class="tags" style={{remainingWidth}}>
{{#if (gt item.Tags.length 0)}}

View File

@ -5,9 +5,10 @@
</ol>
{{/block-slot}}
{{#block-slot 'header'}}
<h1>
{{ item.Service.Service }}
</h1>
<h1>
{{ item.Service.Service }}
<span data-test-external-source="{{service/external-source item.Service}}" style="background-image: {{css-var (concat '--' (service/external-source item.Service) '-color-svg') 'none'}}" data-tooltip="Registered via {{service/external-source item.Service}}">Registered via {{service/external-source item.Service}}</span>
</h1>
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}

View File

@ -61,9 +61,22 @@ Feature: components / catalog-filter
-------------------------------------------------
Scenario: Filtering [Model] in [Page]
Given 1 datacenter model with the value "dc1"
And 2 node models from yaml
And 1 node model from yaml
---
- ID: node-0
ID: node-0
Services:
- ID: 'service-0-with-id'
Port: 65535
Service: 'service-0'
Tags: ['monitor', 'two', 'three']
- ID: 'service-1'
Port: 0
Service: 'service-1'
Tags: ['hard drive', 'monitor', 'three']
- ID: 'service-2'
Port: 1
Service: 'service-2'
Tags: ['one', 'two', 'three']
---
When I visit the [Page] page for yaml
---
@ -71,7 +84,6 @@ Feature: components / catalog-filter
node: node-0
---
# And I see 3 healthcheck model with the name "Disk Util"
# And then pause for 5000
When I click services on the tabs
And I see servicesIsSelected on the tabs

View File

@ -1,16 +0,0 @@
@setupApplicationTest
Feature: Search services within nodes by name and port
Scenario: Given 1 node
Given 1 datacenter model with the value "dc1"
And 1 node models from yaml
---
- ID: node-0
---
When I visit the node page for yaml
---
dc: dc1
node: node-0
---
When I click services on the tabs
And I see servicesIsSelected on the tabs

View File

@ -0,0 +1,53 @@
@setupApplicationTest
Feature: dc / nodes / services / list: Node > Services Listing
Scenario: Given 1 node
Given 1 datacenter model with the value "dc1"
And 1 node model from yaml
---
ID: node-0
Services:
- ID: 'service-0-with-id'
Port: 65535
Service: 'service-0'
Tags: ['monitor', 'two', 'three']
Meta:
external-source: consul
- ID: 'service-1'
Port: 0
Service: 'service-1'
Tags: ['hard drive', 'monitor', 'three']
Meta:
external-source: nomad
- ID: 'service-2'
Port: 1
Service: 'service-2'
Tags: ['one', 'two', 'three']
Meta:
external-source: terraform
- ID: 'service-3'
Port: 2
Service: 'service-3'
Tags: []
Meta:
external-source: kubernetes
- ID: 'service-4'
Port: 3
Service: 'service-4'
Tags: []
Meta: ~
---
When I visit the node page for yaml
---
dc: dc1
node: node-0
---
When I click services on the tabs
And I see servicesIsSelected on the tabs
And I see externalSource on the services like yaml
---
- consul
- nomad
- terraform
- kubernetes
- ~
---

View File

@ -7,7 +7,7 @@ Feature: dc / nodes / sessions / invalidate: Invalidate Lock Sessions
Given 1 datacenter model with the value "dc1"
And 1 node model from yaml
---
- ID: node-0
ID: node-0
---
And 2 session models from yaml
---

View File

@ -7,7 +7,7 @@ Feature: dc / nodes / sessions / list: List Lock Sessions
Given 1 datacenter model with the value "dc1"
And 1 node model from yaml
---
- ID: node-0
ID: node-0
---
And 2 session models from yaml
---

View File

@ -1,12 +1,8 @@
@setupApplicationTest
Feature: Show node
Feature: dc / nodes / show: Show node
Scenario: Given 2 nodes all the tabs are visible and clickable
Given 1 datacenter model with the value "dc1"
And 2 node models from yaml
---
- ID: node-0
- ID: node-1
---
When I visit the node page for yaml
---
dc: dc1
@ -22,12 +18,11 @@ Feature: Show node
When I click lockSessions on the tabs
And I see lockSessionsIsSelected on the tabs
@ignore
Scenario: Given 1 node all the tabs are visible and clickable and the RTT one isn't there
Given 1 datacenter model with the value "dc1"
And 1 node models from yaml
---
- ID: node-0
ID: node-0
---
When I visit the node page for yaml
---
@ -43,4 +38,20 @@ Feature: Show node
When I click lockSessions on the tabs
And I see lockSessionsIsSelected on the tabs
Scenario: Given 1 node with no checks all the tabs are visible but the Services tab is selected
Given 1 datacenter model with the value "dc1"
And 1 node models from yaml
---
ID: node-0
Checks: []
---
When I visit the node page for yaml
---
dc: dc1
node: node-0
---
And I see healthChecks on the tabs
And I see services on the tabs
And I see roundTripTime on the tabs
And I see lockSessions on the tabs
And I see servicesIsSelected on the tabs

View File

@ -1,11 +1,36 @@
@setupApplicationTest
Feature: Services
Feature: dc / services: List Services
Scenario:
Given 1 datacenter model with the value "dc-1"
And 3 service models
And 5 service models from yaml
---
- Name: Service 1
Meta:
external-source: consul
- Name: Service 2
Meta:
external-source: nomad
- Name: Service 3
Meta:
external-source: terraform
- Name: Service 4
Meta:
external-source: kubernetes
- Name: Service 5
Meta: ~
---
When I visit the services page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/services
Then I see 3 service models
Then I see 5 service models
And I see externalSource on the services like yaml
---
- consul
- nomad
- terraform
- kubernetes
- ~
---

View File

@ -1,5 +1,21 @@
@setupApplicationTest
Feature: dc / services / show: Show Service
Scenario: Given a service with an external source, the logo is displayed
Given 1 datacenter model with the value "dc1"
And 1 node models
And 1 service model from yaml
---
- Service:
Tags: ['Tag1', 'Tag2']
Meta:
external-source: consul
---
When I visit the service page for yaml
---
dc: dc1
service: service-0
---
Then I see externalSource like "consul"
Scenario: Given various services with various tags, all tags are displayed
Given 1 datacenter model with the value "dc1"
And 3 node models

View File

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

View File

@ -8,7 +8,7 @@ export default function(type) {
requests = ['/v1/internal/ui/services', '/v1/health/service/'];
break;
case 'node':
requests = ['/v1/internal/ui/nodes'];
requests = ['/v1/internal/ui/nodes', '/v1/internal/ui/node/'];
break;
case 'kv':
requests = ['/v1/kv/'];

View File

@ -0,0 +1,32 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('css-var', 'helper:css-var', {
integration: true,
});
// Replace this with your real tests.
test("it renders nothing if the variable doesn't exist", function(assert) {
this.set('inputValue', '1234');
this.render(hbs`{{css-var inputValue}}`);
assert.equal(
this.$()
.text()
.trim(),
''
);
});
test("it renders a default if the variable doesn't exist", function(assert) {
this.set('inputValue', '1234');
this.render(hbs`{{css-var inputValue 'none'}}`);
assert.equal(
this.$()
.text()
.trim(),
'none'
);
});

View File

@ -0,0 +1,32 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('service/external-source', 'helper:service/external-source', {
integration: true,
});
// Replace this with your real tests.
test('it renders', function(assert) {
this.set('inputValue', { Meta: { 'external-source': 'consul' } });
this.render(hbs`{{service/external-source inputValue}}`);
assert.equal(
this.$()
.text()
.trim(),
'consul'
);
});
test('it renders prefixed', function(assert) {
this.set('inputValue', { Meta: { 'external-source': 'consul' } });
this.render(hbs`{{service/external-source inputValue prefix='external-source-'}}`);
assert.equal(
this.$()
.text()
.trim(),
'external-source-consul'
);
});

View File

@ -9,6 +9,7 @@ export default function(visitable, deletable, clickable, attribute, collection,
id: attribute('data-test-service-id', '[data-test-service-id]'),
name: attribute('data-test-service-name', '[data-test-service-name]'),
port: attribute('data-test-service-port', '.port'),
externalSource: attribute('data-test-external-source', 'a span'),
}),
sessions: collection(
'#lock-sessions [data-test-tabular-row]',

View File

@ -4,6 +4,7 @@ export default function(visitable, clickable, attribute, collection, page, filte
services: collection('[data-test-service]', {
name: attribute('data-test-service'),
service: clickable('a'),
externalSource: attribute('data-test-external-source', 'a span'),
}),
dcs: collection('[data-test-datacenter-picker]'),
navigation: page.navigation,

View File

@ -1,6 +1,7 @@
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'),
}),

View File

@ -317,7 +317,11 @@ export default function(assert) {
// this will catch if we get aren't managing to select a component
assert.ok(iterator.length > 0);
iterator.forEach(function(item, i, arr) {
const actual = _component.objectAt(i)[property];
const actual =
typeof _component.objectAt(i)[property] === 'undefined'
? null
: _component.objectAt(i)[property];
// anything coming from the DOM is going to be text/strings
// if the yaml has numbers, cast them to strings
// TODO: This would get problematic for deeper objects
@ -380,6 +384,13 @@ export default function(assert) {
.then(['I see $property'], function(property) {
assert.ok(currentPage[property], `Expected to see ${property}`);
})
.then(['I see $property like "$value"'], function(property, value) {
assert.equal(
currentPage[property],
value,
`Expected to see ${property}, was ${currentPage[property]}`
);
})
.then(['I see the text "$text" in "$selector"'], function(text, selector) {
assert.ok(
find(selector).textContent.indexOf(text) !== -1,

View File

@ -82,12 +82,12 @@
js-yaml "^3.10.0"
"@hashicorp/consul-api-double@^1.4.0":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.4.3.tgz#0d08e167b1163200885636e6d368585004db1c98"
version "1.5.1"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.5.1.tgz#73ce7696dc4475f69a59462e6690611b73ec6ced"
"@hashicorp/ember-cli-api-double@^1.3.0":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.5.1.tgz#92789eaf2073b5871d859700bc696e9552bb835b"
version "1.6.0"
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.6.0.tgz#b40eae09d14ae8491516598f881cf51cbe7e2787"
dependencies:
"@hashicorp/api-double" "^1.3.0"
array-range "^1.0.1"
@ -5343,9 +5343,9 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ipaddr.js@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
ipaddr.js@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
is-accessor-descriptor@^0.1.6:
version "0.1.6"
@ -6700,9 +6700,9 @@ miller-rabin@^4.0.0:
version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
mime-db@~1.35.0:
version "1.35.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47"
mime-db@~1.36.0:
version "1.36.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.17, mime-types@~2.1.7:
version "2.1.18"
@ -6711,10 +6711,10 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.17, mime-types@~2.1.7:
mime-db "~1.33.0"
mime-types@~2.1.18:
version "2.1.19"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0"
version "2.1.20"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
dependencies:
mime-db "~1.35.0"
mime-db "~1.36.0"
mime@1.4.1:
version "1.4.1"
@ -7548,11 +7548,11 @@ promised-io@*:
resolved "https://registry.yarnpkg.com/promised-io/-/promised-io-0.3.5.tgz#4ad217bb3658bcaae9946b17a8668ecd851e1356"
proxy-addr@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
version "2.0.4"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93"
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.6.0"
ipaddr.js "1.8.0"
pseudomap@^1.0.2:
version "1.0.2"
@ -8109,8 +8109,8 @@ rollup@^0.58.1:
"@types/node" "*"
route-recognizer@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.3.tgz#1d365e27fa6995e091675f7dc940a8c00353bd29"
version "0.3.4"
resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3"
rsvp@^3.0.14, rsvp@^3.0.16, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0, rsvp@^3.2.1, rsvp@^3.3.3, rsvp@^3.5.0:
version "3.6.2"