diff options
Diffstat (limited to 'app/assets/javascripts/clusters_list')
-rw-r--r-- | app/assets/javascripts/clusters_list/components/agent_table.vue | 152 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/components/agents.vue | 55 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/components/clusters.vue | 2 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/components/clusters_actions.vue | 34 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/components/clusters_view_all.vue | 60 | ||||
-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.vue | 52 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/constants.js | 57 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql | 7 | ||||
-rw-r--r-- | app/assets/javascripts/clusters_list/load_main_view.js | 7 |
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) { |