diff --git a/ui/app/components/allocation-status-bar.js b/ui/app/components/allocation-status-bar.js index 28e7a01ea2a1..c081216669bb 100644 --- a/ui/app/components/allocation-status-bar.js +++ b/ui/app/components/allocation-status-bar.js @@ -25,7 +25,7 @@ export default class AllocationStatusBar extends DistributionBar { } @computed( - 'allocationContainer.{queuedAllocs,completeAllocs,failedAllocs,runningAllocs,startingAllocs}', + 'allocationContainer.{queuedAllocs,completeAllocs,failedAllocs,runningAllocs,startingAllocs,lostAllocs,unknownAllocs}', 'job.namespace' ) get data() { @@ -39,7 +39,8 @@ export default class AllocationStatusBar extends DistributionBar { 'failedAllocs', 'runningAllocs', 'startingAllocs', - 'lostAllocs' + 'lostAllocs', + 'unknownAllocs' ); return [ { @@ -67,6 +68,13 @@ export default class AllocationStatusBar extends DistributionBar { className: 'complete', legendLink: this.generateLegendLink(this.job, 'complete'), }, + { + label: 'Unknown', + value: allocs.unknownAllocs, + className: 'unknown', + legendLink: this.generateLegendLink(this.job, 'unknown'), + help: 'Allocation is unknown since its node is disconnected.', + }, { label: 'Failed', value: allocs.failedAllocs, diff --git a/ui/app/components/job-client-status-bar.js b/ui/app/components/job-client-status-bar.js index 4ebcd8d53ae4..5c2e9d9fd6cb 100644 --- a/ui/app/components/job-client-status-bar.js +++ b/ui/app/components/job-client-status-bar.js @@ -23,6 +23,7 @@ export default class JobClientStatusBar extends DistributionBar { failed, lost, notScheduled, + unknown, } = this.jobClientStatus.byStatus; return [ @@ -71,6 +72,18 @@ export default class JobClientStatusBar extends DistributionBar { }, }, }, + { + label: 'Unknown', + value: unknown.length, + className: 'unknown', + legendLink: { + queryParams: { + status: JSON.stringify(['unknown']), + namespace: this.job.namespace.get('id'), + }, + }, + help: 'Some allocations for this job were degraded or lost connectivity.', + }, { label: 'Degraded', value: degraded.length, diff --git a/ui/app/components/job-client-status-row.js b/ui/app/components/job-client-status-row.js index dc467fbf5d87..1a780d3d8b23 100644 --- a/ui/app/components/job-client-status-row.js +++ b/ui/app/components/job-client-status-row.js @@ -48,6 +48,7 @@ export default class ClientRow extends Component { runningAllocs: 0, startingAllocs: 0, lostAllocs: 0, + unknownAllocs: 0, }; switch (this.args.row.model.jobStatus) { @@ -77,6 +78,9 @@ export default class ClientRow extends Component { case 'starting': statusSummary.startingAllocs++; break; + case 'unknown': + statusSummary.unknownAllocs++; + break; } } } diff --git a/ui/app/controllers/clients/client/index.js b/ui/app/controllers/clients/client/index.js index 6a9b2475c17b..05d9ce5c9662 100644 --- a/ui/app/controllers/clients/client/index.js +++ b/ui/app/controllers/clients/client/index.js @@ -211,6 +211,7 @@ export default class ClientController extends Controller.extend( { key: 'complete', label: 'Complete' }, { key: 'failed', label: 'Failed' }, { key: 'lost', label: 'Lost' }, + { key: 'unknown', label: 'Unknown' }, ]; } diff --git a/ui/app/controllers/clients/index.js b/ui/app/controllers/clients/index.js index 3ed795c6959b..de7fc9caf1ce 100644 --- a/ui/app/controllers/clients/index.js +++ b/ui/app/controllers/clients/index.js @@ -103,6 +103,7 @@ export default class IndexController extends Controller.extend( { key: 'down', label: 'Down' }, { key: 'ineligible', label: 'Ineligible' }, { key: 'draining', label: 'Draining' }, + { key: 'disconnected', label: 'Disconnected' }, ]; } diff --git a/ui/app/controllers/jobs/job/allocations.js b/ui/app/controllers/jobs/job/allocations.js index 466b7567dad1..33ee81bdb081 100644 --- a/ui/app/controllers/jobs/job/allocations.js +++ b/ui/app/controllers/jobs/job/allocations.js @@ -116,6 +116,7 @@ export default class AllocationsController extends Controller.extend( { key: 'complete', label: 'Complete' }, { key: 'failed', label: 'Failed' }, { key: 'lost', label: 'Lost' }, + { key: 'unknown', label: 'Unknown' }, ]; } diff --git a/ui/app/controllers/jobs/job/clients.js b/ui/app/controllers/jobs/job/clients.js index 81080b6f6ea8..7019e90fc793 100644 --- a/ui/app/controllers/jobs/job/clients.js +++ b/ui/app/controllers/jobs/job/clients.js @@ -141,6 +141,7 @@ export default class ClientsController extends Controller.extend( { key: 'degraded', label: 'Degraded' }, { key: 'failed', label: 'Failed' }, { key: 'lost', label: 'Lost' }, + { key: 'unknown', label: 'Unknown' }, ]; } diff --git a/ui/app/controllers/jobs/job/task-group.js b/ui/app/controllers/jobs/job/task-group.js index d02252e0a5be..fccb0f40cf88 100644 --- a/ui/app/controllers/jobs/job/task-group.js +++ b/ui/app/controllers/jobs/job/task-group.js @@ -139,6 +139,7 @@ export default class TaskGroupController extends Controller.extend( { key: 'complete', label: 'Complete' }, { key: 'failed', label: 'Failed' }, { key: 'lost', label: 'Lost' }, + { key: 'unknown', label: 'Unknown' }, ]; } diff --git a/ui/app/models/allocation.js b/ui/app/models/allocation.js index fb3aa8445210..231aeec657a7 100644 --- a/ui/app/models/allocation.js +++ b/ui/app/models/allocation.js @@ -12,8 +12,9 @@ const STATUS_ORDER = { pending: 1, running: 2, complete: 3, - failed: 4, - lost: 5, + unknown: 4, + failed: 5, + lost: 6, }; @classic @@ -83,6 +84,7 @@ export default class Allocation extends Model { complete: 'is-complete', failed: 'is-error', lost: 'is-light', + unknown: 'is-unknown', }; return classMap[this.clientStatus] || 'is-dark'; diff --git a/ui/app/models/job-summary.js b/ui/app/models/job-summary.js index a6892517a43d..23171f201507 100644 --- a/ui/app/models/job-summary.js +++ b/ui/app/models/job-summary.js @@ -17,6 +17,7 @@ export default class JobSummary extends Model { @sumAggregation('taskGroupSummaries', 'runningAllocs') runningAllocs; @sumAggregation('taskGroupSummaries', 'completeAllocs') completeAllocs; @sumAggregation('taskGroupSummaries', 'failedAllocs') failedAllocs; + @sumAggregation('taskGroupSummaries', 'unknownAllocs') unknownAllocs; @sumAggregation('taskGroupSummaries', 'lostAllocs') lostAllocs; @collect( @@ -25,7 +26,8 @@ export default class JobSummary extends Model { 'runningAllocs', 'completeAllocs', 'failedAllocs', - 'lostAllocs' + 'lostAllocs', + 'unknownAllocs' ) allocsList; diff --git a/ui/app/models/job.js b/ui/app/models/job.js index 2e921383e864..a232128e0898 100644 --- a/ui/app/models/job.js +++ b/ui/app/models/job.js @@ -131,6 +131,7 @@ export default class Job extends Model { @alias('summary.completeAllocs') completeAllocs; @alias('summary.failedAllocs') failedAllocs; @alias('summary.lostAllocs') lostAllocs; + @alias('summary.unknownAllocs') unknownAllocs; @alias('summary.totalAllocs') totalAllocs; @alias('summary.pendingChildren') pendingChildren; @alias('summary.runningChildren') runningChildren; diff --git a/ui/app/models/task-group-summary.js b/ui/app/models/task-group-summary.js index 8347aa141869..d464f53e9d9f 100644 --- a/ui/app/models/task-group-summary.js +++ b/ui/app/models/task-group-summary.js @@ -13,6 +13,7 @@ export default class TaskGroupSummary extends Fragment { @attr('number') completeAllocs; @attr('number') failedAllocs; @attr('number') lostAllocs; + @attr('number') unknownAllocs; @collect( 'queuedAllocs', @@ -20,7 +21,8 @@ export default class TaskGroupSummary extends Fragment { 'runningAllocs', 'completeAllocs', 'failedAllocs', - 'lostAllocs' + 'lostAllocs', + 'unknownAllocs' ) allocsList; diff --git a/ui/app/styles/charts/colors.scss b/ui/app/styles/charts/colors.scss index 0ddc4a79b096..ac9ac494d77f 100644 --- a/ui/app/styles/charts/colors.scss +++ b/ui/app/styles/charts/colors.scss @@ -49,6 +49,10 @@ $canceled: $dark; .degraded { fill: $degraded; } + + .unknown { + fill: $unknown; + } } .color-swatch { @@ -114,6 +118,10 @@ $canceled: $dark; background: $lost; } + &.unknown { + background: $unknown; + } + &.not-scheduled { background: $not-scheduled; } diff --git a/ui/app/styles/charts/distribution-bar.scss b/ui/app/styles/charts/distribution-bar.scss index 42439b28c157..61a6c78a87af 100644 --- a/ui/app/styles/charts/distribution-bar.scss +++ b/ui/app/styles/charts/distribution-bar.scss @@ -25,7 +25,8 @@ opacity: 0; } - $color-sequence: $orange, $yellow, $green, $turquoise, $blue, $purple, $red; + $color-sequence: $orange, $yellow, $green, $turquoise, $blue, $purple, + $red; @for $i from 1 through length($color-sequence) { .slice-#{$i - 1} { @@ -113,6 +114,12 @@ color: darken($grey-blue, 20%); } } + + // Prevent Orphaned last-elements in the list + // from being visually centered + &:nth-child(2n + 1):last-child { + margin-right: calc(35% + 0.75em); + } } } } diff --git a/ui/app/styles/components/status-text.scss b/ui/app/styles/components/status-text.scss index b8a49ce1cfc0..5b9cd6fd683c 100644 --- a/ui/app/styles/components/status-text.scss +++ b/ui/app/styles/components/status-text.scss @@ -5,6 +5,10 @@ color: $nomad-green-dark; } + &.node-disconnected { + color: $yellow; + } + &.node-down { color: $danger; } diff --git a/ui/app/styles/core/tag.scss b/ui/app/styles/core/tag.scss index 14950c3686a1..4682b8bdba1d 100644 --- a/ui/app/styles/core/tag.scss +++ b/ui/app/styles/core/tag.scss @@ -39,6 +39,11 @@ color: $orange-invert; } + &.is-unknown { + background: $unknown; + color: $primary-invert; + } + &.is-hollow { font-weight: $weight-semibold; color: darken($grey-blue, 20%); diff --git a/ui/app/styles/core/variables.scss b/ui/app/styles/core/variables.scss index aafb869b1243..deb853173c6e 100644 --- a/ui/app/styles/core/variables.scss +++ b/ui/app/styles/core/variables.scss @@ -5,12 +5,14 @@ $purple: $terraform-purple; $red: #c84034; $grey-blue: #bbc4d1; $blue-light: #c0d5ff; +$yellow: #fac402; $primary: $nomad-green; $warning: $orange; $warning-invert: $white; $danger: $red; $info: $blue; +$unknown: $yellow; $dark: #234; $dark-2: darken($dark, 5%); $dark-3: darken($dark, 10%); @@ -25,8 +27,8 @@ $size-7: 0.85rem; $title-weight: $weight-semibold; -$family-sans-serif: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, - Cantarell, 'Helvetica Neue', sans-serif; +$family-sans-serif: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; $text: $black; diff --git a/ui/app/utils/properties/job-client-status.js b/ui/app/utils/properties/job-client-status.js index a715786819ff..63654d63e8df 100644 --- a/ui/app/utils/properties/job-client-status.js +++ b/ui/app/utils/properties/job-client-status.js @@ -9,6 +9,7 @@ const STATUS = [ 'degraded', 'failed', 'lost', + 'unknown', ]; // An Ember.Computed property that computes the aggregated status of a job in a @@ -137,5 +138,9 @@ function jobStatus(allocs, expected) { return 'running'; } + if (summary['unknown'] > 0) { + return 'unknown'; + } + return 'starting'; } diff --git a/ui/mirage/factories/job-summary.js b/ui/mirage/factories/job-summary.js index c5b372407075..aea62e4bfa88 100644 --- a/ui/mirage/factories/job-summary.js +++ b/ui/mirage/factories/job-summary.js @@ -9,7 +9,7 @@ export default Factory.extend({ namespace: null, withSummary: trait({ - Summary: function() { + Summary: function () { return this.groupNames.reduce((summary, group) => { summary[group] = { Queued: faker.random.number(10), @@ -18,6 +18,7 @@ export default Factory.extend({ Running: faker.random.number(10), Starting: faker.random.number(10), Lost: faker.random.number(10), + Unknown: faker.random.number(10), }; return summary; }, {}); diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index ff7827cef0c0..61d81d530c02 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -1140,12 +1140,21 @@ module('Acceptance | client detail', function (hooks) { testFacet('Status', { facet: ClientDetail.facets.status, paramName: 'status', - expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], + expectedOptions: [ + 'Pending', + 'Running', + 'Complete', + 'Failed', + 'Lost', + 'Unknown', + ], async beforeEach() { server.createList('job', 5, { createAllocations: false }); - ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { - server.createList('allocation', 5, { clientStatus: s }); - }); + ['pending', 'running', 'complete', 'failed', 'lost', 'unknown'].forEach( + (s) => { + server.createList('allocation', 5, { clientStatus: s }); + } + ); await ClientDetail.visit({ id: node.id }); }, diff --git a/ui/tests/acceptance/clients-list-test.js b/ui/tests/acceptance/clients-list-test.js index 73d0ce16e19e..81cf449a5855 100644 --- a/ui/tests/acceptance/clients-list-test.js +++ b/ui/tests/acceptance/clients-list-test.js @@ -276,6 +276,7 @@ module('Acceptance | clients list', function (hooks) { 'Down', 'Ineligible', 'Draining', + 'Disconnected', ], async beforeEach() { server.create('agent'); diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index 4f276316d992..a11db02c3eb5 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -152,11 +152,20 @@ module('Acceptance | job allocations', function (hooks) { testFacet('Status', { facet: Allocations.facets.status, paramName: 'status', - expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], + expectedOptions: [ + 'Pending', + 'Running', + 'Complete', + 'Failed', + 'Lost', + 'Unknown', + ], async beforeEach() { - ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { - server.createList('allocation', 5, { clientStatus: s }); - }); + ['pending', 'running', 'complete', 'failed', 'lost', 'unknown'].forEach( + (s) => { + server.createList('allocation', 5, { clientStatus: s }); + } + ); await Allocations.visit({ id: job.id }); }, filter: (alloc, selection) => diff --git a/ui/tests/acceptance/job-clients-test.js b/ui/tests/acceptance/job-clients-test.js index 528a1565377a..1538fa88ed88 100644 --- a/ui/tests/acceptance/job-clients-test.js +++ b/ui/tests/acceptance/job-clients-test.js @@ -200,6 +200,7 @@ module('Acceptance | job clients', function (hooks) { 'Degraded', 'Failed', 'Lost', + 'Unknown', ], async beforeEach() { await Clients.visit({ id: job.id }); diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index dc54767564aa..b984a539f5ec 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -669,11 +669,20 @@ module('Acceptance | task group detail', function (hooks) { testFacet('Status', { facet: TaskGroup.facets.status, paramName: 'status', - expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], + expectedOptions: [ + 'Pending', + 'Running', + 'Complete', + 'Failed', + 'Lost', + 'Unknown', + ], async beforeEach() { - ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { - server.createList('allocation', 5, { clientStatus: s }); - }); + ['pending', 'running', 'complete', 'failed', 'lost', 'unknown'].forEach( + (s) => { + server.createList('allocation', 5, { clientStatus: s }); + } + ); await TaskGroup.visit({ id: job.id, name: taskGroup.name }); }, filter: (alloc, selection) => diff --git a/ui/tests/integration/components/job-client-status-bar-test.js b/ui/tests/integration/components/job-client-status-bar-test.js index b4adac86dc34..454466f49ff2 100644 --- a/ui/tests/integration/components/job-client-status-bar-test.js +++ b/ui/tests/integration/components/job-client-status-bar-test.js @@ -29,6 +29,7 @@ module('Integration | Component | job-client-status-bar', function (hooks) { failed: [], lost: [], notScheduled: [], + unknown: [], }, }, isNarrow: true, diff --git a/ui/tests/unit/models/job-test.js b/ui/tests/unit/models/job-test.js index bff37e302cca..652a82f53b48 100644 --- a/ui/tests/unit/models/job-test.js +++ b/ui/tests/unit/models/job-test.js @@ -19,6 +19,7 @@ module('Unit | Model | job', function (hooks) { completeAllocs: 4, failedAllocs: 5, lostAllocs: 6, + unknownAllocs: 7, }, { name: 'two', @@ -28,6 +29,7 @@ module('Unit | Model | job', function (hooks) { completeAllocs: 8, failedAllocs: 10, lostAllocs: 12, + unknownAllocs: 14, }, { name: 'three', @@ -37,6 +39,7 @@ module('Unit | Model | job', function (hooks) { completeAllocs: 12, failedAllocs: 15, lostAllocs: 18, + unknownAllocs: 21, }, ], }); diff --git a/ui/tests/unit/utils/job-client-status-test.js b/ui/tests/unit/utils/job-client-status-test.js index e625a4f57307..9b39724a5e80 100644 --- a/ui/tests/unit/utils/job-client-status-test.js +++ b/ui/tests/unit/utils/job-client-status-test.js @@ -74,6 +74,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -110,6 +111,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -146,6 +148,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -182,6 +185,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -218,6 +222,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -250,6 +255,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: ['node-1'], queued: [], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -286,6 +292,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: ['node-1'], starting: [], + unknown: [], }, totalNodes: 1, }; @@ -323,6 +330,7 @@ module('Unit | Util | JobClientStatus', function () { notScheduled: [], queued: [], starting: [], + unknown: [], }, totalNodes: 1, };