mirror of
https://github.com/kemko/nomad.git
synced 2026-01-05 09:55:44 +03:00
Merge pull request #12082 from hashicorp/f-ui/refactor-namespace
namespace refactoring
This commit is contained in:
@@ -12,8 +12,7 @@
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="jobs.job.index"
|
||||
@model={{trigger.data.result.plainId}}
|
||||
@query={{hash namespace=(or trigger.data.result.namespace.name "default")}}
|
||||
@model={{trigger.data.result}}
|
||||
data-test-breadcrumb={{"jobs.job.index"}}
|
||||
>
|
||||
<dl>
|
||||
@@ -30,8 +29,7 @@
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="jobs.job.index"
|
||||
@model={{this.job.plainId}}
|
||||
@query={{hash namespace=(or this.job.namespace.name "default")}}
|
||||
@model={{this.job}}
|
||||
data-test-breadcrumb={{"jobs.job.index"}}
|
||||
data-test-job-breadcrumb
|
||||
>
|
||||
|
||||
@@ -104,9 +104,12 @@ export default class JobDispatch extends Component {
|
||||
const dispatch = yield this.args.job.dispatch(paramValues, this.payload);
|
||||
|
||||
// Navigate to the newly created instance.
|
||||
this.router.transitionTo('jobs.job', dispatch.DispatchedJobID, {
|
||||
queryParams: { namespace: this.args.job.get('namespace.name') },
|
||||
});
|
||||
const namespaceId = this.args.job.belongsTo('namespace').id();
|
||||
const jobId = namespaceId
|
||||
? `${dispatch.DispatchedJobID}@${namespaceId}`
|
||||
: dispatch.DispatchedJobID;
|
||||
|
||||
this.router.transitionTo('jobs.job', jobId);
|
||||
} catch (err) {
|
||||
const error = messageFromAdapterError(err) || 'Could not dispatch job';
|
||||
this.errors.pushObject(error);
|
||||
|
||||
@@ -24,7 +24,6 @@ export default class JobClientStatusSummary extends Component {
|
||||
this.router.transitionTo('jobs.job.clients', this.job, {
|
||||
queryParams: {
|
||||
status: JSON.stringify(statusFilter),
|
||||
namespace: this.job.get('namespace.name'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ export default class JobRow extends Component {
|
||||
@action
|
||||
gotoJob() {
|
||||
const { job } = this;
|
||||
this.router.transitionTo('jobs.job', job.plainId, {
|
||||
queryParams: { namespace: job.get('namespace.name') },
|
||||
});
|
||||
this.router.transitionTo('jobs.job.index', job.idWithNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,8 @@ export default class AllocationsAllocationController extends Controller {
|
||||
label: allocation.taskGroupName,
|
||||
args: [
|
||||
'jobs.job.task-group',
|
||||
job.plainId,
|
||||
job.idWithNamespace,
|
||||
allocation.taskGroupName,
|
||||
jobQueryParams,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -114,9 +114,10 @@ export default class IndexController extends Controller.extend(
|
||||
gotoVolume(volume, event) {
|
||||
lazyClick([
|
||||
() =>
|
||||
this.transitionToRoute('csi.volumes.volume', volume.get('plainId'), {
|
||||
queryParams: { volumeNamespace: volume.get('namespace.name') },
|
||||
}),
|
||||
this.transitionToRoute(
|
||||
'csi.volumes.volume',
|
||||
volume.get('idWithNamespace')
|
||||
),
|
||||
event,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Controller from '@ember/controller';
|
||||
import { action, computed, get } from '@ember/object';
|
||||
import { scheduleOnce } from '@ember/runloop';
|
||||
import intersection from 'lodash.intersection';
|
||||
import { qpBuilder } from 'nomad-ui/utils/classes/query-params';
|
||||
import Sortable from 'nomad-ui/mixins/sortable';
|
||||
import Searchable from 'nomad-ui/mixins/searchable';
|
||||
import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting';
|
||||
@@ -174,12 +173,7 @@ export default class TaskGroupController extends Controller.extend(
|
||||
return {
|
||||
title: 'Task Group',
|
||||
label: name,
|
||||
args: [
|
||||
'jobs.job.task-group',
|
||||
job,
|
||||
name,
|
||||
qpBuilder({ jobNamespace: job.get('namespace.name') || 'default' }),
|
||||
],
|
||||
args: ['jobs.job.task-group', job, name],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,17 @@ export default class Job extends Model {
|
||||
@attr() periodicDetails;
|
||||
@attr() parameterizedDetails;
|
||||
|
||||
@computed('plainId')
|
||||
get idWithNamespace() {
|
||||
const namespaceId = this.belongsTo('namespace').id();
|
||||
|
||||
if (!namespaceId || namespaceId === 'default') {
|
||||
return this.plainId;
|
||||
} else {
|
||||
return `${this.plainId}@${namespaceId}`;
|
||||
}
|
||||
}
|
||||
|
||||
@computed('periodic', 'parameterized', 'dispatched')
|
||||
get hasChildren() {
|
||||
return this.periodic || (this.parameterized && !this.dispatched);
|
||||
|
||||
@@ -40,6 +40,13 @@ export default class Volume extends Model {
|
||||
@attr('number') controllersHealthy;
|
||||
@attr('number') controllersExpected;
|
||||
|
||||
@computed('plainId')
|
||||
get idWithNamespace() {
|
||||
// does this handle default namespace -- I think the backend handles this for us
|
||||
// but the client would always need to recreate that logic
|
||||
return `${this.plainId}@${this.belongsTo('namespace').id()}`;
|
||||
}
|
||||
|
||||
@computed('controllersHealthy', 'controllersExpected')
|
||||
get controllersHealthyProportion() {
|
||||
return this.controllersHealthy / this.controllersExpected;
|
||||
|
||||
@@ -21,13 +21,17 @@ export default class VolumeRoute extends Route.extend(WithWatchers) {
|
||||
}
|
||||
|
||||
serialize(model) {
|
||||
return { volume_name: model.get('plainId') };
|
||||
return { volume_name: JSON.parse(model.get('id')).join('@') };
|
||||
}
|
||||
|
||||
model(params, transition) {
|
||||
const namespace = transition.to.queryParams.namespace;
|
||||
const name = params.volume_name;
|
||||
model(params) {
|
||||
// Issue with naming collissions
|
||||
const url = params.volume_name.split('@');
|
||||
const namespace = url.pop();
|
||||
const name = url.join('');
|
||||
|
||||
const fullId = JSON.stringify([`csi/${name}`, namespace || 'default']);
|
||||
|
||||
return RSVP.hash({
|
||||
volume: this.store.findRecord('volume', fullId, { reload: true }),
|
||||
namespaces: this.store.findAll('namespace'),
|
||||
|
||||
@@ -11,12 +11,12 @@ export default class JobRoute extends Route {
|
||||
@service token;
|
||||
|
||||
serialize(model) {
|
||||
return { job_name: model.get('plainId') };
|
||||
return { job_name: model.get('idWithNamespace') };
|
||||
}
|
||||
|
||||
model(params, transition) {
|
||||
const namespace = transition.to.queryParams.namespace || 'default';
|
||||
const name = params.job_name;
|
||||
model(params) {
|
||||
const [name, namespace = 'default'] = params.job_name.split('@');
|
||||
|
||||
const fullId = JSON.stringify([name, namespace]);
|
||||
|
||||
return this.store
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
<div data-test-inline-error class="notification is-danger">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 data-test-inline-error-title class="title is-4">{{this.error.title}}</h3>
|
||||
<h3
|
||||
data-test-inline-error-title
|
||||
class="title is-4"
|
||||
>{{this.error.title}}</h3>
|
||||
<p data-test-inline-error-body>{{this.error.description}}</p>
|
||||
</div>
|
||||
<div class="column is-centered is-minimum">
|
||||
<button data-test-inline-error-close class="button is-danger" onclick={{action this.onDismiss}} type="button">Okay</button>
|
||||
<button
|
||||
data-test-inline-error-close
|
||||
class="button is-danger"
|
||||
onclick={{action this.onDismiss}}
|
||||
type="button"
|
||||
>Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,13 +25,19 @@
|
||||
|
||||
<h1 data-test-title class="title with-headroom with-flex">
|
||||
<div>
|
||||
Allocation {{this.model.name}}
|
||||
<span class="bumper-left tag {{this.model.statusClass}}">{{this.model.clientStatus}}</span>
|
||||
Allocation
|
||||
{{this.model.name}}
|
||||
<span
|
||||
class="bumper-left tag {{this.model.statusClass}}"
|
||||
>{{this.model.clientStatus}}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{#if this.model.isRunning}}
|
||||
<div class="two-step-button">
|
||||
<Exec::OpenButton @job={{this.model.job}} @allocation={{this.model}} />
|
||||
<Exec::OpenButton
|
||||
@job={{this.model.job}}
|
||||
@allocation={{this.model}}
|
||||
/>
|
||||
</div>
|
||||
<TwoStepButton
|
||||
data-test-stop
|
||||
@@ -33,8 +47,12 @@
|
||||
@confirmText="Yes, Stop"
|
||||
@confirmationMessage="Are you sure? This will reschedule the allocation on a different client."
|
||||
@awaitingConfirmation={{this.stopAllocation.isRunning}}
|
||||
@disabled={{or this.stopAllocation.isRunning this.restartAllocation.isRunning}}
|
||||
@onConfirm={{perform this.stopAllocation}} />
|
||||
@disabled={{or
|
||||
this.stopAllocation.isRunning
|
||||
this.restartAllocation.isRunning
|
||||
}}
|
||||
@onConfirm={{perform this.stopAllocation}}
|
||||
/>
|
||||
<TwoStepButton
|
||||
data-test-restart
|
||||
@alignRight={{true}}
|
||||
@@ -43,8 +61,12 @@
|
||||
@confirmText="Yes, Restart"
|
||||
@confirmationMessage="Are you sure? This will restart the allocation in-place."
|
||||
@awaitingConfirmation={{this.restartAllocation.isRunning}}
|
||||
@disabled={{or this.stopAllocation.isRunning this.restartAllocation.isRunning}}
|
||||
@onConfirm={{perform this.restartAllocation}} />
|
||||
@disabled={{or
|
||||
this.stopAllocation.isRunning
|
||||
this.restartAllocation.isRunning
|
||||
}}
|
||||
@onConfirm={{perform this.restartAllocation}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</h1>
|
||||
@@ -55,13 +77,24 @@
|
||||
</span>
|
||||
|
||||
<div class="boxed-section is-small">
|
||||
<div data-test-allocation-details class="boxed-section-body inline-definitions">
|
||||
<div
|
||||
data-test-allocation-details
|
||||
class="boxed-section-body inline-definitions"
|
||||
>
|
||||
<span class="label">Allocation Details</span>
|
||||
<span class="pair job-link"><span class="term">Job</span>
|
||||
<LinkTo @route="jobs.job" @model={{this.model.job}} @query={{hash jobNamespace=this.model.job.namespace.id}} data-test-job-link>{{this.model.job.name}}</LinkTo>
|
||||
<LinkTo
|
||||
@route="jobs.job"
|
||||
@model={{this.model.job}}
|
||||
data-test-job-link
|
||||
>{{this.model.job.name}}</LinkTo>
|
||||
</span>
|
||||
<span class="pair node-link"><span class="term">Client</span>
|
||||
<LinkTo @route="clients.client" @model={{this.model.node}} data-test-client-link>{{this.model.node.shortId}}</LinkTo>
|
||||
<LinkTo
|
||||
@route="clients.client"
|
||||
@model={{this.model.node}}
|
||||
data-test-client-link
|
||||
>{{this.model.node.shortId}}</LinkTo>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,16 +107,26 @@
|
||||
{{#if this.model.isRunning}}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<PrimaryMetric::Allocation @allocation={{this.model}} @metric="cpu" />
|
||||
<PrimaryMetric::Allocation
|
||||
@allocation={{this.model}}
|
||||
@metric="cpu"
|
||||
/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<PrimaryMetric::Allocation @allocation={{this.model}} @metric="memory" />
|
||||
<PrimaryMetric::Allocation
|
||||
@allocation={{this.model}}
|
||||
@metric="memory"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div data-test-resource-error class="empty-message">
|
||||
<h3 data-test-resource-error-headline class="empty-message-headline">Allocation isn't running</h3>
|
||||
<p class="empty-message-body">Only running allocations utilize resources.</p>
|
||||
<h3
|
||||
data-test-resource-error-headline
|
||||
class="empty-message-headline"
|
||||
>Allocation isn't running</h3>
|
||||
<p class="empty-message-body">Only running allocations utilize
|
||||
resources.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -95,13 +138,17 @@
|
||||
<div class="boxed-section-head">
|
||||
Tasks
|
||||
</div>
|
||||
<div class="boxed-section-body {{if this.sortedStates.length "is-full-bleed"}}">
|
||||
<div
|
||||
class="boxed-section-body {{if this.sortedStates.length 'is-full-bleed'}}"
|
||||
>
|
||||
{{#if this.sortedStates.length}}
|
||||
<ListTable
|
||||
@source={{this.sortedStates}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="is-striped" as |t|>
|
||||
@class="is-striped"
|
||||
as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
<t.sort-by @prop="name">Name</t.sort-by>
|
||||
@@ -116,13 +163,20 @@
|
||||
<TaskRow
|
||||
@data-test-task-row={{row.model.name}}
|
||||
@task={{row.model}}
|
||||
@onClick={{action "taskClick" row.model.allocation row.model}} />
|
||||
@onClick={{action "taskClick" row.model.allocation row.model}}
|
||||
/>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
{{else}}
|
||||
<div data-test-empty-tasks-list class="empty-message">
|
||||
<h3 data-test-empty-tasks-list-headline class="empty-message-headline">No Tasks</h3>
|
||||
<p data-test-empty-tasks-list-body class="empty-message-body">Allocations will not have tasks until they are in a running state.</p>
|
||||
<h3
|
||||
data-test-empty-tasks-list-headline
|
||||
class="empty-message-headline"
|
||||
>No Tasks</h3>
|
||||
<p
|
||||
data-test-empty-tasks-list-body
|
||||
class="empty-message-body"
|
||||
>Allocations will not have tasks until they are in a running state.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -144,7 +198,11 @@
|
||||
<tr data-test-allocation-port>
|
||||
<td data-test-allocation-port-name>{{row.model.label}}</td>
|
||||
<td data-test-allocation-port-address>
|
||||
<a href="http://{{row.model.hostIp}}:{{row.model.value}}" target="_blank" rel="noopener noreferrer">{{row.model.hostIp}}:{{row.model.value}}</a>
|
||||
<a
|
||||
href="http://{{row.model.hostIp}}:{{row.model.value}}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>{{row.model.hostIp}}:{{row.model.value}}</a>
|
||||
</td>
|
||||
<td data-test-allocation-port-to>{{row.model.to}}</td>
|
||||
</tr>
|
||||
@@ -173,11 +231,21 @@
|
||||
<tr data-test-service>
|
||||
<td data-test-service-name>{{row.model.name}}</td>
|
||||
<td data-test-service-port>{{row.model.portLabel}}</td>
|
||||
<td data-test-service-tags class="is-long-text">{{join ", " row.model.tags}}</td>
|
||||
<td data-test-service-tags class="is-long-text">{{join
|
||||
", "
|
||||
row.model.tags
|
||||
}}</td>
|
||||
<td data-test-service-onupdate>{{row.model.onUpdate}}</td>
|
||||
<td data-test-service-connect>{{if row.model.connect "Yes" "No"}}</td>
|
||||
<td data-test-service-connect>{{if
|
||||
row.model.connect
|
||||
"Yes"
|
||||
"No"
|
||||
}}</td>
|
||||
<td data-test-service-upstreams>
|
||||
{{#each row.model.connect.sidecarService.proxy.upstreams as |upstream|}}
|
||||
{{#each
|
||||
row.model.connect.sidecarService.proxy.upstreams
|
||||
as |upstream|
|
||||
}}
|
||||
{{upstream.destinationName}}:{{upstream.localBindPort}}
|
||||
{{/each}}
|
||||
</td>
|
||||
@@ -207,48 +275,81 @@
|
||||
<div class="boxed-section is-small">
|
||||
<div class="boxed-section-body inline-definitions">
|
||||
<span class="pair">
|
||||
<span data-test-allocation-status class="tag {{this.preempter.statusClass}}">
|
||||
<span
|
||||
data-test-allocation-status
|
||||
class="tag {{this.preempter.statusClass}}"
|
||||
>
|
||||
{{this.preempter.clientStatus}}
|
||||
</span>
|
||||
</span>
|
||||
<span class="pair">
|
||||
<span class="term" data-test-allocation-name>{{this.preempter.name}}</span>
|
||||
<LinkTo @route="allocations.allocation" @model={{this.preempter}} data-test-allocation-id>{{this.preempter.shortId}}</LinkTo>
|
||||
<span
|
||||
class="term"
|
||||
data-test-allocation-name
|
||||
>{{this.preempter.name}}</span>
|
||||
<LinkTo
|
||||
@route="allocations.allocation"
|
||||
@model={{this.preempter}}
|
||||
data-test-allocation-id
|
||||
>{{this.preempter.shortId}}</LinkTo>
|
||||
</span>
|
||||
<span class="pair job-link"><span class="term">Job</span>
|
||||
<LinkTo @route="jobs.job" @model={{this.preempter.job}} @query={{hash jobNamespace=this.preempter.job.namespace.id}} data-test-job-link>{{this.preempter.job.name}}</LinkTo>
|
||||
<LinkTo
|
||||
@route="jobs.job"
|
||||
@model={{this.preempter.job}}
|
||||
data-test-job-link
|
||||
>{{this.preempter.job.name}}</LinkTo>
|
||||
</span>
|
||||
<span class="pair job-priority"><span class="term">Priority</span>
|
||||
<span data-test-job-priority>{{this.preempter.job.priority}}</span>
|
||||
<span
|
||||
data-test-job-priority
|
||||
>{{this.preempter.job.priority}}</span>
|
||||
</span>
|
||||
<span class="pair node-link"><span class="term">Client</span>
|
||||
<LinkTo @route="clients.client" @model={{this.preempter.node}} data-test-client-link>{{this.preempter.node.shortId}}</LinkTo>
|
||||
<LinkTo
|
||||
@route="clients.client"
|
||||
@model={{this.preempter.node}}
|
||||
data-test-client-link
|
||||
>{{this.preempter.node.shortId}}</LinkTo>
|
||||
</span>
|
||||
<span class="pair"><span class="term">Reserved CPU</span>
|
||||
<span data-test-allocation-cpu>{{format-scheduled-hertz this.preempter.resources.cpu}}</span>
|
||||
<span data-test-allocation-cpu>{{format-scheduled-hertz
|
||||
this.preempter.resources.cpu
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="pair"><span class="term">Reserved Memory</span>
|
||||
<span data-test-allocation-memory>{{format-scheduled-bytes this.preempter.resources.memory start="MiB"}}</span>
|
||||
<span data-test-allocation-memory>{{format-scheduled-bytes
|
||||
this.preempter.resources.memory
|
||||
start="MiB"
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-message">
|
||||
<h3 class="empty-message-headline">Allocation is gone</h3>
|
||||
<p class="empty-message-body">This allocation has been stopped and garbage collected.</p>
|
||||
<p class="empty-message-body">This allocation has been stopped and
|
||||
garbage collected.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and this.model.preemptedAllocations.isFulfilled this.model.preemptedAllocations.length)}}
|
||||
{{#if
|
||||
(and
|
||||
this.model.preemptedAllocations.isFulfilled
|
||||
this.model.preemptedAllocations.length
|
||||
)
|
||||
}}
|
||||
<div class="boxed-section" data-test-preemptions>
|
||||
<div class="boxed-section-head">Preempted Allocations</div>
|
||||
<div class="boxed-section-body">
|
||||
<ListTable
|
||||
@source={{this.model.preemptedAllocations}}
|
||||
@class="allocations is-isolated" as |t|>
|
||||
@class="allocations is-isolated"
|
||||
as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
<th>ID</th>
|
||||
@@ -262,7 +363,11 @@
|
||||
<th>Memory</th>
|
||||
</t.head>
|
||||
<t.body as |row|>
|
||||
<AllocationRow @allocation={{row.model}} @context="job" @data-test-allocation={{row.model.id}} />
|
||||
<AllocationRow
|
||||
@allocation={{row.model}}
|
||||
@context="job"
|
||||
@data-test-allocation={{row.model.id}}
|
||||
/>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
</div>
|
||||
|
||||
@@ -5,23 +5,40 @@
|
||||
<div data-test-inline-error class="notification is-danger">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 data-test-inline-error-title class="title is-4">{{this.error.title}}</h3>
|
||||
<p data-test-inline-error-body>{{this.error.description}}</p>
|
||||
<h3 data-test-inline-error-title class="title is-4">
|
||||
{{this.error.title}}
|
||||
</h3>
|
||||
<p data-test-inline-error-body>
|
||||
{{this.error.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-centered is-minimum">
|
||||
<button data-test-inline-error-close class="button is-danger" onclick={{action this.onDismiss}} type="button">Okay</button>
|
||||
<button
|
||||
data-test-inline-error-close
|
||||
class="button is-danger"
|
||||
onclick={{action this.onDismiss}}
|
||||
type="button"
|
||||
>
|
||||
Okay
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<h1 class="title with-flex" data-test-title>
|
||||
<div>
|
||||
{{this.model.name}}
|
||||
{{#if this.model.isConnectProxy}}
|
||||
<ProxyTag @class="bumper-left" />
|
||||
{{/if}}
|
||||
<span class="{{unless this.model.isConnectProxy "bumper-left"}} tag {{this.model.stateClass}}" data-test-state>{{this.model.state}}</span>
|
||||
<span
|
||||
class="{{unless this.model.isConnectProxy "bumper-left"}}
|
||||
tag
|
||||
{{this.model.stateClass}}"
|
||||
data-test-state
|
||||
>
|
||||
{{this.model.state}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{#if this.model.isRunning}}
|
||||
@@ -30,7 +47,8 @@
|
||||
@job={{this.model.task.taskGroup.job}}
|
||||
@taskGroup={{this.model.task.taskGroup}}
|
||||
@allocation={{this.model.allocation}}
|
||||
@task={{this.model.task}} />
|
||||
@task={{this.model.task}}
|
||||
/>
|
||||
</div>
|
||||
<TwoStepButton
|
||||
data-test-restart
|
||||
@@ -41,35 +59,46 @@
|
||||
@confirmationMessage="Are you sure? This will restart the task in-place."
|
||||
@awaitingConfirmation={{this.restartTask.isRunning}}
|
||||
@disabled={{this.restartTask.isRunning}}
|
||||
@onConfirm={{perform this.restartTask}} />
|
||||
@onConfirm={{perform this.restartTask}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div class="boxed-section is-small">
|
||||
<div class="boxed-section-body inline-definitions">
|
||||
<span class="label">Task Details</span>
|
||||
<span class="label">
|
||||
Task Details
|
||||
</span>
|
||||
<span class="pair" data-test-started-at>
|
||||
<span class="term">Started At</span>
|
||||
<span class="term">
|
||||
Started At
|
||||
</span>
|
||||
{{format-ts this.model.startedAt}}
|
||||
</span>
|
||||
{{#if this.model.finishedAt}}
|
||||
<span class="pair">
|
||||
<span class="term">Finished At</span>
|
||||
<span class="term">
|
||||
Finished At
|
||||
</span>
|
||||
{{format-ts this.model.finishedAt}}
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="pair">
|
||||
<span class="term">Driver</span>
|
||||
<span class="term">
|
||||
Driver
|
||||
</span>
|
||||
{{this.model.task.driver}}
|
||||
</span>
|
||||
<span class="pair">
|
||||
<span class="term">Lifecycle</span>
|
||||
<span data-test-lifecycle>{{this.model.task.lifecycleName}}</span>
|
||||
<span class="term">
|
||||
Lifecycle
|
||||
</span>
|
||||
<span data-test-lifecycle>
|
||||
{{this.model.task.lifecycleName}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head is-hollow">
|
||||
Resource Utilization
|
||||
@@ -86,13 +115,16 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div data-test-resource-error class="empty-message">
|
||||
<h3 data-test-resource-error-headline class="empty-message-headline">Task isn't running</h3>
|
||||
<p class="empty-message-body">Only running tasks utilize resources.</p>
|
||||
<h3 data-test-resource-error-headline class="empty-message-headline">
|
||||
Task isn't running
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
Only running tasks utilize resources.
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.model.task.volumeMounts.length}}
|
||||
<div data-test-volumes class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
@@ -101,20 +133,40 @@
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
<ListTable @source={{this.model.task.volumeMounts}} as |t|>
|
||||
<t.head>
|
||||
<th>Name</th>
|
||||
<th>Destination</th>
|
||||
<th>Permissions</th>
|
||||
<th>Client Source</th>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Destination
|
||||
</th>
|
||||
<th>
|
||||
Permissions
|
||||
</th>
|
||||
<th>
|
||||
Client Source
|
||||
</th>
|
||||
</t.head>
|
||||
<t.body as |row|>
|
||||
<tr data-test-volume>
|
||||
<td data-test-volume-name>{{row.model.volume}}</td>
|
||||
<td data-test-volume-destination><code>{{row.model.destination}}</code></td>
|
||||
<td data-test-volume-permissions>{{if row.model.readOnly "Read" "Read/Write"}}</td>
|
||||
<td data-test-volume-name>
|
||||
{{row.model.volume}}
|
||||
</td>
|
||||
<td data-test-volume-destination>
|
||||
<code>
|
||||
{{row.model.destination}}
|
||||
</code>
|
||||
</td>
|
||||
<td data-test-volume-permissions>
|
||||
{{if row.model.readOnly "Read" "Read/Write"}}
|
||||
</td>
|
||||
<td data-test-volume-client-source>
|
||||
{{#if row.model.isCSI}}
|
||||
<LinkTo @route="csi.volumes.volume" @model={{row.model.source}} @query={{hash volumeNamespace=row.model.namespace.id}}>
|
||||
{{row.model.source}}
|
||||
<LinkTo
|
||||
@route="csi.volumes.volume"
|
||||
@model={{concat row.model.volume "@" row.model.namespace.id
|
||||
}}
|
||||
>
|
||||
{{row.model.volume}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{row.model.source}}
|
||||
@@ -126,27 +178,41 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Recent Events
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
<ListTable @source={{reverse this.model.events}} @class="is-striped" as |t|>
|
||||
<ListTable
|
||||
@source={{reverse this.model.events}}
|
||||
@class="is-striped" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-3">Time</th>
|
||||
<th class="is-1">Type</th>
|
||||
<th>Description</th>
|
||||
<th class="is-3">
|
||||
Time
|
||||
</th>
|
||||
<th class="is-1">
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
Description
|
||||
</th>
|
||||
</t.head>
|
||||
<t.body as |row|>
|
||||
<tr data-test-task-event>
|
||||
<td data-test-task-event-time>{{format-ts row.model.time}}</td>
|
||||
<td data-test-task-event-type>{{row.model.type}}</td>
|
||||
<td data-test-task-event-time>
|
||||
{{format-ts row.model.time}}
|
||||
</td>
|
||||
<td data-test-task-event-type>
|
||||
{{row.model.type}}
|
||||
</td>
|
||||
<td data-test-task-event-message>
|
||||
{{#if row.model.message}}
|
||||
{{row.model.message}}
|
||||
{{else}}
|
||||
<em>No message</em>
|
||||
<em>
|
||||
No message
|
||||
</em>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -154,4 +220,4 @@
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -24,12 +24,20 @@
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if this.system.agent.config.UI.Consul.BaseUIURL}}
|
||||
<a data-test-header-consul-link href={{this.system.agent.config.UI.Consul.BaseUIURL}} class="navbar-item">
|
||||
<a
|
||||
data-test-header-consul-link
|
||||
href={{this.system.agent.config.UI.Consul.BaseUIURL}}
|
||||
class="navbar-item"
|
||||
>
|
||||
Consul
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if this.system.agent.config.UI.Vault.BaseUIURL}}
|
||||
<a data-test-header-vault-link href={{this.system.agent.config.UI.Vault.BaseUIURL}} class="navbar-item">
|
||||
<a
|
||||
data-test-header-vault-link
|
||||
href={{this.system.agent.config.UI.Vault.BaseUIURL}}
|
||||
class="navbar-item"
|
||||
>
|
||||
Vault
|
||||
</a>
|
||||
{{/if}}
|
||||
@@ -50,4 +58,4 @@
|
||||
{{yield}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,8 +1,7 @@
|
||||
<td data-test-job-name>
|
||||
<LinkTo
|
||||
@route="jobs.job"
|
||||
@model={{this.job.plainId}}
|
||||
@query={{hash namespace=this.job.namespace.id}}
|
||||
@route="jobs.job.index"
|
||||
@model={{this.job.idWithNamespace}}
|
||||
class="is-primary"
|
||||
>
|
||||
{{this.job.name}}
|
||||
@@ -47,7 +46,10 @@
|
||||
</em>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<AllocationStatusBar @allocationContainer={{this.job}} @isNarrow={{true}} />
|
||||
<AllocationStatusBar
|
||||
@allocationContainer={{this.job}}
|
||||
@isNarrow={{true}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
@@ -3,7 +3,6 @@
|
||||
<li data-test-tab="overview">
|
||||
<LinkTo
|
||||
@route="jobs.job.index"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
@current-when="jobs.job.index jobs.job.dispatch"
|
||||
@@ -14,7 +13,6 @@
|
||||
<li data-test-tab="definition">
|
||||
<LinkTo
|
||||
@route="jobs.job.definition"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
@@ -24,7 +22,6 @@
|
||||
<li data-test-tab="versions">
|
||||
<LinkTo
|
||||
@route="jobs.job.versions"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
@@ -35,7 +32,6 @@
|
||||
<li data-test-tab="deployments">
|
||||
<LinkTo
|
||||
@route="jobs.job.deployments"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
@@ -46,7 +42,6 @@
|
||||
<li data-test-tab="allocations">
|
||||
<LinkTo
|
||||
@route="jobs.job.allocations"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
@@ -56,7 +51,6 @@
|
||||
<li data-test-tab="evaluations">
|
||||
<LinkTo
|
||||
@route="jobs.job.evaluations"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
@@ -67,7 +61,6 @@
|
||||
<li data-test-tab="clients">
|
||||
<LinkTo
|
||||
@route="jobs.job.clients"
|
||||
@query={{hash namespace=@job.namespace.id}}
|
||||
@model={{@job}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
|
||||
@@ -1,34 +1,54 @@
|
||||
<td class="is-narrow">
|
||||
{{#unless this.task.driverStatus.healthy}}
|
||||
<span data-test-icon="unhealthy-driver" class="tooltip text-center" aria-label="{{this.task.driver}} is unhealthy">
|
||||
<span
|
||||
data-test-icon="unhealthy-driver"
|
||||
class="tooltip text-center"
|
||||
aria-label="{{this.task.driver}} is unhealthy"
|
||||
>
|
||||
{{x-icon "alert-triangle" class="is-warning"}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
</td>
|
||||
<td data-test-name class="nowrap">
|
||||
<LinkTo @route="allocations.allocation.task" @models={{array this.task.allocation this.task}} class="is-primary">
|
||||
<LinkTo
|
||||
@route="allocations.allocation.task"
|
||||
@models={{array this.task.allocation this.task}}
|
||||
class="is-primary"
|
||||
>
|
||||
{{this.task.name}}
|
||||
{{#if this.task.isConnectProxy}}
|
||||
<ProxyTag @class="bumper-left" />
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
<td data-test-state>{{this.task.state}}</td>
|
||||
<td data-test-state>
|
||||
{{this.task.state}}
|
||||
</td>
|
||||
<td data-test-message>
|
||||
{{#if this.task.events.lastObject.message}}
|
||||
{{this.task.events.lastObject.message}}
|
||||
{{else}}
|
||||
<em>No message</em>
|
||||
<em>
|
||||
No message
|
||||
</em>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-time>{{format-ts this.task.events.lastObject.time}}</td>
|
||||
<td data-test-time>
|
||||
{{format-ts this.task.events.lastObject.time}}
|
||||
</td>
|
||||
<td data-test-volumes>
|
||||
<ul>
|
||||
{{#each this.task.task.volumeMounts as |volume|}}
|
||||
<li data-test-volume>
|
||||
<strong>{{volume.volume}}:</strong>
|
||||
<strong>
|
||||
{{volume.volume}}
|
||||
:
|
||||
</strong>
|
||||
{{#if volume.isCSI}}
|
||||
<LinkTo @route="csi.volumes.volume" @model={{volume.source}} @query={{hash volumeNamespace=volume.namespace.id}}>
|
||||
<LinkTo
|
||||
@route="csi.volumes.volume"
|
||||
@model={{concat volume.source "@" volume.namespace.id}}
|
||||
>
|
||||
{{volume.source}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
@@ -43,15 +63,26 @@
|
||||
{{#if (and (not this.cpu) this.fetchStats.isRunning)}}
|
||||
...
|
||||
{{else if this.statsError}}
|
||||
<span class="tooltip text-center" role="tooltip" aria-label="Couldn't collect stats">
|
||||
<span
|
||||
class="tooltip text-center"
|
||||
role="tooltip"
|
||||
aria-label="Couldn't collect stats"
|
||||
>
|
||||
{{x-icon "alert-triangle" class="is-warning"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<div class="inline-chart is-small tooltip" role="tooltip" aria-label="{{format-hertz this.cpu.used}} / {{format-hertz this.taskStats.reservedCPU}}">
|
||||
<div
|
||||
class="inline-chart is-small tooltip"
|
||||
role="tooltip"
|
||||
aria-label="{{format-hertz this.cpu.used}}
|
||||
/
|
||||
{{format-hertz this.taskStats.reservedCPU}}"
|
||||
>
|
||||
<progress
|
||||
class="progress is-info is-small"
|
||||
value="{{this.cpu.percent}}"
|
||||
max="1">
|
||||
max="1"
|
||||
>
|
||||
{{this.cpu.percent}}
|
||||
</progress>
|
||||
</div>
|
||||
@@ -63,18 +94,29 @@
|
||||
{{#if (and (not this.memory) this.fetchStats.isRunning)}}
|
||||
...
|
||||
{{else if this.statsError}}
|
||||
<span class="tooltip is-small text-center" role="tooltip" aria-label="Couldn't collect stats">
|
||||
<span
|
||||
class="tooltip is-small text-center"
|
||||
role="tooltip"
|
||||
aria-label="Couldn't collect stats"
|
||||
>
|
||||
{{x-icon "alert-triangle" class="is-warning"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<div class="inline-chart tooltip" role="tooltip" aria-label="{{format-bytes this.memory.used}} / {{format-bytes this.taskStats.reservedMemory start='MiB'}}">
|
||||
<div
|
||||
class="inline-chart tooltip"
|
||||
role="tooltip"
|
||||
aria-label="{{format-bytes this.memory.used}}
|
||||
/
|
||||
{{format-bytes this.taskStats.reservedMemory start="MiB"}}"
|
||||
>
|
||||
<progress
|
||||
class="progress is-danger is-small"
|
||||
value="{{this.memory.percent}}"
|
||||
max="1">
|
||||
max="1"
|
||||
>
|
||||
{{this.memory.percent}}
|
||||
</progress>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</td>
|
||||
@@ -1,8 +1,16 @@
|
||||
{{page-title "CSI Volumes"}}
|
||||
<div class="tabs is-subnav">
|
||||
<ul>
|
||||
<li data-test-tab="volumes"><LinkTo @route="csi.volumes.index" @activeClass="is-active">Volumes</LinkTo></li>
|
||||
<li data-test-tab="plugins"><LinkTo @route="csi.plugins.index" @activeClass="is-active">Plugins</LinkTo></li>
|
||||
<li data-test-tab="volumes">
|
||||
<LinkTo @route="csi.volumes.index" @activeClass="is-active">
|
||||
Volumes
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li data-test-tab="plugins">
|
||||
<LinkTo @route="csi.plugins.index" @activeClass="is-active">
|
||||
Plugins
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<section class="section">
|
||||
@@ -13,7 +21,8 @@
|
||||
data-test-volumes-search
|
||||
@searchTerm={{mut this.searchTerm}}
|
||||
@onChange={{action this.resetPagination}}
|
||||
@placeholder="Search volumes..." />
|
||||
@placeholder="Search volumes..."
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.system.shouldShowNamespaces}}
|
||||
@@ -24,101 +33,154 @@
|
||||
@label="Namespace"
|
||||
@options={{this.optionsNamespaces}}
|
||||
@selection={{this.qpNamespace}}
|
||||
@onSelect={{action (queue
|
||||
(action this.cacheNamespace)
|
||||
(action this.setFacetQueryParam "qpNamespace")
|
||||
)}} />
|
||||
@onSelect={{action
|
||||
(queue (action this.cacheNamespace) (action this.setFacetQueryParam "qpNamespace"))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.isForbidden}}
|
||||
<ForbiddenMessage />
|
||||
{{else}}
|
||||
{{#if this.sortedVolumes}}
|
||||
<ListPagination
|
||||
@source={{this.sortedVolumes}}
|
||||
@size={{this.pageSize}}
|
||||
@page={{this.currentPage}} as |p|>
|
||||
<ListTable
|
||||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|>
|
||||
<t.head>
|
||||
<t.sort-by @prop="name">Name</t.sort-by>
|
||||
{{else if this.sortedVolumes}}
|
||||
<ListPagination
|
||||
@source={{this.sortedVolumes}}
|
||||
@size={{this.pageSize}}
|
||||
@page={{this.currentPage}} as |p|
|
||||
>
|
||||
<ListTable
|
||||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<t.sort-by @prop="name">
|
||||
Name
|
||||
</t.sort-by>
|
||||
{{#if this.system.shouldShowNamespaces}}
|
||||
<t.sort-by @prop="namespace.name">
|
||||
Namespace
|
||||
</t.sort-by>
|
||||
{{/if}}
|
||||
<t.sort-by @prop="schedulable">
|
||||
Volume Health
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="controllersHealthyProportion">
|
||||
Controller Health
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="nodesHealthyProportion">
|
||||
Node Health
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="provider">
|
||||
Provider
|
||||
</t.sort-by>
|
||||
<th>
|
||||
# Allocs
|
||||
</th>
|
||||
</t.head>
|
||||
<t.body @key="model.name" as |row|>
|
||||
<tr
|
||||
class="is-interactive"
|
||||
data-test-volume-row
|
||||
{{on "click" (action "gotoVolume" row.model)}}
|
||||
>
|
||||
<td data-test-volume-name>
|
||||
<LinkTo
|
||||
@route="csi.volumes.volume"
|
||||
@model={{row.model.idWithNamespace}}
|
||||
class="is-primary"
|
||||
>
|
||||
{{row.model.name}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
{{#if this.system.shouldShowNamespaces}}
|
||||
<t.sort-by @prop="namespace.name">Namespace</t.sort-by>
|
||||
<td data-test-volume-namespace>
|
||||
{{row.model.namespace.name}}
|
||||
</td>
|
||||
{{/if}}
|
||||
<t.sort-by @prop="schedulable">Volume Health</t.sort-by>
|
||||
<t.sort-by @prop="controllersHealthyProportion">Controller Health</t.sort-by>
|
||||
<t.sort-by @prop="nodesHealthyProportion">Node Health</t.sort-by>
|
||||
<t.sort-by @prop="provider">Provider</t.sort-by>
|
||||
<th># Allocs</th>
|
||||
</t.head>
|
||||
<t.body @key="model.name" as |row|>
|
||||
<tr class="is-interactive" data-test-volume-row {{on "click" (action "gotoVolume" row.model)}}>
|
||||
<td data-test-volume-name>
|
||||
<LinkTo
|
||||
@route="csi.volumes.volume"
|
||||
@model={{row.model.plainId}}
|
||||
@query={{hash volumeNamespace=row.model.namespace.name}}
|
||||
class="is-primary">
|
||||
{{row.model.name}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
{{#if this.system.shouldShowNamespaces}}
|
||||
<td data-test-volume-namespace>{{row.model.namespace.name}}</td>
|
||||
<td data-test-volume-schedulable>
|
||||
{{if row.model.schedulable "Schedulable" "Unschedulable"}}
|
||||
</td>
|
||||
<td data-test-volume-controller-health>
|
||||
{{#if row.model.controllerRequired}}
|
||||
{{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}}
|
||||
(
|
||||
{{row.model.controllersHealthy}}
|
||||
/
|
||||
{{row.model.controllersExpected}}
|
||||
)
|
||||
{{else if (gt row.model.controllersExpected 0)}}
|
||||
{{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}}
|
||||
(
|
||||
{{row.model.controllersHealthy}}
|
||||
/
|
||||
{{row.model.controllersExpected}}
|
||||
)
|
||||
{{else}}
|
||||
<em class="is-faded">
|
||||
Node Only
|
||||
</em>
|
||||
{{/if}}
|
||||
<td data-test-volume-schedulable>{{if row.model.schedulable "Schedulable" "Unschedulable"}}</td>
|
||||
<td data-test-volume-controller-health>
|
||||
{{#if row.model.controllerRequired}}
|
||||
{{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}}
|
||||
({{row.model.controllersHealthy}}/{{row.model.controllersExpected}})
|
||||
{{else}}
|
||||
{{#if (gt row.model.controllersExpected 0)}}
|
||||
{{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}}
|
||||
({{row.model.controllersHealthy}}/{{row.model.controllersExpected}})
|
||||
{{else}}
|
||||
<em class="is-faded">Node Only</em>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-volume-node-health>
|
||||
{{if (gt row.model.nodesHealthy 0) "Healthy" "Unhealthy"}}
|
||||
({{row.model.nodesHealthy}}/{{row.model.nodesExpected}})
|
||||
</td>
|
||||
<td data-test-volume-provider>{{row.model.provider}}</td>
|
||||
<td data-test-volume-allocations>{{row.model.allocationCount}}</td>
|
||||
</tr>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
<PageSizeSelect @onChange={{action this.resetPagination}} />
|
||||
<nav class="pagination">
|
||||
<div class="pagination-numbers">
|
||||
{{p.startsAt}}–{{p.endsAt}} of {{this.sortedVolumes.length}}
|
||||
</div>
|
||||
<p.prev @class="pagination-previous">{{x-icon "chevron-left"}}</p.prev>
|
||||
<p.next @class="pagination-next">{{x-icon "chevron-right"}}</p.next>
|
||||
<ul class="pagination-list"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</ListPagination>
|
||||
{{else}}
|
||||
<div data-test-empty-volumes-list class="empty-message">
|
||||
{{#if (eq this.visibleVolumes.length 0)}}
|
||||
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">No Volumes</h3>
|
||||
<p class="empty-message-body">
|
||||
This namespace currently has no CSI Volumes.
|
||||
</p>
|
||||
{{else if this.searchTerm}}
|
||||
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">No Matches</h3>
|
||||
<p class="empty-message-body">
|
||||
No volumes match the term <strong>{{this.searchTerm}}</strong>
|
||||
</p>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-volume-node-health>
|
||||
{{if (gt row.model.nodesHealthy 0) "Healthy" "Unhealthy"}}
|
||||
(
|
||||
{{row.model.nodesHealthy}}
|
||||
/
|
||||
{{row.model.nodesExpected}}
|
||||
)
|
||||
</td>
|
||||
<td data-test-volume-provider>
|
||||
{{row.model.provider}}
|
||||
</td>
|
||||
<td data-test-volume-allocations>
|
||||
{{row.model.allocationCount}}
|
||||
</td>
|
||||
</tr>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
<PageSizeSelect @onChange={{action this.resetPagination}} />
|
||||
<nav class="pagination">
|
||||
<div class="pagination-numbers">
|
||||
{{p.startsAt}}
|
||||
–
|
||||
{{p.endsAt}}
|
||||
of
|
||||
{{this.sortedVolumes.length}}
|
||||
</div>
|
||||
<p.prev @class="pagination-previous">
|
||||
{{x-icon "chevron-left"}}
|
||||
</p.prev>
|
||||
<p.next @class="pagination-next">
|
||||
{{x-icon "chevron-right"}}
|
||||
</p.next>
|
||||
<ul class="pagination-list"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{/if}}
|
||||
</ListPagination>
|
||||
{{else}}
|
||||
<div data-test-empty-volumes-list class="empty-message">
|
||||
{{#if (eq this.visibleVolumes.length 0)}}
|
||||
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">
|
||||
No Volumes
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
This namespace currently has no CSI Volumes.
|
||||
</p>
|
||||
{{else if this.searchTerm}}
|
||||
<h3 data-test-empty-volumes-list-headline class="empty-message-headline">
|
||||
No Matches
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
No volumes match the term
|
||||
<strong>
|
||||
{{this.searchTerm}}
|
||||
</strong>
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
</section>
|
||||
@@ -44,7 +44,10 @@
|
||||
@options={{this.optionsNamespaces}}
|
||||
@selection={{this.qpNamespace}}
|
||||
@onSelect={{action
|
||||
(queue (action this.cacheNamespace) (action this.setFacetQueryParam "qpNamespace"))
|
||||
(queue
|
||||
(action this.cacheNamespace)
|
||||
(action this.setFacetQueryParam "qpNamespace")
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -109,13 +112,15 @@
|
||||
<ListPagination
|
||||
@source={{this.sortedJobs}}
|
||||
@size={{this.pageSize}}
|
||||
@page={{this.currentPage}} as |p|
|
||||
@page={{this.currentPage}}
|
||||
as |p|
|
||||
>
|
||||
<ListTable
|
||||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|
|
||||
@class="with-foot"
|
||||
as |t|
|
||||
>
|
||||
<t.head>
|
||||
<t.sort-by @prop="name">
|
||||
|
||||
@@ -2,64 +2,116 @@
|
||||
{{page-title "Task group " this.model.name " - Job " this.model.job.name}}
|
||||
<div class="tabs is-subnav">
|
||||
<ul>
|
||||
<li><LinkTo @route="jobs.job.task-group" @models={{array this.model.job this.model}} @activeClass="is-active">Overview</LinkTo></li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="jobs.job.task-group"
|
||||
@models={{array this.model.job this.model}}
|
||||
@activeClass="is-active"
|
||||
>
|
||||
Overview
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<section class="section">
|
||||
<h1 class="title with-flex">
|
||||
<span>{{this.model.name}}</span>
|
||||
<span>
|
||||
{{this.model.name}}
|
||||
</span>
|
||||
<div>
|
||||
<Exec::OpenButton @job={{this.model.job}} @taskGroup={{this.model}} />
|
||||
{{#if this.model.scaling}}
|
||||
<StepperInput
|
||||
data-test-task-group-count-stepper
|
||||
aria-label={{this.tooltipText}}
|
||||
@min={{this.model.scaling.min}}
|
||||
@max={{this.model.scaling.max}}
|
||||
@value={{this.model.count}}
|
||||
@class="is-primary is-small"
|
||||
@disabled={{or this.model.job.runningDeployment (cannot "scale job" namespace=this.model.job.namespace.name)}}
|
||||
@onChange={{action "scaleTaskGroup"}}>
|
||||
Count
|
||||
</StepperInput>
|
||||
<StepperInput
|
||||
data-test-task-group-count-stepper
|
||||
aria-label={{this.tooltipText}}
|
||||
@min={{this.model.scaling.min}}
|
||||
@max={{this.model.scaling.max}}
|
||||
@value={{this.model.count}}
|
||||
@class="is-primary is-small"
|
||||
@disabled={{or
|
||||
this.model.job.runningDeployment
|
||||
(cannot "scale job" namespace=this.model.job.namespace.name)
|
||||
}}
|
||||
@onChange={{action "scaleTaskGroup"}}
|
||||
>
|
||||
Count
|
||||
</StepperInput>
|
||||
{{/if}}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div class="boxed-section is-small">
|
||||
<div class="boxed-section-body inline-definitions">
|
||||
<span class="label">Task Group Details</span>
|
||||
|
||||
<span class="pair" data-test-task-group-tasks><span class="term"># Tasks</span> {{this.model.tasks.length}}</span>
|
||||
<span class="pair" data-test-task-group-cpu><span class="term">Reserved CPU</span> {{format-scheduled-hertz this.model.reservedCPU}}</span>
|
||||
<span class="label">
|
||||
Task Group Details
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-tasks>
|
||||
<span class="term">
|
||||
# Tasks
|
||||
</span>
|
||||
{{this.model.tasks.length}}
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-cpu>
|
||||
<span class="term">
|
||||
Reserved CPU
|
||||
</span>
|
||||
{{format-scheduled-hertz this.model.reservedCPU}}
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-mem>
|
||||
<span class="term">Reserved Memory</span>
|
||||
<span class="term">
|
||||
Reserved Memory
|
||||
</span>
|
||||
{{format-scheduled-bytes this.model.reservedMemory start="MiB"}}
|
||||
{{#if (gt this.model.reservedMemoryMax this.model.reservedMemory)}}
|
||||
({{format-scheduled-bytes this.model.reservedMemoryMax start="MiB"}} Max)
|
||||
({{format-scheduled-bytes this.model.reservedMemoryMax start="MiB"}}Max)
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-disk><span class="term">Reserved Disk</span> {{format-scheduled-bytes this.model.reservedEphemeralDisk start="MiB"}}</span>
|
||||
{{#if this.model.scaling}}
|
||||
<span class="pair" data-test-task-group-min><span class="term">Count Range</span>
|
||||
{{this.model.scaling.min}} to {{this.model.scaling.max}}
|
||||
<span class="pair" data-test-task-group-disk>
|
||||
<span class="term">
|
||||
Reserved Disk
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-max><span class="term">Scaling Policy?</span>
|
||||
{{format-scheduled-bytes this.model.reservedEphemeralDisk start="MiB"}}
|
||||
</span>
|
||||
{{#if this.model.scaling}}
|
||||
<span class="pair" data-test-task-group-min>
|
||||
<span class="term">
|
||||
Count Range
|
||||
</span>
|
||||
{{this.model.scaling.min}}
|
||||
to
|
||||
{{this.model.scaling.max}}
|
||||
</span>
|
||||
<span class="pair" data-test-task-group-max>
|
||||
<span class="term">
|
||||
Scaling Policy?
|
||||
</span>
|
||||
{{if this.model.scaling.policy "Yes" "No"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
<div>Allocation Status <span class="badge is-white">{{this.allocations.length}}</span></div>
|
||||
<div>
|
||||
Allocation Status
|
||||
<span class="badge is-white">
|
||||
{{this.allocations.length}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxed-section-body">
|
||||
<AllocationStatusBar @allocationContainer={{this.model.summary}} @class="split-view" as |chart|>
|
||||
<AllocationStatusBar
|
||||
@allocationContainer={{this.model.summary}}
|
||||
@class="split-view" as |chart|
|
||||
>
|
||||
<ol 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"}}">
|
||||
<li
|
||||
class="{{datum.className}}
|
||||
|
||||
{{if (eq datum.label chart.activeDatum.label) "is-active"}}
|
||||
|
||||
{{if (eq datum.value 0) "is-empty"}}"
|
||||
>
|
||||
<JobPage::Parts::SummaryLegendItem @datum={{datum}} @index={{index}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
@@ -100,62 +152,102 @@
|
||||
@source={{this.sortedAllocations}}
|
||||
@size={{this.pageSize}}
|
||||
@page={{this.currentPage}}
|
||||
@class="allocations" as |p|>
|
||||
@class="allocations" as |p|
|
||||
>
|
||||
<ListTable
|
||||
@source={{p.list}}
|
||||
@sortProperty={{this.sortProperty}}
|
||||
@sortDescending={{this.sortDescending}}
|
||||
@class="with-foot" as |t|>
|
||||
@class="with-foot" as |t|
|
||||
>
|
||||
<t.head>
|
||||
<th class="is-narrow"></th>
|
||||
<t.sort-by @prop="shortId">ID</t.sort-by>
|
||||
<t.sort-by @prop="createIndex" @title="Create Index">Created</t.sort-by>
|
||||
<t.sort-by @prop="modifyIndex" @title="Modify Index">Modified</t.sort-by>
|
||||
<t.sort-by @prop="statusIndex">Status</t.sort-by>
|
||||
<t.sort-by @prop="jobVersion">Version</t.sort-by>
|
||||
<t.sort-by @prop="node.shortId">Client</t.sort-by>
|
||||
<th>Volume</th>
|
||||
<th>CPU</th>
|
||||
<th>Memory</th>
|
||||
<t.sort-by @prop="shortId">
|
||||
ID
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="createIndex" @title="Create Index">
|
||||
Created
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="modifyIndex" @title="Modify Index">
|
||||
Modified
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="statusIndex">
|
||||
Status
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="jobVersion">
|
||||
Version
|
||||
</t.sort-by>
|
||||
<t.sort-by @prop="node.shortId">
|
||||
Client
|
||||
</t.sort-by>
|
||||
<th>
|
||||
Volume
|
||||
</th>
|
||||
<th>
|
||||
CPU
|
||||
</th>
|
||||
<th>
|
||||
Memory
|
||||
</th>
|
||||
</t.head>
|
||||
<t.body @key="model.id" as |row|>
|
||||
<AllocationRow @data-test-allocation={{row.model.id}} @allocation={{row.model}} @context="taskGroup" @onClick={{action "gotoAllocation" row.model}} />
|
||||
<AllocationRow
|
||||
@data-test-allocation={{row.model.id}}
|
||||
@allocation={{row.model}}
|
||||
@context="taskGroup"
|
||||
@onClick={{action "gotoAllocation" row.model}}
|
||||
/>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
<div class="table-foot">
|
||||
<PageSizeSelect @onChange={{action this.resetPagination}} />
|
||||
<nav class="pagination">
|
||||
<div class="pagination-numbers">
|
||||
{{p.startsAt}}–{{p.endsAt}} of {{this.sortedAllocations.length}}
|
||||
{{p.startsAt}}
|
||||
–
|
||||
{{p.endsAt}}
|
||||
of
|
||||
{{this.sortedAllocations.length}}
|
||||
</div>
|
||||
<p.prev @class="pagination-previous">{{x-icon "chevron-left"}}</p.prev>
|
||||
<p.next @class="pagination-next">{{x-icon "chevron-right"}}</p.next>
|
||||
<p.prev @class="pagination-previous">
|
||||
{{x-icon "chevron-left"}}
|
||||
</p.prev>
|
||||
<p.next @class="pagination-next">
|
||||
{{x-icon "chevron-right"}}
|
||||
</p.next>
|
||||
<ul class="pagination-list"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</ListPagination>
|
||||
{{else if this.allocations.length}}
|
||||
<div class="boxed-section-body">
|
||||
<div class="empty-message" data-test-empty-allocations-list>
|
||||
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>
|
||||
No Matches
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
No allocations match the term
|
||||
<strong>
|
||||
{{this.searchTerm}}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if this.allocations.length}}
|
||||
<div class="boxed-section-body">
|
||||
<div class="empty-message" data-test-empty-allocations-list>
|
||||
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>No Matches</h3>
|
||||
<p class="empty-message-body">No allocations match the term <strong>{{this.searchTerm}}</strong></p>
|
||||
</div>
|
||||
<div class="boxed-section-body">
|
||||
<div class="empty-message" data-test-empty-allocations-list>
|
||||
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>
|
||||
No Allocations
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
No allocations have been placed.
|
||||
</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="boxed-section-body">
|
||||
<div class="empty-message" data-test-empty-allocations-list>
|
||||
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>No Allocations</h3>
|
||||
<p class="empty-message-body">No allocations have been placed.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LifecycleChart @tasks={{this.model.tasks}} />
|
||||
|
||||
{{#if this.model.scaleState.isVisible}}
|
||||
{{#if this.shouldShowScaleEventTimeline}}
|
||||
<div data-test-scaling-timeline class="boxed-section">
|
||||
@@ -167,7 +259,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div data-test-scaling-events class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Recent Scaling Events
|
||||
@@ -177,7 +268,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.model.volumes.length}}
|
||||
<div data-test-volumes class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
@@ -186,29 +276,46 @@
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
<ListTable @source={{this.model.volumes}} as |t|>
|
||||
<t.head>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Source</th>
|
||||
<th>Permissions</th>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
Source
|
||||
</th>
|
||||
<th>
|
||||
Permissions
|
||||
</th>
|
||||
</t.head>
|
||||
<t.body as |row|>
|
||||
<tr data-test-volume>
|
||||
<td data-test-volume-name>
|
||||
{{#if row.model.isCSI}}
|
||||
<LinkTo @route="csi.volumes.volume" @model={{row.model.source}} @query={{hash volumeNamespace=row.model.namespace.id}}>
|
||||
<LinkTo
|
||||
@route="csi.volumes.volume"
|
||||
@model={{concat row.model.source "@" row.model.namespace.id}}
|
||||
>
|
||||
{{row.model.name}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{row.model.name}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-volume-type>{{row.model.type}}</td>
|
||||
<td data-test-volume-source>{{row.model.source}}</td>
|
||||
<td data-test-volume-permissions>{{if row.model.readOnly "Read" "Read/Write"}}</td>
|
||||
<td data-test-volume-type>
|
||||
{{row.model.type}}
|
||||
</td>
|
||||
<td data-test-volume-source>
|
||||
{{row.model.source}}
|
||||
</td>
|
||||
<td data-test-volume-permissions>
|
||||
{{if row.model.readOnly "Read" "Read/Write"}}
|
||||
</td>
|
||||
</tr>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
</section>
|
||||
@@ -25,8 +25,8 @@ module.exports = function (environment) {
|
||||
|
||||
APP: {
|
||||
blockingQueries: true,
|
||||
mirageScenario: 'topoMedium',
|
||||
mirageWithNamespaces: false,
|
||||
mirageScenario: 'smallCluster',
|
||||
mirageWithNamespaces: true,
|
||||
mirageWithTokens: true,
|
||||
mirageWithRegions: true,
|
||||
showStorybookLink: process.env.STORYBOOK_LINK === 'true',
|
||||
|
||||
@@ -499,6 +499,7 @@ module('Acceptance | allocation detail (preemptions)', function (hooks) {
|
||||
);
|
||||
|
||||
await Allocation.visit({ id: allocation.id });
|
||||
|
||||
await Allocation.preempter.visitJob();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
|
||||
@@ -251,13 +251,16 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
|
||||
test('it passes an accessibility audit', async function (assert) {
|
||||
const namespace = server.db.namespaces.find(job.namespaceId);
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
||||
await a11yAudit(assert);
|
||||
});
|
||||
|
||||
test('when there are namespaces, the job detail page states the namespace for the job', async function (assert) {
|
||||
const namespace = server.db.namespaces.find(job.namespaceId);
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
|
||||
await JobDetail.visit({
|
||||
id: `${job.id}@${namespace.name}`,
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
JobDetail.statFor('namespace').text,
|
||||
@@ -301,7 +304,8 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
assert.notOk(JobDetail.execButton.isDisabled);
|
||||
|
||||
const secondNamespace = server.db.namespaces[1];
|
||||
await JobDetail.visit({ id: job2.id, namespace: secondNamespace.name });
|
||||
await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` });
|
||||
|
||||
assert.ok(JobDetail.execButton.isDisabled);
|
||||
});
|
||||
|
||||
@@ -322,9 +326,9 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
});
|
||||
|
||||
await JobDetail.visit({
|
||||
id: job.id,
|
||||
namespace: server.db.namespaces[1].name,
|
||||
id: `${job.id}@${server.db.namespaces[1].name}`,
|
||||
});
|
||||
|
||||
assert.notOk(JobDetail.execButton.isDisabled);
|
||||
});
|
||||
|
||||
@@ -338,14 +342,13 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
});
|
||||
|
||||
await JobDetail.visit({
|
||||
id: job.id,
|
||||
namespace: server.db.namespaces[1].name,
|
||||
id: `${job.id}@${server.db.namespaces[1].name}`,
|
||||
});
|
||||
|
||||
assert.notOk(JobDetail.metaTable, 'Meta table not present');
|
||||
|
||||
await JobDetail.visit({
|
||||
id: jobWithMeta.id,
|
||||
namespace: server.db.namespaces[1].name,
|
||||
id: `${jobWithMeta.id}@${server.db.namespaces[1].name}`,
|
||||
});
|
||||
assert.ok(JobDetail.metaTable, 'Meta table is present');
|
||||
});
|
||||
@@ -361,7 +364,8 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
await JobDetail.visit({ id: jobFromPack.id, namespace });
|
||||
await JobDetail.visit({ id: `${jobFromPack.id}@${namespace}` });
|
||||
|
||||
assert.ok(JobDetail.packTag, 'Pack tag is present');
|
||||
assert.equal(
|
||||
JobDetail.packStatFor('name').text,
|
||||
@@ -388,8 +392,7 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
|
||||
window.localStorage.nomadTokenSecret = managementToken.secretId;
|
||||
await JobDetail.visit({
|
||||
id: job.id,
|
||||
namespace: server.db.namespaces[1].name,
|
||||
id: `${job.id}@${server.db.namespaces[1].name}`,
|
||||
});
|
||||
|
||||
const groupsWithRecommendations = job.taskGroups.filter((group) =>
|
||||
@@ -439,8 +442,7 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
test('resource recommendations are not fetched when the feature doesn’t exist', async function (assert) {
|
||||
window.localStorage.nomadTokenSecret = managementToken.secretId;
|
||||
await JobDetail.visit({
|
||||
id: job.id,
|
||||
namespace: server.db.namespaces[1].name,
|
||||
id: `${job.id}@${server.db.namespaces[1].name}`,
|
||||
});
|
||||
|
||||
assert.equal(JobDetail.recommendations.length, 0);
|
||||
@@ -518,10 +520,10 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
clientToken.save();
|
||||
window.localStorage.nomadTokenSecret = clientToken.secretId;
|
||||
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
||||
assert.notOk(JobDetail.incrementButton.isDisabled);
|
||||
|
||||
await JobDetail.visit({ id: job2.id, namespace: secondNamespace.name });
|
||||
await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` });
|
||||
assert.ok(JobDetail.incrementButton.isDisabled);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,12 +54,12 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
});
|
||||
|
||||
test('it passes an accessibility audit', async function (assert) {
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
await a11yAudit(assert);
|
||||
});
|
||||
|
||||
test('the dispatch button is displayed with management token', async function (assert) {
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
||||
assert.notOk(JobDetail.dispatchButton.isDisabled);
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
clientToken.policyIds = [policy.id];
|
||||
clientToken.save();
|
||||
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
||||
assert.notOk(JobDetail.dispatchButton.isDisabled);
|
||||
|
||||
// Reset clientToken policies.
|
||||
@@ -93,12 +93,12 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
test('the dispatch button is disabled when not allowed', async function (assert) {
|
||||
window.localStorage.nomadTokenSecret = clientToken.secretId;
|
||||
|
||||
await JobDetail.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
||||
assert.ok(JobDetail.dispatchButton.isDisabled);
|
||||
});
|
||||
|
||||
test('all meta fields are displayed', async function (assert) {
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
assert.equal(
|
||||
JobDispatch.metaFields.length,
|
||||
job.parameterizedJob.MetaOptional.length +
|
||||
@@ -107,7 +107,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
});
|
||||
|
||||
test('required meta fields are properly indicated', async function (assert) {
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
|
||||
JobDispatch.metaFields.forEach((f) => {
|
||||
const hasIndicator = f.label.includes(REQUIRED_INDICATOR);
|
||||
@@ -136,10 +136,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
},
|
||||
});
|
||||
|
||||
await JobDispatch.visit({
|
||||
id: jobWithoutMeta.id,
|
||||
namespace: namespace.name,
|
||||
});
|
||||
await JobDispatch.visit({ id: `${jobWithoutMeta.id}@${namespace.name}` });
|
||||
assert.ok(JobDispatch.dispatchButton.isPresent);
|
||||
});
|
||||
|
||||
@@ -147,7 +144,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
job.parameterizedJob.Payload = 'forbidden';
|
||||
job.save();
|
||||
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
|
||||
assert.ok(JobDispatch.payload.emptyMessage.isPresent);
|
||||
assert.notOk(JobDispatch.payload.editor.isPresent);
|
||||
@@ -170,8 +167,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
});
|
||||
|
||||
await JobDispatch.visit({
|
||||
id: jobPayloadRequired.id,
|
||||
namespace: namespace.name,
|
||||
id: `${jobPayloadRequired.id}@${namespace.name}`,
|
||||
});
|
||||
|
||||
let payloadTitle = JobDispatch.payload.title;
|
||||
@@ -181,8 +177,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
);
|
||||
|
||||
await JobDispatch.visit({
|
||||
id: jobPayloadOptional.id,
|
||||
namespace: namespace.name,
|
||||
id: `${jobPayloadOptional.id}@${namespace.name}`,
|
||||
});
|
||||
|
||||
payloadTitle = JobDispatch.payload.title;
|
||||
@@ -199,7 +194,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
).length;
|
||||
}
|
||||
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
|
||||
// Fill form.
|
||||
JobDispatch.metaFields.map((f) => f.field.input('meta value'));
|
||||
@@ -222,7 +217,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
job.parameterizedJob.Payload = 'forbidden';
|
||||
job.save();
|
||||
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
|
||||
// Fill only optional meta params.
|
||||
JobDispatch.optionalMetaFields.map((f) => f.field.input('meta value'));
|
||||
@@ -237,7 +232,7 @@ function moduleForJobDispatch(title, jobFactory) {
|
||||
job.parameterizedJob.Payload = 'required';
|
||||
job.save();
|
||||
|
||||
await JobDispatch.visit({ id: job.id, namespace: namespace.name });
|
||||
await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
|
||||
await JobDispatch.dispatchButton.click();
|
||||
|
||||
assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
|
||||
|
||||
@@ -30,7 +30,7 @@ module('Acceptance | job versions', function (hooks) {
|
||||
const managementToken = server.create('token');
|
||||
window.localStorage.nomadTokenSecret = managementToken.secretId;
|
||||
|
||||
await Versions.visit({ id: job.id, namespace: namespace.id });
|
||||
await Versions.visit({ id: `${job.id}@${namespace.id}` });
|
||||
});
|
||||
|
||||
test('it passes an accessibility audit', async function (assert) {
|
||||
|
||||
@@ -375,7 +375,7 @@ module('Acceptance | task detail (different namespace)', function (hooks) {
|
||||
await Layout.breadcrumbFor('jobs.job.index').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/jobs/${job.id}?namespace=other-namespace`,
|
||||
`/jobs/${job.id}@other-namespace`,
|
||||
'Job breadcrumb links correctly'
|
||||
);
|
||||
|
||||
@@ -383,7 +383,7 @@ module('Acceptance | task detail (different namespace)', function (hooks) {
|
||||
await Layout.breadcrumbFor('jobs.job.task-group').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/jobs/${job.id}/${taskGroup}?namespace=other-namespace`,
|
||||
`/jobs/${job.id}@other-namespace/${taskGroup}`,
|
||||
'Task Group breadcrumb links correctly'
|
||||
);
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ module('Acceptance | task group detail', function (hooks) {
|
||||
totalMemoryMaxAddendum = ` (${formatScheduledBytes(
|
||||
totalMemoryMax,
|
||||
'MiB'
|
||||
)} Max)`;
|
||||
)}Max)`;
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
@@ -232,25 +232,23 @@ module('Acceptance | task group detail', function (hooks) {
|
||||
window.localStorage.nomadTokenSecret = clientToken.secretId;
|
||||
|
||||
await TaskGroup.visit({
|
||||
id: job.id,
|
||||
id: `${job.id}@${SCALE_AND_WRITE_NAMESPACE}`,
|
||||
name: scalingGroup.name,
|
||||
namespace: SCALE_AND_WRITE_NAMESPACE,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/jobs/${job.id}/scaling?namespace=${SCALE_AND_WRITE_NAMESPACE}`
|
||||
decodeURIComponent(currentURL()),
|
||||
`/jobs/${job.id}@${SCALE_AND_WRITE_NAMESPACE}/scaling`
|
||||
);
|
||||
assert.notOk(TaskGroup.countStepper.increment.isDisabled);
|
||||
|
||||
await TaskGroup.visit({
|
||||
id: job2.id,
|
||||
id: `${job2.id}@${secondNamespace.name}`,
|
||||
name: scalingGroup2.name,
|
||||
namespace: secondNamespace.name,
|
||||
});
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/jobs/${job2.id}/scaling?namespace=${READ_ONLY_NAMESPACE}`
|
||||
decodeURIComponent(currentURL()),
|
||||
`/jobs/${job2.id}@${READ_ONLY_NAMESPACE}/scaling`
|
||||
);
|
||||
assert.ok(TaskGroup.countStepper.increment.isDisabled);
|
||||
});
|
||||
|
||||
@@ -34,12 +34,12 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
});
|
||||
|
||||
test('it passes an accessibility audit', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
await a11yAudit(assert);
|
||||
});
|
||||
|
||||
test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage');
|
||||
assert.equal(Layout.breadcrumbFor('csi.volumes').text, 'Volumes');
|
||||
@@ -47,14 +47,14 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
});
|
||||
|
||||
test('/csi/volumes/:id should show the volume name in the title', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.equal(document.title, `CSI Volume ${volume.name} - Nomad`);
|
||||
assert.equal(VolumeDetail.title, volume.name);
|
||||
});
|
||||
|
||||
test('/csi/volumes/:id should list additional details for the volume below the title', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.ok(
|
||||
VolumeDetail.health.includes(
|
||||
@@ -75,7 +75,7 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc));
|
||||
readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc));
|
||||
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.equal(VolumeDetail.writeAllocations.length, writeAllocations.length);
|
||||
writeAllocations
|
||||
@@ -95,7 +95,7 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc));
|
||||
readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc));
|
||||
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.equal(VolumeDetail.readAllocations.length, readAllocations.length);
|
||||
readAllocations
|
||||
@@ -126,7 +126,7 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
0
|
||||
);
|
||||
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
VolumeDetail.writeAllocations.objectAt(0).as((allocationRow) => {
|
||||
assert.equal(
|
||||
@@ -198,28 +198,28 @@ module('Acceptance | volume detail', function (hooks) {
|
||||
const allocation = server.create('allocation');
|
||||
assignWriteAlloc(volume, allocation);
|
||||
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
await VolumeDetail.writeAllocations.objectAt(0).visit();
|
||||
|
||||
assert.equal(currentURL(), `/allocations/${allocation.id}`);
|
||||
});
|
||||
|
||||
test('when there are no write allocations, the table presents an empty state', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.ok(VolumeDetail.writeTableIsEmpty);
|
||||
assert.equal(VolumeDetail.writeEmptyState.headline, 'No Write Allocations');
|
||||
});
|
||||
|
||||
test('when there are no read allocations, the table presents an empty state', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.ok(VolumeDetail.readTableIsEmpty);
|
||||
assert.equal(VolumeDetail.readEmptyState.headline, 'No Read Allocations');
|
||||
});
|
||||
|
||||
test('the constraints table shows access mode and attachment mode', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@default` });
|
||||
|
||||
assert.equal(VolumeDetail.constraints.accessMode, volume.accessMode);
|
||||
assert.equal(
|
||||
@@ -244,7 +244,7 @@ module('Acceptance | volume detail (with namespaces)', function (hooks) {
|
||||
});
|
||||
|
||||
test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function (assert) {
|
||||
await VolumeDetail.visit({ id: volume.id, namespace: volume.namespaceId });
|
||||
await VolumeDetail.visit({ id: `${volume.id}@${volume.namespaceId}` });
|
||||
|
||||
assert.ok(VolumeDetail.hasNamespace);
|
||||
assert.ok(VolumeDetail.namespace.includes(volume.namespaceId || 'default'));
|
||||
|
||||
@@ -79,7 +79,7 @@ module('Acceptance | volumes list', function (hooks) {
|
||||
const isHealthy = healthy > 0;
|
||||
controllerHealthStr = `${
|
||||
isHealthy ? 'Healthy' : 'Unhealthy'
|
||||
} (${healthy}/${expected})`;
|
||||
} ( ${healthy} / ${expected} )`;
|
||||
}
|
||||
|
||||
const nodeHealthStr = volume.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy';
|
||||
@@ -93,7 +93,7 @@ module('Acceptance | volumes list', function (hooks) {
|
||||
assert.equal(volumeRow.controllerHealth, controllerHealthStr);
|
||||
assert.equal(
|
||||
volumeRow.nodeHealth,
|
||||
`${nodeHealthStr} (${volume.nodesHealthy}/${volume.nodesExpected})`
|
||||
`${nodeHealthStr} ( ${volume.nodesHealthy} / ${volume.nodesExpected} )`
|
||||
);
|
||||
assert.equal(volumeRow.provider, volume.provider);
|
||||
assert.equal(volumeRow.allocations, readAllocs.length + writeAllocs.length);
|
||||
@@ -110,7 +110,7 @@ module('Acceptance | volumes list', function (hooks) {
|
||||
await VolumesList.volumes.objectAt(0).clickName();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/csi/volumes/${volume.id}?namespace=${secondNamespace.id}`
|
||||
`/csi/volumes/${volume.id}@${secondNamespace.id}`
|
||||
);
|
||||
|
||||
await VolumesList.visit({ namespace: '*' });
|
||||
@@ -119,7 +119,7 @@ module('Acceptance | volumes list', function (hooks) {
|
||||
await VolumesList.volumes.objectAt(0).clickRow();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/csi/volumes/${volume.id}?namespace=${secondNamespace.id}`
|
||||
`/csi/volumes/${volume.id}@${secondNamespace.id}`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function moduleForJob(
|
||||
if (!job.namespace || job.namespace === 'default') {
|
||||
await JobDetail.visit({ id: job.id });
|
||||
} else {
|
||||
await JobDetail.visit({ id: job.id, namespace: job.namespace });
|
||||
await JobDetail.visit({ id: `${job.id}@${job.namespace}` });
|
||||
}
|
||||
|
||||
const hasClientStatus = ['system', 'sysbatch'].includes(job.type);
|
||||
@@ -51,52 +51,52 @@ export default function moduleForJob(
|
||||
});
|
||||
|
||||
test('visiting /jobs/:job_id', async function (assert) {
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)
|
||||
);
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}`
|
||||
: `/jobs/${job.name}`;
|
||||
|
||||
assert.equal(decodeURIComponent(currentURL()), expectedURL);
|
||||
assert.equal(document.title, `Job ${job.name} - Nomad`);
|
||||
});
|
||||
|
||||
test('the subnav links to overview', async function (assert) {
|
||||
await JobDetail.tabFor('overview').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)
|
||||
);
|
||||
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}`
|
||||
: `/jobs/${job.name}`;
|
||||
|
||||
assert.equal(decodeURIComponent(currentURL()), expectedURL);
|
||||
});
|
||||
|
||||
test('the subnav links to definition', async function (assert) {
|
||||
await JobDetail.tabFor('definition').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(
|
||||
`/jobs/${encodeURIComponent(job.id)}/definition`,
|
||||
job.namespace
|
||||
)
|
||||
);
|
||||
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}/definition`
|
||||
: `/jobs/${job.name}/definition`;
|
||||
|
||||
assert.equal(decodeURIComponent(currentURL()), expectedURL);
|
||||
});
|
||||
|
||||
test('the subnav links to versions', async function (assert) {
|
||||
await JobDetail.tabFor('versions').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(
|
||||
`/jobs/${encodeURIComponent(job.id)}/versions`,
|
||||
job.namespace
|
||||
)
|
||||
);
|
||||
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}/versions`
|
||||
: `/jobs/${job.name}/versions`;
|
||||
|
||||
assert.equal(decodeURIComponent(currentURL()), expectedURL);
|
||||
});
|
||||
|
||||
test('the subnav links to evaluations', async function (assert) {
|
||||
await JobDetail.tabFor('evaluations').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(
|
||||
`/jobs/${encodeURIComponent(job.id)}/evaluations`,
|
||||
job.namespace
|
||||
)
|
||||
);
|
||||
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}/evaluations`
|
||||
: `/jobs/${job.name}/evaluations`;
|
||||
|
||||
assert.equal(decodeURIComponent(currentURL()), expectedURL);
|
||||
});
|
||||
|
||||
test('the title buttons are dependent on job status', async function (assert) {
|
||||
@@ -145,7 +145,7 @@ export default function moduleForJob(
|
||||
const encodedStatus = encodeURIComponent(JSON.stringify([status]));
|
||||
const expectedURL = new URL(
|
||||
urlWithNamespace(
|
||||
`/jobs/${job.name}/clients?status=${encodedStatus}`,
|
||||
`/jobs/${job.name}@default/clients?status=${encodedStatus}`,
|
||||
job.namespace
|
||||
),
|
||||
window.location
|
||||
@@ -248,13 +248,12 @@ export function moduleForJobWithClientStatus(
|
||||
|
||||
test('the subnav links to clients', async function (assert) {
|
||||
await JobDetail.tabFor('clients').visit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
urlWithNamespace(
|
||||
`/jobs/${encodeURIComponent(job.id)}/clients`,
|
||||
job.namespace
|
||||
)
|
||||
);
|
||||
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.id}@${job.namespace}/clients`
|
||||
: `/jobs/${job.id}/clients`;
|
||||
|
||||
assert.equal(currentURL(), expectedURL);
|
||||
});
|
||||
|
||||
test('job status summary is shown in the overview', async function (assert) {
|
||||
@@ -289,23 +288,12 @@ export function moduleForJobWithClientStatus(
|
||||
await slice.click();
|
||||
|
||||
const encodedStatus = encodeURIComponent(JSON.stringify([status]));
|
||||
const expectedURL = new URL(
|
||||
urlWithNamespace(
|
||||
`/jobs/${job.name}/clients?status=${encodedStatus}`,
|
||||
job.namespace
|
||||
),
|
||||
window.location
|
||||
);
|
||||
const gotURL = new URL(currentURL(), window.location);
|
||||
assert.deepEqual(gotURL.pathname, expectedURL.pathname);
|
||||
|
||||
// Sort and compare URL query params.
|
||||
gotURL.searchParams.sort();
|
||||
expectedURL.searchParams.sort();
|
||||
assert.equal(
|
||||
gotURL.searchParams.toString(),
|
||||
expectedURL.searchParams.toString()
|
||||
);
|
||||
const expectedURL = job.namespace
|
||||
? `/jobs/${job.name}@${job.namespace}/clients?status=${encodedStatus}`
|
||||
: `/jobs/${job.name}/clients?status=${encodedStatus}`;
|
||||
|
||||
assert.deepEqual(currentURL(), expectedURL, 'url is correct');
|
||||
});
|
||||
|
||||
for (var testName in additionalTests) {
|
||||
@@ -368,6 +356,6 @@ async function visitJobDetailPage({ id, namespace }) {
|
||||
if (!namespace || namespace === 'default') {
|
||||
await JobDetail.visit({ id });
|
||||
} else {
|
||||
await JobDetail.visit({ id, namespace });
|
||||
await JobDetail.visit({ id: `${id}@${namespace}` });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user