Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/assets/javascripts/clusters_list
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/assets/javascripts/clusters_list')
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue200
-rw-r--r--app/assets/javascripts/clusters_list/constants.js3
-rw-r--r--app/assets/javascripts/clusters_list/index.js4
-rw-r--r--app/assets/javascripts/clusters_list/store/actions.js49
-rw-r--r--app/assets/javascripts/clusters_list/store/state.js5
5 files changed, 223 insertions, 38 deletions
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index af3f1437c64..a3104038c17 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -1,23 +1,34 @@
<script>
+import * as Sentry from '@sentry/browser';
import { mapState, mapActions } from 'vuex';
-import { GlBadge, GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui';
+import {
+ GlDeprecatedBadge as GlBadge,
+ GlLink,
+ GlLoadingIcon,
+ GlPagination,
+ GlSprintf,
+ GlTable,
+} from '@gitlab/ui';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
export default {
+ nodeMemoryText: __('%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)'),
+ nodeCpuText: __('%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)'),
components: {
GlBadge,
GlLink,
GlLoadingIcon,
GlPagination,
+ GlSprintf,
GlTable,
},
directives: {
tooltip,
},
computed: {
- ...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'totalCulsters']),
+ ...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'providers', 'totalCulsters']),
currentPage: {
get() {
return this.page;
@@ -37,19 +48,18 @@ export default {
key: 'environment_scope',
label: __('Environment scope'),
},
- // Wait for backend to send these fields
- // {
- // key: 'size',
- // label: __('Size'),
- // },
- // {
- // key: 'cpu',
- // label: __('Total cores (vCPUs)'),
- // },
- // {
- // key: 'memory',
- // label: __('Total memory (GB)'),
- // },
+ {
+ key: 'node_size',
+ label: __('Nodes'),
+ },
+ {
+ key: 'total_cpu',
+ label: __('Total cores (CPUs)'),
+ },
+ {
+ key: 'total_memory',
+ label: __('Total memory (GB)'),
+ },
{
key: 'cluster_type',
label: __('Cluster level'),
@@ -66,14 +76,105 @@ export default {
},
methods: {
...mapActions(['fetchClusters', 'setPage']),
- statusClass(status) {
- const iconClass = STATUSES[status] || STATUSES.default;
- return iconClass.className;
+ k8sQuantityToGb(quantity) {
+ if (!quantity) {
+ return 0;
+ } else if (quantity.endsWith(__('Ki'))) {
+ return parseInt(quantity.substr(0, quantity.length - 2), 10) * 0.000001024;
+ } else if (quantity.endsWith(__('Mi'))) {
+ return parseInt(quantity.substr(0, quantity.length - 2), 10) * 0.001048576;
+ }
+
+ // We are trying to track quantity types coming from Kubernetes.
+ // Sentry will notify us if we are missing types.
+ throw new Error(`UnknownK8sMemoryQuantity:${quantity}`);
+ },
+ k8sQuantityToCpu(quantity) {
+ if (!quantity) {
+ return 0;
+ } else if (quantity.endsWith('m')) {
+ return parseInt(quantity.substr(0, quantity.length - 1), 10) / 1000.0;
+ } else if (quantity.endsWith('n')) {
+ return parseInt(quantity.substr(0, quantity.length - 1), 10) / 1000000000.0;
+ }
+
+ // We are trying to track quantity types coming from Kubernetes.
+ // Sentry will notify us if we are missing types.
+ throw new Error(`UnknownK8sCpuQuantity:${quantity}`);
+ },
+ selectedProvider(provider) {
+ return this.providers[provider] || this.providers.default;
},
statusTitle(status) {
const iconTitle = STATUSES[status] || STATUSES.default;
return sprintf(__('Status: %{title}'), { title: iconTitle.title }, false);
},
+ totalMemoryAndUsage(nodes) {
+ try {
+ // For EKS node.usage will not be present unless the user manually
+ // install the metrics server
+ if (nodes && nodes[0].usage) {
+ let totalAllocatableMemory = 0;
+ let totalUsedMemory = 0;
+
+ nodes.reduce((total, node) => {
+ const allocatableMemoryQuantity = node.status.allocatable.memory;
+ const allocatableMemoryGb = this.k8sQuantityToGb(allocatableMemoryQuantity);
+ totalAllocatableMemory += allocatableMemoryGb;
+
+ const usedMemoryQuantity = node.usage.memory;
+ const usedMemoryGb = this.k8sQuantityToGb(usedMemoryQuantity);
+ totalUsedMemory += usedMemoryGb;
+
+ return null;
+ }, 0);
+
+ const freeSpacePercentage = (1 - totalUsedMemory / totalAllocatableMemory) * 100;
+
+ return {
+ totalMemory: totalAllocatableMemory.toFixed(2),
+ freeSpacePercentage: Math.round(freeSpacePercentage),
+ };
+ }
+ } catch (error) {
+ Sentry.captureException(error);
+ }
+
+ return { totalMemory: null, freeSpacePercentage: null };
+ },
+ totalCpuAndUsage(nodes) {
+ try {
+ // For EKS node.usage will not be present unless the user manually
+ // install the metrics server
+ if (nodes && nodes[0].usage) {
+ let totalAllocatableCpu = 0;
+ let totalUsedCpu = 0;
+
+ nodes.reduce((total, node) => {
+ const allocatableCpuQuantity = node.status.allocatable.cpu;
+ const allocatableCpu = this.k8sQuantityToCpu(allocatableCpuQuantity);
+ totalAllocatableCpu += allocatableCpu;
+
+ const usedCpuQuantity = node.usage.cpu;
+ const usedCpuGb = this.k8sQuantityToCpu(usedCpuQuantity);
+ totalUsedCpu += usedCpuGb;
+
+ return null;
+ }, 0);
+
+ const freeSpacePercentage = (1 - totalUsedCpu / totalAllocatableCpu) * 100;
+
+ return {
+ totalCpu: totalAllocatableCpu.toFixed(2),
+ freeSpacePercentage: Math.round(freeSpacePercentage),
+ };
+ }
+ } catch (error) {
+ Sentry.captureException(error);
+ }
+
+ return { totalCpu: null, freeSpacePercentage: null };
+ },
},
};
</script>
@@ -84,27 +185,68 @@ export default {
<section v-else>
<gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
<template #cell(name)="{ item }">
- <div class="d-flex flex-row-reverse flex-md-row js-status">
- <gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path">
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start js-status"
+ >
+ <img
+ :src="selectedProvider(item.provider_type).path"
+ :alt="selectedProvider(item.provider_type).text"
+ class="gl-w-6 gl-h-6 gl-display-flex gl-align-items-center"
+ />
+
+ <gl-link
+ data-qa-selector="cluster"
+ :data-qa-cluster-name="item.name"
+ :href="item.path"
+ class="gl-px-3"
+ >
{{ item.name }}
</gl-link>
<gl-loading-icon
- v-if="item.status === 'deleting'"
+ v-if="item.status === 'deleting' || item.status === 'creating'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
- class="mr-2 ml-md-2"
/>
- <div
- v-else
- v-tooltip
- class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2"
- :class="statusClass(item.status)"
- :title="statusTitle(item.status)"
- ></div>
</div>
</template>
+
+ <template #cell(node_size)="{ item }">
+ <span v-if="item.nodes">{{ item.nodes.length }}</span>
+ <small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-400">{{
+ __('Unknown')
+ }}</small>
+ </template>
+
+ <template #cell(total_cpu)="{ item }">
+ <span v-if="item.nodes">
+ <gl-sprintf :message="$options.nodeCpuText">
+ <template #totalCpu>{{ totalCpuAndUsage(item.nodes).totalCpu }}</template>
+ <template #freeSpacePercentage>{{
+ totalCpuAndUsage(item.nodes).freeSpacePercentage
+ }}</template>
+ <template #percentSymbol
+ >%</template
+ >
+ </gl-sprintf>
+ </span>
+ </template>
+
+ <template #cell(total_memory)="{ item }">
+ <span v-if="item.nodes">
+ <gl-sprintf :message="$options.nodeMemoryText">
+ <template #totalMemory>{{ totalMemoryAndUsage(item.nodes).totalMemory }}</template>
+ <template #freeSpacePercentage>{{
+ totalMemoryAndUsage(item.nodes).freeSpacePercentage
+ }}</template>
+ <template #percentSymbol
+ >%</template
+ >
+ </gl-sprintf>
+ </span>
+ </template>
+
<template #cell(cluster_type)="{value}">
<gl-badge variant="light">
{{ value }}
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index eebcaa086f9..3e8ef3151a6 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -6,6 +6,8 @@ export const CLUSTER_TYPES = {
instance_type: __('Instance'),
};
+export const MAX_REQUESTS = 3;
+
export const STATUSES = {
default: { className: 'bg-white', title: __('Unknown') },
disabled: { className: 'disabled', title: __('Disabled') },
@@ -13,4 +15,5 @@ export const STATUSES = {
unreachable: { className: 'bg-danger', title: __('Unreachable') },
authentication_failure: { className: 'bg-warning', title: __('Authentication Failure') },
deleting: { title: __('Deleting') },
+ creating: { title: __('Creating') },
};
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index 67d0a33030b..51ad8769250 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -9,12 +9,10 @@ export default () => {
return;
}
- const { endpoint } = entryPoint.dataset;
-
// eslint-disable-next-line no-new
new Vue({
el: '#js-clusters-list-app',
- store: createStore({ endpoint }),
+ store: createStore(entryPoint.dataset),
render(createElement) {
return createElement(Clusters);
},
diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js
index 919625f69b4..5245c307c8c 100644
--- a/app/assets/javascripts/clusters_list/store/actions.js
+++ b/app/assets/javascripts/clusters_list/store/actions.js
@@ -2,10 +2,23 @@ import Poll from '~/lib/utils/poll';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import { MAX_REQUESTS } from '../constants';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import * as Sentry from '@sentry/browser';
import * as types from './mutation_types';
+const allNodesPresent = (clusters, retryCount) => {
+ /*
+ Nodes are coming from external Kubernetes clusters.
+ They may fail for reasons GitLab cannot control.
+ MAX_REQUESTS will ensure this poll stops at some point.
+ */
+ return retryCount > MAX_REQUESTS || clusters.every(cluster => cluster.nodes != null);
+};
+
export const fetchClusters = ({ state, commit }) => {
+ let retryCount = 0;
+
const poll = new Poll({
resource: {
fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint),
@@ -13,16 +26,40 @@ export const fetchClusters = ({ state, commit }) => {
data: `${state.endpoint}?page=${state.page}`,
method: 'fetchClusters',
successCallback: ({ data, headers }) => {
- if (data.clusters) {
- const normalizedHeaders = normalizeHeaders(headers);
- const paginationInformation = parseIntPagination(normalizedHeaders);
+ retryCount += 1;
+
+ try {
+ if (data.clusters) {
+ const normalizedHeaders = normalizeHeaders(headers);
+ const paginationInformation = parseIntPagination(normalizedHeaders);
+
+ commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
+ commit(types.SET_LOADING_STATE, false);
- commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
- commit(types.SET_LOADING_STATE, false);
+ if (allNodesPresent(data.clusters, retryCount)) {
+ poll.stop();
+ }
+ }
+ } catch (error) {
poll.stop();
+
+ Sentry.withScope(scope => {
+ scope.setTag('javascript_clusters_list', 'fetchClustersSuccessCallback');
+ Sentry.captureException(error);
+ });
}
},
- errorCallback: () => flash(__('An error occurred while loading clusters')),
+ errorCallback: response => {
+ poll.stop();
+
+ commit(types.SET_LOADING_STATE, false);
+ flash(__('Clusters|An error occurred while loading clusters'));
+
+ Sentry.withScope(scope => {
+ scope.setTag('javascript_clusters_list', 'fetchClustersErrorCallback');
+ Sentry.captureException(response);
+ });
+ },
});
poll.makeRequest();
diff --git a/app/assets/javascripts/clusters_list/store/state.js b/app/assets/javascripts/clusters_list/store/state.js
index d590ea09e66..0023b43ed92 100644
--- a/app/assets/javascripts/clusters_list/store/state.js
+++ b/app/assets/javascripts/clusters_list/store/state.js
@@ -5,5 +5,10 @@ export default (initialState = {}) => ({
clusters: [],
clustersPerPage: 0,
page: 1,
+ providers: {
+ aws: { path: initialState.imgTagsAwsPath, text: initialState.imgTagsAwsText },
+ default: { path: initialState.imgTagsDefaultPath, text: initialState.imgTagsDefaultText },
+ gcp: { path: initialState.imgTagsGcpPath, text: initialState.imgTagsGcpText },
+ },
totalCulsters: 0,
});