Skip to content

Commit

Permalink
Merge pull request #11094 from hashicorp/f-sysbatch-ui-luiz
Browse files Browse the repository at this point in the history
sysbatch UI luiz
  • Loading branch information
ChaiWithJai authored Aug 30, 2021
2 parents 2c96d75 + 91a17cb commit aaac85a
Show file tree
Hide file tree
Showing 21 changed files with 349 additions and 196 deletions.
91 changes: 34 additions & 57 deletions ui/app/components/client-status-bar.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,58 @@
import { computed } from '@ember/object';
import DistributionBar from './distribution-bar';
import classic from 'ember-classic-decorator';
import { countBy } from 'lodash';

@classic
export default class ClientStatusBar extends DistributionBar {
layoutName = 'components/distribution-bar';

'data-test-client-status-bar' = true;
allocationContainer = null;
nodes = null;
totalNodes = null;
jobClientStatus = null;

@computed('nodes')
@computed('jobClientStatus')
get data() {
const statuses = {
queued: 0,
'not scheduled': this.totalNodes - this.nodes.length,
starting: 0,
running: 0,
complete: 0,
degraded: 0,
failed: 0,
lost: 0,
};
const formattedNodes = this.nodes.map(node => {
const [[_, allocs]] = Object.entries(node);
return allocs.map(alloc => alloc.clientStatus);
});
for (const node of formattedNodes) {
const statusCount = countBy(node, status => status);
const hasOnly1Status = Object.keys(statusCount).length === 1;

if (hasOnly1Status) {
if (statusCount.running > 0) {
statuses.running++;
}
if (statusCount.failed > 0) {
statuses.failed++;
}
if (statusCount.lost > 0) {
statuses.lost++;
}
if (statusCount.complete > 0) {
statuses.complete++;
}
} else if (!hasOnly1Status && !!statusCount.running) {
if (!!statusCount.failed || !!statusCount.lost) {
statuses.degraded++;
} else if (statusCount.pending) {
statuses.starting++;
}
} else {
// if no allocations then queued -- job registered, hasn't been assigned clients to run -- no allocations
// may only have this state for a few milliseconds
statuses.queued++;
}
}

return [
{ label: 'Not Scheduled', value: statuses['not scheduled'], className: 'not-scheduled' },
{ label: 'Queued', value: statuses.queued, className: 'queued' },
{
label: 'Queued',
value: this.jobClientStatus.byStatus.queued.length,
className: 'queued',
},
{
label: 'Starting',
value: statuses.starting,
value: this.jobClientStatus.byStatus.starting.length,
className: 'starting',
layers: 2,
},
{ label: 'Running', value: statuses.running, className: 'running' },
{
label: 'Running',
value: this.jobClientStatus.byStatus.running.length,
className: 'running',
},
{
label: 'Complete',
value: statuses.complete,
value: this.jobClientStatus.byStatus.complete.length,
className: 'complete',
},
{ label: 'Degraded', value: statuses.degraded, className: 'degraded' },
{ label: 'Failed', value: statuses.failed, className: 'failed' },
{ label: 'Lost', value: statuses.lost, className: 'lost' },
{
label: 'Degraded',
value: this.jobClientStatus.byStatus.degraded.length,
className: 'degraded',
},
{
label: 'Failed',
value: this.jobClientStatus.byStatus.failed.length,
className: 'failed',
},
{
label: 'Lost',
value: this.jobClientStatus.byStatus.lost.length,
className: 'lost',
},
{
label: 'Not Scheduled',
value: this.jobClientStatus.byStatus.notScheduled.length,
className: 'not-scheduled',
},
];
}
}
25 changes: 25 additions & 0 deletions ui/app/components/job-page/parts/job-client-summary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { classNames } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';

@classic
@classNames('boxed-section')
export default class JobClientSummary extends Component {
@service store;

job = null;
jobClientStatus = null;

@computed
get isExpanded() {
const storageValue = window.localStorage.nomadExpandJobClientSummary;
return storageValue != null ? JSON.parse(storageValue) : true;
}

persist(item, isOpen) {
window.localStorage.nomadExpandJobClientSummary = isOpen;
this.notifyPropertyChange('isExpanded');
}
}
3 changes: 3 additions & 0 deletions ui/app/components/job-page/parts/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import classic from 'ember-classic-decorator';
@classNames('boxed-section')
export default class Summary extends Component {
job = null;
forceCollapsed = false;

@computed
get isExpanded() {
if (this.forceCollapsed) return false;

const storageValue = window.localStorage.nomadExpandJobSummary;
return storageValue != null ? JSON.parse(storageValue) : true;
}
Expand Down
15 changes: 15 additions & 0 deletions ui/app/components/job-page/sysbatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import AbstractJobPage from './abstract';
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
import jobClientStatus from 'nomad-ui/utils/properties/job-client-status';

@classic
export default class Sysbatch extends AbstractJobPage {
@service store;

@jobClientStatus('nodes', 'job') jobClientStatus;

get nodes() {
return this.store.peekAll('node');
}
}
12 changes: 11 additions & 1 deletion ui/app/components/job-page/system.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import AbstractJobPage from './abstract';
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
import jobClientStatus from 'nomad-ui/utils/properties/job-client-status';

@classic
export default class System extends AbstractJobPage {}
export default class System extends AbstractJobPage {
@service store;

@jobClientStatus('nodes', 'job') jobClientStatus;

get nodes() {
return this.store.peekAll('node');
}
}
6 changes: 5 additions & 1 deletion ui/app/controllers/jobs/job/clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class ClientsController extends Controller.extend(
sortDescending = true;

@alias('model') job;
@jobClientStatus('nodes', 'job.status', 'job.allocations') jobClientStatus;
@jobClientStatus('nodes', 'job') jobClientStatus;

@alias('uniqueNodes') listToSort;
@alias('listSorted') listToSearch;
Expand All @@ -59,6 +59,10 @@ export default class ClientsController extends Controller.extend(
return result;
}

get nodes() {
return this.store.peekAll('node');
}

@action
gotoClient(client) {
console.log('goToClient', client);
Expand Down
31 changes: 0 additions & 31 deletions ui/app/controllers/jobs/job/index.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,13 @@
import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import Controller from '@ember/controller';
import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting';
import { action } from '@ember/object';
import classic from 'ember-classic-decorator';
import jobClientStatus from 'nomad-ui/utils/properties/job-client-status';

@classic
export default class IndexController extends Controller.extend(WithNamespaceResetting) {
@service system;
@service store;

@jobClientStatus('nodes', 'job.status', 'job.allocations') jobClientStatus;

@computed('job')
get uniqueNodes() {
// add datacenter filter
const allocs = this.job.allocations;
const nodes = allocs.mapBy('node');
const uniqueNodes = nodes.uniqBy('id').toArray();
const result = uniqueNodes.map(nodeId => {
return {
[nodeId.get('id')]: allocs
.toArray()
.filter(alloc => nodeId.get('id') === alloc.get('node.id'))
.map(alloc => alloc.getProperties('clientStatus', 'name', 'createTime', 'modifyTime')),
};
});
return result;
}

@computed('node')
get totalNodes() {
return this.store.peekAll('node').toArray().length;
}

get nodes() {
return this.store.peekAll('node');
}

queryParams = [
{
Expand Down
2 changes: 1 addition & 1 deletion ui/app/models/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import RSVP from 'rsvp';
import { assert } from '@ember/debug';
import classic from 'ember-classic-decorator';

const JOB_TYPES = ['service', 'batch', 'system'];
const JOB_TYPES = ['service', 'batch', 'system', 'sysbatch'];

@classic
export default class Job extends Model {
Expand Down
4 changes: 3 additions & 1 deletion ui/app/routes/jobs/job/clients.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Route from '@ember/routing/route';

export default class ClientsRoute extends Route {}
export default class ClientsRoute extends Route {
// TODO: add watcher for nodes.
}
6 changes: 3 additions & 3 deletions ui/app/styles/charts/colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $running: $primary;
$complete: $nomad-green-dark;
$failed: $danger;
$lost: $dark;
$not-scheduled: $grey-darker;
$not-scheduled: $blue-200;
$degraded: $warning;

.chart {
Expand Down Expand Up @@ -113,11 +113,11 @@ $degraded: $warning;
}

&.not-scheduled {
fill: $not-scheduled;
background: $not-scheduled;
}

&.degraded {
fill: $degraded;
background: $degraded;
}

@each $name, $pair in $colors {
Expand Down
35 changes: 35 additions & 0 deletions ui/app/templates/components/job-page/parts/job-client-summary.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<ListAccordion data-test-job-summary @source={{array this.job}} @key="id" @startExpanded={{this.isExpanded}} @onToggle={{action this.persist}} as |a|>
<a.head @buttonLabel={{if a.isOpen "collapse" "expand"}}>
<div class="columns">
<div class="column is-minimum nowrap">
Job Status in Client
<span class="badge {{if a.isOpen "is-white" "is-light"}}">
{{this.jobClientStatus.totalNodes}}
</span>
</div>

{{#unless a.isOpen}}
<div class="column">
<div class="inline-chart bumper-left">
<ClientStatusBar @jobClientStatus={{this.jobClientStatus}} @isNarrow={{true}} />
</div>
</div>
{{/unless}}
</div>
</a.head>
<a.body>
<ClientStatusBar @jobClientStatus={{this.jobClientStatus}} class="split-view" as |chart|>
<ol data-test-legend class="legend">
{{#each chart.data as |datum index|}}
<li class="{{datum.className}} {{if (eq datum.label chart.activeDatum.label) "is-active"}} {{if (eq datum.value 0) "is-empty"}}">
<span class="color-swatch {{if datum.className datum.className (concat "swatch-" index)}}" />
<span class="value" data-test-legend-value="{{datum.className}}">{{datum.value}}</span>
<span class="label">
{{datum.label}}
</span>
</li>
{{/each}}
</ol>
</ClientStatusBar>
</a.body>
</ListAccordion>
59 changes: 0 additions & 59 deletions ui/app/templates/components/job-page/parts/summary.hbs
Original file line number Diff line number Diff line change
@@ -1,63 +1,4 @@
<ListAccordion data-test-job-summary @source={{array this.job}} @key="id" @startExpanded={{this.isExpanded}} @onToggle={{action this.persist}} as |a|>
<a.head @buttonLabel={{if a.isOpen "collapse" "expand"}}>
<div class="columns">
<div class="column is-minimum nowrap">
{{#if a.item.hasChildren}}
Children Status
<span class="badge {{if a.isOpen "is-white" "is-light"}}">
{{a.item.summary.totalChildren}}
</span>
{{else}}
Client Status
<span class="badge {{if a.isOpen "is-white" "is-light"}}">
{{a.item.summary.totalAllocs}}
</span>
{{/if}}
</div>

{{#unless a.isOpen}}
<div class="column">
<div class="inline-chart bumper-left">
{{#if a.item.hasChildren}}
{{#if (gt a.item.totalChildren 0)}}
<ChildrenStatusBar @job={{a.item}} @isNarrow={{true}} />
{{else}}
<em class="is-faded">No Children</em>
{{/if}}
{{else}}
<ClientStatusBar @jobClientStatus={{this.jobClientStatus}} @nodes={{this.nodes}} @allocationContainer={{a.item}} @isNarrow={{true}} />
{{/if}}
</div>
</div>
{{/unless}}
</div>
</a.head>
<a.body>
{{#component (if a.item.hasChildren "children-status-bar" "client-status-bar")
allocationContainer=a.item.summary
job=a.item.summary
nodes=this.nodes
totalNodes=this.totalNodes
jobClientStatus=this.jobClientStatus
class="split-view" as |chart|}}
<ol data-test-legend class="legend">
{{#each chart.data as |datum index|}}
<li class="{{datum.className}} {{if (eq datum.label chart.activeDatum.label) "is-active"}} {{if (eq datum.value 0) "is-empty"}}">
<span class="color-swatch {{if datum.className datum.className (concat "swatch-" index)}}" />
<span class="value" data-test-legend-value="{{datum.className}}">{{datum.value}}</span>
<span class="label">
{{datum.label}}
</span>
</li>
{{/each}}
</ol>
{{/component}}
</a.body>
</ListAccordion>

<br/>

<ListAccordion data-test-job-summary @source={{array this.job}} @key="id" @startExpanded={{false}} @onToggle={{action this.persist}} as |a|>
<a.head @buttonLabel={{if a.isOpen "collapse" "expand"}}>
<div class="columns">
<div class="column is-minimum nowrap">
Expand Down
Loading

0 comments on commit aaac85a

Please sign in to comment.