summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/clusters_list/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/clusters_list/components')
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_options.vue200
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue65
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue6
3 files changed, 248 insertions, 23 deletions
diff --git a/app/assets/javascripts/clusters_list/components/agent_options.vue b/app/assets/javascripts/clusters_list/components/agent_options.vue
new file mode 100644
index 00000000000..a364122ba56
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/agent_options.vue
@@ -0,0 +1,200 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+ GlFormGroup,
+ GlFormInput,
+} from '@gitlab/ui';
+import { s__, __, sprintf } from '~/locale';
+import { 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.'),
+ },
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlSprintf,
+ GlFormGroup,
+ GlFormInput,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['projectPath'],
+ props: {
+ agent: {
+ required: true,
+ type: Object,
+ validator: (value) => ['id', 'name'].every((prop) => value[prop]),
+ },
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ maxAgents: {
+ default: null,
+ required: false,
+ type: Number,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ error: null,
+ deleteConfirmText: null,
+ agentName: this.agent.name,
+ };
+ },
+ computed: {
+ getAgentsQueryVariables() {
+ return {
+ defaultBranchName: this.defaultBranchName,
+ first: this.maxAgents,
+ last: null,
+ projectPath: this.projectPath,
+ };
+ },
+ modalId() {
+ return sprintf(DELETE_AGENT_MODAL_ID, {
+ agentName: this.agent.name,
+ });
+ },
+ primaryModalProps() {
+ return {
+ text: this.$options.i18n.modalAction,
+ attributes: [
+ { disabled: this.loading || this.disableModalSubmit, loading: this.loading },
+ { variant: 'danger' },
+ ],
+ };
+ },
+ cancelModalProps() {
+ return {
+ text: this.$options.i18n.modalCancel,
+ attributes: [],
+ };
+ },
+ disableModalSubmit() {
+ return this.deleteConfirmText !== this.agent.name;
+ },
+ },
+ methods: {
+ async deleteAgent() {
+ if (this.disableModalSubmit || this.loading) {
+ return;
+ }
+
+ this.loading = true;
+ this.error = null;
+
+ try {
+ const { errors } = await this.deleteAgentMutation();
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ this.error = error?.message || this.$options.i18n.defaultError;
+ } finally {
+ this.loading = false;
+ const successMessage = sprintf(this.$options.i18n.successMessage, { name: this.agentName });
+
+ this.$toast.show(this.error || successMessage);
+
+ this.$refs.modal.hide();
+ }
+ },
+ deleteAgentMutation() {
+ return this.$apollo
+ .mutate({
+ mutation: deleteAgent,
+ variables: {
+ input: {
+ id: this.agent.id,
+ },
+ },
+ update: (store) => {
+ const deleteClusterAgent = this.agent;
+ removeAgentFromStore(
+ store,
+ deleteClusterAgent,
+ getAgentsQuery,
+ this.getAgentsQueryVariables,
+ );
+ },
+ })
+
+ .then(({ data: { clusterAgentDelete } }) => {
+ return clusterAgentDelete;
+ });
+ },
+ hideModal() {
+ this.loading = false;
+ this.error = null;
+ this.deleteConfirmText = null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown
+ icon="ellipsis_v"
+ right
+ :disabled="loading"
+ :text="$options.i18n.dropdownText"
+ text-sr-only
+ category="tertiary"
+ no-caret
+ >
+ <gl-dropdown-item v-gl-modal-directive="modalId">
+ {{ $options.i18n.deleteButton }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+
+ <gl-modal
+ ref="modal"
+ :modal-id="modalId"
+ :title="$options.i18n.modalTitle"
+ :action-primary="primaryModalProps"
+ :action-cancel="cancelModalProps"
+ size="sm"
+ @primary="deleteAgent"
+ @hide="hideModal"
+ >
+ <p>{{ $options.i18n.modalBody }}</p>
+
+ <gl-form-group>
+ <template #label>
+ <gl-sprintf :message="$options.i18n.modalInputLabel">
+ <template #name>
+ <code>{{ agent.name }}</code>
+ </template>
+ </gl-sprintf>
+ </template>
+ <gl-form-input v-model="deleteConfirmText" @keydown.enter="deleteAgent" />
+ </gl-form-group>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 000730ac1ba..695e16b7b4b 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -1,21 +1,23 @@
<script>
-import {
- GlLink,
- GlModalDirective,
- GlTable,
- GlIcon,
- GlSprintf,
- GlTooltip,
- GlPopover,
-} from '@gitlab/ui';
-import { s__ } from '~/locale';
+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 { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
+import { AGENT_STATUSES } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
+import AgentOptions from './agent_options.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'),
+ },
components: {
GlLink,
GlTable,
@@ -24,14 +26,10 @@ export default {
GlTooltip,
GlPopover,
TimeAgoTooltip,
- },
- directives: {
- GlModalDirective,
+ AgentOptions,
},
mixins: [timeagoMixin],
- INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
-
troubleshooting_link: helpPagePath('user/clusters/agent/index', {
anchor: 'troubleshooting',
}),
@@ -40,6 +38,16 @@ export default {
required: true,
type: Array,
},
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ maxAgents: {
+ default: null,
+ required: false,
+ type: Number,
+ },
},
computed: {
fields() {
@@ -47,22 +55,27 @@ export default {
return [
{
key: 'name',
- label: s__('ClusterAgents|Name'),
+ label: this.$options.i18n.nameLabel,
tdClass,
},
{
key: 'status',
- label: s__('ClusterAgents|Connection status'),
+ label: this.$options.i18n.statusLabel,
tdClass,
},
{
key: 'lastContact',
- label: s__('ClusterAgents|Last contact'),
+ label: this.$options.i18n.lastContactLabel,
tdClass,
},
{
key: 'configuration',
- label: s__('ClusterAgents|Configuration'),
+ label: this.$options.i18n.configurationLabel,
+ tdClass,
+ },
+ {
+ key: 'options',
+ label: this.$options.i18n.optionsLabel,
tdClass,
},
];
@@ -118,7 +131,7 @@ export default {
</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
+ {{ $options.i18n.troubleshootingText }}</gl-link
>
</p>
</gl-popover>
@@ -127,7 +140,7 @@ export default {
<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 v-else>{{ $options.i18n.neverConnectedText }}</span>
</span>
</template>
@@ -140,5 +153,13 @@ export default {
<span v-else>{{ getAgentConfigPath(item.name) }}</span>
</span>
</template>
+
+ <template #cell(options)="{ item }">
+ <agent-options
+ :agent="item"
+ :default-branch-name="defaultBranchName"
+ :max-agents="maxAgents"
+ />
+ </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 45108a28e37..4fc421e7c31 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -151,7 +151,11 @@ export default {
<section v-else-if="agentList">
<div v-if="agentList.length">
- <agent-table :agents="agentList" />
+ <agent-table
+ :agents="agentList"
+ :default-branch-name="defaultBranchName"
+ :max-agents="cursor.first"
+ />
<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" />