Merge pull request #11754 from hashicorp/b-ui/fix-linter

ui:  fix linter and prettier
This commit is contained in:
Jai
2022-01-24 09:08:47 -05:00
committed by GitHub
427 changed files with 9025 additions and 4441 deletions

1
.gitignore vendored
View File

@@ -89,6 +89,7 @@ rkt-*
# misc
/ui/.sass-cache
/ui/.eslintcache
/ui/.storybook/preview-head.html
/ui/connect.lock
/ui/coverage/*

View File

@@ -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
View 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

View File

@@ -1,7 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"overrides": [
{
"files": "*.hbs",

5
ui/.prettierrc.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
module.exports = {
singleQuote: true,
};

View File

@@ -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 }],
[

View File

@@ -10,7 +10,7 @@ addParameters({
},
});
addDecorator(storyFn => {
addDecorator((storyFn) => {
let { template, context } = storyFn();
let wrapperElementStyle = {

View File

@@ -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',

View File

@@ -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')],

View File

@@ -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) {

View File

@@ -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');
}
}

View File

@@ -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');
});

View File

@@ -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));
}

View File

@@ -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')

View File

@@ -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.[]')

View File

@@ -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);
};
}

View File

@@ -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}&region=${region}` : `${url}?region=${region}`;
return url.indexOf('?') !== -1
? `${url}&region=${region}`
: `${url}?region=${region}`;
}

View File

@@ -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,

View File

@@ -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', {

View File

@@ -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),

View File

@@ -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 };

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();
});
}
}

View File

@@ -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);
}

View File

@@ -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' ? {} : [];
}

View File

@@ -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,

View File

@@ -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')) {

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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]);

View File

@@ -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;

View File

@@ -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' },
];
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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 [];

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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'];

View File

@@ -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';
}
});

View File

@@ -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);

View File

@@ -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({

View File

@@ -78,6 +78,7 @@ export default class GaugeChart extends Component.extend(WindowResizable) {
}
didInsertElement() {
super.didInsertElement(...arguments);
this.updateDimensions();
}

View File

@@ -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);

View File

@@ -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') },

View File

@@ -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 {}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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()]);
}

View File

@@ -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) {

View File

@@ -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)
),
});
}

View File

@@ -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()]);
}

View File

@@ -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();

View File

@@ -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'),

View File

@@ -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 {}

View File

@@ -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 doesnt produce a new version',
description:
'Reverting to an identical older version doesnt 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 (

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
);
}
}

View File

@@ -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,

View File

@@ -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,
}));
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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]);
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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}`;
}
}

View File

@@ -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]);
}

View File

@@ -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')),
[]
);
}

View File

@@ -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
);

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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',

View File

@@ -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) {

View File

@@ -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,
],
};
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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,
]);
}
}

View File

@@ -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,
]);
}
}

View File

@@ -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) {

View File

@@ -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',
}),
],
},
];

View File

@@ -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.`
);
}
}
}

View File

@@ -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