github.com/hernad/nomad@v1.6.112/ui/app/controllers/evaluations/index.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  import { getOwner } from '@ember/application';
     7  import Controller from '@ember/controller';
     8  import { action } from '@ember/object';
     9  import { tracked } from '@glimmer/tracking';
    10  import { schedule } from '@ember/runloop';
    11  import { inject as service } from '@ember/service';
    12  import { useMachine } from 'ember-statecharts';
    13  import { use } from 'ember-usable';
    14  import evaluationsMachine from '../../machines/evaluations';
    15  
    16  const ALL_NAMESPACE_WILDCARD = '*';
    17  
    18  export default class EvaluationsController extends Controller {
    19    @service store;
    20    @service userSettings;
    21  
    22    // We use statecharts here to manage complex user flows for the sidebar logic
    23    @use
    24    statechart = useMachine(evaluationsMachine).withConfig({
    25      services: {
    26        loadEvaluation: this.loadEvaluation,
    27      },
    28      actions: {
    29        updateEvaluationQueryParameter: this.updateEvaluationQueryParameter,
    30        removeCurrentEvaluationQueryParameter:
    31          this.removeCurrentEvaluationQueryParameter,
    32      },
    33      guards: {
    34        sidebarIsOpen: this._sidebarIsOpen,
    35      },
    36    });
    37  
    38    queryParams = [
    39      'nextToken',
    40      'currentEval',
    41      'pageSize',
    42      'status',
    43      { qpNamespace: 'namespace' },
    44      'type',
    45      'searchTerm',
    46    ];
    47    @tracked currentEval = null;
    48  
    49    @action
    50    _sidebarIsOpen() {
    51      return !!this.currentEval;
    52    }
    53  
    54    @action
    55    async loadEvaluation(context, { evaluation }) {
    56      let evaluationId;
    57      if (evaluation?.id) {
    58        evaluationId = evaluation.id;
    59      } else {
    60        evaluationId = this.currentEval;
    61      }
    62  
    63      return this.store.findRecord('evaluation', evaluationId, {
    64        reload: true,
    65        adapterOptions: { related: true },
    66      });
    67    }
    68  
    69    @action
    70    async handleEvaluationClick(evaluation, e) {
    71      if (
    72        e instanceof MouseEvent ||
    73        (e instanceof KeyboardEvent &&
    74          (e.code === 'Enter' || e.code === 'Space')) ||
    75        !e
    76      ) {
    77        this.statechart.send('LOAD_EVALUATION', { evaluation });
    78      }
    79    }
    80  
    81    @action
    82    notifyEvalChange([evaluation]) {
    83      schedule('actions', this, () => {
    84        this.statechart.send('CHANGE_EVAL', { evaluation });
    85      });
    86    }
    87  
    88    @action
    89    updateEvaluationQueryParameter(context, { evaluation }) {
    90      this.currentEval = evaluation.id;
    91    }
    92  
    93    @action
    94    removeCurrentEvaluationQueryParameter() {
    95      this.currentEval = null;
    96    }
    97  
    98    get shouldDisableNext() {
    99      return !this.model.meta?.nextToken;
   100    }
   101  
   102    get shouldDisablePrev() {
   103      return !this.previousTokens.length;
   104    }
   105  
   106    get optionsEvaluationsStatus() {
   107      return [
   108        { key: null, label: 'All' },
   109        { key: 'blocked', label: 'Blocked' },
   110        { key: 'pending', label: 'Pending' },
   111        { key: 'complete', label: 'Complete' },
   112        { key: 'failed', label: 'Failed' },
   113        { key: 'canceled', label: 'Canceled' },
   114      ];
   115    }
   116  
   117    get optionsTriggeredBy() {
   118      return [
   119        { key: null, label: 'All' },
   120        { key: 'job-register', label: 'Job Register' },
   121        { key: 'job-deregister', label: 'Job Deregister' },
   122        { key: 'periodic-job', label: 'Periodic Job' },
   123        { key: 'node-drain', label: 'Node Drain' },
   124        { key: 'node-update', label: 'Node Update' },
   125        { key: 'alloc-stop', label: 'Allocation Stop' },
   126        { key: 'scheduled', label: 'Scheduled' },
   127        { key: 'rolling-update', label: 'Rolling Update' },
   128        { key: 'deployment-watcher', label: 'Deployment Watcher' },
   129        { key: 'failed-follow-up', label: 'Failed Follow Up' },
   130        { key: 'max-disconnect-timeout', label: 'Max Disconnect Timeout' },
   131        { key: 'max-plan-attempts', label: 'Max Plan Attempts' },
   132        { key: 'alloc-failure', label: 'Allocation Failure' },
   133        { key: 'queued-allocs', label: 'Queued Allocations' },
   134        { key: 'preemption', label: 'Preemption' },
   135        { key: 'job-scaling', label: 'Job Scalling' },
   136      ];
   137    }
   138  
   139    get optionsNamespaces() {
   140      const namespaces = this.store.peekAll('namespace').map((namespace) => ({
   141        key: namespace.name,
   142        label: namespace.name,
   143      }));
   144  
   145      // Create default namespace selection
   146      namespaces.unshift({
   147        key: ALL_NAMESPACE_WILDCARD,
   148        label: 'All (*)',
   149      });
   150  
   151      return namespaces;
   152    }
   153  
   154    get optionsType() {
   155      return [
   156        { key: null, label: 'All' },
   157        { key: 'client', label: 'Client' },
   158        { key: 'no client', label: 'No Client' },
   159      ];
   160    }
   161  
   162    filters = ['status', 'qpNamespace', 'type', 'triggeredBy', 'searchTerm'];
   163  
   164    get hasFiltersApplied() {
   165      return this.filters.reduce((result, filter) => {
   166        // By default we always set qpNamespace to the '*' wildcard
   167        // We need to ensure that if namespace is the only filter, that we send the correct error message to the user
   168        if (this[filter] && filter !== 'qpNamespace') {
   169          result = true;
   170        }
   171        return result;
   172      }, false);
   173    }
   174  
   175    get currentFilters() {
   176      const result = [];
   177      for (const filter of this.filters) {
   178        const isNamespaceWildcard =
   179          filter === 'qpNamespace' && this[filter] === '*';
   180        if (this[filter] && !isNamespaceWildcard) {
   181          result.push({ [filter]: this[filter] });
   182        }
   183      }
   184      return result;
   185    }
   186  
   187    get noMatchText() {
   188      let text = '';
   189      const cleanNames = {
   190        status: 'Status',
   191        qpNamespace: 'Namespace',
   192        type: 'Type',
   193        triggeredBy: 'Triggered By',
   194        searchTerm: 'Search Term',
   195      };
   196      if (this.hasFiltersApplied) {
   197        for (let i = 0; i < this.currentFilters.length; i++) {
   198          const filter = this.currentFilters[i];
   199          const [name] = Object.keys(filter);
   200          const filterName = cleanNames[name];
   201          const filterValue = filter[name];
   202          if (this.currentFilters.length === 1)
   203            return `${filterName}: ${filterValue}.`;
   204          if (i !== 0 && i !== this.currentFilters.length - 1)
   205            text = text.concat(`, ${filterName}: ${filterValue}`);
   206          if (i === 0) text = text.concat(`${filterName}: ${filterValue}`);
   207          if (i === this.currentFilters.length - 1) {
   208            return text.concat(`, ${filterName}: ${filterValue}.`);
   209          }
   210        }
   211      }
   212  
   213      return text;
   214    }
   215  
   216    @tracked pageSize = this.userSettings.pageSize;
   217    @tracked nextToken = null;
   218    @tracked previousTokens = [];
   219    @tracked status = null;
   220    @tracked triggeredBy = null;
   221    @tracked qpNamespace = ALL_NAMESPACE_WILDCARD;
   222    @tracked type = null;
   223    @tracked searchTerm = null;
   224  
   225    @action
   226    onChange(newPageSize) {
   227      this.pageSize = newPageSize;
   228    }
   229  
   230    @action
   231    onNext(nextToken) {
   232      this.previousTokens = [...this.previousTokens, this.nextToken];
   233      this.nextToken = nextToken;
   234    }
   235  
   236    @action
   237    onPrev() {
   238      const lastToken = this.previousTokens.pop();
   239      this.previousTokens = [...this.previousTokens];
   240      this.nextToken = lastToken;
   241    }
   242  
   243    @action
   244    refresh() {
   245      const isDefaultParams = this.nextToken === null && this.status === null;
   246      if (isDefaultParams) {
   247        getOwner(this).lookup('route:evaluations.index').refresh();
   248        return;
   249      }
   250  
   251      this._resetTokens();
   252      this.status = null;
   253      this.pageSize = this.userSettings.pageSize;
   254    }
   255  
   256    @action
   257    setQueryParam(qp, selection) {
   258      this._resetTokens();
   259      this[qp] = selection;
   260    }
   261  
   262    @action
   263    toggle() {
   264      this._resetTokens();
   265      this.shouldOnlyDisplayClientEvals = !this.shouldOnlyDisplayClientEvals;
   266    }
   267  
   268    @action
   269    _resetTokens() {
   270      this.nextToken = null;
   271      this.previousTokens = [];
   272    }
   273  }