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')
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue152
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue55
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue2
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_actions.vue34
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_view_all.vue60
-rw-r--r--app/assets/javascripts/clusters_list/components/delete_agent_button.vue (renamed from app/assets/javascripts/clusters_list/components/agent_options.vue)62
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue52
-rw-r--r--app/assets/javascripts/clusters_list/constants.js57
-rw-r--r--app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql7
-rw-r--r--app/assets/javascripts/clusters_list/load_main_view.js7
10 files changed, 378 insertions, 110 deletions
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 695e16b7b4b..61c4904aacf 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -1,23 +1,14 @@
<script>
import { GlLink, GlTable, GlIcon, GlSprintf, GlTooltip, GlPopover } from '@gitlab/ui';
-import { s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { AGENT_STATUSES } from '../constants';
+import { AGENT_STATUSES, I18N_AGENT_TABLE } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
-import AgentOptions from './agent_options.vue';
+import DeleteAgentButton from './delete_agent_button.vue';
export default {
- i18n: {
- nameLabel: s__('ClusterAgents|Name'),
- statusLabel: s__('ClusterAgents|Connection status'),
- lastContactLabel: s__('ClusterAgents|Last contact'),
- configurationLabel: s__('ClusterAgents|Configuration'),
- optionsLabel: __('Options'),
- troubleshootingText: s__('ClusterAgents|Learn how to troubleshoot'),
- neverConnectedText: s__('ClusterAgents|Never'),
- },
+ i18n: I18N_AGENT_TABLE,
components: {
GlLink,
GlTable,
@@ -26,13 +17,15 @@ export default {
GlTooltip,
GlPopover,
TimeAgoTooltip,
- AgentOptions,
+ DeleteAgentButton,
},
mixins: [timeagoMixin],
AGENT_STATUSES,
- troubleshooting_link: helpPagePath('user/clusters/agent/index', {
- anchor: 'troubleshooting',
+ troubleshootingLink: helpPagePath('user/clusters/agent/troubleshooting'),
+ versionUpdateLink: helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'update-the-agent-version',
}),
+ inject: ['gitlabVersion'],
props: {
agents: {
required: true,
@@ -69,30 +62,93 @@ export default {
tdClass,
},
{
+ key: 'version',
+ label: this.$options.i18n.versionLabel,
+ tdClass,
+ },
+ {
key: 'configuration',
label: this.$options.i18n.configurationLabel,
tdClass,
},
{
key: 'options',
- label: this.$options.i18n.optionsLabel,
+ label: '',
tdClass,
},
];
},
+ agentsList() {
+ if (!this.agents.length) {
+ return [];
+ }
+
+ return this.agents.map((agent) => {
+ const versions = this.getAgentVersions(agent);
+ return { ...agent, versions };
+ });
+ },
},
methods: {
- getCellId(item) {
+ getStatusCellId(item) {
return `connection-status-${item.name}`;
},
+ getVersionCellId(item) {
+ return `version-${item.name}`;
+ },
+ getPopoverTestId(item) {
+ return `popover-${item.name}`;
+ },
getAgentConfigPath,
+ getAgentVersions(agent) {
+ const agentConnections = agent.connections?.nodes || [];
+
+ const agentVersions = agentConnections.map((agentConnection) =>
+ agentConnection.metadata.version.replace('v', ''),
+ );
+
+ const uniqueAgentVersions = [...new Set(agentVersions)];
+
+ return uniqueAgentVersions.sort((a, b) => a.localeCompare(b));
+ },
+ getAgentVersionString(agent) {
+ return agent.versions[0] || '';
+ },
+ isVersionMismatch(agent) {
+ return agent.versions.length > 1;
+ },
+ isVersionOutdated(agent) {
+ if (!agent.versions.length) return false;
+
+ const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.');
+ const [gitlabMajorVersion, gitlabMinorVersion] = this.gitlabVersion.split('.');
+
+ const majorVersionMismatch = agentMajorVersion !== gitlabMajorVersion;
+
+ // We should warn user if their current GitLab and agent versions are more than 1 minor version apart:
+ const minorVersionMismatch = Math.abs(agentMinorVersion - gitlabMinorVersion) > 1;
+
+ return majorVersionMismatch || minorVersionMismatch;
+ },
+
+ getVersionPopoverTitle(agent) {
+ if (this.isVersionMismatch(agent) && this.isVersionOutdated(agent)) {
+ return this.$options.i18n.versionMismatchOutdatedTitle;
+ } else if (this.isVersionMismatch(agent)) {
+ return this.$options.i18n.versionMismatchTitle;
+ } else if (this.isVersionOutdated(agent)) {
+ return this.$options.i18n.versionOutdatedTitle;
+ }
+
+ return null;
+ },
},
};
</script>
<template>
<gl-table
- :items="agents"
+ :items="agentsList"
:fields="fields"
stacked="md"
head-variant="white"
@@ -107,19 +163,23 @@ export default {
</template>
<template #cell(status)="{ item }">
- <span :id="getCellId(item)" class="gl-md-pr-5" data-testid="cluster-agent-connection-status">
+ <span
+ :id="getStatusCellId(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-tooltip v-if="item.status === 'active'" :target="getStatusCellId(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)"
+ :target="getStatusCellId(item)"
:title="$options.AGENT_STATUSES[item.status].tooltip.title"
placement="right"
container="viewport"
@@ -130,7 +190,7 @@ export default {
>
</p>
<p class="gl-mb-0">
- <gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
+ <gl-link :href="$options.troubleshootingLink" target="_blank" class="gl-font-sm">
{{ $options.i18n.troubleshootingText }}</gl-link
>
</p>
@@ -144,6 +204,52 @@ export default {
</span>
</template>
+ <template #cell(version)="{ item }">
+ <span :id="getVersionCellId(item)" data-testid="cluster-agent-version">
+ {{ getAgentVersionString(item) }}
+
+ <gl-icon
+ v-if="isVersionMismatch(item) || isVersionOutdated(item)"
+ name="warning"
+ class="gl-text-orange-500 gl-ml-2"
+ />
+ </span>
+
+ <gl-popover
+ v-if="isVersionMismatch(item) || isVersionOutdated(item)"
+ :target="getVersionCellId(item)"
+ :title="getVersionPopoverTitle(item)"
+ :data-testid="getPopoverTestId(item)"
+ placement="right"
+ container="viewport"
+ >
+ <div v-if="isVersionMismatch(item) && isVersionOutdated(item)">
+ <p>{{ $options.i18n.versionMismatchText }}</p>
+
+ <p class="gl-mb-0">
+ <gl-sprintf :message="$options.i18n.versionOutdatedText">
+ <template #version>{{ gitlabVersion }}</template>
+ </gl-sprintf>
+ <gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
+ {{ $options.i18n.viewDocsText }}</gl-link
+ >
+ </p>
+ </div>
+ <p v-else-if="isVersionMismatch(item)" class="gl-mb-0">
+ {{ $options.i18n.versionMismatchText }}
+ </p>
+
+ <p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
+ <gl-sprintf :message="$options.i18n.versionOutdatedText">
+ <template #version>{{ gitlabVersion }}</template>
+ </gl-sprintf>
+ <gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
+ {{ $options.i18n.viewDocsText }}</gl-link
+ >
+ </p>
+ </gl-popover>
+ </template>
+
<template #cell(configuration)="{ item }">
<span data-testid="cluster-agent-configuration-link">
<gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
@@ -155,7 +261,7 @@ export default {
</template>
<template #cell(options)="{ item }">
- <agent-options
+ <delete-agent-button
:agent="item"
:default-branch-name="defaultBranchName"
:max-agents="maxAgents"
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 4fc421e7c31..bf096f53e9d 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -1,11 +1,29 @@
<script>
-import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
-import { MAX_LIST_COUNT, ACTIVE_CONNECTION_TIME } from '../constants';
+import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import {
+ MAX_LIST_COUNT,
+ ACTIVE_CONNECTION_TIME,
+ AGENT_FEEDBACK_ISSUE,
+ AGENT_FEEDBACK_KEY,
+} from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
export default {
+ i18n: {
+ feedbackBannerTitle: s__('ClusterAgents|Tell us what you think'),
+ feedbackBannerText: s__(
+ 'ClusterAgents|We would love to learn more about your experience with the GitLab Agent.',
+ ),
+ feedbackBannerButton: s__('ClusterAgents|Give feedback'),
+ error: s__('ClusterAgents|An error occurred while loading your Agents'),
+ },
+ AGENT_FEEDBACK_ISSUE,
+ AGENT_FEEDBACK_KEY,
apollo: {
agents: {
query: getAgentsQuery,
@@ -31,7 +49,10 @@ export default {
GlAlert,
GlKeysetPagination,
GlLoadingIcon,
+ GlBanner,
+ LocalStorageSync,
},
+ mixins: [glFeatureFlagMixin()],
inject: ['projectPath'],
props: {
defaultBranchName: {
@@ -57,6 +78,7 @@ export default {
last: null,
},
folderList: {},
+ feedbackBannerDismissed: false,
};
},
computed: {
@@ -86,6 +108,12 @@ export default {
treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
},
+ feedbackBannerEnabled() {
+ return this.glFeatures.showGitlabAgentFeedback;
+ },
+ feedbackBannerClasses() {
+ return this.isChildComponent ? 'gl-my-2' : 'gl-mb-4';
+ },
},
methods: {
reloadAgents() {
@@ -142,6 +170,9 @@ export default {
const count = this.agents?.project?.clusterAgents?.count;
this.$emit('onAgentsLoad', count);
},
+ handleBannerClose() {
+ this.feedbackBannerDismissed = true;
+ },
},
};
</script>
@@ -151,6 +182,24 @@ export default {
<section v-else-if="agentList">
<div v-if="agentList.length">
+ <local-storage-sync
+ v-if="feedbackBannerEnabled"
+ v-model="feedbackBannerDismissed"
+ :storage-key="$options.AGENT_FEEDBACK_KEY"
+ >
+ <gl-banner
+ v-if="!feedbackBannerDismissed"
+ variant="introduction"
+ :class="feedbackBannerClasses"
+ :title="$options.i18n.feedbackBannerTitle"
+ :button-text="$options.i18n.feedbackBannerButton"
+ :button-link="$options.AGENT_FEEDBACK_ISSUE"
+ @close="handleBannerClose"
+ >
+ <p>{{ $options.i18n.feedbackBannerText }}</p>
+ </gl-banner>
+ </local-storage-sync>
+
<agent-table
:agents="agentList"
:default-branch-name="defaultBranchName"
@@ -166,6 +215,6 @@ export default {
</section>
<gl-alert v-else variant="danger" :dismissible="false">
- {{ s__('ClusterAgents|An error occurred while loading your GitLab Agents') }}
+ {{ $options.i18n.error }}
</gl-alert>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index 9c330045596..7fb3aa3ff7e 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -57,7 +57,7 @@ export default {
'totalClusters',
]),
contentAlignClasses() {
- return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start';
+ return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-md-justify-content-start';
},
currentPage: {
get() {
diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
index 25f67462223..5b8dc74b84f 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_actions.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
@@ -1,5 +1,13 @@
<script>
-import { GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlModalDirective,
+ GlTooltipDirective,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+} from '@gitlab/ui';
+
import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '../constants';
export default {
@@ -8,11 +16,20 @@ export default {
components: {
GlDropdown,
GlDropdownItem,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['newClusterPath', 'addClusterPath', 'canAddCluster'],
+ computed: {
+ tooltip() {
+ const { connectWithAgent, dropdownDisabledHint } = this.$options.i18n;
+ return this.canAddCluster ? connectWithAgent : dropdownDisabledHint;
+ },
},
- inject: ['newClusterPath', 'addClusterPath'],
};
</script>
@@ -20,22 +37,27 @@ export default {
<div class="nav-controls gl-ml-auto">
<gl-dropdown
ref="dropdown"
- v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ v-gl-modal-directive="canAddCluster && $options.INSTALL_AGENT_MODAL_ID"
+ v-gl-tooltip="tooltip"
category="primary"
variant="confirm"
:text="$options.i18n.actionsButton"
+ :disabled="!canAddCluster"
split
right
>
- <gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
- {{ $options.i18n.createNewCluster }}
- </gl-dropdown-item>
+ <gl-dropdown-section-header>{{ $options.i18n.agent }}</gl-dropdown-section-header>
<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-divider />
+ <gl-dropdown-section-header>{{ $options.i18n.certificate }}</gl-dropdown-section-header>
+ <gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
+ {{ $options.i18n.createNewCluster }}
+ </gl-dropdown-item>
<gl-dropdown-item :href="addClusterPath" data-testid="connect-cluster-link" @click.stop>
{{ $options.i18n.connectExistingCluster }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
index 0e312d21e4e..b730c0adfa2 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
@@ -8,6 +8,7 @@ import {
GlBadge,
GlLoadingIcon,
GlModalDirective,
+ GlTooltipDirective,
} from '@gitlab/ui';
import { mapState } from 'vuex';
import {
@@ -33,6 +34,7 @@ export default {
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
MAX_CLUSTERS_LIST,
INSTALL_AGENT_MODAL_ID,
@@ -40,7 +42,7 @@ export default {
agent: AGENT_CARD_INFO,
certificate: CERTIFICATE_BASED_CARD_INFO,
},
- inject: ['addClusterPath'],
+ inject: ['addClusterPath', 'canAddCluster'],
props: {
defaultBranchName: {
default: '.noBranch',
@@ -91,6 +93,14 @@ export default {
return cardTitle;
},
+ installAgentTooltip() {
+ return this.canAddCluster ? '' : this.$options.i18n.agent.installAgentDisabledHint;
+ },
+ connectExistingClusterTooltip() {
+ return this.canAddCluster
+ ? ''
+ : this.$options.i18n.certificate.connectExistingClusterDisabledHint;
+ },
},
methods: {
cardFooterNumber(number) {
@@ -113,7 +123,7 @@ export default {
<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"
+ body-class="gl-pb-0 cluster-card-item"
footer-class="gl-text-right"
>
<template #header>
@@ -166,20 +176,29 @@ export default {
><gl-sprintf :message="$options.i18n.agent.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.i18n.agent.actionText }}</gl-button
>
+ <div
+ v-gl-tooltip="installAgentTooltip"
+ class="gl-display-inline-block"
+ tabindex="-1"
+ data-testid="install-agent-button-tooltip"
+ >
+ <gl-button
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ class="gl-ml-4"
+ category="secondary"
+ variant="confirm"
+ :disabled="!canAddCluster"
+ >{{ $options.i18n.agent.actionText }}</gl-button
+ >
+ </div>
</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"
+ body-class="gl-pb-0 cluster-card-item"
footer-class="gl-text-right"
>
<template #header>
@@ -206,14 +225,23 @@ export default {
><gl-sprintf :message="$options.i18n.certificate.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.i18n.certificate.actionText }}</gl-button
>
+ <div
+ v-gl-tooltip="connectExistingClusterTooltip"
+ class="gl-display-inline-block"
+ tabindex="-1"
+ data-testid="connect-existing-cluster-button-tooltip"
+ >
+ <gl-button
+ category="secondary"
+ data-qa-selector="connect_existing_cluster_button"
+ variant="confirm"
+ class="gl-ml-4"
+ :href="addClusterPath"
+ :disabled="!canAddCluster"
+ >{{ $options.i18n.certificate.actionText }}</gl-button
+ >
+ </div>
</template>
</gl-card>
</div>
diff --git a/app/assets/javascripts/clusters_list/components/agent_options.vue b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue
index a364122ba56..6588d304d5c 100644
--- a/app/assets/javascripts/clusters_list/components/agent_options.vue
+++ b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue
@@ -1,36 +1,23 @@
<script>
import {
- GlDropdown,
- GlDropdownItem,
+ GlButton,
GlModal,
GlModalDirective,
GlSprintf,
GlFormGroup,
GlFormInput,
+ GlTooltipDirective,
} from '@gitlab/ui';
-import { s__, __, sprintf } from '~/locale';
-import { DELETE_AGENT_MODAL_ID } from '../constants';
+import { sprintf } from '~/locale';
+import { DELETE_AGENT_BUTTON, DELETE_AGENT_MODAL_ID } from '../constants';
import deleteAgent from '../graphql/mutations/delete_agent.mutation.graphql';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import { removeAgentFromStore } from '../graphql/cache_update';
export default {
- i18n: {
- dropdownText: __('More options'),
- deleteButton: s__('ClusterAgents|Delete agent'),
- modalTitle: __('Are you sure?'),
- modalBody: s__(
- 'ClusterAgents|Are you sure you want to delete this agent? You cannot undo this.',
- ),
- modalInputLabel: s__('ClusterAgents|To delete the agent, type %{name} to confirm:'),
- modalAction: s__('ClusterAgents|Delete'),
- modalCancel: __('Cancel'),
- successMessage: s__('ClusterAgents|%{name} successfully deleted'),
- defaultError: __('An error occurred. Please try again.'),
- },
+ i18n: DELETE_AGENT_BUTTON,
components: {
- GlDropdown,
- GlDropdownItem,
+ GlButton,
GlModal,
GlSprintf,
GlFormGroup,
@@ -38,8 +25,9 @@ export default {
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
- inject: ['projectPath'],
+ inject: ['projectPath', 'canAdminCluster'],
props: {
agent: {
required: true,
@@ -66,6 +54,13 @@ export default {
};
},
computed: {
+ deleteButtonDisabled() {
+ return this.loading || !this.canAdminCluster;
+ },
+ deleteButtonTooltip() {
+ const { deleteButton, disabledHint } = this.$options.i18n;
+ return this.deleteButtonDisabled ? disabledHint : deleteButton;
+ },
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
@@ -159,19 +154,22 @@ export default {
<template>
<div>
- <gl-dropdown
- icon="ellipsis_v"
- right
- :disabled="loading"
- :text="$options.i18n.dropdownText"
- text-sr-only
- category="tertiary"
- no-caret
+ <div
+ v-gl-tooltip="deleteButtonTooltip"
+ class="gl-display-inline-block"
+ tabindex="-1"
+ data-testid="delete-agent-button-tooltip"
>
- <gl-dropdown-item v-gl-modal-directive="modalId">
- {{ $options.i18n.deleteButton }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-button
+ ref="deleteAgentButton"
+ v-gl-modal-directive="modalId"
+ icon="remove"
+ category="secondary"
+ variant="danger"
+ :disabled="deleteButtonDisabled"
+ :aria-label="$options.i18n.deleteButton"
+ />
+ </div>
<gl-modal
ref="modal"
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 5eef76252bd..8fc0a66cd7e 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -111,6 +111,9 @@ export default {
canCancel() {
return !this.registered && !this.registering && this.isAgentRegistrationModal;
},
+ canRegister() {
+ return !this.registered && this.isAgentRegistrationModal;
+ },
agentRegistrationCommand() {
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
},
@@ -142,6 +145,9 @@ export default {
isAgentRegistrationModal() {
return this.modalType === MODAL_TYPE_REGISTER;
},
+ isKasEnabledInEmptyStateModal() {
+ return this.isEmptyStateModal && !this.kasDisabled;
+ },
},
methods: {
setAgentName(name) {
@@ -350,18 +356,18 @@ export default {
<img :alt="i18n.altText" :src="emptyStateImage" height="100" />
</div>
- <p>
- <gl-sprintf :message="i18n.modalBody">
+ <p v-if="kasDisabled">
+ <gl-sprintf :message="i18n.enableKasText">
<template #link="{ content }">
- <gl-link :href="$options.installAgentPath"> {{ content }}</gl-link>
+ <gl-link :href="$options.enableKasPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
- <p v-if="kasDisabled">
- <gl-sprintf :message="i18n.enableKasText">
+ <p v-else>
+ <gl-sprintf :message="i18n.modalBody">
<template #link="{ content }">
- <gl-link :href="$options.enableKasPath"> {{ content }}</gl-link>
+ <gl-link :href="$options.installAgentPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
@@ -380,7 +386,16 @@ export default {
</gl-button>
<gl-button
- v-else-if="isAgentRegistrationModal"
+ v-if="canCancel"
+ :data-track-action="$options.EVENT_ACTIONS_CLICK"
+ :data-track-label="$options.EVENT_LABEL_MODAL"
+ data-track-property="cancel"
+ @click="closeModal"
+ >{{ i18n.cancel }}
+ </gl-button>
+
+ <gl-button
+ v-if="canRegister"
:disabled="!nextButtonDisabled"
variant="confirm"
category="primary"
@@ -392,32 +407,21 @@ export default {
</gl-button>
<gl-button
- v-if="canCancel"
+ v-if="isEmptyStateModal"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
- data-track-property="cancel"
+ data-track-property="done"
@click="closeModal"
- >{{ i18n.cancel }}
+ >{{ i18n.done }}
</gl-button>
<gl-button
- v-if="isEmptyStateModal"
+ v-if="isKasEnabledInEmptyStateModal"
:href="repositoryPath"
variant="confirm"
- category="secondary"
- data-testid="agent-secondary-button"
- >{{ i18n.secondaryButton }}
- </gl-button>
-
- <gl-button
- v-if="isEmptyStateModal"
- variant="confirm"
category="primary"
- :data-track-action="$options.EVENT_ACTIONS_CLICK"
- :data-track-label="$options.EVENT_LABEL_MODAL"
- data-track-property="done"
- @click="closeModal"
- >{{ i18n.done }}
+ data-testid="agent-primary-button"
+ >{{ i18n.primaryButton }}
</gl-button>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index 380a5d0aada..5cf6fd050a1 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -64,6 +64,27 @@ export const STATUSES = {
creating: { title: __('Creating') },
};
+export const I18N_AGENT_TABLE = {
+ nameLabel: s__('ClusterAgents|Name'),
+ statusLabel: s__('ClusterAgents|Connection status'),
+ lastContactLabel: s__('ClusterAgents|Last contact'),
+ versionLabel: __('Version'),
+ configurationLabel: s__('ClusterAgents|Configuration'),
+ optionsLabel: __('Options'),
+ troubleshootingText: s__('ClusterAgents|Learn how to troubleshoot'),
+ neverConnectedText: s__('ClusterAgents|Never'),
+ versionMismatchTitle: s__('ClusterAgents|Agent version mismatch'),
+ versionMismatchText: s__(
+ "ClusterAgents|The Agent version do not match each other across your cluster's pods. This can happen when a new Agent version was just deployed and Kubernetes is shutting down the old pods.",
+ ),
+ versionOutdatedTitle: s__('ClusterAgents|Agent version update required'),
+ versionOutdatedText: s__(
+ 'ClusterAgents|Your Agent version is out of sync with your GitLab version (v%{version}), which might cause compatibility problems. Update the Agent installed on your cluster to the most recent version.',
+ ),
+ versionMismatchOutdatedTitle: s__('ClusterAgents|Agent version mismatch and update'),
+ viewDocsText: s__('ClusterAgents|How to update the Agent?'),
+};
+
export const I18N_AGENT_MODAL = {
agent_registration: {
registerAgentButton: s__('ClusterAgents|Register'),
@@ -112,7 +133,7 @@ export const I18N_AGENT_MODAL = {
"ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it.",
),
altText: s__('ClusterAgents|GitLab Agent for Kubernetes'),
- secondaryButton: s__('ClusterAgents|Go to the repository files'),
+ primaryButton: s__('ClusterAgents|Go to the repository files'),
done: __('Cancel'),
},
};
@@ -176,8 +197,8 @@ export const I18N_CLUSTERS_EMPTY_STATE = {
export const AGENT_CARD_INFO = {
tabName: 'agent',
- title: sprintf(s__('ClusterAgents|%{number} of %{total} agents')),
- emptyTitle: s__('ClusterAgents|No agents'),
+ title: sprintf(s__('ClusterAgents|%{number} of %{total} Agents')),
+ emptyTitle: s__('ClusterAgents|No Agents'),
tooltip: {
label: s__('ClusterAgents|Recommended'),
title: s__('ClusterAgents|GitLab Agent'),
@@ -188,8 +209,11 @@ export const AGENT_CARD_INFO = {
),
link: helpPagePath('user/clusters/agent/index'),
},
- actionText: s__('ClusterAgents|Install a new agent'),
+ actionText: s__('ClusterAgents|Install new Agent'),
footerText: sprintf(s__('ClusterAgents|View all %{number} agents')),
+ installAgentDisabledHint: s__(
+ 'ClusterAgents|Requires a Maintainer or greater role to install new agents',
+ ),
};
export const CERTIFICATE_BASED_CARD_INFO = {
@@ -201,6 +225,9 @@ export const CERTIFICATE_BASED_CARD_INFO = {
actionText: s__('ClusterAgents|Connect existing cluster'),
footerText: sprintf(s__('ClusterAgents|View all %{number} clusters')),
badgeText: s__('ClusterAgents|Deprecated'),
+ connectExistingClusterDisabledHint: s__(
+ 'ClusterAgents|Requires a maintainer or greater role to connect existing clusters',
+ ),
};
export const MAX_CLUSTERS_LIST = 6;
@@ -226,8 +253,25 @@ export const CLUSTERS_TABS = [
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
createNewCluster: s__('ClusterAgents|Create a new cluster'),
- connectWithAgent: s__('ClusterAgents|Connect with the Agent'),
+ connectWithAgent: s__('ClusterAgents|Connect with Agent'),
connectExistingCluster: s__('ClusterAgents|Connect with a certificate'),
+ agent: s__('ClusterAgents|Agent'),
+ certificate: s__('ClusterAgents|Certificate'),
+ dropdownDisabledHint: s__(
+ 'ClusterAgents|Requires a Maintainer or greater role to perform these actions',
+ ),
+};
+
+export const DELETE_AGENT_BUTTON = {
+ deleteButton: s__('ClusterAgents|Delete agent'),
+ disabledHint: s__('ClusterAgents|Requires a Maintainer or greater role to delete agents'),
+ modalTitle: __('Are you sure?'),
+ modalBody: s__('ClusterAgents|Are you sure you want to delete this agent? You cannot undo this.'),
+ modalInputLabel: s__('ClusterAgents|To delete the agent, type %{name} to confirm:'),
+ modalAction: s__('ClusterAgents|Delete'),
+ modalCancel: __('Cancel'),
+ successMessage: s__('ClusterAgents|%{name} successfully deleted'),
+ defaultError: __('An error occurred. Please try again.'),
};
export const AGENT = 'agent';
@@ -244,3 +288,6 @@ export const MODAL_TYPE_EMPTY = 'empty_state';
export const MODAL_TYPE_REGISTER = 'agent_registration';
export const DELETE_AGENT_MODAL_ID = 'delete-agent-modal-%{agentName}';
+
+export const AGENT_FEEDBACK_ISSUE = 'https://gitlab.com/gitlab-org/gitlab/-/issues/342696';
+export const AGENT_FEEDBACK_KEY = 'agent_feedback_banner';
diff --git a/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
index cd46dfee170..05d2525ab98 100644
--- a/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
+++ b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
@@ -2,6 +2,13 @@ fragment ClusterAgentFragment on ClusterAgent {
id
name
webPath
+ connections {
+ nodes {
+ metadata {
+ version
+ }
+ }
+ }
tokens {
nodes {
id
diff --git a/app/assets/javascripts/clusters_list/load_main_view.js b/app/assets/javascripts/clusters_list/load_main_view.js
index 08c99b46e16..d52b1d4a64d 100644
--- a/app/assets/javascripts/clusters_list/load_main_view.js
+++ b/app/assets/javascripts/clusters_list/load_main_view.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { parseBoolean } from '~/lib/utils/common_utils';
import createDefaultClient from '~/lib/graphql';
import ClustersMainView from './components/clusters_main_view.vue';
import { createStore } from './store';
@@ -24,6 +25,9 @@ export default () => {
addClusterPath,
emptyStateHelpText,
clustersEmptyStateImage,
+ canAddCluster,
+ canAdminCluster,
+ gitlabVersion,
} = el.dataset;
return new Vue({
@@ -37,6 +41,9 @@ export default () => {
addClusterPath,
emptyStateHelpText,
clustersEmptyStateImage,
+ canAddCluster: parseBoolean(canAddCluster),
+ canAdminCluster: parseBoolean(canAdminCluster),
+ gitlabVersion,
},
store: createStore(el.dataset),
render(createElement) {