diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-25 21:08:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-25 21:08:55 +0300 |
commit | 1e3f5ab634699e9d50779f05d2ae8dfc8a3ab9b3 (patch) | |
tree | c96727e136f4dc4fdbc1190895439d41e0b07fc5 /app/assets/javascripts/environments | |
parent | ba8e92f7c9938d7dba333d2396cdd14bfa0de726 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/environments')
9 files changed, 279 insertions, 2 deletions
diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue index 736eaa7062d..41abfcf6dc8 100644 --- a/app/assets/javascripts/environments/components/kubernetes_overview.vue +++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue @@ -5,6 +5,7 @@ import csrf from '~/lib/utils/csrf'; import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils'; import KubernetesAgentInfo from './kubernetes_agent_info.vue'; import KubernetesPods from './kubernetes_pods.vue'; +import KubernetesTabs from './kubernetes_tabs.vue'; export default { components: { @@ -13,6 +14,7 @@ export default { GlAlert, KubernetesAgentInfo, KubernetesPods, + KubernetesTabs, }, inject: ['kasTunnelUrl'], props: { @@ -103,6 +105,10 @@ export default { :configuration="k8sAccessConfiguration" :namespace="namespace" class="gl-mb-5" + @cluster-error="onClusterError" /> + <kubernetes-tabs + :configuration="k8sAccessConfiguration" + class="gl-mb-5" @cluster-error="onClusterError" /></template> </gl-collapse> diff --git a/app/assets/javascripts/environments/components/kubernetes_tabs.vue b/app/assets/javascripts/environments/components/kubernetes_tabs.vue new file mode 100644 index 00000000000..b1eb92a4049 --- /dev/null +++ b/app/assets/javascripts/environments/components/kubernetes_tabs.vue @@ -0,0 +1,158 @@ +<script> +import { GlTabs, GlTab, GlLoadingIcon, GlBadge, GlTable, GlPagination } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import k8sServicesQuery from '../graphql/queries/k8s_services.query.graphql'; +import { generateServicePortsString, getServiceAge } from '../helpers/k8s_integration_helper'; +import { SERVICES_LIMIT_PER_PAGE } from '../constants'; + +const tableHeadingClasses = 'gl-bg-gray-50! gl-font-weight-bold gl-white-space-nowrap'; + +export default { + components: { + GlTabs, + GlTab, + GlBadge, + GlTable, + GlPagination, + GlLoadingIcon, + }, + apollo: { + k8sServices: { + query: k8sServicesQuery, + variables() { + return { + configuration: this.configuration, + }; + }, + update(data) { + return data?.k8sServices || []; + }, + error(error) { + this.$emit('cluster-error', error); + }, + }, + }, + props: { + configuration: { + required: true, + type: Object, + }, + }, + data() { + return { + currentPage: 1, + }; + }, + computed: { + servicesItems() { + if (!this.k8sServices?.length) return []; + + return this.k8sServices.map((service) => { + return { + name: service?.metadata?.name, + namespace: service?.metadata?.namespace, + type: service?.spec?.type, + clusterIP: service?.spec?.clusterIP, + externalIP: service?.spec?.externalIP, + ports: generateServicePortsString(service?.spec?.ports), + age: getServiceAge(service?.metadata?.creationTimestamp), + }; + }); + }, + servicesLoading() { + return this.$apollo.queries.k8sServices.loading; + }, + showPagination() { + return this.servicesItems.length > SERVICES_LIMIT_PER_PAGE; + }, + prevPage() { + return Math.max(this.currentPage - 1, 0); + }, + nextPage() { + const nextPage = this.currentPage + 1; + return nextPage > Math.ceil(this.servicesItems.length / SERVICES_LIMIT_PER_PAGE) + ? null + : nextPage; + }, + }, + i18n: { + servicesTitle: s__('Environment|Services'), + name: __('Name'), + namespace: __('Namespace'), + status: __('Status'), + type: __('Type'), + clusterIP: s__('Environment|Cluster IP'), + externalIP: s__('Environment|External IP'), + ports: s__('Environment|Ports'), + age: s__('Environment|Age'), + }, + servicesFields: [ + { + key: 'name', + label: __('Name'), + thClass: tableHeadingClasses, + }, + { + key: 'namespace', + label: __('Namespace'), + thClass: tableHeadingClasses, + }, + { + key: 'type', + label: __('Type'), + thClass: tableHeadingClasses, + }, + { + key: 'clusterIP', + label: s__('Environment|Cluster IP'), + thClass: tableHeadingClasses, + }, + { + key: 'externalIP', + label: s__('Environment|External IP'), + thClass: tableHeadingClasses, + }, + { + key: 'ports', + label: s__('Environment|Ports'), + thClass: tableHeadingClasses, + }, + { + key: 'age', + label: s__('Environment|Age'), + thClass: tableHeadingClasses, + }, + ], + SERVICES_LIMIT_PER_PAGE, +}; +</script> +<template> + <gl-tabs> + <gl-tab> + <template #title> + {{ $options.i18n.servicesTitle }} + <gl-badge size="sm" class="gl-tab-counter-badge">{{ servicesItems.length }}</gl-badge> + </template> + + <gl-loading-icon v-if="servicesLoading" /> + + <gl-table + v-else + :fields="$options.servicesFields" + :items="servicesItems" + :per-page="$options.SERVICES_LIMIT_PER_PAGE" + :current-page="currentPage" + stacked="lg" + class="gl-bg-white! gl-mt-3" + /> + <gl-pagination + v-if="showPagination" + v-model="currentPage" + :prev-page="prevPage" + :next-page="nextPage" + align="center" + class="gl-mt-6" + /> + </gl-tab> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 28424322dd2..e675a73ba7d 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -87,3 +87,5 @@ export const ENVIRONMENT_NEW_HELP_TEXT = __( ); export const ENVIRONMENT_EDIT_HELP_TEXT = ENVIRONMENT_NEW_HELP_TEXT; + +export const SERVICES_LIMIT_PER_PAGE = 10; diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index 0482741979b..bb6f57e7e80 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -6,6 +6,7 @@ import environmentToDeleteQuery from './queries/environment_to_delete.query.grap import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; import environmentToStopQuery from './queries/environment_to_stop.query.graphql'; import k8sPodsQuery from './queries/k8s_pods.query.graphql'; +import k8sServicesQuery from './queries/k8s_services.query.graphql'; import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; @@ -87,7 +88,23 @@ export const apolloProvider = (endpoint) => { query: k8sPodsQuery, data: { status: { - phase: '', + phase: null, + }, + }, + }); + cache.writeQuery({ + query: k8sServicesQuery, + data: { + metadata: { + name: null, + namespace: null, + creationTimestamp: null, + }, + spec: { + type: null, + clusterIP: null, + externalIP: null, + ports: [], }, }, }); diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql index 818bca24d51..2d57ede8c15 100644 --- a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql @@ -1,4 +1,4 @@ -query getK8sPods($configuration: Object, $namespace: String) { +query getK8sPods($configuration: LocalConfiguration, $namespace: String) { k8sPods(configuration: $configuration, namespace: $namespace) @client { status { phase diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql new file mode 100644 index 00000000000..d97849eecc1 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql @@ -0,0 +1,15 @@ +query getK8sServices($configuration: LocalConfiguration) { + k8sServices(configuration: $configuration) @client { + metadata { + name + namespace + creationTimestamp + } + spec { + type + clusterIP + externalIP + ports + } + } +} diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 013467e34be..8ebeeb92a53 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -85,6 +85,30 @@ export const resolvers = (endpoint) => ({ throw error; }); }, + k8sServices(_, { configuration }) { + const coreV1Api = new CoreV1Api(new Configuration(configuration)); + return coreV1Api + .listCoreV1ServiceForAllNamespaces() + .then((res) => { + const items = res?.data?.items || []; + return items.map((item) => { + const { type, clusterIP, externalIP, ports } = item.spec; + return { + metadata: item.metadata, + spec: { + type, + clusterIP: clusterIP || '-', + externalIP: externalIP || '-', + ports, + }, + }; + }); + }) + .catch((err) => { + const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; + throw error; + }); + }, }, Mutation: { stopEnvironmentREST(_, { environment }, { client }) { diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index 4de271935d6..b85bf1e10d3 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -75,6 +75,24 @@ input LocalConfiguration { baseOptions: JSON } +type k8sServiceMetadata { + name: String + namespace: String + creationTimestamp: String +} + +type k8sServiceSpec { + type: String + clusterIP: String + externalIP: String + ports: JSON +} + +type LocalK8sServices { + metadata: k8sServiceMetadata + spec: k8sServiceSpec +} + extend type Query { environmentApp(page: Int, scope: String): LocalEnvironmentApp folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder @@ -85,6 +103,7 @@ extend type Query { isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean isLastDeployment(environment: LocalEnvironmentInput): Boolean k8sPods(configuration: LocalConfiguration, namespace: String): [LocalK8sPods] + k8sServices(configuration: LocalConfiguration): [LocalK8sServices] } extend type Mutation { diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js new file mode 100644 index 00000000000..6a94ac31fa1 --- /dev/null +++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js @@ -0,0 +1,36 @@ +import { differenceInSeconds } from '~/lib/utils/datetime_utility'; + +export function generateServicePortsString(ports) { + if (!ports?.length) return ''; + + return ports + .map((port) => { + const nodePort = port.nodePort ? `:${port.nodePort}` : ''; + return `${port.port}${nodePort}/${port.protocol}`; + }) + .join(', '); +} + +export function getServiceAge(creationTimestamp) { + if (!creationTimestamp) return ''; + + const timeDifference = differenceInSeconds(new Date(creationTimestamp), new Date()); + + const seconds = Math.floor(timeDifference); + const minutes = Math.floor(seconds / 60) % 60; + const hours = Math.floor(seconds / 60 / 60) % 24; + const days = Math.floor(seconds / 60 / 60 / 24); + + let ageString; + if (days > 0) { + ageString = `${days}d`; + } else if (hours > 0) { + ageString = `${hours}h`; + } else if (minutes > 0) { + ageString = `${minutes}m`; + } else { + ageString = `${seconds}s`; + } + + return ageString; +} |