diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
commit | 41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch) | |
tree | 9c8d89a8624828992f06d892cd2f43818ff5dcc8 /app/assets/javascripts/clusters | |
parent | 0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff) |
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/clusters')
8 files changed, 318 insertions, 24 deletions
diff --git a/app/assets/javascripts/clusters/agents/components/create_token_button.vue b/app/assets/javascripts/clusters/agents/components/create_token_button.vue new file mode 100644 index 00000000000..3e1a8994fb8 --- /dev/null +++ b/app/assets/javascripts/clusters/agents/components/create_token_button.vue @@ -0,0 +1,246 @@ +<script> +import { + GlButton, + GlModalDirective, + GlTooltip, + GlModal, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlAlert, +} from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import Tracking from '~/tracking'; +import AgentToken from '~/clusters_list/components/agent_token.vue'; +import { + CREATE_TOKEN_MODAL, + EVENT_LABEL_MODAL, + EVENT_ACTIONS_OPEN, + EVENT_ACTIONS_CLICK, + TOKEN_NAME_LIMIT, + TOKEN_STATUS_ACTIVE, +} from '../constants'; +import createNewAgentToken from '../graphql/mutations/create_new_agent_token.mutation.graphql'; +import getClusterAgentQuery from '../graphql/queries/get_cluster_agent.query.graphql'; +import { addAgentTokenToStore } from '../graphql/cache_update'; + +const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL }); + +export default { + components: { + AgentToken, + GlButton, + GlTooltip, + GlModal, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlAlert, + }, + directives: { + GlModalDirective, + }, + mixins: [trackingMixin], + inject: ['agentName', 'projectPath', 'canAdminCluster'], + props: { + clusterAgentId: { + required: true, + type: String, + }, + cursor: { + required: true, + type: Object, + }, + }, + modalId: CREATE_TOKEN_MODAL, + EVENT_ACTIONS_OPEN, + EVENT_ACTIONS_CLICK, + EVENT_LABEL_MODAL, + TOKEN_NAME_LIMIT, + i18n: { + createTokenButton: s__('ClusterAgents|Create token'), + modalTitle: s__('ClusterAgents|Create agent access token'), + unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'), + errorTitle: s__('ClusterAgents|Failed to create a token'), + dropdownDisabledHint: s__( + 'ClusterAgents|Requires a Maintainer or greater role to perform these actions', + ), + modalCancel: __('Cancel'), + modalClose: __('Close'), + tokenNameLabel: __('Name'), + tokenDescriptionLabel: __('Description (optional)'), + }, + data() { + return { + token: { + name: null, + description: null, + }, + agentToken: null, + error: null, + loading: false, + variables: { + agentName: this.agentName, + projectPath: this.projectPath, + tokenStatus: TOKEN_STATUS_ACTIVE, + ...this.cursor, + }, + }; + }, + computed: { + modalBtnDisabled() { + return this.loading || !this.hasTokenName; + }, + hasTokenName() { + return Boolean(this.token.name?.length); + }, + }, + methods: { + async createToken() { + this.loading = true; + this.error = null; + + try { + const { errors: tokenErrors, secret } = await this.createAgentTokenMutation(); + + if (tokenErrors?.length > 0) { + throw new Error(tokenErrors[0]); + } + + this.agentToken = secret; + } catch (error) { + if (error) { + this.error = error.message; + } else { + this.error = this.$options.i18n.unknownError; + } + } finally { + this.loading = false; + } + }, + resetModal() { + this.agentToken = null; + this.token.name = null; + this.token.description = null; + this.error = null; + }, + closeModal() { + this.$refs.modal.hide(); + }, + createAgentTokenMutation() { + return this.$apollo + .mutate({ + mutation: createNewAgentToken, + variables: { + input: { + clusterAgentId: this.clusterAgentId, + name: this.token.name, + description: this.token.description, + }, + }, + update: (store, { data: { clusterAgentTokenCreate } }) => { + addAgentTokenToStore( + store, + clusterAgentTokenCreate, + getClusterAgentQuery, + this.variables, + ); + }, + }) + .then(({ data: { clusterAgentTokenCreate } }) => clusterAgentTokenCreate); + }, + }, +}; +</script> + +<template> + <div> + <div ref="addToken" class="gl-display-inline-block"> + <gl-button + v-gl-modal-directive="$options.modalId" + :disabled="!canAdminCluster" + category="primary" + variant="confirm" + >{{ $options.i18n.createTokenButton }} + </gl-button> + + <gl-tooltip + v-if="!canAdminCluster" + :target="() => $refs.addToken" + :title="$options.i18n.dropdownDisabledHint" + /> + </div> + + <gl-modal + ref="modal" + :modal-id="$options.modalId" + :title="$options.i18n.modalTitle" + static + lazy + @hidden="resetModal" + @show="track($options.EVENT_ACTIONS_OPEN)" + > + <gl-alert + v-if="error" + :title="$options.i18n.errorTitle" + :dismissible="false" + variant="danger" + class="gl-mb-5" + > + {{ error }} + </gl-alert> + + <template v-if="!agentToken"> + <gl-form-group :label="$options.i18n.tokenNameLabel"> + <gl-form-input + v-model="token.name" + :max-length="$options.TOKEN_NAME_LIMIT" + :disabled="loading" + required + /> + </gl-form-group> + + <gl-form-group :label="$options.i18n.tokenDescriptionLabel"> + <gl-form-textarea v-model="token.description" :disabled="loading" name="description" /> + </gl-form-group> + </template> + + <agent-token v-else :agent-token="agentToken" :modal-id="$options.modalId" /> + + <template #modal-footer> + <gl-button + v-if="!agentToken && !loading" + :data-track-action="$options.EVENT_ACTIONS_CLICK" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="close" + data-testid="agent-token-close-button" + @click="closeModal" + >{{ $options.i18n.modalCancel }} + </gl-button> + + <gl-button + v-if="!agentToken" + :disabled="modalBtnDisabled" + :loading="loading" + :data-track-action="$options.EVENT_ACTIONS_CLICK" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="create-token" + variant="confirm" + type="submit" + @click="createToken" + >{{ $options.i18n.createTokenButton }} + </gl-button> + + <gl-button + v-else + :data-track-action="$options.EVENT_ACTIONS_CLICK" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="close" + variant="confirm" + @click="closeModal" + >{{ $options.i18n.modalClose }} + </gl-button> + </template> + </gl-modal> + </div> +</template> diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue index 63f068a9327..5df3e0811a5 100644 --- a/app/assets/javascripts/clusters/agents/components/show.vue +++ b/app/assets/javascripts/clusters/agents/components/show.vue @@ -143,7 +143,7 @@ export default { <gl-loading-icon v-if="isLoading" size="md" class="gl-m-3" /> <div v-else> - <token-table :tokens="tokens" /> + <token-table :tokens="tokens" :cluster-agent-id="clusterAgent.id" :cursor="cursor" /> <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5"> <gl-keyset-pagination v-bind="tokenPageInfo" @prev="prevPage" @next="nextPage" /> diff --git a/app/assets/javascripts/clusters/agents/components/token_table.vue b/app/assets/javascripts/clusters/agents/components/token_table.vue index 019fac531d1..fbb39c28d78 100644 --- a/app/assets/javascripts/clusters/agents/components/token_table.vue +++ b/app/assets/javascripts/clusters/agents/components/token_table.vue @@ -1,17 +1,17 @@ <script> -import { GlEmptyState, GlLink, GlTable, GlTooltip, GlTruncate } from '@gitlab/ui'; -import { helpPagePath } from '~/helpers/help_page_helper'; +import { GlEmptyState, GlTable, GlTooltip, GlTruncate } from '@gitlab/ui'; import { s__ } from '~/locale'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import CreateTokenButton from './create_token_button.vue'; export default { components: { GlEmptyState, - GlLink, GlTable, GlTooltip, GlTruncate, TimeAgoTooltip, + CreateTokenButton, }, i18n: { createdBy: s__('ClusterAgents|Created by'), @@ -19,7 +19,6 @@ export default { dateCreated: s__('ClusterAgents|Date created'), description: s__('ClusterAgents|Description'), lastUsed: s__('ClusterAgents|Last contact'), - learnMore: s__('ClusterAgents|Learn how to create an agent access token'), name: s__('ClusterAgents|Name'), neverUsed: s__('ClusterAgents|Never'), noTokens: s__('ClusterAgents|This agent has no tokens'), @@ -30,6 +29,14 @@ export default { required: true, type: Array, }, + clusterAgentId: { + required: true, + type: String, + }, + cursor: { + required: true, + type: Object, + }, }, computed: { fields() { @@ -61,11 +68,6 @@ export default { }, ]; }, - learnMoreUrl() { - return helpPagePath('user/clusters/agent/install/index', { - anchor: 'register-an-agent-with-gitlab', - }); - }, }, methods: { createdByName(token) { @@ -77,11 +79,11 @@ export default { <template> <div v-if="tokens.length"> - <div class="gl-text-right gl-my-5"> - <gl-link target="_blank" :href="learnMoreUrl"> - {{ $options.i18n.learnMore }} - </gl-link> - </div> + <create-token-button + class="gl-text-right gl-my-5" + :cluster-agent-id="clusterAgentId" + :cursor="cursor" + /> <gl-table :items="tokens" @@ -120,10 +122,9 @@ export default { </gl-table> </div> - <gl-empty-state - v-else - :title="$options.i18n.noTokens" - :primary-button-link="learnMoreUrl" - :primary-button-text="$options.i18n.learnMore" - /> + <gl-empty-state v-else :title="$options.i18n.noTokens"> + <template #actions> + <create-token-button :cluster-agent-id="clusterAgentId" :cursor="cursor" /> + </template> + </gl-empty-state> </template> diff --git a/app/assets/javascripts/clusters/agents/constants.js b/app/assets/javascripts/clusters/agents/constants.js index 98d4707b4de..50d8f5e9e40 100644 --- a/app/assets/javascripts/clusters/agents/constants.js +++ b/app/assets/javascripts/clusters/agents/constants.js @@ -37,3 +37,10 @@ export const EVENT_DETAILS = { export const DEFAULT_ICON = 'token'; export const TOKEN_STATUS_ACTIVE = 'ACTIVE'; + +export const CREATE_TOKEN_MODAL = 'create-token'; +export const EVENT_LABEL_MODAL = 'agent_token_creation_modal'; +export const EVENT_ACTIONS_OPEN = 'open_modal'; +export const EVENT_ACTIONS_CLICK = 'click_button'; + +export const TOKEN_NAME_LIMIT = 255; diff --git a/app/assets/javascripts/clusters/agents/graphql/cache_update.js b/app/assets/javascripts/clusters/agents/graphql/cache_update.js new file mode 100644 index 00000000000..0219c4150eb --- /dev/null +++ b/app/assets/javascripts/clusters/agents/graphql/cache_update.js @@ -0,0 +1,24 @@ +import produce from 'immer'; + +export const hasErrors = ({ errors = [] }) => errors?.length; + +export function addAgentTokenToStore(store, clusterAgentTokenCreate, query, variables) { + if (!hasErrors(clusterAgentTokenCreate)) { + const { token } = clusterAgentTokenCreate; + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, (draftData) => { + draftData.project.clusterAgent.tokens.nodes.unshift(token); + draftData.project.clusterAgent.tokens.count += 1; + }); + + store.writeQuery({ + query, + variables, + data, + }); + } +} diff --git a/app/assets/javascripts/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql b/app/assets/javascripts/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql new file mode 100644 index 00000000000..4a61263ba70 --- /dev/null +++ b/app/assets/javascripts/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql @@ -0,0 +1,11 @@ +#import "../fragments/cluster_agent_token.fragment.graphql" + +mutation createNewAgentToken($input: ClusterAgentTokenCreateInput!) { + clusterAgentTokenCreate(input: $input) { + secret + token { + ...Token + } + errors + } +} diff --git a/app/assets/javascripts/clusters/agents/index.js b/app/assets/javascripts/clusters/agents/index.js index ba7b3edba72..8a447f57f00 100644 --- a/app/assets/javascripts/clusters/agents/index.js +++ b/app/assets/javascripts/clusters/agents/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue'; import apolloProvider from './graphql/provider'; import createRouter from './router'; @@ -16,6 +17,8 @@ export default () => { canAdminVulnerability, emptyStateSvgPath, projectPath, + kasAddress, + canAdminCluster, } = el.dataset; return new Vue({ @@ -28,6 +31,8 @@ export default () => { canAdminVulnerability, emptyStateSvgPath, projectPath, + kasAddress, + canAdminCluster: parseBoolean(canAdminCluster), }, render(createElement) { return createElement(AgentShowPage); diff --git a/app/assets/javascripts/clusters/components/new_cluster.vue b/app/assets/javascripts/clusters/components/new_cluster.vue index 2e74ad073c5..8f3e2916270 100644 --- a/app/assets/javascripts/clusters/components/new_cluster.vue +++ b/app/assets/javascripts/clusters/components/new_cluster.vue @@ -5,9 +5,9 @@ import { s__ } from '~/locale'; export default { i18n: { - title: s__('ClusterIntegration|Enter the details for your Kubernetes cluster'), + title: s__('ClusterIntegration|Enter your Kubernetes cluster certificate details'), information: s__( - 'ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{linkStart}documentation%{linkEnd} on Kubernetes', + 'ClusterIntegration|Enter details about your cluster. %{linkStart}How do I use a certificate to connect to my cluster?%{linkEnd}', ), }, components: { @@ -21,7 +21,7 @@ export default { </script> <template> - <div> + <div class="gl-pt-4"> <h4>{{ $options.i18n.title }}</h4> <p> <gl-sprintf :message="$options.i18n.information"> |