mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
* Failing test and TODO for wildcard * Alias the namespace query parameter for Evals * eval: fix list when using ACLs and * namespace Apply the same verification process as in job, allocs and scaling policy list endpoints to handle the eval list when using an ACL token with limited namespace support but querying using the `*` wildcard namespace. * changelog: add entry for #13530 * ui: set namespace when querying eval Evals have a unique UUID as ID, but when querying them the Nomad API still expects a namespace query param, otherwise it assumes `default`. Co-authored-by: Luiz Aoqui <luiz@hashicorp.com>
267 lines
7.1 KiB
JavaScript
267 lines
7.1 KiB
JavaScript
import { getOwner } from '@ember/application';
|
|
import Controller from '@ember/controller';
|
|
import { action } from '@ember/object';
|
|
import { tracked } from '@glimmer/tracking';
|
|
import { schedule } from '@ember/runloop';
|
|
import { inject as service } from '@ember/service';
|
|
import { useMachine } from 'ember-statecharts';
|
|
import { use } from 'ember-usable';
|
|
import evaluationsMachine from '../../machines/evaluations';
|
|
|
|
const ALL_NAMESPACE_WILDCARD = '*';
|
|
|
|
export default class EvaluationsController extends Controller {
|
|
@service store;
|
|
@service userSettings;
|
|
|
|
// We use statecharts here to manage complex user flows for the sidebar logic
|
|
@use
|
|
statechart = useMachine(evaluationsMachine).withConfig({
|
|
services: {
|
|
loadEvaluation: this.loadEvaluation,
|
|
},
|
|
actions: {
|
|
updateEvaluationQueryParameter: this.updateEvaluationQueryParameter,
|
|
removeCurrentEvaluationQueryParameter:
|
|
this.removeCurrentEvaluationQueryParameter,
|
|
},
|
|
guards: {
|
|
sidebarIsOpen: this._sidebarIsOpen,
|
|
},
|
|
});
|
|
|
|
queryParams = [
|
|
'nextToken',
|
|
'currentEval',
|
|
'pageSize',
|
|
'status',
|
|
{ qpNamespace: 'namespace' },
|
|
'type',
|
|
'searchTerm',
|
|
];
|
|
@tracked currentEval = null;
|
|
|
|
@action
|
|
_sidebarIsOpen() {
|
|
return !!this.currentEval;
|
|
}
|
|
|
|
@action
|
|
async loadEvaluation(context, { evaluation }) {
|
|
let evaluationId;
|
|
if (evaluation?.id) {
|
|
evaluationId = evaluation.id;
|
|
} else {
|
|
evaluationId = this.currentEval;
|
|
}
|
|
|
|
return this.store.findRecord('evaluation', evaluationId, {
|
|
reload: true,
|
|
adapterOptions: { related: true },
|
|
});
|
|
}
|
|
|
|
@action
|
|
async handleEvaluationClick(evaluation, e) {
|
|
if (
|
|
e instanceof MouseEvent ||
|
|
(e instanceof KeyboardEvent && (e.code === 'Enter' || e.code === 'Space'))
|
|
) {
|
|
this.statechart.send('LOAD_EVALUATION', { evaluation });
|
|
}
|
|
}
|
|
|
|
@action
|
|
notifyEvalChange([evaluation]) {
|
|
schedule('actions', this, () => {
|
|
this.statechart.send('CHANGE_EVAL', { evaluation });
|
|
});
|
|
}
|
|
|
|
@action
|
|
updateEvaluationQueryParameter(context, { evaluation }) {
|
|
this.currentEval = evaluation.id;
|
|
}
|
|
|
|
@action
|
|
removeCurrentEvaluationQueryParameter() {
|
|
this.currentEval = null;
|
|
}
|
|
|
|
get shouldDisableNext() {
|
|
return !this.model.meta?.nextToken;
|
|
}
|
|
|
|
get shouldDisablePrev() {
|
|
return !this.previousTokens.length;
|
|
}
|
|
|
|
get optionsEvaluationsStatus() {
|
|
return [
|
|
{ key: null, label: 'All' },
|
|
{ key: 'blocked', label: 'Blocked' },
|
|
{ key: 'pending', label: 'Pending' },
|
|
{ key: 'complete', label: 'Complete' },
|
|
{ key: 'failed', label: 'Failed' },
|
|
{ key: 'canceled', label: 'Canceled' },
|
|
];
|
|
}
|
|
|
|
get optionsTriggeredBy() {
|
|
return [
|
|
{ key: null, label: 'All' },
|
|
{ key: 'job-register', label: 'Job Register' },
|
|
{ key: 'job-deregister', label: 'Job Deregister' },
|
|
{ key: 'periodic-job', label: 'Periodic Job' },
|
|
{ key: 'node-drain', label: 'Node Drain' },
|
|
{ key: 'node-update', label: 'Node Update' },
|
|
{ key: 'alloc-stop', label: 'Allocation Stop' },
|
|
{ key: 'scheduled', label: 'Scheduled' },
|
|
{ key: 'rolling-update', label: 'Rolling Update' },
|
|
{ key: 'deployment-watcher', label: 'Deployment Watcher' },
|
|
{ key: 'failed-follow-up', label: 'Failed Follow Up' },
|
|
{ key: 'max-disconnect-timeout', label: 'Max Disconnect Timeout' },
|
|
{ key: 'max-plan-attempts', label: 'Max Plan Attempts' },
|
|
{ key: 'alloc-failure', label: 'Allocation Failure' },
|
|
{ key: 'queued-allocs', label: 'Queued Allocations' },
|
|
{ key: 'preemption', label: 'Preemption' },
|
|
{ key: 'job-scaling', label: 'Job Scalling' },
|
|
];
|
|
}
|
|
|
|
get optionsNamespaces() {
|
|
const namespaces = this.store.peekAll('namespace').map((namespace) => ({
|
|
key: namespace.name,
|
|
label: namespace.name,
|
|
}));
|
|
|
|
// Create default namespace selection
|
|
namespaces.unshift({
|
|
key: ALL_NAMESPACE_WILDCARD,
|
|
label: 'All (*)',
|
|
});
|
|
|
|
return namespaces;
|
|
}
|
|
|
|
get optionsType() {
|
|
return [
|
|
{ key: null, label: 'All' },
|
|
{ key: 'client', label: 'Client' },
|
|
{ key: 'no client', label: 'No Client' },
|
|
];
|
|
}
|
|
|
|
filters = ['status', 'qpNamespace', 'type', 'triggeredBy', 'searchTerm'];
|
|
|
|
get hasFiltersApplied() {
|
|
return this.filters.reduce((result, filter) => {
|
|
// By default we always set qpNamespace to the '*' wildcard
|
|
// We need to ensure that if namespace is the only filter, that we send the correct error message to the user
|
|
if (this[filter] && filter !== 'qpNamespace') {
|
|
result = true;
|
|
}
|
|
return result;
|
|
}, false);
|
|
}
|
|
|
|
get currentFilters() {
|
|
const result = [];
|
|
for (const filter of this.filters) {
|
|
const isNamespaceWildcard =
|
|
filter === 'qpNamespace' && this[filter] === '*';
|
|
if (this[filter] && !isNamespaceWildcard) {
|
|
result.push({ [filter]: this[filter] });
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
get noMatchText() {
|
|
let text = '';
|
|
const cleanNames = {
|
|
status: 'Status',
|
|
qpNamespace: 'Namespace',
|
|
type: 'Type',
|
|
triggeredBy: 'Triggered By',
|
|
searchTerm: 'Search Term',
|
|
};
|
|
if (this.hasFiltersApplied) {
|
|
for (let i = 0; i < this.currentFilters.length; i++) {
|
|
const filter = this.currentFilters[i];
|
|
const [name] = Object.keys(filter);
|
|
const filterName = cleanNames[name];
|
|
const filterValue = filter[name];
|
|
if (this.currentFilters.length === 1)
|
|
return `${filterName}: ${filterValue}.`;
|
|
if (i !== 0 && i !== this.currentFilters.length - 1)
|
|
text = text.concat(`, ${filterName}: ${filterValue}`);
|
|
if (i === 0) text = text.concat(`${filterName}: ${filterValue}`);
|
|
if (i === this.currentFilters.length - 1) {
|
|
return text.concat(`, ${filterName}: ${filterValue}.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
@tracked pageSize = this.userSettings.pageSize;
|
|
@tracked nextToken = null;
|
|
@tracked previousTokens = [];
|
|
@tracked status = null;
|
|
@tracked triggeredBy = null;
|
|
@tracked qpNamespace = ALL_NAMESPACE_WILDCARD;
|
|
@tracked type = null;
|
|
@tracked searchTerm = null;
|
|
|
|
@action
|
|
onChange(newPageSize) {
|
|
this.pageSize = newPageSize;
|
|
}
|
|
|
|
@action
|
|
onNext(nextToken) {
|
|
this.previousTokens = [...this.previousTokens, this.nextToken];
|
|
this.nextToken = nextToken;
|
|
}
|
|
|
|
@action
|
|
onPrev() {
|
|
const lastToken = this.previousTokens.pop();
|
|
this.previousTokens = [...this.previousTokens];
|
|
this.nextToken = lastToken;
|
|
}
|
|
|
|
@action
|
|
refresh() {
|
|
const isDefaultParams = this.nextToken === null && this.status === null;
|
|
if (isDefaultParams) {
|
|
getOwner(this).lookup('route:evaluations.index').refresh();
|
|
return;
|
|
}
|
|
|
|
this._resetTokens();
|
|
this.status = null;
|
|
this.pageSize = this.userSettings.pageSize;
|
|
}
|
|
|
|
@action
|
|
setQueryParam(qp, selection) {
|
|
this._resetTokens();
|
|
this[qp] = selection;
|
|
}
|
|
|
|
@action
|
|
toggle() {
|
|
this._resetTokens();
|
|
this.shouldOnlyDisplayClientEvals = !this.shouldOnlyDisplayClientEvals;
|
|
}
|
|
|
|
@action
|
|
_resetTokens() {
|
|
this.nextToken = null;
|
|
this.previousTokens = [];
|
|
}
|
|
}
|