mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Merge pull request #11754 from hashicorp/b-ui/fix-linter
ui: fix linter and prettier
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -89,6 +89,7 @@ rkt-*
|
||||
|
||||
# misc
|
||||
/ui/.sass-cache
|
||||
/ui/.eslintcache
|
||||
/ui/.storybook/preview-head.html
|
||||
/ui/connect.lock
|
||||
/ui/coverage/*
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
server: true,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:ember/recommended',
|
||||
],
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
@@ -21,37 +10,42 @@ module.exports = {
|
||||
legacyDecorators: true,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
'ember'
|
||||
globals: {
|
||||
server: true,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
plugins: ['ember'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:ember/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {
|
||||
indent: ['error', 2, { SwitchCase: 1 }],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single', 'avoid-escape'],
|
||||
semi: ['error', 'always'],
|
||||
'no-constant-condition': [
|
||||
'error',
|
||||
{
|
||||
checkLoops: false,
|
||||
},
|
||||
],
|
||||
'ember/classic-decorator-hooks': 'error',
|
||||
'ember/classic-decorator-no-classic-methods': 'error',
|
||||
'ember/no-get': 'off',
|
||||
'ember/no-mixins': 'off',
|
||||
'ember/no-classic-classes': 'off',
|
||||
'ember/no-computed-properties-in-native-classes': 'off',
|
||||
'ember/no-classic-components': 'off',
|
||||
'ember/no-component-lifecycle-hooks': 'off',
|
||||
'ember/require-tagless-components': 'off',
|
||||
},
|
||||
overrides: [
|
||||
// node files
|
||||
{
|
||||
files: [
|
||||
'.eslintrc.js',
|
||||
'.prettierrc.js',
|
||||
'.template-lintrc.js',
|
||||
'ember-cli-build.js',
|
||||
'testem.js',
|
||||
'blueprints/*/index.js',
|
||||
'config/**/*.js',
|
||||
'server/**/*.js',
|
||||
'lib/*/index.js',
|
||||
'server/**/*.js',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
@@ -61,14 +55,15 @@ module.exports = {
|
||||
node: true,
|
||||
},
|
||||
plugins: ['node'],
|
||||
extends: ['plugin:node/recommended'],
|
||||
rules: {
|
||||
'node/no-unpublished-require': 'off'
|
||||
// this can be removed once the following is fixed
|
||||
// https://github.com/mysticatea/eslint-plugin-node/issues/77
|
||||
'node/no-unpublished-require': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'stories/**/*.js'
|
||||
],
|
||||
files: ['stories/**/*.js'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
21
ui/.prettierignore
Normal file
21
ui/.prettierignore
Normal file
@@ -0,0 +1,21 @@
|
||||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
/vendor/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
/tmp/
|
||||
|
||||
# dependencies
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
.eslintcache
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
||||
/bower.json.ember-try
|
||||
/package.json.ember-try
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.hbs",
|
||||
|
||||
5
ui/.prettierrc.js
Normal file
5
ui/.prettierrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
};
|
||||
@@ -14,7 +14,11 @@ module.exports = {
|
||||
shippedProposals: true,
|
||||
useBuiltIns: 'usage',
|
||||
corejs: '3',
|
||||
targets: ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'],
|
||||
targets: [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Safari versions',
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -27,7 +31,10 @@ module.exports = {
|
||||
],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
|
||||
[
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
'babel-plugin-macros',
|
||||
['emotion', { sourceMap: true, autoLabel: true }],
|
||||
[
|
||||
|
||||
@@ -10,7 +10,7 @@ addParameters({
|
||||
},
|
||||
});
|
||||
|
||||
addDecorator(storyFn => {
|
||||
addDecorator((storyFn) => {
|
||||
let { template, context } = storyFn();
|
||||
|
||||
let wrapperElementStyle = {
|
||||
|
||||
@@ -18,7 +18,8 @@ export default create({
|
||||
|
||||
// Typography
|
||||
// From variables.scss
|
||||
fontBase: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
|
||||
fontBase:
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
|
||||
// From Bulma
|
||||
fontCode: 'monospace',
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-env node */
|
||||
module.exports = function({ config }) {
|
||||
module.exports = function ({ config }) {
|
||||
config.module.rules.push({
|
||||
test: /\.stories\.jsx?$/,
|
||||
loaders: [require.resolve('@storybook/source-loader')],
|
||||
|
||||
@@ -26,17 +26,26 @@ export default class Abstract extends Ability {
|
||||
get rulesForNamespace() {
|
||||
let namespace = this._namespace;
|
||||
|
||||
return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => {
|
||||
let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || [];
|
||||
return (this.get('token.selfTokenPolicies') || [])
|
||||
.toArray()
|
||||
.reduce((rules, policy) => {
|
||||
let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || [];
|
||||
|
||||
let matchingNamespace = this._findMatchingNamespace(policyNamespaces, namespace);
|
||||
let matchingNamespace = this._findMatchingNamespace(
|
||||
policyNamespaces,
|
||||
namespace
|
||||
);
|
||||
|
||||
if (matchingNamespace) {
|
||||
rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace));
|
||||
}
|
||||
if (matchingNamespace) {
|
||||
rules.push(
|
||||
policyNamespaces.find(
|
||||
(namespace) => namespace.Name === matchingNamespace
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return rules;
|
||||
}, []);
|
||||
return rules;
|
||||
}, []);
|
||||
}
|
||||
|
||||
@computed('token.selfTokenPolicies.[]')
|
||||
@@ -44,15 +53,17 @@ export default class Abstract extends Ability {
|
||||
return (this.get('token.selfTokenPolicies') || [])
|
||||
.toArray()
|
||||
.reduce((allCapabilities, policy) => {
|
||||
(get(policy, 'rulesJSON.Namespaces') || []).forEach(({ Capabilities }) => {
|
||||
allCapabilities = allCapabilities.concat(Capabilities);
|
||||
});
|
||||
(get(policy, 'rulesJSON.Namespaces') || []).forEach(
|
||||
({ Capabilities }) => {
|
||||
allCapabilities = allCapabilities.concat(Capabilities);
|
||||
}
|
||||
);
|
||||
return allCapabilities;
|
||||
}, []);
|
||||
}
|
||||
|
||||
namespaceIncludesCapability(capability) {
|
||||
return this.rulesForNamespace.some(rules => {
|
||||
return this.rulesForNamespace.some((rules) => {
|
||||
let capabilities = get(rules, 'Capabilities') || [];
|
||||
return capabilities.includes(capability);
|
||||
});
|
||||
@@ -76,12 +87,16 @@ export default class Abstract extends Ability {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
let globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*'));
|
||||
let globNamespaceNames = namespaceNames.filter((namespaceName) =>
|
||||
namespaceName.includes('*')
|
||||
);
|
||||
|
||||
let matchingNamespaceName = globNamespaceNames.reduce(
|
||||
(mostMatching, namespaceName) => {
|
||||
// Convert * wildcards to .* for regex matching
|
||||
let namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*'));
|
||||
let namespaceNameRegExp = new RegExp(
|
||||
namespaceName.replace(/\*/g, '.*')
|
||||
);
|
||||
let characterDifference = namespace.length - namespaceName.length;
|
||||
|
||||
if (
|
||||
@@ -96,7 +111,10 @@ export default class Abstract extends Ability {
|
||||
return mostMatching;
|
||||
}
|
||||
},
|
||||
{ mostMatchingNamespaceName: null, mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER }
|
||||
{
|
||||
mostMatchingNamespaceName: null,
|
||||
mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER,
|
||||
}
|
||||
).mostMatchingNamespaceName;
|
||||
|
||||
if (matchingNamespaceName) {
|
||||
|
||||
@@ -3,16 +3,20 @@ import { computed, get } from '@ember/object';
|
||||
import { or } from '@ember/object/computed';
|
||||
|
||||
export default class Client extends AbstractAbility {
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeAgentReadOrWrite')
|
||||
@or(
|
||||
'bypassAuthorization',
|
||||
'selfTokenIsManagement',
|
||||
'policiesIncludeAgentReadOrWrite'
|
||||
)
|
||||
canRead;
|
||||
|
||||
@computed('token.selfTokenPolicies.[]')
|
||||
get policiesIncludeAgentReadOrWrite() {
|
||||
const policies = (get(this, 'token.selfTokenPolicies') || [])
|
||||
.toArray()
|
||||
.map(policy => get(policy, 'rulesJSON.Agent.Policy'))
|
||||
.map((policy) => get(policy, 'rulesJSON.Agent.Policy'))
|
||||
.compact();
|
||||
|
||||
return policies.some(policy => policy === 'read' || policy === 'write');
|
||||
return policies.some((policy) => policy === 'read' || policy === 'write');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class Allocation extends AbstractAbility {
|
||||
|
||||
@computed('rulesForNamespace.@each.capabilities')
|
||||
get policiesSupportExec() {
|
||||
return this.rulesForNamespace.some(rules => {
|
||||
return this.rulesForNamespace.some((rules) => {
|
||||
let capabilities = get(rules, 'Capabilities') || [];
|
||||
return capabilities.includes('alloc-exec');
|
||||
});
|
||||
|
||||
@@ -10,17 +10,26 @@ export default class Client extends AbstractAbility {
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeNodeRead')
|
||||
canRead;
|
||||
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeNodeWrite')
|
||||
@or(
|
||||
'bypassAuthorization',
|
||||
'selfTokenIsManagement',
|
||||
'policiesIncludeNodeWrite'
|
||||
)
|
||||
canWrite;
|
||||
|
||||
@computed('token.selfTokenPolicies.[]')
|
||||
get policiesIncludeNodeRead() {
|
||||
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), ['read', 'write']);
|
||||
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [
|
||||
'read',
|
||||
'write',
|
||||
]);
|
||||
}
|
||||
|
||||
@computed('token.selfTokenPolicies.[]')
|
||||
get policiesIncludeNodeWrite() {
|
||||
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), ['write']);
|
||||
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [
|
||||
'write',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +37,9 @@ function policiesIncludePermissions(policies = [], permissions = []) {
|
||||
// For each policy record, extract the Node policy
|
||||
const nodePolicies = policies
|
||||
.toArray()
|
||||
.map(policy => get(policy, 'rulesJSON.Node.Policy'))
|
||||
.map((policy) => get(policy, 'rulesJSON.Node.Policy'))
|
||||
.compact();
|
||||
|
||||
// Check for requested permissions
|
||||
return nodePolicies.some(policy => permissions.includes(policy));
|
||||
return nodePolicies.some((policy) => permissions.includes(policy));
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ export default class Job extends AbstractAbility {
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement')
|
||||
canListAll;
|
||||
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement', 'policiesSupportDispatching')
|
||||
@or(
|
||||
'bypassAuthorization',
|
||||
'selfTokenIsManagement',
|
||||
'policiesSupportDispatching'
|
||||
)
|
||||
canDispatch;
|
||||
|
||||
@computed('rulesForNamespace.@each.capabilities')
|
||||
|
||||
@@ -6,7 +6,11 @@ export default class Recommendation extends AbstractAbility {
|
||||
@and('dynamicApplicationSizingIsPresent', 'hasPermissions')
|
||||
canAccept;
|
||||
|
||||
@or('bypassAuthorization', 'selfTokenIsManagement', 'policiesSupportAcceptingOnAnyNamespace')
|
||||
@or(
|
||||
'bypassAuthorization',
|
||||
'selfTokenIsManagement',
|
||||
'policiesSupportAcceptingOnAnyNamespace'
|
||||
)
|
||||
hasPermissions;
|
||||
|
||||
@computed('capabilitiesForAllNamespaces.[]')
|
||||
|
||||
@@ -14,13 +14,17 @@ export default class AllocationAdapter extends Watchable {
|
||||
|
||||
ls(model, path) {
|
||||
return this.token
|
||||
.authorizedRequest(`/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}`)
|
||||
.authorizedRequest(
|
||||
`/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}`
|
||||
)
|
||||
.then(handleFSResponse);
|
||||
}
|
||||
|
||||
stat(model, path) {
|
||||
return this.token
|
||||
.authorizedRequest(`/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}`)
|
||||
.authorizedRequest(
|
||||
`/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}`
|
||||
)
|
||||
.then(handleFSResponse);
|
||||
}
|
||||
}
|
||||
@@ -39,8 +43,11 @@ async function handleFSResponse(response) {
|
||||
}
|
||||
|
||||
function adapterAction(path, verb = 'POST') {
|
||||
return function(allocation) {
|
||||
const url = addToPath(this.urlForFindRecord(allocation.id, 'allocation'), path);
|
||||
return function (allocation) {
|
||||
const url = addToPath(
|
||||
this.urlForFindRecord(allocation.id, 'allocation'),
|
||||
path
|
||||
);
|
||||
return this.ajax(url, verb);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class ApplicationAdapter extends RESTAdapter {
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return super.findAll(...arguments).catch(error => {
|
||||
return super.findAll(...arguments).catch((error) => {
|
||||
const errorCodes = codesForError(error);
|
||||
|
||||
const isNotImplemented = errorCodes.includes('501');
|
||||
@@ -66,14 +66,14 @@ export default class ApplicationAdapter extends RESTAdapter {
|
||||
// In order to remove stale records from the store, findHasMany has to unload
|
||||
// all records related to the request in question.
|
||||
findHasMany(store, snapshot, link, relationship) {
|
||||
return super.findHasMany(...arguments).then(payload => {
|
||||
return super.findHasMany(...arguments).then((payload) => {
|
||||
const relationshipType = relationship.type;
|
||||
const inverse = snapshot.record.inverseFor(relationship.key);
|
||||
if (inverse) {
|
||||
store
|
||||
.peekAll(relationshipType)
|
||||
.filter(record => record.get(`${inverse.name}.id`) === snapshot.id)
|
||||
.forEach(record => {
|
||||
.filter((record) => record.get(`${inverse.name}.id`) === snapshot.id)
|
||||
.forEach((record) => {
|
||||
removeRecord(store, record);
|
||||
});
|
||||
}
|
||||
@@ -96,6 +96,7 @@ export default class ApplicationAdapter extends RESTAdapter {
|
||||
let prefix = this.urlPrefix();
|
||||
|
||||
if (modelName) {
|
||||
/* eslint-disable-next-line ember/no-string-prototype-extensions */
|
||||
path = modelName.camelize();
|
||||
if (path) {
|
||||
url.push(path);
|
||||
@@ -124,5 +125,7 @@ export default class ApplicationAdapter extends RESTAdapter {
|
||||
}
|
||||
|
||||
function associateRegion(url, region) {
|
||||
return url.indexOf('?') !== -1 ? `${url}®ion=${region}` : `${url}?region=${region}`;
|
||||
return url.indexOf('?') !== -1
|
||||
? `${url}®ion=${region}`
|
||||
: `${url}?region=${region}`;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ export default class DeploymentAdapter extends Watchable {
|
||||
|
||||
promote(deployment) {
|
||||
const id = deployment.get('id');
|
||||
const url = urlForAction(this.urlForFindRecord(id, 'deployment'), '/promote');
|
||||
const url = urlForAction(
|
||||
this.urlForFindRecord(id, 'deployment'),
|
||||
'/promote'
|
||||
);
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
DeploymentId: id,
|
||||
|
||||
@@ -5,7 +5,10 @@ export default class JobVersionAdapter extends ApplicationAdapter {
|
||||
revertTo(jobVersion) {
|
||||
const jobAdapter = this.store.adapterFor('job');
|
||||
|
||||
const url = addToPath(jobAdapter.urlForFindRecord(jobVersion.get('job.id'), 'job'), '/revert');
|
||||
const url = addToPath(
|
||||
jobAdapter.urlForFindRecord(jobVersion.get('job.id'), 'job'),
|
||||
'/revert'
|
||||
);
|
||||
const [jobName] = JSON.parse(jobVersion.get('job.id'));
|
||||
|
||||
return this.ajax(url, 'POST', {
|
||||
|
||||
@@ -14,7 +14,10 @@ export default class JobAdapter extends WatchableNamespaceIDs {
|
||||
|
||||
forcePeriodic(job) {
|
||||
if (job.get('periodic')) {
|
||||
const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/periodic/force');
|
||||
const url = addToPath(
|
||||
this.urlForFindRecord(job.get('id'), 'job'),
|
||||
'/periodic/force'
|
||||
);
|
||||
return this.ajax(url, 'POST');
|
||||
}
|
||||
}
|
||||
@@ -44,7 +47,7 @@ export default class JobAdapter extends WatchableNamespaceIDs {
|
||||
Job: job.get('_newDefinitionJSON'),
|
||||
Diff: true,
|
||||
},
|
||||
}).then(json => {
|
||||
}).then((json) => {
|
||||
json.ID = jobId;
|
||||
store.pushPayload('job-plan', { jobPlans: [json] });
|
||||
return store.peekRecord('job-plan', jobId);
|
||||
@@ -71,7 +74,10 @@ export default class JobAdapter extends WatchableNamespaceIDs {
|
||||
}
|
||||
|
||||
scale(job, group, count, message) {
|
||||
const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/scale');
|
||||
const url = addToPath(
|
||||
this.urlForFindRecord(job.get('id'), 'job'),
|
||||
'/scale'
|
||||
);
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
Count: count,
|
||||
@@ -87,7 +93,10 @@ export default class JobAdapter extends WatchableNamespaceIDs {
|
||||
}
|
||||
|
||||
dispatch(job, meta, payload) {
|
||||
const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/dispatch');
|
||||
const url = addToPath(
|
||||
this.urlForFindRecord(job.get('id'), 'job'),
|
||||
'/dispatch'
|
||||
);
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
Payload: base64EncodeString(payload),
|
||||
|
||||
@@ -3,7 +3,7 @@ import codesForError from '../utils/codes-for-error';
|
||||
|
||||
export default class NamespaceAdapter extends Watchable {
|
||||
findRecord(store, modelClass, id) {
|
||||
return super.findRecord(...arguments).catch(error => {
|
||||
return super.findRecord(...arguments).catch((error) => {
|
||||
const errorCodes = codesForError(error);
|
||||
if (errorCodes.includes('501')) {
|
||||
return { Name: id };
|
||||
|
||||
@@ -11,7 +11,10 @@ export default class NodeAdapter extends Watchable {
|
||||
}
|
||||
|
||||
setEligibility(node, isEligible) {
|
||||
const url = addToPath(this.urlForFindRecord(node.id, 'node'), '/eligibility');
|
||||
const url = addToPath(
|
||||
this.urlForFindRecord(node.id, 'node'),
|
||||
'/eligibility'
|
||||
);
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
NodeID: node.id,
|
||||
|
||||
@@ -9,13 +9,20 @@ export default class RecommendationSummaryAdapter extends ApplicationAdapter {
|
||||
}
|
||||
|
||||
updateRecord(store, type, snapshot) {
|
||||
const url = `${super.urlForCreateRecord('recommendations', snapshot)}/apply`;
|
||||
const url = `${super.urlForCreateRecord(
|
||||
'recommendations',
|
||||
snapshot
|
||||
)}/apply`;
|
||||
|
||||
const allRecommendationIds = snapshot.hasMany('recommendations').mapBy('id');
|
||||
const excludedRecommendationIds = (snapshot.hasMany('excludedRecommendations') || []).mapBy(
|
||||
'id'
|
||||
const allRecommendationIds = snapshot
|
||||
.hasMany('recommendations')
|
||||
.mapBy('id');
|
||||
const excludedRecommendationIds = (
|
||||
snapshot.hasMany('excludedRecommendations') || []
|
||||
).mapBy('id');
|
||||
const includedRecommendationIds = allRecommendationIds.removeObjects(
|
||||
excludedRecommendationIds
|
||||
);
|
||||
const includedRecommendationIds = allRecommendationIds.removeObjects(excludedRecommendationIds);
|
||||
|
||||
const data = {
|
||||
Apply: includedRecommendationIds,
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class TokenAdapter extends ApplicationAdapter {
|
||||
namespace = namespace + '/acl';
|
||||
|
||||
findSelf() {
|
||||
return this.ajax(`${this.buildURL()}/token/self`, 'GET').then(token => {
|
||||
return this.ajax(`${this.buildURL()}/token/self`, 'GET').then((token) => {
|
||||
const store = this.store;
|
||||
store.pushPayload('token', {
|
||||
tokens: [token],
|
||||
@@ -23,15 +23,20 @@ export default class TokenAdapter extends ApplicationAdapter {
|
||||
data: {
|
||||
OneTimeSecretID: oneTimeToken,
|
||||
},
|
||||
}).then(({ Token: token }) => {
|
||||
const store = this.store;
|
||||
store.pushPayload('token', {
|
||||
tokens: [token],
|
||||
});
|
||||
})
|
||||
.then(({ Token: token }) => {
|
||||
const store = this.store;
|
||||
store.pushPayload('token', {
|
||||
tokens: [token],
|
||||
});
|
||||
|
||||
return store.peekRecord('token', store.normalize('token', token).data.id);
|
||||
}).catch(() => {
|
||||
throw new OTTExchangeError();
|
||||
});
|
||||
return store.peekRecord(
|
||||
'token',
|
||||
store.normalize('token', token).data.id
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
throw new OTTExchangeError();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ export default class WatchableNamespaceIDs extends Watchable {
|
||||
@service system;
|
||||
|
||||
findAll() {
|
||||
return super.findAll(...arguments).then(data => {
|
||||
data.forEach(record => {
|
||||
return super.findAll(...arguments).then((data) => {
|
||||
data.forEach((record) => {
|
||||
record.Namespace = 'default';
|
||||
});
|
||||
return data;
|
||||
@@ -16,8 +16,8 @@ export default class WatchableNamespaceIDs extends Watchable {
|
||||
}
|
||||
|
||||
query(store, type, { namespace }) {
|
||||
return super.query(...arguments).then(data => {
|
||||
data.forEach(record => {
|
||||
return super.query(...arguments).then((data) => {
|
||||
data.forEach((record) => {
|
||||
if (!record.Namespace) record.Namespace = namespace;
|
||||
});
|
||||
return data;
|
||||
@@ -26,7 +26,8 @@ export default class WatchableNamespaceIDs extends Watchable {
|
||||
|
||||
findRecord(store, type, id, snapshot) {
|
||||
const [, namespace] = JSON.parse(id);
|
||||
const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};
|
||||
const namespaceQuery =
|
||||
namespace && namespace !== 'default' ? { namespace } : {};
|
||||
|
||||
return super.findRecord(store, type, id, snapshot, namespaceQuery);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ export default class Watchable extends ApplicationAdapter {
|
||||
params.index = this.watchList.getIndexFor(url);
|
||||
}
|
||||
|
||||
const signal = get(snapshotRecordArray || {}, 'adapterOptions.abortController.signal');
|
||||
const signal = get(
|
||||
snapshotRecordArray || {},
|
||||
'adapterOptions.abortController.signal'
|
||||
);
|
||||
return this.ajax(url, 'GET', {
|
||||
signal,
|
||||
data: params,
|
||||
@@ -48,9 +51,18 @@ export default class Watchable extends ApplicationAdapter {
|
||||
}
|
||||
|
||||
findRecord(store, type, id, snapshot, additionalParams = {}) {
|
||||
const originalUrl = this.buildURL(type.modelName, id, snapshot, 'findRecord');
|
||||
const originalUrl = this.buildURL(
|
||||
type.modelName,
|
||||
id,
|
||||
snapshot,
|
||||
'findRecord'
|
||||
);
|
||||
let [url, params] = originalUrl.split('?');
|
||||
params = assign(queryString.parse(params) || {}, this.buildQuery(), additionalParams);
|
||||
params = assign(
|
||||
queryString.parse(params) || {},
|
||||
this.buildQuery(),
|
||||
additionalParams
|
||||
);
|
||||
|
||||
if (get(snapshot || {}, 'adapterOptions.watch')) {
|
||||
params.index = this.watchList.getIndexFor(originalUrl);
|
||||
@@ -60,7 +72,7 @@ export default class Watchable extends ApplicationAdapter {
|
||||
return this.ajax(url, 'GET', {
|
||||
signal,
|
||||
data: params,
|
||||
}).catch(error => {
|
||||
}).catch((error) => {
|
||||
if (error instanceof AbortError || error.name == 'AbortError') {
|
||||
return;
|
||||
}
|
||||
@@ -68,27 +80,43 @@ export default class Watchable extends ApplicationAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
query(store, type, query, snapshotRecordArray, options, additionalParams = {}) {
|
||||
query(
|
||||
store,
|
||||
type,
|
||||
query,
|
||||
snapshotRecordArray,
|
||||
options,
|
||||
additionalParams = {}
|
||||
) {
|
||||
const url = this.buildURL(type.modelName, null, null, 'query', query);
|
||||
let [urlPath, params] = url.split('?');
|
||||
params = assign(queryString.parse(params) || {}, this.buildQuery(), additionalParams, query);
|
||||
params = assign(
|
||||
queryString.parse(params) || {},
|
||||
this.buildQuery(),
|
||||
additionalParams,
|
||||
query
|
||||
);
|
||||
|
||||
if (get(options, 'adapterOptions.watch')) {
|
||||
// The intended query without additional blocking query params is used
|
||||
// to track the appropriate query index.
|
||||
params.index = this.watchList.getIndexFor(`${urlPath}?${queryString.stringify(query)}`);
|
||||
params.index = this.watchList.getIndexFor(
|
||||
`${urlPath}?${queryString.stringify(query)}`
|
||||
);
|
||||
}
|
||||
|
||||
const signal = get(options, 'adapterOptions.abortController.signal');
|
||||
return this.ajax(urlPath, 'GET', {
|
||||
signal,
|
||||
data: params,
|
||||
}).then(payload => {
|
||||
}).then((payload) => {
|
||||
const adapter = store.adapterFor(type.modelName);
|
||||
|
||||
// Query params may not necessarily map one-to-one to attribute names.
|
||||
// Adapters are responsible for declaring param mappings.
|
||||
const queryParamsToAttrs = Object.keys(adapter.queryParamsToAttrs || {}).map(key => ({
|
||||
const queryParamsToAttrs = Object.keys(
|
||||
adapter.queryParamsToAttrs || {}
|
||||
).map((key) => ({
|
||||
queryParam: key,
|
||||
attr: adapter.queryParamsToAttrs[key],
|
||||
}));
|
||||
@@ -97,12 +125,12 @@ export default class Watchable extends ApplicationAdapter {
|
||||
// deletes have occurred, the store won't have stale records.
|
||||
store
|
||||
.peekAll(type.modelName)
|
||||
.filter(record =>
|
||||
.filter((record) =>
|
||||
queryParamsToAttrs.some(
|
||||
mapping => get(record, mapping.attr) === query[mapping.queryParam]
|
||||
(mapping) => get(record, mapping.attr) === query[mapping.queryParam]
|
||||
)
|
||||
)
|
||||
.forEach(record => {
|
||||
.forEach((record) => {
|
||||
removeRecord(store, record);
|
||||
});
|
||||
|
||||
@@ -110,7 +138,11 @@ export default class Watchable extends ApplicationAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
reloadRelationship(model, relationshipName, options = { watch: false, abortController: null }) {
|
||||
reloadRelationship(
|
||||
model,
|
||||
relationshipName,
|
||||
options = { watch: false, abortController: null }
|
||||
) {
|
||||
const { watch, abortController } = options;
|
||||
const relationship = model.relationshipFor(relationshipName);
|
||||
if (relationship.kind !== 'belongsTo' && relationship.kind !== 'hasMany') {
|
||||
@@ -129,7 +161,7 @@ export default class Watchable extends ApplicationAdapter {
|
||||
// in the URL and in options.data
|
||||
if (url.includes('?')) {
|
||||
const paramsInUrl = queryString.parse(url.split('?')[1]);
|
||||
Object.keys(paramsInUrl).forEach(key => {
|
||||
Object.keys(paramsInUrl).forEach((key) => {
|
||||
delete params[key];
|
||||
});
|
||||
}
|
||||
@@ -138,7 +170,7 @@ export default class Watchable extends ApplicationAdapter {
|
||||
signal: abortController && abortController.signal,
|
||||
data: params,
|
||||
}).then(
|
||||
json => {
|
||||
(json) => {
|
||||
const store = this.store;
|
||||
const normalizeMethod =
|
||||
relationship.kind === 'belongsTo'
|
||||
@@ -146,10 +178,14 @@ export default class Watchable extends ApplicationAdapter {
|
||||
: 'normalizeFindHasManyResponse';
|
||||
const serializer = store.serializerFor(relationship.type);
|
||||
const modelClass = store.modelFor(relationship.type);
|
||||
const normalizedData = serializer[normalizeMethod](store, modelClass, json);
|
||||
const normalizedData = serializer[normalizeMethod](
|
||||
store,
|
||||
modelClass,
|
||||
json
|
||||
);
|
||||
store.push(normalizedData);
|
||||
},
|
||||
error => {
|
||||
(error) => {
|
||||
if (error instanceof AbortError || error.name === 'AbortError') {
|
||||
return relationship.kind === 'belongsTo' ? {} : [];
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class AgentMonitor extends Component {
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this.updateLogger();
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ export default class AgentMonitor extends Component {
|
||||
this.set(
|
||||
'logger',
|
||||
Log.create({
|
||||
logFetch: url => this.token.authorizedRequest(url),
|
||||
logFetch: (url) => this.token.authorizedRequest(url),
|
||||
params: this.monitorParams,
|
||||
url: this.monitorUrl,
|
||||
tail: currentTail,
|
||||
|
||||
@@ -33,7 +33,7 @@ export default class AllocationRow extends Component {
|
||||
if (!this.get('allocation.isRunning')) return undefined;
|
||||
|
||||
return AllocationStatsTracker.create({
|
||||
fetch: url => this.token.authorizedRequest(url),
|
||||
fetch: (url) => this.token.authorizedRequest(url),
|
||||
allocation: this.allocation,
|
||||
});
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export default class AllocationRow extends Component {
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
this.updateStatsTracker();
|
||||
}
|
||||
|
||||
@@ -61,13 +62,11 @@ export default class AllocationRow extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
do {
|
||||
if (this.stats) {
|
||||
try {
|
||||
yield this.get('stats.poll')
|
||||
.linked()
|
||||
.perform();
|
||||
yield this.get('stats.poll').linked().perform();
|
||||
this.set('statsError', false);
|
||||
} catch (error) {
|
||||
this.set('statsError', true);
|
||||
@@ -86,7 +85,9 @@ async function qualifyAllocation() {
|
||||
// Make sure the allocation is a complete record and not a partial so we
|
||||
// can show information such as preemptions and rescheduled allocation.
|
||||
if (allocation.isPartial) {
|
||||
await this.store.findRecord('allocation', allocation.id, { backgroundReload: false });
|
||||
await this.store.findRecord('allocation', allocation.id, {
|
||||
backgroundReload: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (allocation.get('job.isPending')) {
|
||||
|
||||
@@ -42,8 +42,10 @@ export default class AllocationStat extends Component {
|
||||
|
||||
@computed('metric', 'statsTracker.{reservedMemory,reservedCPU}')
|
||||
get formattedReserved() {
|
||||
if (this.metric === 'memory') return formatBytes(this.statsTracker.reservedMemory, 'MiB');
|
||||
if (this.metric === 'cpu') return formatHertz(this.statsTracker.reservedCPU, 'MHz');
|
||||
if (this.metric === 'memory')
|
||||
return formatBytes(this.statsTracker.reservedMemory, 'MiB');
|
||||
if (this.metric === 'cpu')
|
||||
return formatHertz(this.statsTracker.reservedCPU, 'MHz');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ export default class ChartPrimitiveArea extends Component {
|
||||
get colorClass() {
|
||||
if (this.args.colorClass) return this.args.colorClass;
|
||||
if (this.args.colorScale && this.args.index != null)
|
||||
return `${this.args.colorScale} ${this.args.colorScale}-${this.args.index + 1}`;
|
||||
return `${this.args.colorScale} ${this.args.colorScale}-${
|
||||
this.args.index + 1
|
||||
}`;
|
||||
return 'is-primary';
|
||||
}
|
||||
|
||||
@@ -31,9 +33,9 @@ export default class ChartPrimitiveArea extends Component {
|
||||
|
||||
const builder = line()
|
||||
.curve(d3Shape[this.curveMethod])
|
||||
.defined(d => d[yProp] != null)
|
||||
.x(d => xScale(d[xProp]))
|
||||
.y(d => yScale(d[yProp]));
|
||||
.defined((d) => d[yProp] != null)
|
||||
.x((d) => xScale(d[xProp]))
|
||||
.y((d) => yScale(d[yProp]));
|
||||
|
||||
return builder(this.args.data);
|
||||
}
|
||||
@@ -43,10 +45,10 @@ export default class ChartPrimitiveArea extends Component {
|
||||
|
||||
const builder = area()
|
||||
.curve(d3Shape[this.curveMethod])
|
||||
.defined(d => d[yProp] != null)
|
||||
.x(d => xScale(d[xProp]))
|
||||
.defined((d) => d[yProp] != null)
|
||||
.x((d) => xScale(d[xProp]))
|
||||
.y0(yScale(0))
|
||||
.y1(d => yScale(d[yProp]));
|
||||
.y1((d) => yScale(d[yProp]));
|
||||
|
||||
return builder(this.args.data);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class ChartPrimitiveVAnnotations extends Component {
|
||||
|
||||
let sortedAnnotations = annotations.sortBy(prop).reverse();
|
||||
|
||||
return sortedAnnotations.map(annotation => {
|
||||
return sortedAnnotations.map((annotation) => {
|
||||
const y = scale(annotation[prop]);
|
||||
const x = 0;
|
||||
const formattedY = format()(annotation[prop]);
|
||||
|
||||
@@ -33,7 +33,7 @@ export default class ChartPrimitiveVAnnotations extends Component {
|
||||
|
||||
let prevX = 0;
|
||||
let prevHigh = false;
|
||||
return sortedAnnotations.map(annotation => {
|
||||
return sortedAnnotations.map((annotation) => {
|
||||
const x = scale(annotation[prop]);
|
||||
if (prevX && !prevHigh && Math.abs(x - prevX) < 30) {
|
||||
prevHigh = true;
|
||||
|
||||
@@ -16,10 +16,22 @@ export default class ChildrenStatusBar extends DistributionBar {
|
||||
return [];
|
||||
}
|
||||
|
||||
const children = this.job.getProperties('pendingChildren', 'runningChildren', 'deadChildren');
|
||||
const children = this.job.getProperties(
|
||||
'pendingChildren',
|
||||
'runningChildren',
|
||||
'deadChildren'
|
||||
);
|
||||
return [
|
||||
{ label: 'Pending', value: children.pendingChildren, className: 'queued' },
|
||||
{ label: 'Running', value: children.runningChildren, className: 'running' },
|
||||
{
|
||||
label: 'Pending',
|
||||
value: children.pendingChildren,
|
||||
className: 'queued',
|
||||
},
|
||||
{
|
||||
label: 'Running',
|
||||
value: children.runningChildren,
|
||||
className: 'running',
|
||||
},
|
||||
{ label: 'Dead', value: children.deadChildren, className: 'complete' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import classic from 'ember-classic-decorator';
|
||||
@classic
|
||||
@tagName('tr')
|
||||
@classNames('client-node-row', 'is-interactive')
|
||||
export default class ClientNodeRow extends Component.extend(WithVisibilityDetection) {
|
||||
export default class ClientNodeRow extends Component.extend(
|
||||
WithVisibilityDetection
|
||||
) {
|
||||
@service store;
|
||||
|
||||
node = null;
|
||||
@@ -22,6 +24,7 @@ export default class ClientNodeRow extends Component.extend(WithVisibilityDetect
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
// Reload the node in order to get detail information
|
||||
const node = this.node;
|
||||
if (node) {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class CopyButton extends Component {
|
||||
clipboardText = null;
|
||||
state = null;
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
this.set('state', 'success');
|
||||
|
||||
yield timeout(2000);
|
||||
|
||||
@@ -4,7 +4,8 @@ import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class DasDismissedComponent extends Component {
|
||||
@localStorageProperty('nomadRecommendationDismssalUnderstood', false) explanationUnderstood;
|
||||
@localStorageProperty('nomadRecommendationDismssalUnderstood', false)
|
||||
explanationUnderstood;
|
||||
|
||||
@tracked dismissInTheFuture = false;
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ export default class DasRecommendationAccordionComponent extends Component {
|
||||
@tracked closing = false;
|
||||
@tracked animationContainerStyle = htmlSafe('');
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
this.closing = true;
|
||||
this.animationContainerStyle = htmlSafe(`height: ${this.accordionElement.clientHeight}px`);
|
||||
this.animationContainerStyle = htmlSafe(
|
||||
`height: ${this.accordionElement.clientHeight}px`
|
||||
);
|
||||
|
||||
yield timeout(10);
|
||||
|
||||
|
||||
@@ -93,35 +93,39 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
get taskToggleRows() {
|
||||
const taskNameToTaskToggles = {};
|
||||
|
||||
return this.args.summary.recommendations.reduce((taskToggleRows, recommendation) => {
|
||||
let taskToggleRow = taskNameToTaskToggles[recommendation.task.name];
|
||||
return this.args.summary.recommendations.reduce(
|
||||
(taskToggleRows, recommendation) => {
|
||||
let taskToggleRow = taskNameToTaskToggles[recommendation.task.name];
|
||||
|
||||
if (!taskToggleRow) {
|
||||
taskToggleRow = {
|
||||
recommendations: [],
|
||||
task: recommendation.task,
|
||||
if (!taskToggleRow) {
|
||||
taskToggleRow = {
|
||||
recommendations: [],
|
||||
task: recommendation.task,
|
||||
};
|
||||
|
||||
taskNameToTaskToggles[recommendation.task.name] = taskToggleRow;
|
||||
taskToggleRows.push(taskToggleRow);
|
||||
}
|
||||
|
||||
const isCpu = recommendation.resource === 'CPU';
|
||||
const rowResourceProperty = isCpu ? 'cpu' : 'memory';
|
||||
|
||||
taskToggleRow[rowResourceProperty] = {
|
||||
recommendation,
|
||||
isActive:
|
||||
!this.args.summary.excludedRecommendations.includes(recommendation),
|
||||
};
|
||||
|
||||
taskNameToTaskToggles[recommendation.task.name] = taskToggleRow;
|
||||
taskToggleRows.push(taskToggleRow);
|
||||
}
|
||||
if (isCpu) {
|
||||
taskToggleRow.recommendations.unshift(recommendation);
|
||||
} else {
|
||||
taskToggleRow.recommendations.push(recommendation);
|
||||
}
|
||||
|
||||
const isCpu = recommendation.resource === 'CPU';
|
||||
const rowResourceProperty = isCpu ? 'cpu' : 'memory';
|
||||
|
||||
taskToggleRow[rowResourceProperty] = {
|
||||
recommendation,
|
||||
isActive: !this.args.summary.excludedRecommendations.includes(recommendation),
|
||||
};
|
||||
|
||||
if (isCpu) {
|
||||
taskToggleRow.recommendations.unshift(recommendation);
|
||||
} else {
|
||||
taskToggleRow.recommendations.push(recommendation);
|
||||
}
|
||||
|
||||
return taskToggleRows;
|
||||
}, []);
|
||||
return taskToggleRows;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
get showToggleAllToggles() {
|
||||
@@ -129,23 +133,30 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
}
|
||||
|
||||
get allCpuToggleDisabled() {
|
||||
return !this.args.summary.recommendations.filterBy('resource', 'CPU').length;
|
||||
return !this.args.summary.recommendations.filterBy('resource', 'CPU')
|
||||
.length;
|
||||
}
|
||||
|
||||
get allMemoryToggleDisabled() {
|
||||
return !this.args.summary.recommendations.filterBy('resource', 'MemoryMB').length;
|
||||
return !this.args.summary.recommendations.filterBy('resource', 'MemoryMB')
|
||||
.length;
|
||||
}
|
||||
|
||||
get cannotAccept() {
|
||||
return (
|
||||
this.args.summary.excludedRecommendations.length == this.args.summary.recommendations.length
|
||||
this.args.summary.excludedRecommendations.length ==
|
||||
this.args.summary.recommendations.length
|
||||
);
|
||||
}
|
||||
|
||||
get copyButtonLink() {
|
||||
const path = this.router.urlFor('optimize.summary', this.args.summary.slug, {
|
||||
queryParams: { namespace: this.args.summary.jobNamespace },
|
||||
});
|
||||
const path = this.router.urlFor(
|
||||
'optimize.summary',
|
||||
this.args.summary.slug,
|
||||
{
|
||||
queryParams: { namespace: this.args.summary.jobNamespace },
|
||||
}
|
||||
);
|
||||
const { origin } = window.location;
|
||||
|
||||
return `${origin}${path}`;
|
||||
@@ -173,9 +184,9 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
.save()
|
||||
.then(
|
||||
() => this.onApplied.perform(),
|
||||
e => this.onError.perform(e)
|
||||
(e) => this.onError.perform(e)
|
||||
)
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
if (!didCancel(e)) {
|
||||
throw e;
|
||||
}
|
||||
@@ -185,21 +196,23 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
@action
|
||||
dismiss() {
|
||||
this.storeCardHeight();
|
||||
this.args.summary.excludedRecommendations.pushObjects(this.args.summary.recommendations);
|
||||
this.args.summary.excludedRecommendations.pushObjects(
|
||||
this.args.summary.recommendations
|
||||
);
|
||||
this.args.summary
|
||||
.save()
|
||||
.then(
|
||||
() => this.onDismissed.perform(),
|
||||
e => this.onError.perform(e)
|
||||
(e) => this.onError.perform(e)
|
||||
)
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
if (!didCancel(e)) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
this.interstitialComponent = 'accepted';
|
||||
yield timeout(Ember.testing ? 0 : 2000);
|
||||
|
||||
@@ -208,8 +221,8 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
}).drop())
|
||||
onApplied;
|
||||
|
||||
@(task(function*() {
|
||||
const { manuallyDismissed } = yield new Promise(resolve => {
|
||||
@(task(function* () {
|
||||
const { manuallyDismissed } = yield new Promise((resolve) => {
|
||||
this.proceedPromiseResolve = resolve;
|
||||
this.interstitialComponent = 'dismissed';
|
||||
});
|
||||
@@ -223,8 +236,8 @@ export default class DasRecommendationCardComponent extends Component {
|
||||
}).drop())
|
||||
onDismissed;
|
||||
|
||||
@(task(function*(error) {
|
||||
yield new Promise(resolve => {
|
||||
@(task(function* (error) {
|
||||
yield new Promise((resolve) => {
|
||||
this.proceedPromiseResolve = resolve;
|
||||
this.interstitialComponent = 'error';
|
||||
this.error = error.toString();
|
||||
|
||||
@@ -64,10 +64,12 @@ export default class RecommendationChartComponent extends Component {
|
||||
edgeTickHeight = 23;
|
||||
centerTickOffset = 6;
|
||||
|
||||
centerY = this.tickTextHeight + this.centerTickOffset + this.edgeTickHeight / 2;
|
||||
centerY =
|
||||
this.tickTextHeight + this.centerTickOffset + this.edgeTickHeight / 2;
|
||||
|
||||
edgeTickY1 = this.tickTextHeight + this.centerTickOffset;
|
||||
edgeTickY2 = this.tickTextHeight + this.edgeTickHeight + this.centerTickOffset;
|
||||
edgeTickY2 =
|
||||
this.tickTextHeight + this.edgeTickHeight + this.centerTickOffset;
|
||||
|
||||
deltaTextY = this.edgeTickY2;
|
||||
|
||||
@@ -129,7 +131,10 @@ export default class RecommendationChartComponent extends Component {
|
||||
},
|
||||
rect: {
|
||||
x: this.gutterWidthLeft,
|
||||
y: (this.edgeTickHeight - rectHeight) / 2 + this.centerTickOffset + this.tickTextHeight,
|
||||
y:
|
||||
(this.edgeTickHeight - rectHeight) / 2 +
|
||||
this.centerTickOffset +
|
||||
this.tickTextHeight,
|
||||
width: rectWidth,
|
||||
height: rectHeight,
|
||||
},
|
||||
@@ -145,7 +150,10 @@ export default class RecommendationChartComponent extends Component {
|
||||
}
|
||||
|
||||
get maximumX() {
|
||||
return Math.max(this.higherValue, get(this.args.stats, 'max') || Number.MIN_SAFE_INTEGER);
|
||||
return Math.max(
|
||||
this.higherValue,
|
||||
get(this.args.stats, 'max') || Number.MIN_SAFE_INTEGER
|
||||
);
|
||||
}
|
||||
|
||||
get lowerValue() {
|
||||
@@ -212,9 +220,13 @@ export default class RecommendationChartComponent extends Component {
|
||||
let translateX;
|
||||
|
||||
if (this.shown) {
|
||||
translateX = this.isIncrease ? this.higherValueWidth : this.lowerValueWidth;
|
||||
translateX = this.isIncrease
|
||||
? this.higherValueWidth
|
||||
: this.lowerValueWidth;
|
||||
} else {
|
||||
translateX = this.isIncrease ? this.lowerValueWidth : this.higherValueWidth;
|
||||
translateX = this.isIncrease
|
||||
? this.lowerValueWidth
|
||||
: this.higherValueWidth;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -222,7 +234,9 @@ export default class RecommendationChartComponent extends Component {
|
||||
points: `
|
||||
0,${this.center.y1}
|
||||
0,${this.center.y1 - this.deltaTriangleHeight / 2}
|
||||
${(directionXMultiplier * this.deltaTriangleHeight) / 2},${this.center.y1}
|
||||
${(directionXMultiplier * this.deltaTriangleHeight) / 2},${
|
||||
this.center.y1
|
||||
}
|
||||
0,${this.center.y1 + this.deltaTriangleHeight / 2}
|
||||
`,
|
||||
};
|
||||
@@ -236,7 +250,9 @@ export default class RecommendationChartComponent extends Component {
|
||||
},
|
||||
delta: {
|
||||
style: htmlSafe(
|
||||
`transform: translateX(${this.shown ? this.higherValueWidth : this.lowerValueWidth}px)`
|
||||
`transform: translateX(${
|
||||
this.shown ? this.higherValueWidth : this.lowerValueWidth
|
||||
}px)`
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -247,7 +263,9 @@ export default class RecommendationChartComponent extends Component {
|
||||
},
|
||||
delta: {
|
||||
style: htmlSafe(
|
||||
`transform: translateX(${this.shown ? this.lowerValueWidth : this.higherValueWidth}px)`
|
||||
`transform: translateX(${
|
||||
this.shown ? this.lowerValueWidth : this.higherValueWidth
|
||||
}px)`
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -271,7 +289,8 @@ export default class RecommendationChartComponent extends Component {
|
||||
};
|
||||
|
||||
const percentText = formatPercent(
|
||||
(this.args.recommendedValue - this.args.currentValue) / this.args.currentValue
|
||||
(this.args.recommendedValue - this.args.currentValue) /
|
||||
this.args.currentValue
|
||||
);
|
||||
|
||||
const percent = {
|
||||
@@ -316,7 +335,10 @@ export default class RecommendationChartComponent extends Component {
|
||||
};
|
||||
|
||||
return Object.keys(statsWithCurrentAndRecommended)
|
||||
.map(key => ({ label: statsKeyToLabel[key], value: statsWithCurrentAndRecommended[key] }))
|
||||
.map((key) => ({
|
||||
label: statsKeyToLabel[key],
|
||||
value: statsWithCurrentAndRecommended[key],
|
||||
}))
|
||||
.sortBy('value');
|
||||
} else {
|
||||
return [];
|
||||
|
||||
@@ -34,24 +34,24 @@ export default class DistributionBar extends Component.extend(WindowResizable) {
|
||||
const data = copy(this.data, true);
|
||||
const sum = data.mapBy('value').reduce(sumAggregate, 0);
|
||||
|
||||
return data.map(({ label, value, className, layers, legendLink, help }, index) => ({
|
||||
label,
|
||||
value,
|
||||
className,
|
||||
layers,
|
||||
legendLink,
|
||||
help,
|
||||
index,
|
||||
percent: value / sum,
|
||||
offset:
|
||||
data
|
||||
.slice(0, index)
|
||||
.mapBy('value')
|
||||
.reduce(sumAggregate, 0) / sum,
|
||||
}));
|
||||
return data.map(
|
||||
({ label, value, className, layers, legendLink, help }, index) => ({
|
||||
label,
|
||||
value,
|
||||
className,
|
||||
layers,
|
||||
legendLink,
|
||||
help,
|
||||
index,
|
||||
percent: value / sum,
|
||||
offset:
|
||||
data.slice(0, index).mapBy('value').reduce(sumAggregate, 0) / sum,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
const svg = this.element.querySelector('svg');
|
||||
const chart = d3.select(svg);
|
||||
const maskId = `dist-mask-${guidFor(this)}`;
|
||||
@@ -74,6 +74,7 @@ export default class DistributionBar extends Component.extend(WindowResizable) {
|
||||
}
|
||||
|
||||
didUpdateAttrs() {
|
||||
super.didUpdateAttrs();
|
||||
this.renderChart();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export default class DrainPopover extends Component {
|
||||
@localStorageProperty('nomadDrainOptions', {}) drainOptions;
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
// Load drain config values from local storage if availabe.
|
||||
[
|
||||
'deadlineEnabled',
|
||||
@@ -33,14 +34,14 @@ export default class DrainPopover extends Component {
|
||||
'forceDrain',
|
||||
'drainSystemJobs',
|
||||
'selectedDurationQuickOption',
|
||||
].forEach(k => {
|
||||
].forEach((k) => {
|
||||
if (k in this.drainOptions) {
|
||||
this[k] = this.drainOptions[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@overridable(function() {
|
||||
@overridable(function () {
|
||||
return this.durationQuickOptions[0];
|
||||
})
|
||||
selectedDurationQuickOption;
|
||||
@@ -72,7 +73,7 @@ export default class DrainPopover extends Component {
|
||||
return this.selectedDurationQuickOption.value;
|
||||
}
|
||||
|
||||
@task(function*(close) {
|
||||
@task(function* (close) {
|
||||
if (!this.client) return;
|
||||
const isUpdating = this.client.isDraining;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import classic from 'ember-classic-decorator';
|
||||
@classNames('terminal-container')
|
||||
export default class ExecTerminal extends Component.extend(WindowResizable) {
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
let fitAddon = new FitAddon();
|
||||
this.fitAddon = fitAddon;
|
||||
this.terminal.loadAddon(fitAddon);
|
||||
|
||||
@@ -31,7 +31,9 @@ export default class TaskGroupParent extends Component {
|
||||
|
||||
@computed('taskGroup.allocations.@each.clientStatus')
|
||||
get hasPendingAllocations() {
|
||||
return this.taskGroup.allocations.any(allocation => allocation.clientStatus === 'pending');
|
||||
return this.taskGroup.allocations.any(
|
||||
(allocation) => allocation.clientStatus === 'pending'
|
||||
);
|
||||
}
|
||||
|
||||
@mapBy('taskGroup.allocations', 'states') allocationTaskStatesRecordArrays;
|
||||
@@ -39,7 +41,10 @@ export default class TaskGroupParent extends Component {
|
||||
get allocationTaskStates() {
|
||||
const flattenRecordArrays = (accumulator, recordArray) =>
|
||||
accumulator.concat(recordArray.toArray());
|
||||
return this.allocationTaskStatesRecordArrays.reduce(flattenRecordArrays, []);
|
||||
return this.allocationTaskStatesRecordArrays.reduce(
|
||||
flattenRecordArrays,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
@filterBy('allocationTaskStates', 'isActive') activeTaskStates;
|
||||
@@ -55,12 +60,17 @@ export default class TaskGroupParent extends Component {
|
||||
)
|
||||
get tasksWithRunningStates() {
|
||||
const activeTaskStateNames = this.activeTaskStates
|
||||
.filter(taskState => {
|
||||
return taskState.task && taskState.task.taskGroup.name === this.taskGroup.name;
|
||||
.filter((taskState) => {
|
||||
return (
|
||||
taskState.task &&
|
||||
taskState.task.taskGroup.name === this.taskGroup.name
|
||||
);
|
||||
})
|
||||
.mapBy('name');
|
||||
|
||||
return this.taskGroup.tasks.filter(task => activeTaskStateNames.includes(task.name));
|
||||
return this.taskGroup.tasks.filter((task) =>
|
||||
activeTaskStateNames.includes(task.name)
|
||||
);
|
||||
}
|
||||
|
||||
taskSorting = ['name'];
|
||||
|
||||
@@ -18,7 +18,9 @@ export default class FlexMasonry extends Component {
|
||||
// There's nothing to do if there is no element
|
||||
if (!this.element) return;
|
||||
|
||||
const items = this.element.querySelectorAll(':scope > .flex-masonry-item');
|
||||
const items = this.element.querySelectorAll(
|
||||
':scope > .flex-masonry-item'
|
||||
);
|
||||
|
||||
// Clear out specified order and flex-basis values in case this was once a multi-column layout
|
||||
if (this.args.columns === 1 || !this.args.columns) {
|
||||
@@ -43,7 +45,7 @@ export default class FlexMasonry extends Component {
|
||||
const height = item.clientHeight;
|
||||
|
||||
// Pick the shortest column accounting for margins
|
||||
const column = columns[minIndex(columns, c => c.height)];
|
||||
const column = columns[minIndex(columns, (c) => c.height)];
|
||||
|
||||
// Add the new element's height to the column height
|
||||
column.height += marginTop + height + marginBottom;
|
||||
@@ -62,10 +64,12 @@ export default class FlexMasonry extends Component {
|
||||
// beteen the height of the column and the previous column, then flexbox will naturally place the first
|
||||
// item at the end of the previous column).
|
||||
columns.forEach((column, index) => {
|
||||
const nextHeight = index < columns.length - 1 ? columns[index + 1].height : 0;
|
||||
const nextHeight =
|
||||
index < columns.length - 1 ? columns[index + 1].height : 0;
|
||||
const item = column.elements.lastObject;
|
||||
if (item) {
|
||||
item.style.flexBasis = item.clientHeight + Math.max(0, nextHeight - column.height) + 'px';
|
||||
item.style.flexBasis =
|
||||
item.clientHeight + Math.max(0, nextHeight - column.height) + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -39,11 +39,18 @@ export default class Browser extends Component {
|
||||
@filterBy('directoryEntries', 'IsDir') directories;
|
||||
@filterBy('directoryEntries', 'IsDir', false) files;
|
||||
|
||||
@computed('directories', 'directoryEntries.[]', 'files', 'sortDescending', 'sortProperty')
|
||||
@computed(
|
||||
'directories',
|
||||
'directoryEntries.[]',
|
||||
'files',
|
||||
'sortDescending',
|
||||
'sortProperty'
|
||||
)
|
||||
get sortedDirectoryEntries() {
|
||||
const sortProperty = this.sortProperty;
|
||||
|
||||
const directorySortProperty = sortProperty === 'Size' ? 'Name' : sortProperty;
|
||||
const directorySortProperty =
|
||||
sortProperty === 'Size' ? 'Name' : sortProperty;
|
||||
|
||||
const sortedDirectories = this.directories.sortBy(directorySortProperty);
|
||||
const sortedFiles = this.files.sortBy(sortProperty);
|
||||
|
||||
@@ -39,7 +39,10 @@ export default class File extends Component {
|
||||
|
||||
if (contentType.startsWith('image/')) {
|
||||
return 'image';
|
||||
} else if (contentType.startsWith('text/') || contentType.startsWith('application/json')) {
|
||||
} else if (
|
||||
contentType.startsWith('text/') ||
|
||||
contentType.startsWith('application/json')
|
||||
) {
|
||||
return 'stream';
|
||||
} else {
|
||||
return 'unknown';
|
||||
@@ -108,7 +111,14 @@ export default class File extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@computed('clientTimeout', 'fileParams', 'fileUrl', 'mode', 'serverTimeout', 'useServer')
|
||||
@computed(
|
||||
'clientTimeout',
|
||||
'fileParams',
|
||||
'fileUrl',
|
||||
'mode',
|
||||
'serverTimeout',
|
||||
'useServer'
|
||||
)
|
||||
get logger() {
|
||||
// The cat and readat APIs are in plainText while the stream API is always encoded.
|
||||
const plainText = this.mode === 'head' || this.mode === 'tail';
|
||||
@@ -116,15 +126,15 @@ export default class File extends Component {
|
||||
// If the file request can't settle in one second, the client
|
||||
// must be unavailable and the server should be used instead
|
||||
const timing = this.useServer ? this.serverTimeout : this.clientTimeout;
|
||||
const logFetch = url =>
|
||||
const logFetch = (url) =>
|
||||
RSVP.race([this.token.authorizedRequest(url), timeout(timing)]).then(
|
||||
response => {
|
||||
(response) => {
|
||||
if (!response || !response.ok) {
|
||||
this.nextErrorState(response);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
error => this.nextErrorState(error)
|
||||
(error) => this.nextErrorState(error)
|
||||
);
|
||||
|
||||
return Log.create({
|
||||
|
||||
@@ -78,6 +78,7 @@ export default class GaugeChart extends Component.extend(WindowResizable) {
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this.updateDimensions();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,23 +32,28 @@ export default class GlobalSearchControl extends Component {
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
set(this, '_keyDownHandler', this.keyDownHandler.bind(this));
|
||||
document.addEventListener('keydown', this._keyDownHandler);
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
document.removeEventListener('keydown', this._keyDownHandler);
|
||||
}
|
||||
|
||||
@task(function*(string) {
|
||||
const searchResponse = yield this.token.authorizedRequest('/v1/search/fuzzy', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
Text: string,
|
||||
Context: 'all',
|
||||
Namespace: '*',
|
||||
}),
|
||||
});
|
||||
@task(function* (string) {
|
||||
const searchResponse = yield this.token.authorizedRequest(
|
||||
'/v1/search/fuzzy',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
Text: string,
|
||||
Context: 'all',
|
||||
Namespace: '*',
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const results = yield searchResponse.json();
|
||||
|
||||
@@ -93,11 +98,13 @@ export default class GlobalSearchControl extends Component {
|
||||
label: `${namespace} > ${jobId} > ${id}`,
|
||||
}));
|
||||
|
||||
const csiPluginResults = allCSIPluginResults.slice(0, MAXIMUM_RESULTS).map(({ ID: id }) => ({
|
||||
type: 'plugin',
|
||||
id,
|
||||
label: id,
|
||||
}));
|
||||
const csiPluginResults = allCSIPluginResults
|
||||
.slice(0, MAXIMUM_RESULTS)
|
||||
.map(({ ID: id }) => ({
|
||||
type: 'plugin',
|
||||
id,
|
||||
label: id,
|
||||
}));
|
||||
|
||||
const {
|
||||
jobs: jobsTruncated,
|
||||
@@ -109,11 +116,21 @@ export default class GlobalSearchControl extends Component {
|
||||
|
||||
return [
|
||||
{
|
||||
groupName: resultsGroupLabel('Jobs', jobResults, allJobResults, jobsTruncated),
|
||||
groupName: resultsGroupLabel(
|
||||
'Jobs',
|
||||
jobResults,
|
||||
allJobResults,
|
||||
jobsTruncated
|
||||
),
|
||||
options: jobResults,
|
||||
},
|
||||
{
|
||||
groupName: resultsGroupLabel('Clients', nodeResults, allNodeResults, nodesTruncated),
|
||||
groupName: resultsGroupLabel(
|
||||
'Clients',
|
||||
nodeResults,
|
||||
allNodeResults,
|
||||
nodesTruncated
|
||||
),
|
||||
options: nodeResults,
|
||||
},
|
||||
{
|
||||
@@ -189,10 +206,14 @@ export default class GlobalSearchControl extends Component {
|
||||
openOnClickOrTab(select, { target }) {
|
||||
// Bypass having to press enter to access search after clicking/tabbing
|
||||
const targetClassList = target.classList;
|
||||
const targetIsTrigger = targetClassList.contains('ember-power-select-trigger');
|
||||
const targetIsTrigger = targetClassList.contains(
|
||||
'ember-power-select-trigger'
|
||||
);
|
||||
|
||||
// Allow tabbing out of search
|
||||
const triggerIsNotActive = !targetClassList.contains('ember-power-select-trigger--active');
|
||||
const triggerIsNotActive = !targetClassList.contains(
|
||||
'ember-power-select-trigger--active'
|
||||
);
|
||||
|
||||
if (targetIsTrigger && triggerIsNotActive) {
|
||||
debounce(this, this.open, 150);
|
||||
|
||||
@@ -44,7 +44,9 @@ export default class GutterMenu extends Component {
|
||||
// an intent to reset context, but where to reset to depends on where the namespace
|
||||
// is being switched from. Jobs take precedence, but if the namespace is switched from
|
||||
// a storage-related page, context should be reset to volumes.
|
||||
const destination = this.router.currentRouteName.startsWith('csi.') ? 'csi.volumes' : 'jobs';
|
||||
const destination = this.router.currentRouteName.startsWith('csi.')
|
||||
? 'csi.volumes'
|
||||
: 'jobs';
|
||||
|
||||
this.router.transitionTo(destination, {
|
||||
queryParams: { namespace: namespace.get('id') },
|
||||
|
||||
@@ -2,5 +2,4 @@ import Component from '@ember/component';
|
||||
import { tagName } from '@ember-decorators/component';
|
||||
|
||||
@tagName('')
|
||||
export default class HamburgerMenu extends Component {
|
||||
}
|
||||
export default class HamburgerMenu extends Component {}
|
||||
|
||||
@@ -5,7 +5,11 @@ import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
@classNames('job-diff')
|
||||
@classNameBindings('isEdited:is-edited', 'isAdded:is-added', 'isDeleted:is-deleted')
|
||||
@classNameBindings(
|
||||
'isEdited:is-edited',
|
||||
'isAdded:is-added',
|
||||
'isDeleted:is-deleted'
|
||||
)
|
||||
export default class JobDiff extends Component {
|
||||
diff = null;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export default class JobDispatch extends Component {
|
||||
// Helper for mapping the params into a useable form.
|
||||
const mapper = (values, required) =>
|
||||
values.map(
|
||||
x =>
|
||||
(x) =>
|
||||
new MetaField({
|
||||
name: x,
|
||||
required,
|
||||
@@ -59,8 +59,14 @@ export default class JobDispatch extends Component {
|
||||
);
|
||||
|
||||
// Fetch the different types of parameters.
|
||||
const required = mapper(this.args.job.parameterizedDetails.MetaRequired || [], true);
|
||||
const optional = mapper(this.args.job.parameterizedDetails.MetaOptional || [], false);
|
||||
const required = mapper(
|
||||
this.args.job.parameterizedDetails.MetaRequired || [],
|
||||
true
|
||||
);
|
||||
const optional = mapper(
|
||||
this.args.job.parameterizedDetails.MetaOptional || [],
|
||||
false
|
||||
);
|
||||
|
||||
// Merge them, required before optional.
|
||||
this.metaFields = required.concat(optional);
|
||||
@@ -94,7 +100,7 @@ export default class JobDispatch extends Component {
|
||||
// Try to create the dispatch.
|
||||
try {
|
||||
let paramValues = {};
|
||||
this.metaFields.forEach(m => (paramValues[m.name] = m.value));
|
||||
this.metaFields.forEach((m) => (paramValues[m.name] = m.value));
|
||||
const dispatch = yield this.args.job.dispatch(paramValues, this.payload);
|
||||
|
||||
// Navigate to the newly created instance.
|
||||
@@ -123,7 +129,7 @@ export default class JobDispatch extends Component {
|
||||
this.resetErrors();
|
||||
|
||||
// Make sure that we have all of the meta fields that we need.
|
||||
this.metaFields.forEach(f => {
|
||||
this.metaFields.forEach((f) => {
|
||||
f.validate();
|
||||
if (f.error) {
|
||||
this.errors.pushObject(f.error);
|
||||
|
||||
@@ -25,7 +25,10 @@ export default class JobEditor extends Component {
|
||||
set context(value) {
|
||||
const allowedValues = ['new', 'edit'];
|
||||
|
||||
assert(`context must be one of: ${allowedValues.join(', ')}`, allowedValues.includes(value));
|
||||
assert(
|
||||
`context must be one of: ${allowedValues.join(', ')}`,
|
||||
allowedValues.includes(value)
|
||||
);
|
||||
|
||||
this.set('_context', value);
|
||||
}
|
||||
@@ -45,7 +48,7 @@ export default class JobEditor extends Component {
|
||||
return this.planOutput ? 'plan' : 'editor';
|
||||
}
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
this.reset();
|
||||
|
||||
try {
|
||||
@@ -68,7 +71,7 @@ export default class JobEditor extends Component {
|
||||
}).drop())
|
||||
plan;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
if (this.context === 'new') {
|
||||
yield this.job.run();
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class JobClientStatusSummary extends Component {
|
||||
|
||||
@action
|
||||
onSliceClick(ev, slice) {
|
||||
/* eslint-disable-next-line ember/no-string-prototype-extensions */
|
||||
this.gotoClients([slice.className.camelize()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class LatestDeployment extends Component {
|
||||
|
||||
isShowingDeploymentDetails = false;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.get('job.latestDeployment.content').promote();
|
||||
} catch (err) {
|
||||
@@ -25,7 +25,7 @@ export default class LatestDeployment extends Component {
|
||||
})
|
||||
promote;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.get('job.latestDeployment.content').fail();
|
||||
} catch (err) {
|
||||
|
||||
@@ -16,11 +16,8 @@ export default class RecentAllocations extends Component {
|
||||
@computed('job.allocations.@each.modifyIndex')
|
||||
get sortedAllocations() {
|
||||
return PromiseArray.create({
|
||||
promise: this.get('job.allocations').then(allocations =>
|
||||
allocations
|
||||
.sortBy('modifyIndex')
|
||||
.reverse()
|
||||
.slice(0, 5)
|
||||
promise: this.get('job.allocations').then((allocations) =>
|
||||
allocations.sortBy('modifyIndex').reverse().slice(0, 5)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class Summary extends Component {
|
||||
|
||||
@action
|
||||
onSliceClick(ev, slice) {
|
||||
/* eslint-disable-next-line ember/no-string-prototype-extensions */
|
||||
this.gotoAllocations([slice.label.camelize()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class Title extends Component {
|
||||
|
||||
handleError() {}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
const job = this.job;
|
||||
yield job.stop();
|
||||
@@ -27,7 +27,7 @@ export default class Title extends Component {
|
||||
})
|
||||
stopJob;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
const job = this.job;
|
||||
const definition = yield job.fetchRawDefinition();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class Periodic extends AbstractJobPage {
|
||||
|
||||
@action
|
||||
forceLaunch() {
|
||||
this.job.forcePeriodic().catch(err => {
|
||||
this.job.forcePeriodic().catch((err) => {
|
||||
this.set('errorMessage', {
|
||||
title: 'Could Not Force Launch',
|
||||
description: messageForError(err, 'submit jobs'),
|
||||
|
||||
@@ -2,5 +2,4 @@ import Component from '@ember/component';
|
||||
import { tagName } from '@ember-decorators/component';
|
||||
|
||||
@tagName('')
|
||||
export default class JobSubnav extends Component {
|
||||
}
|
||||
export default class JobSubnav extends Component {}
|
||||
|
||||
@@ -31,7 +31,9 @@ export default class JobVersion extends Component {
|
||||
return (
|
||||
fieldChanges(diff) +
|
||||
taskGroups.reduce(arrayOfFieldChanges, 0) +
|
||||
(taskGroups.mapBy('Tasks') || []).reduce(flatten, []).reduce(arrayOfFieldChanges, 0)
|
||||
(taskGroups.mapBy('Tasks') || [])
|
||||
.reduce(flatten, [])
|
||||
.reduce(arrayOfFieldChanges, 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,7 +47,7 @@ export default class JobVersion extends Component {
|
||||
this.toggleProperty('isOpen');
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
const versionBeforeReversion = this.get('version.job.version');
|
||||
|
||||
@@ -58,7 +60,8 @@ export default class JobVersion extends Component {
|
||||
this.handleError({
|
||||
level: 'warn',
|
||||
title: 'Reversion Had No Effect',
|
||||
description: 'Reverting to an identical older version doesn’t produce a new version',
|
||||
description:
|
||||
'Reverting to an identical older version doesn’t produce a new version',
|
||||
});
|
||||
} else {
|
||||
const job = this.get('version.job');
|
||||
@@ -79,7 +82,8 @@ export default class JobVersion extends Component {
|
||||
}
|
||||
|
||||
const flatten = (accumulator, array) => accumulator.concat(array);
|
||||
const countChanges = (total, field) => (changeTypes.includes(field.Type) ? total + 1 : total);
|
||||
const countChanges = (total, field) =>
|
||||
changeTypes.includes(field.Type) ? total + 1 : total;
|
||||
|
||||
function fieldChanges(diff) {
|
||||
return (
|
||||
|
||||
@@ -24,7 +24,9 @@ export default class JobVersionsStream extends Component {
|
||||
meta.showDate = true;
|
||||
} else {
|
||||
const previousVersion = versions.objectAt(index - 1);
|
||||
const previousStart = moment(previousVersion.get('submitTime')).startOf('day');
|
||||
const previousStart = moment(previousVersion.get('submitTime')).startOf(
|
||||
'day'
|
||||
);
|
||||
const currentStart = moment(version.get('submitTime')).startOf('day');
|
||||
if (previousStart.diff(currentStart, 'days') > 0) {
|
||||
meta.showDate = true;
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class LifecycleChart extends Component {
|
||||
mains: [],
|
||||
};
|
||||
|
||||
tasksOrStates.forEach(taskOrState => {
|
||||
tasksOrStates.forEach((taskOrState) => {
|
||||
const task = taskOrState.task || taskOrState;
|
||||
|
||||
if (task.lifecycleName) {
|
||||
@@ -31,7 +31,7 @@ export default class LifecycleChart extends Component {
|
||||
});
|
||||
|
||||
const phases = [];
|
||||
const stateActiveIterator = state => state.state === 'running';
|
||||
const stateActiveIterator = (state) => state.state === 'running';
|
||||
|
||||
if (lifecycles.mains.length < tasksOrStates.length) {
|
||||
phases.push({
|
||||
@@ -60,12 +60,12 @@ export default class LifecycleChart extends Component {
|
||||
return phases;
|
||||
}
|
||||
|
||||
@sort('taskStates', function(a, b) {
|
||||
@sort('taskStates', function (a, b) {
|
||||
return getTaskSortPrefix(a.task).localeCompare(getTaskSortPrefix(b.task));
|
||||
})
|
||||
sortedLifecycleTaskStates;
|
||||
|
||||
@sort('tasks', function(a, b) {
|
||||
@sort('tasks', function (a, b) {
|
||||
return getTaskSortPrefix(a).localeCompare(getTaskSortPrefix(b));
|
||||
})
|
||||
sortedLifecycleTasks;
|
||||
|
||||
@@ -23,11 +23,11 @@ const lerp = ([low, high], numPoints) => {
|
||||
};
|
||||
|
||||
// Round a number or an array of numbers
|
||||
const nice = val => (val instanceof Array ? val.map(nice) : Math.round(val));
|
||||
const nice = (val) => (val instanceof Array ? val.map(nice) : Math.round(val));
|
||||
|
||||
const defaultXScale = (data, yAxisOffset, xProp, timeseries) => {
|
||||
const scale = timeseries ? d3Scale.scaleTime() : d3Scale.scaleLinear();
|
||||
const domain = data.length ? d3Array.extent(data, d => d[xProp]) : [0, 1];
|
||||
const domain = data.length ? d3Array.extent(data, (d) => d[xProp]) : [0, 1];
|
||||
|
||||
scale.rangeRound([10, yAxisOffset]).domain(domain);
|
||||
|
||||
@@ -35,15 +35,12 @@ const defaultXScale = (data, yAxisOffset, xProp, timeseries) => {
|
||||
};
|
||||
|
||||
const defaultYScale = (data, xAxisOffset, yProp) => {
|
||||
let max = d3Array.max(data, d => d[yProp]) || 1;
|
||||
let max = d3Array.max(data, (d) => d[yProp]) || 1;
|
||||
if (max > 1) {
|
||||
max = nice(max);
|
||||
}
|
||||
|
||||
return d3Scale
|
||||
.scaleLinear()
|
||||
.rangeRound([xAxisOffset, 10])
|
||||
.domain([0, max]);
|
||||
return d3Scale.scaleLinear().rangeRound([xAxisOffset, 10]).domain([0, max]);
|
||||
};
|
||||
|
||||
export default class LineChart extends Component {
|
||||
@@ -95,7 +92,9 @@ export default class LineChart extends Component {
|
||||
@action
|
||||
xFormat(timeseries) {
|
||||
if (this.args.xFormat) return this.args.xFormat;
|
||||
return timeseries ? d3TimeFormat.timeFormat('%b %d, %H:%M') : d3Format.format(',');
|
||||
return timeseries
|
||||
? d3TimeFormat.timeFormat('%b %d, %H:%M')
|
||||
: d3Format.format(',');
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -134,7 +133,7 @@ export default class LineChart extends Component {
|
||||
|
||||
get xRange() {
|
||||
const { xProp, data } = this;
|
||||
const range = d3Array.extent(data, d => d[xProp]);
|
||||
const range = d3Array.extent(data, (d) => d[xProp]);
|
||||
const formatter = this.xFormat(this.args.timeseries);
|
||||
|
||||
return range.map(formatter);
|
||||
@@ -142,7 +141,7 @@ export default class LineChart extends Component {
|
||||
|
||||
get yRange() {
|
||||
const yProp = this.yProp;
|
||||
const range = d3Array.extent(this.data, d => d[yProp]);
|
||||
const range = d3Array.extent(this.data, (d) => d[yProp]);
|
||||
const formatter = this.yFormat();
|
||||
|
||||
return range.map(formatter);
|
||||
@@ -232,14 +231,14 @@ export default class LineChart extends Component {
|
||||
const updateActiveDatum = this.updateActiveDatum.bind(this);
|
||||
|
||||
const chart = this;
|
||||
canvas.on('mouseenter', function(ev) {
|
||||
canvas.on('mouseenter', function (ev) {
|
||||
const mouseX = d3.pointer(ev, this)[0];
|
||||
chart.latestMouseX = mouseX;
|
||||
updateActiveDatum(mouseX);
|
||||
run.schedule('afterRender', chart, () => (chart.isActive = true));
|
||||
});
|
||||
|
||||
canvas.on('mousemove', function(ev) {
|
||||
canvas.on('mousemove', function (ev) {
|
||||
const mouseX = d3.pointer(ev, this)[0];
|
||||
chart.latestMouseX = mouseX;
|
||||
updateActiveDatum(mouseX);
|
||||
@@ -264,7 +263,7 @@ export default class LineChart extends Component {
|
||||
}
|
||||
|
||||
// Map screen coordinates to data domain
|
||||
const bisector = d3Array.bisector(d => d[xProp]).left;
|
||||
const bisector = d3Array.bisector((d) => d[xProp]).left;
|
||||
const x = xScale.invert(mouseX);
|
||||
|
||||
// Find the closest datum to the cursor for each series
|
||||
@@ -308,13 +307,17 @@ export default class LineChart extends Component {
|
||||
// Of the selected data, determine which is closest
|
||||
const closestDatum = activeData
|
||||
.slice()
|
||||
.sort((a, b) => Math.abs(a.datum.datum[xProp] - x) - Math.abs(b.datum.datum[xProp] - x))[0];
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Math.abs(a.datum.datum[xProp] - x) -
|
||||
Math.abs(b.datum.datum[xProp] - x)
|
||||
)[0];
|
||||
|
||||
// If any other selected data are beyond a distance threshold, drop them from the list
|
||||
// xScale is used here to measure distance in screen-space rather than data-space.
|
||||
const dist = Math.abs(xScale(closestDatum.datum.datum[xProp]) - mouseX);
|
||||
const filteredData = activeData.filter(
|
||||
d => Math.abs(xScale(d.datum.datum[xProp]) - mouseX) < dist + 10
|
||||
(d) => Math.abs(xScale(d.datum.datum[xProp]) - mouseX) < dist + 10
|
||||
);
|
||||
|
||||
this.activeData = filteredData;
|
||||
@@ -351,7 +354,9 @@ export default class LineChart extends Component {
|
||||
if (!this.isDestroyed && !this.isDestroying) {
|
||||
d3.select(this.element.querySelector('.x-axis')).call(this.xAxis);
|
||||
d3.select(this.element.querySelector('.y-axis')).call(this.yAxis);
|
||||
d3.select(this.element.querySelector('.y-gridlines')).call(this.yGridlines);
|
||||
d3.select(this.element.querySelector('.y-gridlines')).call(
|
||||
this.yGridlines
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class ListAccordion extends Component {
|
||||
const deepKey = `item.${key}`;
|
||||
const startExpanded = this.startExpanded;
|
||||
|
||||
const decoratedSource = this.source.map(item => {
|
||||
const decoratedSource = this.source.map((item) => {
|
||||
const cacheItem = stateCache.findBy(deepKey, get(item, key));
|
||||
return {
|
||||
item,
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class ListTable extends Component {
|
||||
// Plan for a future with metadata (e.g., isSelected)
|
||||
@computed('source.[]')
|
||||
get decoratedSource() {
|
||||
return (this.source || []).map(row => ({
|
||||
return (this.source || []).map((row) => ({
|
||||
model: row,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export default class MultiSelectDropdown extends Component {
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
const dropdown = this.dropdown;
|
||||
if (this.isOpen && dropdown) {
|
||||
run.scheduleOnce('afterRender', this, this.repositionDropdown);
|
||||
@@ -59,8 +60,12 @@ export default class MultiSelectDropdown extends Component {
|
||||
dropdown.actions.open(e);
|
||||
e.preventDefault();
|
||||
} else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) {
|
||||
const optionsId = this.element.querySelector('.dropdown-trigger').getAttribute('aria-owns');
|
||||
const firstElement = document.querySelector(`#${optionsId} .dropdown-option`);
|
||||
const optionsId = this.element
|
||||
.querySelector('.dropdown-trigger')
|
||||
.getAttribute('aria-owns');
|
||||
const firstElement = document.querySelector(
|
||||
`#${optionsId} .dropdown-option`
|
||||
);
|
||||
|
||||
if (firstElement) {
|
||||
firstElement.focus();
|
||||
|
||||
@@ -32,6 +32,7 @@ export default class PopoverMenu extends Component {
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
const dropdown = this.dropdown;
|
||||
if (this.isOpen && dropdown) {
|
||||
run.scheduleOnce('afterRender', this, this.repositionDropdown);
|
||||
@@ -48,7 +49,9 @@ export default class PopoverMenu extends Component {
|
||||
dropdown.actions.open(e);
|
||||
e.preventDefault();
|
||||
} else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) {
|
||||
const optionsId = this.element.querySelector('.popover-trigger').getAttribute('aria-owns');
|
||||
const optionsId = this.element
|
||||
.querySelector('.popover-trigger')
|
||||
.getAttribute('aria-owns');
|
||||
const popoverContentEl = document.querySelector(`#${optionsId}`);
|
||||
const firstFocusableElement = popoverContentEl.querySelector(FOCUSABLE);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class AllocationPrimaryMetric extends Component {
|
||||
@computed('tracker.tasks.[]', 'metric')
|
||||
get series() {
|
||||
const ret = this.tracker.tasks
|
||||
.map(task => ({
|
||||
.map((task) => ({
|
||||
name: task.task,
|
||||
data: task[this.metric],
|
||||
}))
|
||||
@@ -64,7 +64,7 @@ export default class AllocationPrimaryMetric extends Component {
|
||||
return 'ordinal';
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
do {
|
||||
this.tracker.poll.perform();
|
||||
yield timeout(100);
|
||||
@@ -78,6 +78,7 @@ export default class AllocationPrimaryMetric extends Component {
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.poller.cancelAll();
|
||||
this.tracker.signalPause.perform();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { task, timeout } from 'ember-concurrency';
|
||||
import { assert } from '@ember/debug';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action, get } from '@ember/object';
|
||||
import { formatScheduledBytes, formatScheduledHertz } from 'nomad-ui/utils/units';
|
||||
import {
|
||||
formatScheduledBytes,
|
||||
formatScheduledHertz,
|
||||
} from 'nomad-ui/utils/units';
|
||||
|
||||
export default class NodePrimaryMetric extends Component {
|
||||
@service('stats-trackers-registry') statsTrackersRegistry;
|
||||
@@ -64,7 +67,7 @@ export default class NodePrimaryMetric extends Component {
|
||||
return [];
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
do {
|
||||
this.tracker.poll.perform();
|
||||
yield timeout(100);
|
||||
@@ -78,6 +81,7 @@ export default class NodePrimaryMetric extends Component {
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.poller.cancelAll();
|
||||
this.tracker.signalPause.perform();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export default class TaskPrimaryMetric extends Component {
|
||||
return 'is-primary';
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
do {
|
||||
this.tracker.poll.perform();
|
||||
yield timeout(100);
|
||||
@@ -53,11 +53,14 @@ export default class TaskPrimaryMetric extends Component {
|
||||
@action
|
||||
start() {
|
||||
this.taskState = this.args.taskState;
|
||||
this.tracker = this.statsTrackersRegistry.getTracker(this.args.taskState.allocation);
|
||||
this.tracker = this.statsTrackersRegistry.getTracker(
|
||||
this.args.taskState.allocation
|
||||
);
|
||||
this.poller.perform();
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.poller.cancelAll();
|
||||
this.tracker.signalPause.perform();
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ export default class RegionSwitcher extends Component {
|
||||
|
||||
@computed('system.regions')
|
||||
get sortedRegions() {
|
||||
return this.get('system.regions')
|
||||
.toArray()
|
||||
.sort();
|
||||
return this.get('system.regions').toArray().sort();
|
||||
}
|
||||
|
||||
gotoRegion(region) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class RescheduleEventRow extends Component {
|
||||
allocationId = null;
|
||||
|
||||
// An allocation can also be provided directly
|
||||
@overridable('allocationId', function() {
|
||||
@overridable('allocationId', function () {
|
||||
if (this.allocationId) {
|
||||
return this.store.findRecord('allocation', this.allocationId);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default class ScaleEventsChart extends Component {
|
||||
}
|
||||
|
||||
get annotations() {
|
||||
return this.args.events.rejectBy('hasCount').map(ev => ({
|
||||
return this.args.events.rejectBy('hasCount').map((ev) => ({
|
||||
type: ev.error ? 'error' : 'info',
|
||||
time: ev.time,
|
||||
event: copy(ev),
|
||||
@@ -40,7 +40,10 @@ export default class ScaleEventsChart extends Component {
|
||||
}
|
||||
|
||||
toggleEvent(ev) {
|
||||
if (this.activeEvent && get(this.activeEvent, 'event.uid') === get(ev, 'event.uid')) {
|
||||
if (
|
||||
this.activeEvent &&
|
||||
get(this.activeEvent, 'event.uid') === get(ev, 'event.uid')
|
||||
) {
|
||||
this.closeEventDetails();
|
||||
} else {
|
||||
this.activeEvent = ev;
|
||||
|
||||
@@ -3,7 +3,11 @@ import { alias } from '@ember/object/computed';
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { lazyClick } from '../helpers/lazy-click';
|
||||
import { classNames, classNameBindings, tagName } from '@ember-decorators/component';
|
||||
import {
|
||||
classNames,
|
||||
classNameBindings,
|
||||
tagName,
|
||||
} from '@ember-decorators/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
@@ -38,7 +42,8 @@ export default class ServerAgentRow extends Component {
|
||||
}
|
||||
|
||||
click() {
|
||||
const transition = () => this.router.transitionTo('servers.server', this.agent);
|
||||
const transition = () =>
|
||||
this.router.transitionTo('servers.server', this.agent);
|
||||
lazyClick([transition, event]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class StatsTimeSeries extends Component {
|
||||
// Specific a11y descriptors
|
||||
get description() {
|
||||
const data = this.args.data;
|
||||
const yRange = d3Array.extent(data, d => d.percent);
|
||||
const xRange = d3Array.extent(data, d => d.timestamp);
|
||||
const yRange = d3Array.extent(data, (d) => d.percent);
|
||||
const xRange = d3Array.extent(data, (d) => d.timestamp);
|
||||
const yFormatter = this.yFormat;
|
||||
|
||||
const duration = formatDuration(xRange[1] - xRange[0], 'ms', true);
|
||||
@@ -36,19 +36,21 @@ export default class StatsTimeSeries extends Component {
|
||||
xScale(data, yAxisOffset) {
|
||||
const scale = scaleTime();
|
||||
|
||||
const [low, high] = d3Array.extent(data, d => d.timestamp);
|
||||
const minLow = moment(high)
|
||||
.subtract(5, 'minutes')
|
||||
.toDate();
|
||||
const [low, high] = d3Array.extent(data, (d) => d.timestamp);
|
||||
const minLow = moment(high).subtract(5, 'minutes').toDate();
|
||||
|
||||
const extent = data.length ? [Math.min(low, minLow), high] : [minLow, new Date()];
|
||||
const extent = data.length
|
||||
? [Math.min(low, minLow), high]
|
||||
: [minLow, new Date()];
|
||||
scale.rangeRound([10, yAxisOffset]).domain(extent);
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
yScale(data, xAxisOffset) {
|
||||
const yValues = (data || []).mapBy(this.args.dataProp ? 'percentStack' : 'percent');
|
||||
const yValues = (data || []).mapBy(
|
||||
this.args.dataProp ? 'percentStack' : 'percent'
|
||||
);
|
||||
|
||||
let [low, high] = [0, 1];
|
||||
if (yValues.compact().length) {
|
||||
|
||||
@@ -9,7 +9,12 @@ const ESC = 27;
|
||||
|
||||
@classic
|
||||
@classNames('stepper-input')
|
||||
@classNameBindings('class', 'disabled:is-disabled', 'disabled:tooltip', 'disabled:multiline')
|
||||
@classNameBindings(
|
||||
'class',
|
||||
'disabled:is-disabled',
|
||||
'disabled:tooltip',
|
||||
'disabled:multiline'
|
||||
)
|
||||
export default class StepperInput extends Component {
|
||||
min = 0;
|
||||
max = 10;
|
||||
@@ -41,7 +46,9 @@ export default class StepperInput extends Component {
|
||||
@action
|
||||
setValue(e) {
|
||||
if (e.target.value !== '') {
|
||||
const newValue = Math.floor(Math.min(this.max, Math.max(this.min, e.target.value)));
|
||||
const newValue = Math.floor(
|
||||
Math.min(this.max, Math.max(this.min, e.target.value))
|
||||
);
|
||||
this.set('internalValue', newValue);
|
||||
this.update(this.internalValue);
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
requestFrame = true;
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
@@ -58,7 +59,10 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
if (this.requestFrame) {
|
||||
window.requestAnimationFrame(() => {
|
||||
// If the scroll position is close enough to the bottom, autoscroll to the bottom
|
||||
this.set('follow', cli.scrollHeight - cli.scrollTop - cli.clientHeight < 20);
|
||||
this.set(
|
||||
'follow',
|
||||
cli.scrollHeight - cli.scrollTop - cli.clientHeight < 20
|
||||
);
|
||||
this.requestFrame = true;
|
||||
});
|
||||
}
|
||||
@@ -79,6 +83,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this.fillAvailableHeight();
|
||||
|
||||
this.set('_scrollHandler', this.scrollHandler.bind(this));
|
||||
@@ -89,6 +94,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
this.element.removeEventListener('scroll', this._scrollHandler);
|
||||
document.removeEventListener('keydown', this._keyDownHandler);
|
||||
}
|
||||
@@ -102,10 +108,12 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
// of having the log window fill available height is worth the hack.
|
||||
const margins = 30; // Account for padding and margin on either side of the CLI
|
||||
const cliWindow = this.element;
|
||||
cliWindow.style.height = `${window.innerHeight - cliWindow.offsetTop - margins}px`;
|
||||
cliWindow.style.height = `${
|
||||
window.innerHeight - cliWindow.offsetTop - margins
|
||||
}px`;
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
yield this.get('logger.gotoHead').perform();
|
||||
run.scheduleOnce('afterRender', this, this.scrollToTop);
|
||||
})
|
||||
@@ -115,7 +123,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
this.element.scrollTop = 0;
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
yield this.get('logger.gotoTail').perform();
|
||||
})
|
||||
tail;
|
||||
@@ -126,7 +134,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
}
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
// Follow the log if the scroll position is near the bottom of the cli window
|
||||
this.logger.on('tick', this, 'scheduleScrollSynchronization');
|
||||
|
||||
@@ -140,6 +148,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.logger.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ export default class TaskGroupRow extends Component {
|
||||
get tooltipText() {
|
||||
if (this.can.cannot('scale job', null, { namespace: this.namespace }))
|
||||
return "You aren't allowed to scale task groups";
|
||||
if (this.runningDeployment) return 'You cannot scale task groups during a deployment';
|
||||
if (this.runningDeployment)
|
||||
return 'You cannot scale task groups during a deployment';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,21 +58,23 @@ export default class TaskLog extends Component {
|
||||
// If the log request can't settle in one second, the client
|
||||
// must be unavailable and the server should be used instead
|
||||
|
||||
const aborter = window.AbortController ? new AbortController() : new MockAbortController();
|
||||
const aborter = window.AbortController
|
||||
? new AbortController()
|
||||
: new MockAbortController();
|
||||
const timing = this.useServer ? this.serverTimeout : this.clientTimeout;
|
||||
|
||||
// Capture the state of useServer at logger create time to avoid a race
|
||||
// between the stdout logger and stderr logger running at once.
|
||||
const useServer = this.useServer;
|
||||
return url =>
|
||||
return (url) =>
|
||||
RSVP.race([
|
||||
this.token.authorizedRequest(url, { signal: aborter.signal }),
|
||||
timeout(timing),
|
||||
]).then(
|
||||
response => {
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
error => {
|
||||
(error) => {
|
||||
aborter.abort();
|
||||
if (useServer) {
|
||||
this.set('noConnection', true);
|
||||
|
||||
@@ -50,13 +50,11 @@ export default class TaskRow extends Component {
|
||||
lazyClick([this.onClick, event]);
|
||||
}
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
do {
|
||||
if (this.stats) {
|
||||
try {
|
||||
yield this.get('stats.poll')
|
||||
.linked()
|
||||
.perform();
|
||||
yield this.get('stats.poll').linked().perform();
|
||||
this.set('statsError', false);
|
||||
} catch (error) {
|
||||
this.set('statsError', true);
|
||||
@@ -69,6 +67,7 @@ export default class TaskRow extends Component {
|
||||
fetchStats;
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs();
|
||||
const allocation = this.get('task.allocation');
|
||||
|
||||
if (allocation) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import Component from '@ember/component';
|
||||
import { classNames, classNameBindings, tagName } from '@ember-decorators/component';
|
||||
import {
|
||||
classNames,
|
||||
classNameBindings,
|
||||
tagName,
|
||||
} from '@ember-decorators/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
|
||||
@@ -8,7 +8,9 @@ export default class Tooltip extends Component {
|
||||
}
|
||||
|
||||
const prefix = inputText.substr(0, 15).trim();
|
||||
const suffix = inputText.substr(inputText.length - 10, inputText.length).trim();
|
||||
const suffix = inputText
|
||||
.substr(inputText.length - 10, inputText.length)
|
||||
.trim();
|
||||
return `${prefix}...${suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,14 @@ export default class TopoViz extends Component {
|
||||
@styleStringProperty('tooltipProps') tooltipStyle;
|
||||
|
||||
get isSingleColumn() {
|
||||
if (this.topology.datacenters.length <= 1 || this.viewportColumns === 1) return true;
|
||||
if (this.topology.datacenters.length <= 1 || this.viewportColumns === 1)
|
||||
return true;
|
||||
|
||||
// Compute the coefficient of variance to determine if it would be
|
||||
// better to stack datacenters or place them in columns
|
||||
const nodeCounts = this.topology.datacenters.map(datacenter => datacenter.nodes.length);
|
||||
const nodeCounts = this.topology.datacenters.map(
|
||||
(datacenter) => datacenter.nodes.length
|
||||
);
|
||||
const variationCoefficient = deviation(nodeCounts) / mean(nodeCounts);
|
||||
|
||||
// The point at which the varation is too extreme for a two column layout
|
||||
@@ -43,7 +46,10 @@ export default class TopoViz extends Component {
|
||||
// If there are enough nodes, use two columns of nodes within
|
||||
// a single column layout of datacenters to increase density.
|
||||
if (this.viewportColumns === 1) return true;
|
||||
return !this.isSingleColumn || (this.isSingleColumn && this.args.nodes.length <= 20);
|
||||
return (
|
||||
!this.isSingleColumn ||
|
||||
(this.isSingleColumn && this.args.nodes.length <= 20)
|
||||
);
|
||||
}
|
||||
|
||||
// Once a cluster is large enough, the exact details of a node are
|
||||
@@ -89,7 +95,7 @@ export default class TopoViz extends Component {
|
||||
// Wrap nodes in a topo viz specific data structure and build an index to speed up allocation assignment
|
||||
const nodeContainers = [];
|
||||
const nodeIndex = {};
|
||||
nodes.forEach(node => {
|
||||
nodes.forEach((node) => {
|
||||
if (!node.resources) {
|
||||
badNodes.push(node);
|
||||
return;
|
||||
@@ -103,14 +109,17 @@ export default class TopoViz extends Component {
|
||||
// Wrap allocations in a topo viz specific data structure, assign allocations to nodes, and build an allocation
|
||||
// index keyed off of job and task group
|
||||
const allocationIndex = {};
|
||||
allocations.forEach(allocation => {
|
||||
allocations.forEach((allocation) => {
|
||||
const nodeId = allocation.belongsTo('node').id();
|
||||
const nodeContainer = nodeIndex[nodeId];
|
||||
|
||||
// Ignore orphaned allocations and allocations on nodes with an old Nomad agent version.
|
||||
if (!nodeContainer) return;
|
||||
|
||||
const allocationContainer = this.dataForAllocation(allocation, nodeContainer);
|
||||
const allocationContainer = this.dataForAllocation(
|
||||
allocation,
|
||||
nodeContainer
|
||||
);
|
||||
nodeContainer.allocations.push(allocationContainer);
|
||||
|
||||
const key = allocationContainer.groupKey;
|
||||
@@ -119,15 +128,19 @@ export default class TopoViz extends Component {
|
||||
});
|
||||
|
||||
// Group nodes into datacenters
|
||||
const datacentersMap = nodeContainers.reduce((datacenters, nodeContainer) => {
|
||||
if (!datacenters[nodeContainer.datacenter]) datacenters[nodeContainer.datacenter] = [];
|
||||
datacenters[nodeContainer.datacenter].push(nodeContainer);
|
||||
return datacenters;
|
||||
}, {});
|
||||
const datacentersMap = nodeContainers.reduce(
|
||||
(datacenters, nodeContainer) => {
|
||||
if (!datacenters[nodeContainer.datacenter])
|
||||
datacenters[nodeContainer.datacenter] = [];
|
||||
datacenters[nodeContainer.datacenter].push(nodeContainer);
|
||||
return datacenters;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// Turn hash of datacenters into a sorted array
|
||||
const datacenters = Object.keys(datacentersMap)
|
||||
.map(key => ({ name: key, nodes: datacentersMap[key] }))
|
||||
.map((key) => ({ name: key, nodes: datacentersMap[key] }))
|
||||
.sortBy('name');
|
||||
|
||||
const topology = {
|
||||
@@ -191,9 +204,10 @@ export default class TopoViz extends Component {
|
||||
this.activeEdges = [];
|
||||
|
||||
if (this.topology.selectedKey) {
|
||||
const selectedAllocations = this.topology.allocationIndex[this.topology.selectedKey];
|
||||
const selectedAllocations =
|
||||
this.topology.allocationIndex[this.topology.selectedKey];
|
||||
if (selectedAllocations) {
|
||||
selectedAllocations.forEach(allocation => {
|
||||
selectedAllocations.forEach((allocation) => {
|
||||
set(allocation, 'isSelected', false);
|
||||
});
|
||||
}
|
||||
@@ -205,30 +219,37 @@ export default class TopoViz extends Component {
|
||||
}
|
||||
this.activeNode = null;
|
||||
this.activeAllocation = allocation;
|
||||
const selectedAllocations = this.topology.allocationIndex[this.topology.selectedKey];
|
||||
const selectedAllocations =
|
||||
this.topology.allocationIndex[this.topology.selectedKey];
|
||||
if (selectedAllocations) {
|
||||
selectedAllocations.forEach(allocation => {
|
||||
selectedAllocations.forEach((allocation) => {
|
||||
set(allocation, 'isSelected', false);
|
||||
});
|
||||
}
|
||||
|
||||
set(this.topology, 'selectedKey', allocation.groupKey);
|
||||
const newAllocations = this.topology.allocationIndex[this.topology.selectedKey];
|
||||
const newAllocations =
|
||||
this.topology.allocationIndex[this.topology.selectedKey];
|
||||
if (newAllocations) {
|
||||
newAllocations.forEach(allocation => {
|
||||
newAllocations.forEach((allocation) => {
|
||||
set(allocation, 'isSelected', true);
|
||||
});
|
||||
}
|
||||
|
||||
// Only show the lines if the selected allocations are sparse (low count relative to the client count or low count generally).
|
||||
if (newAllocations.length < 10 || newAllocations.length < this.args.nodes.length * 0.75) {
|
||||
if (
|
||||
newAllocations.length < 10 ||
|
||||
newAllocations.length < this.args.nodes.length * 0.75
|
||||
) {
|
||||
this.computedActiveEdges();
|
||||
} else {
|
||||
this.activeEdges = [];
|
||||
}
|
||||
}
|
||||
if (this.args.onAllocationSelect)
|
||||
this.args.onAllocationSelect(this.activeAllocation && this.activeAllocation.allocation);
|
||||
this.args.onAllocationSelect(
|
||||
this.activeAllocation && this.activeAllocation.allocation
|
||||
);
|
||||
if (this.args.onNodeSelect) this.args.onNodeSelect(this.activeNode);
|
||||
}
|
||||
|
||||
@@ -251,24 +272,28 @@ export default class TopoViz extends Component {
|
||||
const path = line().curve(curveBasis);
|
||||
// 1. Get the active element
|
||||
const allocation = this.activeAllocation.allocation;
|
||||
const activeEl = this.element.querySelector(`[data-allocation-id="${allocation.id}"]`);
|
||||
const activeEl = this.element.querySelector(
|
||||
`[data-allocation-id="${allocation.id}"]`
|
||||
);
|
||||
const activePoint = centerOfBBox(activeEl.getBoundingClientRect());
|
||||
|
||||
// 2. Collect the mem and cpu pairs for all selected allocs
|
||||
const selectedMem = Array.from(this.element.querySelectorAll('.memory .bar.is-selected'));
|
||||
const selectedPairs = selectedMem.map(mem => {
|
||||
const selectedMem = Array.from(
|
||||
this.element.querySelectorAll('.memory .bar.is-selected')
|
||||
);
|
||||
const selectedPairs = selectedMem.map((mem) => {
|
||||
const id = mem.closest('[data-allocation-id]').dataset.allocationId;
|
||||
const cpu = mem
|
||||
.closest('.topo-viz-node')
|
||||
.querySelector(`.cpu .bar[data-allocation-id="${id}"]`);
|
||||
return [mem, cpu];
|
||||
});
|
||||
const selectedPoints = selectedPairs.map(pair => {
|
||||
return pair.map(el => centerOfBBox(el.getBoundingClientRect()));
|
||||
const selectedPoints = selectedPairs.map((pair) => {
|
||||
return pair.map((el) => centerOfBBox(el.getBoundingClientRect()));
|
||||
});
|
||||
|
||||
// 3. For each pair, compute the midpoint of the truncated triangle of points [Mem, Cpu, Active]
|
||||
selectedPoints.forEach(points => {
|
||||
selectedPoints.forEach((points) => {
|
||||
const d1 = pointBetween(points[0], activePoint, 100, 0.5);
|
||||
const d2 = pointBetween(points[1], activePoint, 100, 0.5);
|
||||
points.push(midpoint(d1, d2));
|
||||
@@ -281,14 +306,20 @@ export default class TopoViz extends Component {
|
||||
const stepsMain = [0, 0.8, 1.0];
|
||||
// The second prong the fork does not need to retrace the entire path from the activePoint
|
||||
const stepsSecondary = [0.8, 1.0];
|
||||
selectedPoints.forEach(points => {
|
||||
selectedPoints.forEach((points) => {
|
||||
curves.push(
|
||||
curveFromPoints(...pointsAlongPath(activePoint, points[2], stepsMain), points[0]),
|
||||
curveFromPoints(...pointsAlongPath(activePoint, points[2], stepsSecondary), points[1])
|
||||
curveFromPoints(
|
||||
...pointsAlongPath(activePoint, points[2], stepsMain),
|
||||
points[0]
|
||||
),
|
||||
curveFromPoints(
|
||||
...pointsAlongPath(activePoint, points[2], stepsSecondary),
|
||||
points[1]
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
this.activeEdges = curves.map(curve => path(curve));
|
||||
this.activeEdges = curves.map((curve) => path(curve));
|
||||
this.edgeOffset = { x: window.scrollX, y: window.scrollY };
|
||||
});
|
||||
}
|
||||
@@ -319,7 +350,7 @@ function pointBetweenPct(p1, p2, pct) {
|
||||
}
|
||||
|
||||
function pointsAlongPath(p1, p2, pcts) {
|
||||
return pcts.map(pct => pointBetweenPct(p1, p2, pct));
|
||||
return pcts.map((pct) => pointBetweenPct(p1, p2, pct));
|
||||
}
|
||||
|
||||
function midpoint(p1, p2) {
|
||||
@@ -327,5 +358,5 @@ function midpoint(p1, p2) {
|
||||
}
|
||||
|
||||
function curveFromPoints(...points) {
|
||||
return points.map(p => [p.x, p.y]);
|
||||
return points.map((p) => [p.x, p.y]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import Component from '@glimmer/component';
|
||||
export default class TopoVizDatacenter extends Component {
|
||||
get scheduledAllocations() {
|
||||
return this.args.datacenter.nodes.reduce(
|
||||
(all, node) => all.concat(node.allocations.filterBy('allocation.isScheduled')),
|
||||
(all, node) =>
|
||||
all.concat(node.allocations.filterBy('allocation.isScheduled')),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ export default class TopoVizNode extends Component {
|
||||
@tracked activeAllocation = null;
|
||||
|
||||
get height() {
|
||||
return this.args.heightScale ? this.args.heightScale(this.args.node.memory) : 15;
|
||||
return this.args.heightScale
|
||||
? this.args.heightScale(this.args.node.memory)
|
||||
: 15;
|
||||
}
|
||||
|
||||
get labelHeight() {
|
||||
@@ -57,11 +59,13 @@ export default class TopoVizNode extends Component {
|
||||
get allocations() {
|
||||
// Sort by the delta between memory and cpu percent. This creates the least amount of
|
||||
// drift between the positional alignment of an alloc's cpu and memory representations.
|
||||
return this.args.node.allocations.filterBy('allocation.isScheduled').sort((a, b) => {
|
||||
const deltaA = Math.abs(a.memoryPercent - a.cpuPercent);
|
||||
const deltaB = Math.abs(b.memoryPercent - b.cpuPercent);
|
||||
return deltaA - deltaB;
|
||||
});
|
||||
return this.args.node.allocations
|
||||
.filterBy('allocation.isScheduled')
|
||||
.sort((a, b) => {
|
||||
const deltaA = Math.abs(a.memoryPercent - a.cpuPercent);
|
||||
const deltaB = Math.abs(b.memoryPercent - b.cpuPercent);
|
||||
return deltaA - deltaB;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -91,7 +95,8 @@ export default class TopoVizNode extends Component {
|
||||
@action
|
||||
highlightAllocation(allocation, { target }) {
|
||||
this.activeAllocation = allocation;
|
||||
this.args.onAllocationFocus && this.args.onAllocationFocus(allocation, target);
|
||||
this.args.onAllocationFocus &&
|
||||
this.args.onAllocationFocus(allocation, target);
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -118,7 +123,7 @@ export default class TopoVizNode extends Component {
|
||||
|
||||
containsActiveTaskGroup() {
|
||||
return this.args.node.allocations.some(
|
||||
allocation =>
|
||||
(allocation) =>
|
||||
allocation.taskGroupName === this.args.activeTaskGroup &&
|
||||
allocation.belongsTo('job').id() === this.args.activeJobId
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ export default class Trigger extends Component {
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
this._reset();
|
||||
try {
|
||||
this.result = yield this.args.do();
|
||||
|
||||
@@ -9,7 +9,10 @@ import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
@classNames('two-step-button')
|
||||
@classNameBindings('inlineText:has-inline-text', 'fadingBackground:has-fading-background')
|
||||
@classNameBindings(
|
||||
'inlineText:has-inline-text',
|
||||
'fadingBackground:has-fading-background'
|
||||
)
|
||||
export default class TwoStepButton extends Component {
|
||||
idleText = '';
|
||||
cancelText = '';
|
||||
@@ -26,7 +29,7 @@ export default class TwoStepButton extends Component {
|
||||
@equal('state', 'idle') isIdle;
|
||||
@equal('state', 'prompt') isPendingConfirmation;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
while (true) {
|
||||
let ev = yield waitForEvent(document.body, 'click');
|
||||
if (!this.element.contains(ev.target) && !this.awaitingConfirmation) {
|
||||
|
||||
@@ -35,7 +35,12 @@ export default class AllocationsAllocationController extends Controller {
|
||||
{
|
||||
title: 'Task Group',
|
||||
label: allocation.taskGroupName,
|
||||
args: ['jobs.job.task-group', job.plainId, allocation.taskGroupName, jobQueryParams],
|
||||
args: [
|
||||
'jobs.job.task-group',
|
||||
job.plainId,
|
||||
allocation.taskGroupName,
|
||||
jobQueryParams,
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Allocation',
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class IndexController extends Controller.extend(Sortable) {
|
||||
// Set in the route
|
||||
preempter = null;
|
||||
|
||||
@overridable(function() {
|
||||
@overridable(function () {
|
||||
// { title, description }
|
||||
return null;
|
||||
})
|
||||
@@ -66,7 +66,7 @@ export default class IndexController extends Controller.extend(Sortable) {
|
||||
}
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.model.stop();
|
||||
// Eagerly update the allocation clientStatus to avoid flickering
|
||||
@@ -80,7 +80,7 @@ export default class IndexController extends Controller.extend(Sortable) {
|
||||
})
|
||||
stopAllocation;
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.model.restart();
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,11 @@ export default class AllocationsAllocationTaskController extends Controller {
|
||||
return {
|
||||
title: 'Task',
|
||||
label: this.task.get('name'),
|
||||
args: ['allocations.allocation.task', this.task.get('allocation'), this.task],
|
||||
args: [
|
||||
'allocations.allocation.task',
|
||||
this.task.get('allocation'),
|
||||
this.task,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class IndexController extends Controller {
|
||||
this.set('error', null);
|
||||
}
|
||||
|
||||
@task(function*() {
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.model.restart();
|
||||
} catch (err) {
|
||||
|
||||
@@ -10,11 +10,17 @@ import intersection from 'lodash.intersection';
|
||||
import Sortable from 'nomad-ui/mixins/sortable';
|
||||
import Searchable from 'nomad-ui/mixins/searchable';
|
||||
import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error';
|
||||
import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize';
|
||||
import {
|
||||
serialize,
|
||||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class ClientController extends Controller.extend(Sortable, Searchable) {
|
||||
export default class ClientController extends Controller.extend(
|
||||
Sortable,
|
||||
Searchable
|
||||
) {
|
||||
queryParams = [
|
||||
{
|
||||
currentPage: 'page',
|
||||
@@ -66,18 +72,32 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
return this.onlyPreemptions ? this.preemptions : this.model.allocations;
|
||||
}
|
||||
|
||||
@computed('visibleAllocations.[]', 'selectionNamespace', 'selectionJob', 'selectionStatus')
|
||||
@computed(
|
||||
'visibleAllocations.[]',
|
||||
'selectionNamespace',
|
||||
'selectionJob',
|
||||
'selectionStatus'
|
||||
)
|
||||
get filteredAllocations() {
|
||||
const { selectionNamespace, selectionJob, selectionStatus } = this;
|
||||
|
||||
return this.visibleAllocations.filter(alloc => {
|
||||
if (selectionNamespace.length && !selectionNamespace.includes(alloc.get('namespace'))) {
|
||||
return this.visibleAllocations.filter((alloc) => {
|
||||
if (
|
||||
selectionNamespace.length &&
|
||||
!selectionNamespace.includes(alloc.get('namespace'))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (selectionJob.length && !selectionJob.includes(alloc.get('plainJobId'))) {
|
||||
if (
|
||||
selectionJob.length &&
|
||||
!selectionJob.includes(alloc.get('plainJobId'))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (selectionStatus.length && !selectionStatus.includes(alloc.clientStatus)) {
|
||||
if (
|
||||
selectionStatus.length &&
|
||||
!selectionStatus.includes(alloc.clientStatus)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -106,9 +126,7 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
|
||||
@computed('model.events.@each.time')
|
||||
get sortedEvents() {
|
||||
return this.get('model.events')
|
||||
.sortBy('time')
|
||||
.reverse();
|
||||
return this.get('model.events').sortBy('time').reverse();
|
||||
}
|
||||
|
||||
@computed('model.drivers.@each.name')
|
||||
@@ -121,7 +139,7 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
return this.model.hostVolumes.sortBy('name');
|
||||
}
|
||||
|
||||
@(task(function*(value) {
|
||||
@(task(function* (value) {
|
||||
try {
|
||||
yield value ? this.model.setEligible() : this.model.setIneligible();
|
||||
} catch (err) {
|
||||
@@ -131,7 +149,7 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
}).drop())
|
||||
setEligibility;
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
try {
|
||||
this.set('flagAsDraining', false);
|
||||
yield this.model.cancelDrain();
|
||||
@@ -144,7 +162,7 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
}).drop())
|
||||
stopDrain;
|
||||
|
||||
@(task(function*() {
|
||||
@(task(function* () {
|
||||
try {
|
||||
yield this.model.forceDrain({
|
||||
IgnoreSystemJobs: this.model.drainStrategy.ignoreSystemJobs,
|
||||
@@ -203,7 +221,7 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
const jobs = Array.from(
|
||||
new Set(
|
||||
this.model.allocations
|
||||
.filter(a => ns.length === 0 || ns.includes(a.namespace))
|
||||
.filter((a) => ns.length === 0 || ns.includes(a.namespace))
|
||||
.mapBy('plainJobId')
|
||||
)
|
||||
).compact();
|
||||
@@ -214,20 +232,25 @@ export default class ClientController extends Controller.extend(Sortable, Search
|
||||
this.set('qpJob', serialize(intersection(jobs, this.selectionJob)));
|
||||
});
|
||||
|
||||
return jobs.sort().map(job => ({ key: job, label: job }));
|
||||
return jobs.sort().map((job) => ({ key: job, label: job }));
|
||||
}
|
||||
|
||||
@computed('model.allocations.[]', 'selectionNamespace')
|
||||
get optionsNamespace() {
|
||||
const ns = Array.from(new Set(this.model.allocations.mapBy('namespace'))).compact();
|
||||
const ns = Array.from(
|
||||
new Set(this.model.allocations.mapBy('namespace'))
|
||||
).compact();
|
||||
|
||||
// Update query param when the list of namespaces changes.
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpNamespace', serialize(intersection(ns, this.selectionNamespace)));
|
||||
this.set(
|
||||
'qpNamespace',
|
||||
serialize(intersection(ns, this.selectionNamespace))
|
||||
);
|
||||
});
|
||||
|
||||
return ns.sort().map(n => ({ key: n, label: n }));
|
||||
return ns.sort().map((n) => ({ key: n, label: n }));
|
||||
}
|
||||
|
||||
setFacetQueryParam(queryParam, selection) {
|
||||
|
||||
@@ -7,14 +7,17 @@ import { scheduleOnce } from '@ember/runloop';
|
||||
import intersection from 'lodash.intersection';
|
||||
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
|
||||
import Searchable from 'nomad-ui/mixins/searchable';
|
||||
import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize';
|
||||
import {
|
||||
serialize,
|
||||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class IndexController extends Controller.extend(
|
||||
SortableFactory(['id', 'name', 'compositeStatus', 'datacenter', 'version']),
|
||||
Searchable
|
||||
) {
|
||||
SortableFactory(['id', 'name', 'compositeStatus', 'datacenter', 'version']),
|
||||
Searchable
|
||||
) {
|
||||
@service userSettings;
|
||||
@controller('clients') clientsController;
|
||||
|
||||
@@ -83,10 +86,13 @@ export default class IndexController extends Controller.extend(
|
||||
// Remove any invalid node classes from the query param/selection
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpClass', serialize(intersection(classes, this.selectionClass)));
|
||||
this.set(
|
||||
'qpClass',
|
||||
serialize(intersection(classes, this.selectionClass))
|
||||
);
|
||||
});
|
||||
|
||||
return classes.sort().map(dc => ({ key: dc, label: dc }));
|
||||
return classes.sort().map((dc) => ({ key: dc, label: dc }));
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -102,15 +108,20 @@ export default class IndexController extends Controller.extend(
|
||||
|
||||
@computed('nodes.[]', 'selectionDatacenter')
|
||||
get optionsDatacenter() {
|
||||
const datacenters = Array.from(new Set(this.nodes.mapBy('datacenter'))).compact();
|
||||
const datacenters = Array.from(
|
||||
new Set(this.nodes.mapBy('datacenter'))
|
||||
).compact();
|
||||
|
||||
// Remove any invalid datacenters from the query param/selection
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpDatacenter', serialize(intersection(datacenters, this.selectionDatacenter)));
|
||||
this.set(
|
||||
'qpDatacenter',
|
||||
serialize(intersection(datacenters, this.selectionDatacenter))
|
||||
);
|
||||
});
|
||||
|
||||
return datacenters.sort().map(dc => ({ key: dc, label: dc }));
|
||||
return datacenters.sort().map((dc) => ({ key: dc, label: dc }));
|
||||
}
|
||||
|
||||
@computed('nodes.[]', 'selectionVersion')
|
||||
@@ -120,10 +131,13 @@ export default class IndexController extends Controller.extend(
|
||||
// Remove any invalid versions from the query param/selection
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpVersion', serialize(intersection(versions, this.selectionVersion)));
|
||||
this.set(
|
||||
'qpVersion',
|
||||
serialize(intersection(versions, this.selectionVersion))
|
||||
);
|
||||
});
|
||||
|
||||
return versions.sort().map(v => ({ key: v, label: v }));
|
||||
return versions.sort().map((v) => ({ key: v, label: v }));
|
||||
}
|
||||
|
||||
@computed('nodes.[]', 'selectionVolume')
|
||||
@@ -135,10 +149,13 @@ export default class IndexController extends Controller.extend(
|
||||
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpVolume', serialize(intersection(volumes, this.selectionVolume)));
|
||||
this.set(
|
||||
'qpVolume',
|
||||
serialize(intersection(volumes, this.selectionVolume))
|
||||
);
|
||||
});
|
||||
|
||||
return volumes.sort().map(volume => ({ key: volume, label: volume }));
|
||||
return volumes.sort().map((volume) => ({ key: volume, label: volume }));
|
||||
}
|
||||
|
||||
@computed(
|
||||
@@ -164,12 +181,19 @@ export default class IndexController extends Controller.extend(
|
||||
// states is a composite of node status and other node states
|
||||
const statuses = states.without('ineligible').without('draining');
|
||||
|
||||
return this.nodes.filter(node => {
|
||||
if (classes.length && !classes.includes(node.get('nodeClass'))) return false;
|
||||
if (statuses.length && !statuses.includes(node.get('status'))) return false;
|
||||
if (datacenters.length && !datacenters.includes(node.get('datacenter'))) return false;
|
||||
if (versions.length && !versions.includes(node.get('version'))) return false;
|
||||
if (volumes.length && !node.hostVolumes.find(volume => volumes.includes(volume.name)))
|
||||
return this.nodes.filter((node) => {
|
||||
if (classes.length && !classes.includes(node.get('nodeClass')))
|
||||
return false;
|
||||
if (statuses.length && !statuses.includes(node.get('status')))
|
||||
return false;
|
||||
if (datacenters.length && !datacenters.includes(node.get('datacenter')))
|
||||
return false;
|
||||
if (versions.length && !versions.includes(node.get('version')))
|
||||
return false;
|
||||
if (
|
||||
volumes.length &&
|
||||
!node.hostVolumes.find((volume) => volumes.includes(volume.name))
|
||||
)
|
||||
return false;
|
||||
|
||||
if (onlyIneligible && node.get('isEligible')) return false;
|
||||
|
||||
@@ -9,14 +9,14 @@ import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class IndexController extends Controller.extend(
|
||||
SortableFactory([
|
||||
'plainId',
|
||||
'controllersHealthyProportion',
|
||||
'nodesHealthyProportion',
|
||||
'provider',
|
||||
]),
|
||||
Searchable
|
||||
) {
|
||||
SortableFactory([
|
||||
'plainId',
|
||||
'controllersHealthyProportion',
|
||||
'nodesHealthyProportion',
|
||||
'provider',
|
||||
]),
|
||||
Searchable
|
||||
) {
|
||||
@service userSettings;
|
||||
@controller('csi/plugins') pluginsController;
|
||||
|
||||
@@ -59,6 +59,9 @@ export default class IndexController extends Controller.extend(
|
||||
|
||||
@action
|
||||
gotoPlugin(plugin, event) {
|
||||
lazyClick([() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), event]);
|
||||
lazyClick([
|
||||
() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId),
|
||||
event,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@ import { action, computed } from '@ember/object';
|
||||
import { alias, readOnly } from '@ember/object/computed';
|
||||
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
|
||||
import { lazyClick } from 'nomad-ui/helpers/lazy-click';
|
||||
import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize';
|
||||
import {
|
||||
serialize,
|
||||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class AllocationsController extends Controller.extend(
|
||||
SortableFactory(['updateTime', 'healthy'])
|
||||
) {
|
||||
SortableFactory(['updateTime', 'healthy'])
|
||||
) {
|
||||
@service userSettings;
|
||||
|
||||
queryParams = [
|
||||
@@ -45,12 +48,18 @@ export default class AllocationsController extends Controller.extend(
|
||||
|
||||
@computed
|
||||
get optionsType() {
|
||||
return [{ key: 'controller', label: 'Controller' }, { key: 'node', label: 'Node' }];
|
||||
return [
|
||||
{ key: 'controller', label: 'Controller' },
|
||||
{ key: 'node', label: 'Node' },
|
||||
];
|
||||
}
|
||||
|
||||
@computed
|
||||
get optionsHealth() {
|
||||
return [{ key: 'true', label: 'Healthy' }, { key: 'false', label: 'Unhealthy' }];
|
||||
return [
|
||||
{ key: 'true', label: 'Healthy' },
|
||||
{ key: 'false', label: 'Unhealthy' },
|
||||
];
|
||||
}
|
||||
|
||||
@computed('model.{controllers.[],nodes.[]}')
|
||||
@@ -76,7 +85,8 @@ export default class AllocationsController extends Controller.extend(
|
||||
listToFilter = this.model.nodes;
|
||||
}
|
||||
|
||||
if (healths.length === 1 && healths[0] === 'true') return listToFilter.filterBy('healthy');
|
||||
if (healths.length === 1 && healths[0] === 'true')
|
||||
return listToFilter.filterBy('healthy');
|
||||
if (healths.length === 1 && healths[0] === 'false')
|
||||
return listToFilter.filterBy('healthy', false);
|
||||
return listToFilter;
|
||||
@@ -97,6 +107,9 @@ export default class AllocationsController extends Controller.extend(
|
||||
|
||||
@action
|
||||
gotoAllocation(allocation, event) {
|
||||
lazyClick([() => this.transitionToRoute('allocations.allocation', allocation), event]);
|
||||
lazyClick([
|
||||
() => this.transitionToRoute('allocations.allocation', allocation),
|
||||
event,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action, computed } from '@ember/object';
|
||||
import { alias, readOnly } from '@ember/object/computed';
|
||||
@@ -11,15 +12,15 @@ import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class IndexController extends Controller.extend(
|
||||
SortableFactory([
|
||||
'id',
|
||||
'schedulable',
|
||||
'controllersHealthyProportion',
|
||||
'nodesHealthyProportion',
|
||||
'provider',
|
||||
]),
|
||||
Searchable
|
||||
) {
|
||||
SortableFactory([
|
||||
'id',
|
||||
'schedulable',
|
||||
'controllersHealthyProportion',
|
||||
'nodesHealthyProportion',
|
||||
'provider',
|
||||
]),
|
||||
Searchable
|
||||
) {
|
||||
@service system;
|
||||
@service userSettings;
|
||||
@controller('csi/volumes') volumesController;
|
||||
@@ -65,7 +66,7 @@ export default class IndexController extends Controller.extend(
|
||||
|
||||
@computed('qpNamespace', 'model.namespaces.[]', 'system.cachedNamespace')
|
||||
get optionsNamespaces() {
|
||||
const availableNamespaces = this.model.namespaces.map(namespace => ({
|
||||
const availableNamespaces = this.model.namespaces.map((namespace) => ({
|
||||
key: namespace.name,
|
||||
label: namespace.name,
|
||||
}));
|
||||
@@ -102,7 +103,7 @@ export default class IndexController extends Controller.extend(
|
||||
|
||||
@action
|
||||
cacheNamespace(namespace) {
|
||||
this.system.cachedNamespace = namespace;
|
||||
set(this, 'system.cachedNamespace', namespace);
|
||||
}
|
||||
|
||||
setFacetQueryParam(queryParam, selection) {
|
||||
|
||||
@@ -25,7 +25,9 @@ export default class VolumeController extends Controller {
|
||||
label: 'Volumes',
|
||||
args: [
|
||||
'csi.volumes',
|
||||
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }),
|
||||
qpBuilder({
|
||||
volumeNamespace: volume.get('namespace.name') || 'default',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -33,7 +35,9 @@ export default class VolumeController extends Controller {
|
||||
args: [
|
||||
'csi.volumes.volume',
|
||||
volume.plainId,
|
||||
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }),
|
||||
qpBuilder({
|
||||
volumeNamespace: volume.get('namespace.name') || 'default',
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -25,18 +25,25 @@ export default class ExecController extends Controller {
|
||||
@computed('model.allocations.@each.clientStatus')
|
||||
get pendingAndRunningAllocations() {
|
||||
return this.model.allocations.filter(
|
||||
allocation => allocation.clientStatus === 'pending' || allocation.clientStatus === 'running'
|
||||
(allocation) =>
|
||||
allocation.clientStatus === 'pending' ||
|
||||
allocation.clientStatus === 'running'
|
||||
);
|
||||
}
|
||||
|
||||
@mapBy('pendingAndRunningAllocations', 'taskGroup') pendingAndRunningTaskGroups;
|
||||
@mapBy('pendingAndRunningAllocations', 'taskGroup')
|
||||
pendingAndRunningTaskGroups;
|
||||
@uniq('pendingAndRunningTaskGroups') uniquePendingAndRunningTaskGroups;
|
||||
|
||||
taskGroupSorting = ['name'];
|
||||
@sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting') sortedTaskGroups;
|
||||
@sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting')
|
||||
sortedTaskGroups;
|
||||
|
||||
setUpTerminal(Terminal) {
|
||||
this.terminal = new Terminal({ fontFamily: 'monospace', fontWeight: '400' });
|
||||
this.terminal = new Terminal({
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: '400',
|
||||
});
|
||||
window.execTerminal = this.terminal; // Issue to improve: https://github.com/hashicorp/nomad/issues/7457
|
||||
|
||||
this.terminal.write(ANSI_UI_GRAY_400);
|
||||
@@ -63,7 +70,7 @@ export default class ExecController extends Controller {
|
||||
if (this.allocationShortId) {
|
||||
allocation = this.allocations.findBy('shortId', this.allocationShortId);
|
||||
} else {
|
||||
allocation = this.allocations.find(allocation =>
|
||||
allocation = this.allocations.find((allocation) =>
|
||||
allocation.states
|
||||
.filterBy('isActive')
|
||||
.mapBy('name')
|
||||
@@ -72,7 +79,7 @@ export default class ExecController extends Controller {
|
||||
}
|
||||
|
||||
if (allocation) {
|
||||
return allocation.states.find(state => state.name === this.taskName);
|
||||
return allocation.states.find((state) => state.name === this.taskName);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -97,7 +104,9 @@ export default class ExecController extends Controller {
|
||||
this.terminal.writeln('');
|
||||
}
|
||||
|
||||
this.terminal.writeln('Customize your command, then hit ‘return’ to run.');
|
||||
this.terminal.writeln(
|
||||
'Customize your command, then hit ‘return’ to run.'
|
||||
);
|
||||
this.terminal.writeln('');
|
||||
this.terminal.write(
|
||||
`$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${
|
||||
@@ -129,7 +138,9 @@ export default class ExecController extends Controller {
|
||||
|
||||
new ExecSocketXtermAdapter(this.terminal, this.socket, this.token.secret);
|
||||
} else {
|
||||
this.terminal.writeln(`Failed to open a socket because task ${this.taskName} is not active.`);
|
||||
this.terminal.writeln(
|
||||
`Failed to open a socket because task ${this.taskName} is not active.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */
|
||||
import { set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias, readOnly } from '@ember/object/computed';
|
||||
import Controller from '@ember/controller';
|
||||
@@ -7,11 +8,17 @@ import { scheduleOnce } from '@ember/runloop';
|
||||
import intersection from 'lodash.intersection';
|
||||
import Sortable from 'nomad-ui/mixins/sortable';
|
||||
import Searchable from 'nomad-ui/mixins/searchable';
|
||||
import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize';
|
||||
import {
|
||||
serialize,
|
||||
deserializedQueryParam as selection,
|
||||
} from 'nomad-ui/utils/qp-serialize';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
export default class IndexController extends Controller.extend(Sortable, Searchable) {
|
||||
export default class IndexController extends Controller.extend(
|
||||
Sortable,
|
||||
Searchable
|
||||
) {
|
||||
@service system;
|
||||
@service userSettings;
|
||||
|
||||
@@ -99,7 +106,9 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
@computed('selectionDatacenter', 'visibleJobs.[]')
|
||||
get optionsDatacenter() {
|
||||
const flatten = (acc, val) => acc.concat(val);
|
||||
const allDatacenters = new Set(this.visibleJobs.mapBy('datacenters').reduce(flatten, []));
|
||||
const allDatacenters = new Set(
|
||||
this.visibleJobs.mapBy('datacenters').reduce(flatten, [])
|
||||
);
|
||||
|
||||
// Remove any invalid datacenters from the query param/selection
|
||||
const availableDatacenters = Array.from(allDatacenters).compact();
|
||||
@@ -111,7 +120,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
);
|
||||
});
|
||||
|
||||
return availableDatacenters.sort().map(dc => ({ key: dc, label: dc }));
|
||||
return availableDatacenters.sort().map((dc) => ({ key: dc, label: dc }));
|
||||
}
|
||||
|
||||
@computed('selectionPrefix', 'visibleJobs.[]')
|
||||
@@ -131,23 +140,26 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
}, {});
|
||||
|
||||
// Convert to an array
|
||||
const nameTable = Object.keys(nameHistogram).map(key => ({
|
||||
const nameTable = Object.keys(nameHistogram).map((key) => ({
|
||||
prefix: key,
|
||||
count: nameHistogram[key],
|
||||
}));
|
||||
|
||||
// Only consider prefixes that match more than one name
|
||||
const prefixes = nameTable.filter(name => name.count > 1);
|
||||
const prefixes = nameTable.filter((name) => name.count > 1);
|
||||
|
||||
// Remove any invalid prefixes from the query param/selection
|
||||
const availablePrefixes = prefixes.mapBy('prefix');
|
||||
scheduleOnce('actions', () => {
|
||||
// eslint-disable-next-line ember/no-side-effects
|
||||
this.set('qpPrefix', serialize(intersection(availablePrefixes, this.selectionPrefix)));
|
||||
this.set(
|
||||
'qpPrefix',
|
||||
serialize(intersection(availablePrefixes, this.selectionPrefix))
|
||||
);
|
||||
});
|
||||
|
||||
// Sort, format, and include the count in the label
|
||||
return prefixes.sortBy('prefix').map(name => ({
|
||||
return prefixes.sortBy('prefix').map((name) => ({
|
||||
key: name.prefix,
|
||||
label: `${name.prefix} (${name.count})`,
|
||||
}));
|
||||
@@ -155,7 +167,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
|
||||
@computed('qpNamespace', 'model.namespaces.[]', 'system.cachedNamespace')
|
||||
get optionsNamespaces() {
|
||||
const availableNamespaces = this.model.namespaces.map(namespace => ({
|
||||
const availableNamespaces = this.model.namespaces.map((namespace) => ({
|
||||
key: namespace.name,
|
||||
label: namespace.name,
|
||||
}));
|
||||
@@ -185,8 +197,8 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
if (!this.model || !this.model.jobs) return [];
|
||||
return this.model.jobs
|
||||
.compact()
|
||||
.filter(job => !job.isNew)
|
||||
.filter(job => !job.get('parent.content'));
|
||||
.filter((job) => !job.isNew)
|
||||
.filter((job) => !job.get('parent.content'));
|
||||
}
|
||||
|
||||
@computed(
|
||||
@@ -206,7 +218,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
|
||||
// A job must match ALL filter facets, but it can match ANY selection within a facet
|
||||
// Always return early to prevent unnecessary facet predicates.
|
||||
return this.visibleJobs.filter(job => {
|
||||
return this.visibleJobs.filter((job) => {
|
||||
if (types.length && !types.includes(job.get('displayType'))) {
|
||||
return false;
|
||||
}
|
||||
@@ -215,12 +227,18 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
return false;
|
||||
}
|
||||
|
||||
if (datacenters.length && !job.get('datacenters').find(dc => datacenters.includes(dc))) {
|
||||
if (
|
||||
datacenters.length &&
|
||||
!job.get('datacenters').find((dc) => datacenters.includes(dc))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const name = job.get('name');
|
||||
if (prefixes.length && !prefixes.find(prefix => name.startsWith(prefix))) {
|
||||
if (
|
||||
prefixes.length &&
|
||||
!prefixes.find((prefix) => name.startsWith(prefix))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -236,7 +254,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha
|
||||
|
||||
@action
|
||||
cacheNamespace(namespace) {
|
||||
this.system.cachedNamespace = namespace;
|
||||
set(this, 'system.cachedNamespace', namespace);
|
||||
}
|
||||
|
||||
setFacetQueryParam(queryParam, selection) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user