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/clusters_list/components')
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_empty_state.vue81
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue162
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue36
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue54
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_actions.vue44
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_empty_state.vue76
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_main_view.vue73
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_view_all.vue218
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue52
9 files changed, 633 insertions, 163 deletions
diff --git a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
index 405339b3d36..af44a23b4b3 100644
--- a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
@@ -1,9 +1,16 @@
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui';
-import { INSTALL_AGENT_MODAL_ID } from '../constants';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants';
export default {
+ i18n: I18N_AGENTS_EMPTY_STATE,
modalId: INSTALL_AGENT_MODAL_ID,
+ multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
+ installDocsUrl: helpPagePath('administration/clusters/kas'),
+ getStartedDocsUrl: helpPagePath('user/clusters/agent/index', {
+ anchor: 'define-a-configuration-repository',
+ }),
components: {
GlButton,
GlEmptyState,
@@ -14,19 +21,17 @@ export default {
directives: {
GlModalDirective,
},
- inject: [
- 'emptyStateImage',
- 'projectPath',
- 'agentDocsUrl',
- 'installDocsUrl',
- 'getStartedDocsUrl',
- 'integrationDocsUrl',
- ],
+ inject: ['emptyStateImage', 'projectPath'],
props: {
hasConfigurations: {
type: Boolean,
required: true,
},
+ isChildComponent: {
+ default: false,
+ required: false,
+ type: Boolean,
+ },
},
computed: {
repositoryPath() {
@@ -37,22 +42,19 @@ export default {
</script>
<template>
- <gl-empty-state
- :svg-path="emptyStateImage"
- :title="s__('ClusterAgents|Integrate Kubernetes with a GitLab Agent')"
- class="empty-state--agent"
- >
+ <gl-empty-state :svg-path="emptyStateImage" title="" class="agents-empty-state">
<template #description>
- <p class="mw-460 gl-mx-auto">
- <gl-sprintf
- :message="
- s__(
- 'ClusterAgents|The GitLab Kubernetes Agent allows an Infrastructure as Code, GitOps approach to integrating Kubernetes clusters with GitLab. %{linkStart}Learn more.%{linkEnd}',
- )
- "
- >
+ <p class="mw-460 gl-mx-auto gl-text-left">
+ {{ $options.i18n.introText }}
+ </p>
+ <p class="mw-460 gl-mx-auto gl-text-left">
+ <gl-sprintf :message="$options.i18n.multipleClustersText">
<template #link="{ content }">
- <gl-link :href="agentDocsUrl" target="_blank" data-testid="agent-docs-link">
+ <gl-link
+ :href="$options.multipleClustersDocsUrl"
+ target="_blank"
+ data-testid="multiple-clusters-docs-link"
+ >
{{ content }}
</gl-link>
</template>
@@ -60,19 +62,9 @@ export default {
</p>
<p class="mw-460 gl-mx-auto">
- <gl-sprintf
- :message="
- s__(
- 'ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link :href="installDocsUrl" target="_blank" data-testid="install-docs-link">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
+ <gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
+ {{ $options.i18n.learnMoreText }}
+ </gl-link>
</p>
<gl-alert
@@ -81,24 +73,20 @@ export default {
class="gl-mb-5 text-left"
:dismissible="false"
>
- {{
- s__(
- 'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
- )
- }}
+ {{ $options.i18n.warningText }}
<template #actions>
<gl-button
category="primary"
variant="info"
- :href="getStartedDocsUrl"
+ :href="$options.getStartedDocsUrl"
target="_blank"
class="gl-ml-0!"
>
- {{ s__('ClusterAgents|Read more about getting started') }}
+ {{ $options.i18n.readMoreText }}
</gl-button>
<gl-button category="secondary" variant="info" :href="repositoryPath">
- {{ s__('ClusterAgents|Go to the repository') }}
+ {{ $options.i18n.repositoryButtonText }}
</gl-button>
</template>
</gl-alert>
@@ -106,13 +94,14 @@ export default {
<template #actions>
<gl-button
+ v-if="!isChildComponent"
v-gl-modal-directive="$options.modalId"
:disabled="!hasConfigurations"
data-testid="integration-primary-button"
category="primary"
- variant="success"
+ variant="confirm"
>
- {{ s__('ClusterAgents|Integrate with the GitLab Agent') }}
+ {{ $options.i18n.primaryButtonText }}
</gl-button>
</template>
</gl-empty-state>
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 487e512c06d..000730ac1ba 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -1,6 +1,5 @@
<script>
import {
- GlButton,
GlLink,
GlModalDirective,
GlTable,
@@ -12,11 +11,12 @@ import {
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES, TROUBLESHOOTING_LINK } from '../constants';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
+import { getAgentConfigPath } from '../clusters_util';
export default {
components: {
- GlButton,
GlLink,
GlTable,
GlIcon,
@@ -29,10 +29,12 @@ export default {
GlModalDirective,
},
mixins: [timeagoMixin],
- inject: ['integrationDocsUrl'],
INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
- TROUBLESHOOTING_LINK,
+
+ troubleshooting_link: helpPagePath('user/clusters/agent/index', {
+ anchor: 'troubleshooting',
+ }),
props: {
agents: {
required: true,
@@ -41,112 +43,102 @@ export default {
},
computed: {
fields() {
+ const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: s__('ClusterAgents|Name'),
+ tdClass,
},
{
key: 'status',
label: s__('ClusterAgents|Connection status'),
+ tdClass,
},
{
key: 'lastContact',
label: s__('ClusterAgents|Last contact'),
+ tdClass,
},
{
key: 'configuration',
label: s__('ClusterAgents|Configuration'),
+ tdClass,
},
];
},
},
+ methods: {
+ getCellId(item) {
+ return `connection-status-${item.name}`;
+ },
+ getAgentConfigPath,
+ },
};
</script>
<template>
- <div>
- <div class="gl-display-block gl-text-right gl-my-3">
- <gl-button
- v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
- variant="confirm"
- category="primary"
- >{{ s__('ClusterAgents|Install a new GitLab Agent') }}
- </gl-button>
- </div>
+ <gl-table
+ :items="agents"
+ :fields="fields"
+ stacked="md"
+ head-variant="white"
+ thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
+ class="gl-mb-4!"
+ data-testid="cluster-agent-list-table"
+ >
+ <template #cell(name)="{ item }">
+ <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">
+ {{ item.name }}
+ </gl-link>
+ </template>
- <gl-table
- :items="agents"
- :fields="fields"
- stacked="md"
- head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
- data-testid="cluster-agent-list-table"
- >
- <template #cell(name)="{ item }">
- <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">
- {{ item.name }}
- </gl-link>
- </template>
-
- <template #cell(status)="{ item }">
- <span
- :id="`connection-status-${item.name}`"
- class="gl-pr-5"
- data-testid="cluster-agent-connection-status"
- >
- <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
- <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="12" /></span
- >{{ $options.AGENT_STATUSES[item.status].name }}
- </span>
- <gl-tooltip
- v-if="item.status === 'active'"
- :target="`connection-status-${item.name}`"
- placement="right"
- >
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
- </gl-sprintf>
- </gl-tooltip>
- <gl-popover
- v-else
- :target="`connection-status-${item.name}`"
- :title="$options.AGENT_STATUSES[item.status].tooltip.title"
- placement="right"
- container="viewport"
- >
- <p>
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
- >
- </p>
- <p class="gl-mb-0">
- {{ s__('ClusterAgents|For more troubleshooting information go to') }}
- <gl-link :href="$options.TROUBLESHOOTING_LINK" target="_blank" class="gl-font-sm">
- {{ $options.TROUBLESHOOTING_LINK }}</gl-link
- >
- </p>
- </gl-popover>
- </template>
+ <template #cell(status)="{ item }">
+ <span :id="getCellId(item)" class="gl-md-pr-5" data-testid="cluster-agent-connection-status">
+ <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
+ <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="12" /></span
+ >{{ $options.AGENT_STATUSES[item.status].name }}
+ </span>
+ <gl-tooltip v-if="item.status === 'active'" :target="getCellId(item)" placement="right">
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
+ </gl-sprintf>
+ </gl-tooltip>
+ <gl-popover
+ v-else
+ :target="getCellId(item)"
+ :title="$options.AGENT_STATUSES[item.status].tooltip.title"
+ placement="right"
+ container="viewport"
+ >
+ <p>
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
+ >
+ </p>
+ <p class="gl-mb-0">
+ <gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
+ {{ s__('ClusterAgents|Learn how to troubleshoot') }}</gl-link
+ >
+ </p>
+ </gl-popover>
+ </template>
- <template #cell(lastContact)="{ item }">
- <span data-testid="cluster-agent-last-contact">
- <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
- <span v-else>{{ s__('ClusterAgents|Never') }}</span>
- </span>
- </template>
+ <template #cell(lastContact)="{ item }">
+ <span data-testid="cluster-agent-last-contact">
+ <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
+ <span v-else>{{ s__('ClusterAgents|Never') }}</span>
+ </span>
+ </template>
- <template #cell(configuration)="{ item }">
- <span data-testid="cluster-agent-configuration-link">
- <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
- <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
- .gitlab/agents/{{ item.name }}
- </gl-link>
+ <template #cell(configuration)="{ item }">
+ <span data-testid="cluster-agent-configuration-link">
+ <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
+ {{ getAgentConfigPath(item.name) }}
+ </gl-link>
- <span v-else>.gitlab/agents/{{ item.name }}</span>
- <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
- </span>
- </template>
- </gl-table>
- </div>
+ <span v-else>{{ getAgentConfigPath(item.name) }}</span>
+ </span>
+ </template>
+ </gl-table>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index ed44c1f5fa7..fb5cf7d1206 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -4,7 +4,6 @@ import { MAX_LIST_COUNT, ACTIVE_CONNECTION_TIME } from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
-import InstallAgentModal from './install_agent_modal.vue';
export default {
apollo: {
@@ -21,12 +20,14 @@ export default {
this.updateTreeList(data);
return data;
},
+ result() {
+ this.emitAgentsLoaded();
+ },
},
},
components: {
AgentEmptyState,
AgentTable,
- InstallAgentModal,
GlAlert,
GlKeysetPagination,
GlLoadingIcon,
@@ -38,11 +39,21 @@ export default {
required: false,
type: String,
},
+ isChildComponent: {
+ default: false,
+ required: false,
+ type: Boolean,
+ },
+ limit: {
+ default: null,
+ required: false,
+ type: Number,
+ },
},
data() {
return {
cursor: {
- first: MAX_LIST_COUNT,
+ first: this.limit ? this.limit : MAX_LIST_COUNT,
last: null,
},
folderList: {},
@@ -70,7 +81,7 @@ export default {
return this.$apollo.queries.agents.loading;
},
showPagination() {
- return this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage;
+ return !this.limit && (this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage);
},
treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
@@ -130,24 +141,31 @@ export default {
}
return 'unused';
},
+ emitAgentsLoaded() {
+ const count = this.agents?.project?.clusterAgents?.count;
+ this.$emit('onAgentsLoad', count);
+ },
},
};
</script>
<template>
- <gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" />
+ <gl-loading-icon v-if="isLoading" size="md" />
- <section v-else-if="agentList" class="gl-mt-3">
+ <section v-else-if="agentList">
<div v-if="agentList.length">
- <AgentTable :agents="agentList" />
+ <agent-table :agents="agentList" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />
</div>
</div>
- <AgentEmptyState v-else :has-configurations="hasConfigurations" />
- <InstallAgentModal @agentRegistered="reloadAgents" />
+ <agent-empty-state
+ v-else
+ :has-configurations="hasConfigurations"
+ :is-child-component="isChildComponent"
+ />
</section>
<gl-alert v-else variant="danger" :dismissible="false">
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index 0d1534d20e0..9c330045596 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -14,6 +14,7 @@ import { __, sprintf } from '~/locale';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import AncestorNotice from './ancestor_notice.vue';
import NodeErrorHelpText from './node_error_help_text.vue';
+import ClustersEmptyState from './clusters_empty_state.vue';
export default {
nodeMemoryText: __('%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)'),
@@ -28,10 +29,23 @@ export default {
GlSprintf,
GlTable,
NodeErrorHelpText,
+ ClustersEmptyState,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ props: {
+ isChildComponent: {
+ default: false,
+ required: false,
+ type: Boolean,
+ },
+ limit: {
+ default: null,
+ required: false,
+ type: Number,
+ },
+ },
computed: {
...mapState([
'clusters',
@@ -40,7 +54,7 @@ export default {
'loadingNodes',
'page',
'providers',
- 'totalCulsters',
+ 'totalClusters',
]),
contentAlignClasses() {
return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start';
@@ -55,43 +69,57 @@ export default {
},
},
fields() {
+ const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: __('Kubernetes cluster'),
+ tdClass,
},
{
key: 'environment_scope',
label: __('Environment scope'),
+ tdClass,
},
{
key: 'node_size',
label: __('Nodes'),
+ tdClass,
},
{
key: 'total_cpu',
label: __('Total cores (CPUs)'),
+ tdClass,
},
{
key: 'total_memory',
label: __('Total memory (GB)'),
+ tdClass,
},
{
key: 'cluster_type',
label: __('Cluster level'),
+ tdClass,
formatter: (value) => CLUSTER_TYPES[value],
},
];
},
- hasClusters() {
+ hasClustersPerPage() {
return this.clustersPerPage > 0;
},
+ hasClusters() {
+ return this.totalClusters > 0;
+ },
},
mounted() {
+ if (this.limit) {
+ this.setClustersPerPage(this.limit);
+ }
+
this.fetchClusters();
},
methods: {
- ...mapActions(['fetchClusters', 'reportSentryError', 'setPage']),
+ ...mapActions(['fetchClusters', 'reportSentryError', 'setPage', 'setClustersPerPage']),
k8sQuantityToGb(quantity) {
if (!quantity) {
return 0;
@@ -196,18 +224,20 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loadingClusters" size="md" class="gl-mt-3" />
+ <gl-loading-icon v-if="loadingClusters" size="md" />
<section v-else>
<ancestor-notice />
<gl-table
+ v-if="hasClusters"
:items="clusters"
:fields="fields"
+ fixed
stacked="md"
head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
- class="qa-clusters-table"
+ thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
+ class="qa-clusters-table gl-mb-4!"
data-testid="cluster_list_table"
>
<template #cell(name)="{ item }">
@@ -241,7 +271,7 @@ export default {
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
- <NodeErrorHelpText
+ <node-error-help-text
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.connection_error"
@@ -262,7 +292,7 @@ export default {
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
- <NodeErrorHelpText
+ <node-error-help-text
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.node_connection_error"
@@ -283,7 +313,7 @@ export default {
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
- <NodeErrorHelpText
+ <node-error-help-text
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.metrics_connection_error"
@@ -298,11 +328,13 @@ export default {
</template>
</gl-table>
+ <clusters-empty-state v-else :is-child-component="isChildComponent" />
+
<gl-pagination
- v-if="hasClusters"
+ v-if="hasClustersPerPage && !limit"
v-model="currentPage"
:per-page="clustersPerPage"
- :total-items="totalCulsters"
+ :total-items="totalClusters"
:prev-text="__('Prev')"
:next-text="__('Next')"
align="center"
diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
new file mode 100644
index 00000000000..25f67462223
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
@@ -0,0 +1,44 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
+import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '../constants';
+
+export default {
+ i18n: CLUSTERS_ACTIONS,
+ INSTALL_AGENT_MODAL_ID,
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['newClusterPath', 'addClusterPath'],
+};
+</script>
+
+<template>
+ <div class="nav-controls gl-ml-auto">
+ <gl-dropdown
+ ref="dropdown"
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ category="primary"
+ variant="confirm"
+ :text="$options.i18n.actionsButton"
+ split
+ right
+ >
+ <gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
+ {{ $options.i18n.createNewCluster }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ data-testid="connect-new-agent-link"
+ >
+ {{ $options.i18n.connectWithAgent }}
+ </gl-dropdown-item>
+ <gl-dropdown-item :href="addClusterPath" data-testid="connect-cluster-link" @click.stop>
+ {{ $options.i18n.connectExistingCluster }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
new file mode 100644
index 00000000000..3879af6e9cb
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
@@ -0,0 +1,76 @@
+<script>
+import { GlEmptyState, GlButton, GlLink, GlSprintf } from '@gitlab/ui';
+import { mapState } from 'vuex';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { I18N_CLUSTERS_EMPTY_STATE } from '../constants';
+
+export default {
+ i18n: I18N_CLUSTERS_EMPTY_STATE,
+ components: {
+ GlEmptyState,
+ GlButton,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
+ props: {
+ isChildComponent: {
+ default: false,
+ required: false,
+ type: Boolean,
+ },
+ },
+ learnMoreHelpUrl: helpPagePath('user/project/clusters/index'),
+ multipleClustersHelpUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
+ computed: {
+ ...mapState(['canAddCluster']),
+ },
+};
+</script>
+
+<template>
+ <gl-empty-state :svg-path="clustersEmptyStateImage" title="">
+ <template #description>
+ <p class="gl-text-left">
+ {{ $options.i18n.description }}
+ </p>
+ <p class="gl-text-left">
+ <gl-sprintf :message="$options.i18n.multipleClustersText">
+ <template #link="{ content }">
+ <gl-link
+ :href="$options.multipleClustersHelpUrl"
+ target="_blank"
+ data-testid="multiple-clusters-docs-link"
+ >
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <p v-if="emptyStateHelpText" data-testid="clusters-empty-state-text">
+ {{ emptyStateHelpText }}
+ </p>
+
+ <p>
+ <gl-link :href="$options.learnMoreHelpUrl" target="_blank" data-testid="clusters-docs-link">
+ {{ $options.i18n.learnMoreLinkText }}
+ </gl-link>
+ </p>
+ </template>
+
+ <template #actions>
+ <gl-button
+ v-if="!isChildComponent"
+ data-testid="integration-primary-button"
+ data-qa-selector="add_kubernetes_cluster_link"
+ category="primary"
+ variant="confirm"
+ :disabled="!canAddCluster"
+ :href="newClusterPath"
+ >
+ {{ $options.i18n.buttonText }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
new file mode 100644
index 00000000000..9e03093aa67
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlTabs, GlTab } from '@gitlab/ui';
+import { CLUSTERS_TABS, MAX_CLUSTERS_LIST, MAX_LIST_COUNT, AGENT } from '../constants';
+import Agents from './agents.vue';
+import InstallAgentModal from './install_agent_modal.vue';
+import ClustersActions from './clusters_actions.vue';
+import Clusters from './clusters.vue';
+import ClustersViewAll from './clusters_view_all.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ ClustersActions,
+ ClustersViewAll,
+ Clusters,
+ Agents,
+ InstallAgentModal,
+ },
+ CLUSTERS_TABS,
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ },
+ data() {
+ return {
+ selectedTabIndex: 0,
+ maxAgents: MAX_CLUSTERS_LIST,
+ };
+ },
+ methods: {
+ onTabChange(tabName) {
+ this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName);
+
+ this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-tabs
+ v-model="selectedTabIndex"
+ sync-active-tab-with-query-params
+ nav-class="gl-flex-grow-1 gl-align-items-center"
+ lazy
+ >
+ <gl-tab
+ v-for="(tab, idx) in $options.CLUSTERS_TABS"
+ :key="idx"
+ :title="tab.title"
+ :query-param-value="tab.queryParamValue"
+ class="gl-line-height-20 gl-mt-5"
+ >
+ <component
+ :is="tab.component"
+ :default-branch-name="defaultBranchName"
+ data-testid="clusters-tab-component"
+ @changeTab="onTabChange"
+ />
+ </gl-tab>
+
+ <template #tabs-end>
+ <clusters-actions />
+ </template>
+ </gl-tabs>
+
+ <install-agent-modal :default-branch-name="defaultBranchName" :max-agents="maxAgents" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
new file mode 100644
index 00000000000..285876e57d8
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
@@ -0,0 +1,218 @@
+<script>
+import {
+ GlCard,
+ GlSprintf,
+ GlPopover,
+ GlLink,
+ GlButton,
+ GlBadge,
+ GlLoadingIcon,
+ GlModalDirective,
+} from '@gitlab/ui';
+import { mapState } from 'vuex';
+import {
+ AGENT_CARD_INFO,
+ CERTIFICATE_BASED_CARD_INFO,
+ MAX_CLUSTERS_LIST,
+ INSTALL_AGENT_MODAL_ID,
+} from '../constants';
+import Clusters from './clusters.vue';
+import Agents from './agents.vue';
+
+export default {
+ components: {
+ GlCard,
+ GlSprintf,
+ GlPopover,
+ GlLink,
+ GlButton,
+ GlBadge,
+ GlLoadingIcon,
+ Clusters,
+ Agents,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ AGENT_CARD_INFO,
+ CERTIFICATE_BASED_CARD_INFO,
+ MAX_CLUSTERS_LIST,
+ INSTALL_AGENT_MODAL_ID,
+ inject: ['addClusterPath'],
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ },
+ data() {
+ return {
+ loadingAgents: true,
+ totalAgents: null,
+ };
+ },
+ computed: {
+ ...mapState(['loadingClusters', 'totalClusters']),
+ isLoading() {
+ return this.loadingAgents || this.loadingClusters;
+ },
+ agentsCardTitle() {
+ let cardTitle;
+ if (this.totalAgents > 0) {
+ cardTitle = {
+ message: AGENT_CARD_INFO.title,
+ number: this.totalAgents < MAX_CLUSTERS_LIST ? this.totalAgents : MAX_CLUSTERS_LIST,
+ total: this.totalAgents,
+ };
+ } else {
+ cardTitle = {
+ message: AGENT_CARD_INFO.emptyTitle,
+ };
+ }
+
+ return cardTitle;
+ },
+ clustersCardTitle() {
+ let cardTitle;
+ if (this.totalClusters > 0) {
+ cardTitle = {
+ message: CERTIFICATE_BASED_CARD_INFO.title,
+ number: this.totalClusters < MAX_CLUSTERS_LIST ? this.totalClusters : MAX_CLUSTERS_LIST,
+ total: this.totalClusters,
+ };
+ } else {
+ cardTitle = {
+ message: CERTIFICATE_BASED_CARD_INFO.emptyTitle,
+ };
+ }
+
+ return cardTitle;
+ },
+ },
+ methods: {
+ cardFooterNumber(number) {
+ return number > MAX_CLUSTERS_LIST ? number : '';
+ },
+ onAgentsLoad(number) {
+ this.totalAgents = number;
+ this.loadingAgents = false;
+ },
+ changeTab($event, tab) {
+ $event.preventDefault();
+ this.$emit('changeTab', tab);
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-loading-icon v-if="isLoading" size="md" />
+ <div v-show="!isLoading" data-testid="clusters-cards-container">
+ <gl-card
+ header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between gl-py-4"
+ body-class="gl-pb-0"
+ footer-class="gl-text-right"
+ >
+ <template #header>
+ <h3 data-testid="agent-card-title" class="gl-my-0 gl-font-weight-normal gl-font-size-h2">
+ <gl-sprintf :message="agentsCardTitle.message"
+ ><template #number>{{ agentsCardTitle.number }}</template>
+ <template #total>{{ agentsCardTitle.total }}</template>
+ </gl-sprintf>
+ </h3>
+
+ <gl-badge id="clusters-recommended-badge" size="md" variant="info">{{
+ $options.AGENT_CARD_INFO.tooltip.label
+ }}</gl-badge>
+
+ <gl-popover
+ target="clusters-recommended-badge"
+ container="viewport"
+ placement="bottom"
+ :title="$options.AGENT_CARD_INFO.tooltip.title"
+ >
+ <p class="gl-mb-0">
+ <gl-sprintf :message="$options.AGENT_CARD_INFO.tooltip.text">
+ <template #link="{ content }">
+ <gl-link
+ :href="$options.AGENT_CARD_INFO.tooltip.link"
+ target="_blank"
+ class="gl-font-sm"
+ >
+ {{ content }}</gl-link
+ >
+ </template>
+ </gl-sprintf>
+ </p>
+ </gl-popover>
+ </template>
+
+ <agents
+ :limit="$options.MAX_CLUSTERS_LIST"
+ :default-branch-name="defaultBranchName"
+ :is-child-component="true"
+ @onAgentsLoad="onAgentsLoad"
+ />
+
+ <template #footer>
+ <gl-link
+ v-if="totalAgents"
+ data-testid="agents-tab-footer-link"
+ :href="`?tab=${$options.AGENT_CARD_INFO.tabName}`"
+ @click="changeTab($event, $options.AGENT_CARD_INFO.tabName)"
+ ><gl-sprintf :message="$options.AGENT_CARD_INFO.footerText"
+ ><template #number>{{ cardFooterNumber(totalAgents) }}</template></gl-sprintf
+ ></gl-link
+ ><gl-button
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ class="gl-ml-4"
+ category="secondary"
+ variant="confirm"
+ >{{ $options.AGENT_CARD_INFO.actionText }}</gl-button
+ >
+ </template>
+ </gl-card>
+
+ <gl-card
+ class="gl-mt-6"
+ header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between"
+ body-class="gl-pb-0"
+ footer-class="gl-text-right"
+ >
+ <template #header>
+ <h3
+ class="gl-my-1 gl-font-weight-normal gl-font-size-h2"
+ data-testid="clusters-card-title"
+ >
+ <gl-sprintf :message="clustersCardTitle.message"
+ ><template #number>{{ clustersCardTitle.number }}</template>
+ <template #total>{{ clustersCardTitle.total }}</template>
+ </gl-sprintf>
+ </h3>
+ </template>
+
+ <clusters :limit="$options.MAX_CLUSTERS_LIST" :is-child-component="true" />
+
+ <template #footer>
+ <gl-link
+ v-if="totalClusters"
+ data-testid="clusters-tab-footer-link"
+ :href="`?tab=${$options.CERTIFICATE_BASED_CARD_INFO.tabName}`"
+ @click="changeTab($event, $options.CERTIFICATE_BASED_CARD_INFO.tabName)"
+ ><gl-sprintf :message="$options.CERTIFICATE_BASED_CARD_INFO.footerText"
+ ><template #number>{{ cardFooterNumber(totalClusters) }}</template></gl-sprintf
+ ></gl-link
+ ><gl-button
+ category="secondary"
+ data-qa-selector="connect_existing_cluster_button"
+ variant="confirm"
+ class="gl-ml-4"
+ :href="addClusterPath"
+ >{{ $options.CERTIFICATE_BASED_CARD_INFO.actionText }}</gl-button
+ >
+ </template>
+ </gl-card>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
index 5f192fe4d5a..6eb2e85ecea 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -13,8 +13,10 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { generateAgentRegistrationCommand } from '../clusters_util';
import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL } from '../constants';
+import { addAgentToStore } from '../graphql/cache_update';
import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
+import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AvailableAgentsDropdown from './available_agents_dropdown.vue';
export default {
@@ -33,12 +35,24 @@ export default {
GlSprintf,
},
inject: ['projectPath', 'kasAddress'],
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ maxAgents: {
+ required: true,
+ type: Number,
+ },
+ },
data() {
return {
registering: false,
agentName: null,
agentToken: null,
error: null,
+ clusterAgent: null,
};
},
computed: {
@@ -55,27 +69,31 @@ export default {
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
},
basicInstallPath() {
- return helpPagePath('user/clusters/agent/index', {
+ return helpPagePath('user/clusters/agent/install/index', {
anchor: 'install-the-agent-into-the-cluster',
});
},
advancedInstallPath() {
- return helpPagePath('user/clusters/agent/index', { anchor: 'advanced-installation' });
+ return helpPagePath('user/clusters/agent/install/index', { anchor: 'advanced-installation' });
+ },
+ getAgentsQueryVariables() {
+ return {
+ defaultBranchName: this.defaultBranchName,
+ first: this.maxAgents,
+ last: null,
+ projectPath: this.projectPath,
+ };
},
},
methods: {
setAgentName(name) {
this.agentName = name;
},
- cancelClicked() {
- this.$refs.modal.hide();
- },
- doneClicked() {
- this.$emit('agentRegistered');
+ closeModal() {
this.$refs.modal.hide();
},
resetModal() {
- this.registering = null;
+ this.registering = false;
this.agentName = null;
this.agentToken = null;
this.error = null;
@@ -90,6 +108,14 @@ export default {
projectPath: this.projectPath,
},
},
+ update: (store, { data: { createClusterAgent } }) => {
+ addAgentToStore(
+ store,
+ createClusterAgent,
+ getAgentsQuery,
+ this.getAgentsQueryVariables,
+ );
+ },
})
.then(({ data: { createClusterAgent } }) => createClusterAgent);
},
@@ -117,6 +143,8 @@ export default {
throw new Error(agentErrors[0]);
}
+ this.clusterAgent = clusterAgent;
+
const { errors: tokenErrors, secret } = await this.createAgentTokenMutation(
clusterAgent.id,
);
@@ -240,10 +268,10 @@ export default {
</template>
<template #modal-footer>
- <gl-button v-if="canCancel" @click="cancelClicked">{{ $options.i18n.cancel }} </gl-button>
+ <gl-button v-if="canCancel" @click="closeModal">{{ $options.i18n.cancel }} </gl-button>
- <gl-button v-if="registered" variant="confirm" category="primary" @click="doneClicked"
- >{{ $options.i18n.done }}
+ <gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal"
+ >{{ $options.i18n.close }}
</gl-button>
<gl-button
@@ -252,7 +280,7 @@ export default {
variant="confirm"
category="primary"
@click="registerAgent"
- >{{ $options.i18n.next }}
+ >{{ $options.i18n.registerAgentButton }}
</gl-button>
</template>
</gl-modal>