diff --git a/assets/translations/en-us.yaml b/assets/translations/en-us.yaml index f4aa4a505a..5edfda1c37 100644 --- a/assets/translations/en-us.yaml +++ b/assets/translations/en-us.yaml @@ -4195,6 +4195,7 @@ tableHeaders: one { Host } other { Hosts } } + health: Health id: ID image: Image imageSize: Size @@ -4732,6 +4733,8 @@ workload: label: Successful Job History Limit tip: The number of successful finished jobs to retain. suspend: Suspend + list: + errorCannotScale: Failed to scale {workloadName} {direction, select, up { up } down { down } } metrics: pod: Pod Metrics metricsView: Metrics View diff --git a/components/GrowlManager.vue b/components/GrowlManager.vue index db0d9bf6ea..4b24d3d108 100644 --- a/components/GrowlManager.vue +++ b/components/GrowlManager.vue @@ -97,6 +97,7 @@ export default { diff --git a/components/formatter/WorkloadHealthScale.vue b/components/formatter/WorkloadHealthScale.vue new file mode 100644 index 0000000000..ad9cd490d8 --- /dev/null +++ b/components/formatter/WorkloadHealthScale.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/config/product/explorer.js b/config/product/explorer.js index fd2dca969f..d184baae64 100644 --- a/config/product/explorer.js +++ b/config/product/explorer.js @@ -16,7 +16,7 @@ import { USER_ID, USERNAME, USER_DISPLAY_NAME, USER_PROVIDER, WORKLOAD_ENDPOINTS, STORAGE_CLASS_DEFAULT, STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, - ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON + ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE } from '@/config/table-headers'; import { DSL } from '@/store/type-map'; @@ -168,14 +168,14 @@ export function init(store) { headers(SERVICE, [STATE, NAME_COL, NAMESPACE_COL, TARGET_PORT, SELECTOR, SPEC_TYPE, AGE]); headers(HPA, [STATE, NAME_COL, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, AGE]); - headers(WORKLOAD, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, TYPE, 'Ready', AGE]); - headers(WORKLOAD_TYPES.DEPLOYMENT, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', AGE]); - headers(WORKLOAD_TYPES.DAEMON_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]); - headers(WORKLOAD_TYPES.REPLICA_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]); - headers(WORKLOAD_TYPES.STATEFUL_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', AGE]); - headers(WORKLOAD_TYPES.JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Completions', 'Duration', AGE]); - headers(WORKLOAD_TYPES.CRON_JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Schedule', 'Last Schedule', AGE]); - headers(WORKLOAD_TYPES.REPLICATION_CONTROLLER, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]); + headers(WORKLOAD, [STATE, NAME_COL, NAMESPACE_COL, TYPE, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.DEPLOYMENT, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.DAEMON_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.REPLICA_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.STATEFUL_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Completions', 'Duration', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.CRON_JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Schedule', 'Last Schedule', AGE, WORKLOAD_HEALTH_SCALE]); + headers(WORKLOAD_TYPES.REPLICATION_CONTROLLER, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]); headers(POD, [STATE, NAME_COL, NAMESPACE_COL, POD_IMAGES, 'Ready', 'Restarts', 'IP', NODE_COL, AGE]); headers(STORAGE_CLASS, [STATE, NAME_COL, STORAGE_CLASS_PROVISIONER, STORAGE_CLASS_DEFAULT, AGE]); diff --git a/config/table-headers.js b/config/table-headers.js index 844c126ff5..86e5b8ff24 100644 --- a/config/table-headers.js +++ b/config/table-headers.js @@ -1,5 +1,6 @@ import { CATTLE_PUBLIC_ENDPOINTS } from '@/config/labels-annotations'; import { NODE as NODE_TYPE } from '@/config/types'; +import { COLUMN_BREAKPOINTS } from '@/components/SortableTable/index.vue'; // Note: 'id' is always the last sort, so you don't have to specify it here. @@ -602,7 +603,11 @@ export const WORKSPACE = { sort: ['metadata.namespace', 'nameSort'], }; -export const WORKLOAD_IMAGES = { ...POD_IMAGES, value: '' }; +export const WORKLOAD_IMAGES = { + ...POD_IMAGES, + value: '', + breakpoint: COLUMN_BREAKPOINTS.LAPTOP +}; export const WORKLOAD_ENDPOINTS = { name: 'workloadEndpoints', @@ -610,6 +615,15 @@ export const WORKLOAD_ENDPOINTS = { value: `$['metadata']['annotations']['${ CATTLE_PUBLIC_ENDPOINTS }']`, formatter: 'Endpoints', dashIfEmpty: true, + breakpoint: COLUMN_BREAKPOINTS.DESKTOP +}; + +export const WORKLOAD_HEALTH_SCALE = { + name: 'workloadHealthScale', + labelKey: 'tableHeaders.health', + formatter: 'WorkloadHealthScale', + width: 150, + skipSelect: true }; export const FLEET_SUMMARY = { diff --git a/detail/workload/index.vue b/detail/workload/index.vue index 12386655f3..353c9aa4d8 100644 --- a/detail/workload/index.vue +++ b/detail/workload/index.vue @@ -8,7 +8,6 @@ import Loading from '@/components/Loading'; import ResourceTabs from '@/components/form/ResourceTabs'; import CountGauge from '@/components/CountGauge'; import { allHash } from '@/utils/promise'; -import { get } from '@/utils/object'; import DashboardMetrics from '@/components/DashboardMetrics'; import V1WorkloadMetrics from '@/mixins/v1-workload-metrics'; import { mapGetters } from 'vuex'; @@ -72,16 +71,7 @@ export default { computed: { ...mapGetters(['currentCluster']), - pods() { - const relationships = get(this.value, 'metadata.relationships') || []; - const podRelationship = relationships.filter(relationship => relationship.toType === POD)[0]; - if (podRelationship) { - return this.$store.getters['cluster/matching'](POD, podRelationship.selector).filter(pod => pod?.metadata?.namespace === this.value.metadata.namespace); - } else { - return []; - } - }, isJob() { return this.value.type === WORKLOAD_TYPES.JOB; }, @@ -108,24 +98,6 @@ export default { return this.podTemplateSpec?.containers[0]; }, - jobRelationships() { - if (!this.isCronJob) { - return; - } - - return (get(this.value, 'metadata.relationships') || []).filter(relationship => relationship.toType === WORKLOAD_TYPES.JOB); - }, - - jobs() { - if (!this.isCronJob) { - return; - } - - return this.jobRelationships.map((obj) => { - return this.$store.getters['cluster/byId'](WORKLOAD_TYPES.JOB, obj.toId ); - }).filter(x => !!x); - }, - jobSchema() { return this.$store.getters['cluster/schemaFor'](WORKLOAD_TYPES.JOB); }, @@ -134,38 +106,12 @@ export default { return this.$store.getters['type-map/headersFor'](this.jobSchema); }, - jobGauges() { - const out = { - succeeded: { color: 'success', count: 0 }, running: { color: 'info', count: 0 }, failed: { color: 'error', count: 0 } - }; - - if (this.value.type === WORKLOAD_TYPES.CRON_JOB) { - this.jobs.forEach((job) => { - const { status = {} } = job; - - out.running.count += status.active || 0; - out.succeeded.count += status.succeeded || 0; - out.failed.count += status.failed || 0; - }); - } else if (this.value.type === WORKLOAD_TYPES.JOB) { - const { status = {} } = this.value; - - out.running.count = status.active || 0; - out.succeeded.count = status.succeeded || 0; - out.failed.count = status.failed || 0; - } else { - return null; - } - - return out; - }, - totalRuns() { - if (!this.jobs) { + if (!this.value.jobs) { return; } - return this.jobs.reduce((total, job) => { + return this.value.jobs.reduce((total, job) => { const { status = {} } = job; total += (status.active || 0); @@ -177,7 +123,7 @@ export default { }, podRestarts() { - return this.pods.reduce((total, pod) => { + return this.value.pods.reduce((total, pod) => { const { status:{ containerStatuses = [] } } = pod; if (containerStatuses.length) { @@ -192,39 +138,6 @@ export default { }, 0); }, - podGauges() { - const out = { - active: { color: 'success' }, transitioning: { color: 'info' }, warning: { color: 'warning' }, error: { color: 'error' } - }; - - if (!this.pods) { - return out; - } - - this.pods.map((pod) => { - const { status:{ phase } } = pod; - let group; - - switch (phase) { - case 'Running': - group = 'active'; - break; - case 'Pending': - group = 'transitioning'; - break; - case 'Failed': - group = 'error'; - break; - default: - group = 'warning'; - } - - out[group].count ? out[group].count++ : out[group].count = 1; - }); - - return out; - }, - podHeaders() { return [ STATE, @@ -255,12 +168,12 @@ export default {

{{ isJob || isCronJob ? t('workload.detailTop.runs') :t('workload.detailTop.pods') }}

-
-