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:
Diffstat (limited to 'app/assets/javascripts/kubernetes_dashboard')
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue7
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/components/workload_layout.vue19
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/components/workload_table.vue9
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/constants.js44
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/client.js92
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js56
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql18
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql18
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_services.query.graphql17
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js130
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js30
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue84
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue80
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/services_page.vue69
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/constants.js6
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/routes.js34
16 files changed, 658 insertions, 55 deletions
diff --git a/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue b/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
index 0d219f915c9..bcc0ddf824a 100644
--- a/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
+++ b/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
@@ -14,8 +14,7 @@ export default {
item: {
type: Object,
required: true,
- validator: (item) =>
- ['name', 'kind', 'labels', 'annotations', 'status'].every((key) => item[key]),
+ validator: (item) => ['name', 'kind', 'labels', 'annotations'].every((key) => item[key]),
},
},
computed: {
@@ -51,7 +50,7 @@ export default {
<template>
<ul class="gl-list-style-none">
<workload-details-item :label="$options.i18n.name">
- {{ item.name }}
+ <span class="gl-word-break-word"> {{ item.name }}</span>
</workload-details-item>
<workload-details-item :label="$options.i18n.kind">
{{ item.kind }}
@@ -63,7 +62,7 @@ export default {
</gl-badge>
</div>
</workload-details-item>
- <workload-details-item :label="$options.i18n.status">
+ <workload-details-item v-if="item.status" :label="$options.i18n.status">
<gl-badge :variant="$options.WORKLOAD_STATUS_BADGE_VARIANTS[item.status]">{{
item.status
}}</gl-badge></workload-details-item
diff --git a/app/assets/javascripts/kubernetes_dashboard/components/workload_layout.vue b/app/assets/javascripts/kubernetes_dashboard/components/workload_layout.vue
index 8c6a08ad504..6579e0229e6 100644
--- a/app/assets/javascripts/kubernetes_dashboard/components/workload_layout.vue
+++ b/app/assets/javascripts/kubernetes_dashboard/components/workload_layout.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlAlert, GlDrawer } from '@gitlab/ui';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
+import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import WorkloadStats from './workload_stats.vue';
import WorkloadTable from './workload_table.vue';
import WorkloadDetails from './workload_details.vue';
@@ -33,6 +34,11 @@ export default {
type: Array,
required: true,
},
+ fields: {
+ type: Array,
+ required: false,
+ default: undefined,
+ },
},
data() {
return {
@@ -40,6 +46,11 @@ export default {
selectedItem: {},
};
},
+ computed: {
+ getDrawerHeaderHeight() {
+ return getContentWrapperHeight();
+ },
+ },
methods: {
closeDetailsDrawer() {
this.showDetailsDrawer = false;
@@ -59,16 +70,18 @@ export default {
</gl-alert>
<div v-else>
<workload-stats :stats="stats" />
- <workload-table :items="items" @select-item="onItemSelect" />
+ <workload-table :items="items" :fields="fields" @select-item="onItemSelect" />
<gl-drawer
:open="showDetailsDrawer"
- header-height="calc(var(--top-bar-height) + var(--performance-bar-height))"
+ :header-height="getDrawerHeaderHeight"
:z-index="$options.DRAWER_Z_INDEX"
@close="closeDetailsDrawer"
>
<template #title>
- <h4 class="gl-font-weight-bold gl-font-size-h2 gl-m-0">{{ selectedItem.name }}</h4>
+ <h4 class="gl-font-weight-bold gl-font-size-h2 gl-m-0 gl-word-break-word">
+ {{ selectedItem.name }}
+ </h4>
</template>
<template #default>
<workload-details :item="selectedItem" />
diff --git a/app/assets/javascripts/kubernetes_dashboard/components/workload_table.vue b/app/assets/javascripts/kubernetes_dashboard/components/workload_table.vue
index d3704863538..83940fb91c8 100644
--- a/app/assets/javascripts/kubernetes_dashboard/components/workload_table.vue
+++ b/app/assets/javascripts/kubernetes_dashboard/components/workload_table.vue
@@ -1,9 +1,9 @@
<script>
import { GlTable, GlBadge, GlPagination } from '@gitlab/ui';
+import { __ } from '~/locale';
import {
WORKLOAD_STATUS_BADGE_VARIANTS,
PAGE_SIZE,
- TABLE_HEADING_CLASSES,
DEFAULT_WORKLOAD_TABLE_FIELDS,
} from '../constants';
@@ -34,7 +34,6 @@ export default {
return this.fields.map((field) => {
return {
...field,
- thClass: TABLE_HEADING_CLASSES,
sortable: true,
};
});
@@ -45,6 +44,9 @@ export default {
this.$emit('select-item', item);
},
},
+ i18n: {
+ emptyText: __('No results found'),
+ },
PAGE_SIZE,
WORKLOAD_STATUS_BADGE_VARIANTS,
TABLE_CELL_CLASSES: 'gl-p-2',
@@ -58,9 +60,10 @@ export default {
:fields="tableFields"
:per-page="$options.PAGE_SIZE"
:current-page="currentPage"
+ :empty-text="$options.i18n.emptyText"
tbody-tr-class="gl-hover-cursor-pointer"
+ show-empty
stacked="md"
- bordered
hover
@row-clicked="selectItem"
>
diff --git a/app/assets/javascripts/kubernetes_dashboard/constants.js b/app/assets/javascripts/kubernetes_dashboard/constants.js
index b93740aec90..458a79cbcb6 100644
--- a/app/assets/javascripts/kubernetes_dashboard/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/constants.js
@@ -1,10 +1,12 @@
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
export const STATUS_RUNNING = 'Running';
export const STATUS_PENDING = 'Pending';
export const STATUS_SUCCEEDED = 'Succeeded';
export const STATUS_FAILED = 'Failed';
export const STATUS_READY = 'Ready';
+export const STATUS_COMPLETED = 'Completed';
+export const STATUS_SUSPENDED = 'Suspended';
export const STATUS_LABELS = {
[STATUS_RUNNING]: s__('KubernetesDashboard|Running'),
@@ -12,6 +14,8 @@ export const STATUS_LABELS = {
[STATUS_SUCCEEDED]: s__('KubernetesDashboard|Succeeded'),
[STATUS_FAILED]: s__('KubernetesDashboard|Failed'),
[STATUS_READY]: s__('KubernetesDashboard|Ready'),
+ [STATUS_COMPLETED]: s__('KubernetesDashboard|Completed'),
+ [STATUS_SUSPENDED]: s__('KubernetesDashboard|Suspended'),
};
export const WORKLOAD_STATUS_BADGE_VARIANTS = {
@@ -20,24 +24,27 @@ export const WORKLOAD_STATUS_BADGE_VARIANTS = {
[STATUS_SUCCEEDED]: 'success',
[STATUS_FAILED]: 'danger',
[STATUS_READY]: 'success',
+ [STATUS_COMPLETED]: 'success',
+ [STATUS_SUSPENDED]: 'neutral',
};
export const PAGE_SIZE = 20;
-export const TABLE_HEADING_CLASSES = 'gl-bg-gray-50! gl-font-weight-bold gl-white-space-nowrap';
-
export const DEFAULT_WORKLOAD_TABLE_FIELDS = [
{
key: 'name',
label: s__('KubernetesDashboard|Name'),
+ tdClass: 'gl-md-w-half gl-lg-w-40p gl-word-break-word',
},
{
key: 'status',
label: s__('KubernetesDashboard|Status'),
+ tdClass: 'gl-md-w-15',
},
{
key: 'namespace',
label: s__('KubernetesDashboard|Namespace'),
+ tdClass: 'gl-md-w-30p gl-lg-w-40p gl-word-break-word',
},
{
key: 'age',
@@ -47,3 +54,34 @@ export const DEFAULT_WORKLOAD_TABLE_FIELDS = [
export const STATUS_TRUE = 'True';
export const STATUS_FALSE = 'False';
+
+export const SERVICES_TABLE_FIELDS = [
+ {
+ key: 'name',
+ label: __('Name'),
+ },
+ {
+ key: 'namespace',
+ label: __('Namespace'),
+ },
+ {
+ key: 'type',
+ label: __('Type'),
+ },
+ {
+ key: 'clusterIP',
+ label: s__('Environment|Cluster IP'),
+ },
+ {
+ key: 'externalIP',
+ label: s__('Environment|External IP'),
+ },
+ {
+ key: 'ports',
+ label: s__('Environment|Ports'),
+ },
+ {
+ key: 'age',
+ label: s__('Environment|Age'),
+ },
+];
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
index 5894472d83b..9454465df9d 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
@@ -6,6 +6,9 @@ import k8sDeploymentsQuery from './queries/k8s_dashboard_deployments.query.graph
import k8sStatefulSetsQuery from './queries/k8s_dashboard_stateful_sets.query.graphql';
import k8sReplicaSetsQuery from './queries/k8s_dashboard_replica_sets.query.graphql';
import k8sDaemonSetsQuery from './queries/k8s_dashboard_daemon_sets.query.graphql';
+import k8sJobsQuery from './queries/k8s_dashboard_jobs.query.graphql';
+import k8sCronJobsQuery from './queries/k8s_dashboard_cron_jobs.query.graphql';
+import k8sServicesQuery from './queries/k8s_dashboard_services.query.graphql';
import { resolvers } from './resolvers';
export const apolloProvider = () => {
@@ -14,16 +17,18 @@ export const apolloProvider = () => {
});
const { cache } = defaultClient;
+ const metadata = {
+ name: null,
+ namespace: null,
+ creationTimestamp: null,
+ labels: null,
+ annotations: null,
+ };
+
cache.writeQuery({
query: k8sPodsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
phase: null,
},
@@ -33,13 +38,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sDeploymentsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
conditions: null,
},
@@ -49,13 +48,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sStatefulSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
readyReplicas: null,
},
@@ -68,13 +61,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sReplicaSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
readyReplicas: null,
},
@@ -87,13 +74,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sDaemonSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
numberMisscheduled: null,
numberReady: null,
@@ -102,6 +83,47 @@ export const apolloProvider = () => {
},
});
+ cache.writeQuery({
+ query: k8sJobsQuery,
+ data: {
+ metadata,
+ status: {
+ failed: null,
+ succeeded: null,
+ },
+ spec: {
+ completions: null,
+ },
+ },
+ });
+
+ cache.writeQuery({
+ query: k8sCronJobsQuery,
+ data: {
+ metadata,
+ status: {
+ active: null,
+ lastScheduleTime: null,
+ },
+ spec: {
+ suspend: null,
+ },
+ },
+ });
+
+ cache.writeQuery({
+ query: k8sServicesQuery,
+ data: {
+ metadata,
+ spec: {
+ type: null,
+ clusterIP: null,
+ externalIP: null,
+ ports: null,
+ },
+ },
+ });
+
return new VueApollo({
defaultClient,
});
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
index 47c2f543357..b9c195d83d0 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
@@ -43,6 +43,62 @@ export const mapSetItem = (item) => {
return { status, metadata, spec };
};
+export const mapJobItem = (item) => {
+ const metadata = {
+ ...item.metadata,
+ annotations: item.metadata?.annotations || {},
+ labels: item.metadata?.labels || {},
+ };
+
+ const status = {
+ failed: item.status?.failed || 0,
+ succeeded: item.status?.succeeded || 0,
+ };
+
+ return {
+ status,
+ metadata,
+ spec: item.spec,
+ };
+};
+
+export const mapServicesItems = (item) => {
+ const { type, clusterIP, externalIP, ports } = item.spec;
+
+ return {
+ metadata: {
+ ...item.metadata,
+ annotations: item.metadata?.annotations || {},
+ labels: item.metadata?.labels || {},
+ },
+ spec: {
+ type,
+ clusterIP: clusterIP || '-',
+ externalIP: externalIP || '-',
+ ports,
+ },
+ };
+};
+
+export const mapCronJobItem = (item) => {
+ const metadata = {
+ ...item.metadata,
+ annotations: item.metadata?.annotations || {},
+ labels: item.metadata?.labels || {},
+ };
+
+ const status = {
+ active: item.status?.active || 0,
+ lastScheduleTime: item.status?.lastScheduleTime || null,
+ };
+
+ return {
+ status,
+ metadata,
+ spec: item.spec,
+ };
+};
+
export const watchWorkloadItems = ({
client,
query,
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql
new file mode 100644
index 00000000000..fe20cd2e70e
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql
@@ -0,0 +1,18 @@
+query getK8sDashboardCronJobs($configuration: LocalConfiguration) {
+ k8sCronJobs(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ status {
+ active
+ lastScheduleTime
+ }
+ spec {
+ suspend
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql
new file mode 100644
index 00000000000..86afb47f2f9
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql
@@ -0,0 +1,18 @@
+query getK8sDashboardJobs($configuration: LocalConfiguration) {
+ k8sJobs(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ status {
+ failed
+ succeeded
+ }
+ spec {
+ completions
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_services.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_services.query.graphql
new file mode 100644
index 00000000000..7d42d66183e
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_services.query.graphql
@@ -0,0 +1,17 @@
+query getK8sDashboardServices($configuration: LocalConfiguration) {
+ k8sServices(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ spec {
+ type
+ clusterIP
+ externalIP
+ ports
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
index e59bed5581b..75285ad2cca 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
@@ -1,4 +1,4 @@
-import { Configuration, AppsV1Api } from '@gitlab/cluster-client';
+import { Configuration, CoreV1Api, AppsV1Api, BatchV1Api } from '@gitlab/cluster-client';
import {
getK8sPods,
@@ -7,12 +7,18 @@ import {
mapSetItem,
buildWatchPath,
watchWorkloadItems,
+ mapJobItem,
+ mapCronJobItem,
+ mapServicesItems,
} from '../helpers/resolver_helpers';
import k8sDashboardPodsQuery from '../queries/k8s_dashboard_pods.query.graphql';
import k8sDashboardDeploymentsQuery from '../queries/k8s_dashboard_deployments.query.graphql';
import k8sDashboardStatefulSetsQuery from '../queries/k8s_dashboard_stateful_sets.query.graphql';
import k8sDashboardReplicaSetsQuery from '../queries/k8s_dashboard_replica_sets.query.graphql';
import k8sDaemonSetsQuery from '../queries/k8s_dashboard_daemon_sets.query.graphql';
+import k8sJobsQuery from '../queries/k8s_dashboard_jobs.query.graphql';
+import k8sCronJobsQuery from '../queries/k8s_dashboard_cron_jobs.query.graphql';
+import k8sServicesQuery from '../queries/k8s_dashboard_services.query.graphql';
export default {
k8sPods(_, { configuration }, { client }) {
@@ -61,10 +67,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const statefulSetsApi = namespace
? appsV1api.listAppsV1NamespacedStatefulSet({ namespace })
: appsV1api.listAppsV1StatefulSetForAllNamespaces();
- return deploymentsApi
+ return statefulSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'statefulsets',
@@ -98,10 +104,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const replicaSetsApi = namespace
? appsV1api.listAppsV1NamespacedReplicaSet({ namespace })
: appsV1api.listAppsV1ReplicaSetForAllNamespaces();
- return deploymentsApi
+ return replicaSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'replicasets',
@@ -135,10 +141,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const daemonSetsApi = namespace
? appsV1api.listAppsV1NamespacedDaemonSet({ namespace })
: appsV1api.listAppsV1DaemonSetForAllNamespaces();
- return deploymentsApi
+ return daemonSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'daemonsets',
@@ -166,4 +172,114 @@ export default {
}
});
},
+
+ k8sJobs(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const batchV1api = new BatchV1Api(config);
+ const jobsApi = namespace
+ ? batchV1api.listBatchV1NamespacedJob({ namespace })
+ : batchV1api.listBatchV1JobForAllNamespaces();
+ return jobsApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'jobs',
+ api: 'apis/batch/v1',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sJobsQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sJobs',
+ mapFn: mapJobItem,
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapJobItem);
+ })
+ .catch(async (err) => {
+ try {
+ await handleClusterError(err);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ });
+ },
+
+ k8sCronJobs(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const batchV1api = new BatchV1Api(config);
+ const cronJobsApi = namespace
+ ? batchV1api.listBatchV1NamespacedCronJob({ namespace })
+ : batchV1api.listBatchV1CronJobForAllNamespaces();
+ return cronJobsApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'cronjobs',
+ api: 'apis/batch/v1',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sCronJobsQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sCronJobs',
+ mapFn: mapCronJobItem,
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapCronJobItem);
+ })
+ .catch(async (err) => {
+ try {
+ await handleClusterError(err);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ });
+ },
+
+ k8sServices(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const coreV1Api = new CoreV1Api(config);
+ const servicesApi = namespace
+ ? coreV1Api.listCoreV1NamespacedService({ namespace })
+ : coreV1Api.listCoreV1ServiceForAllNamespaces();
+ return servicesApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'services',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sServicesQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sServices',
+ mapFn: mapServicesItems,
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapServicesItems);
+ })
+ .catch(async (err) => {
+ try {
+ await handleClusterError(err);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ });
+ },
};
diff --git a/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js b/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
index 24f43e21506..d3116fd611a 100644
--- a/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
+++ b/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
@@ -5,6 +5,8 @@ import {
STATUS_PENDING,
STATUS_READY,
STATUS_FAILED,
+ STATUS_COMPLETED,
+ STATUS_SUSPENDED,
} from '../constants';
export function getAge(creationTimestamp) {
@@ -58,3 +60,31 @@ export function calculateDaemonSetStatus(item) {
}
return STATUS_FAILED;
}
+
+export function calculateJobStatus(item) {
+ if (item.status.failed > 0 || item.status?.succeeded !== item.spec?.completions) {
+ return STATUS_FAILED;
+ }
+ return STATUS_COMPLETED;
+}
+
+export function calculateCronJobStatus(item) {
+ if (item.status?.active > 0 && !item.status?.lastScheduleTime) {
+ return STATUS_FAILED;
+ }
+ if (item.spec?.suspend) {
+ return STATUS_SUSPENDED;
+ }
+ return STATUS_READY;
+}
+
+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(', ');
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue
new file mode 100644
index 00000000000..2d57bfdc9fc
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue
@@ -0,0 +1,84 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, calculateCronJobStatus } from '../helpers/k8s_integration_helper';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sCronJobsQuery from '../graphql/queries/k8s_dashboard_cron_jobs.query.graphql';
+import { STATUS_FAILED, STATUS_READY, STATUS_SUSPENDED, STATUS_LABELS } from '../constants';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sCronJobs: {
+ query: k8sCronJobsQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.k8sCronJobs?.map((job) => {
+ return {
+ name: job.metadata?.name,
+ namespace: job.metadata?.namespace,
+ status: calculateCronJobStatus(job),
+ age: getAge(job.metadata?.creationTimestamp),
+ labels: job.metadata?.labels,
+ annotations: job.metadata?.annotations,
+ kind: s__('KubernetesDashboard|CronJob'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sCronJobs: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ cronJobsStats() {
+ return [
+ {
+ value: this.countJobsByStatus(STATUS_READY),
+ title: STATUS_LABELS[STATUS_READY],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_FAILED),
+ title: STATUS_LABELS[STATUS_FAILED],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_SUSPENDED),
+ title: STATUS_LABELS[STATUS_SUSPENDED],
+ },
+ ];
+ },
+ loading() {
+ return this.$apollo.queries.k8sCronJobs.loading;
+ },
+ },
+ methods: {
+ countJobsByStatus(phase) {
+ const filteredJobs = this.k8sCronJobs.filter((item) => item.status === phase) || [];
+
+ return filteredJobs.length;
+ },
+ },
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="cronJobsStats"
+ :items="k8sCronJobs"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue
new file mode 100644
index 00000000000..f9dbb53e8b4
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue
@@ -0,0 +1,80 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, calculateJobStatus } from '../helpers/k8s_integration_helper';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sJobsQuery from '../graphql/queries/k8s_dashboard_jobs.query.graphql';
+import { STATUS_FAILED, STATUS_COMPLETED, STATUS_LABELS } from '../constants';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sJobs: {
+ query: k8sJobsQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.k8sJobs?.map((job) => {
+ return {
+ name: job.metadata?.name,
+ namespace: job.metadata?.namespace,
+ status: calculateJobStatus(job),
+ age: getAge(job.metadata?.creationTimestamp),
+ labels: job.metadata?.labels,
+ annotations: job.metadata?.annotations,
+ kind: s__('KubernetesDashboard|Job'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sJobs: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ jobsStats() {
+ return [
+ {
+ value: this.countJobsByStatus(STATUS_COMPLETED),
+ title: STATUS_LABELS[STATUS_COMPLETED],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_FAILED),
+ title: STATUS_LABELS[STATUS_FAILED],
+ },
+ ];
+ },
+ loading() {
+ return this.$apollo.queries.k8sJobs.loading;
+ },
+ },
+ methods: {
+ countJobsByStatus(phase) {
+ const filteredJobs = this.k8sJobs.filter((item) => item.status === phase) || [];
+
+ return filteredJobs.length;
+ },
+ },
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="jobsStats"
+ :items="k8sJobs"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/services_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/services_page.vue
new file mode 100644
index 00000000000..4dc8fb6b6c0
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/services_page.vue
@@ -0,0 +1,69 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, generateServicePortsString } from '../helpers/k8s_integration_helper';
+import { SERVICES_TABLE_FIELDS } from '../constants';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sServicesQuery from '../graphql/queries/k8s_dashboard_services.query.graphql';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sServices: {
+ query: k8sServicesQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.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: getAge(service.metadata?.creationTimestamp),
+ labels: service.metadata?.labels,
+ annotations: service.metadata?.annotations,
+ kind: s__('KubernetesDashboard|Service'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sServices: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ loading() {
+ return this.$apollo.queries.k8sServices.loading;
+ },
+ servicesStats() {
+ return [];
+ },
+ },
+ SERVICES_TABLE_FIELDS,
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="servicesStats"
+ :items="k8sServices"
+ :fields="$options.SERVICES_TABLE_FIELDS"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/constants.js b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
index 700f501ade4..f02c01d7973 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
@@ -3,9 +3,15 @@ export const DEPLOYMENTS_ROUTE_NAME = 'deployments';
export const STATEFUL_SETS_ROUTE_NAME = 'statefulSets';
export const REPLICA_SETS_ROUTE_NAME = 'replicaSets';
export const DAEMON_SETS_ROUTE_NAME = 'daemonSets';
+export const JOBS_ROUTE_NAME = 'jobs';
+export const CRON_JOBS_ROUTE_NAME = 'cronJobs';
+export const SERVICES_ROUTE_NAME = 'services';
export const PODS_ROUTE_PATH = '/pods';
export const DEPLOYMENTS_ROUTE_PATH = '/deployments';
export const STATEFUL_SETS_ROUTE_PATH = '/statefulsets';
export const REPLICA_SETS_ROUTE_PATH = '/replicasets';
export const DAEMON_SETS_ROUTE_PATH = '/daemonsets';
+export const JOBS_ROUTE_PATH = '/jobs';
+export const CRON_JOBS_ROUTE_PATH = '/cronjobs';
+export const SERVICES_ROUTE_PATH = '/services';
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/routes.js b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
index a1684a62ca4..7448508de8a 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/routes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
@@ -4,6 +4,10 @@ import DeploymentsPage from '../pages/deployments_page.vue';
import StatefulSetsPage from '../pages/stateful_sets_page.vue';
import ReplicaSetsPage from '../pages/replica_sets_page.vue';
import DaemonSetsPage from '../pages/daemon_sets_page.vue';
+import JobsPage from '../pages/jobs_page.vue';
+import CronJobsPage from '../pages/cron_jobs_page.vue';
+import ServicesPage from '../pages/services_page.vue';
+
import {
PODS_ROUTE_NAME,
PODS_ROUTE_PATH,
@@ -15,6 +19,12 @@ import {
REPLICA_SETS_ROUTE_PATH,
DAEMON_SETS_ROUTE_NAME,
DAEMON_SETS_ROUTE_PATH,
+ JOBS_ROUTE_NAME,
+ JOBS_ROUTE_PATH,
+ CRON_JOBS_ROUTE_NAME,
+ CRON_JOBS_ROUTE_PATH,
+ SERVICES_ROUTE_NAME,
+ SERVICES_ROUTE_PATH,
} from './constants';
export default [
@@ -58,4 +68,28 @@ export default [
title: s__('KubernetesDashboard|DaemonSets'),
},
},
+ {
+ name: JOBS_ROUTE_NAME,
+ path: JOBS_ROUTE_PATH,
+ component: JobsPage,
+ meta: {
+ title: s__('KubernetesDashboard|Jobs'),
+ },
+ },
+ {
+ name: CRON_JOBS_ROUTE_NAME,
+ path: CRON_JOBS_ROUTE_PATH,
+ component: CronJobsPage,
+ meta: {
+ title: s__('KubernetesDashboard|CronJobs'),
+ },
+ },
+ {
+ name: SERVICES_ROUTE_NAME,
+ path: SERVICES_ROUTE_PATH,
+ component: ServicesPage,
+ meta: {
+ title: s__('KubernetesDashboard|Services'),
+ },
+ },
];