summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/clusters_list/components
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/clusters_list/components
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/clusters_list/components')
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_empty_state.vue119
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue152
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue156
-rw-r--r--app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue83
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue259
5 files changed, 769 insertions, 0 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
new file mode 100644
index 00000000000..405339b3d36
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue
@@ -0,0 +1,119 @@
+<script>
+import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui';
+import { INSTALL_AGENT_MODAL_ID } from '../constants';
+
+export default {
+ modalId: INSTALL_AGENT_MODAL_ID,
+ components: {
+ GlButton,
+ GlEmptyState,
+ GlLink,
+ GlSprintf,
+ GlAlert,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: [
+ 'emptyStateImage',
+ 'projectPath',
+ 'agentDocsUrl',
+ 'installDocsUrl',
+ 'getStartedDocsUrl',
+ 'integrationDocsUrl',
+ ],
+ props: {
+ hasConfigurations: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ repositoryPath() {
+ return `/${this.projectPath}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-empty-state
+ :svg-path="emptyStateImage"
+ :title="s__('ClusterAgents|Integrate Kubernetes with a GitLab Agent')"
+ class="empty-state--agent"
+ >
+ <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}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="agentDocsUrl" target="_blank" data-testid="agent-docs-link">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </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>
+ </p>
+
+ <gl-alert
+ v-if="!hasConfigurations"
+ variant="warning"
+ 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.',
+ )
+ }}
+
+ <template #actions>
+ <gl-button
+ category="primary"
+ variant="info"
+ :href="getStartedDocsUrl"
+ target="_blank"
+ class="gl-ml-0!"
+ >
+ {{ s__('ClusterAgents|Read more about getting started') }}
+ </gl-button>
+ <gl-button category="secondary" variant="info" :href="repositoryPath">
+ {{ s__('ClusterAgents|Go to the repository') }}
+ </gl-button>
+ </template>
+ </gl-alert>
+ </template>
+
+ <template #actions>
+ <gl-button
+ v-gl-modal-directive="$options.modalId"
+ :disabled="!hasConfigurations"
+ data-testid="integration-primary-button"
+ category="primary"
+ variant="success"
+ >
+ {{ s__('ClusterAgents|Integrate with the GitLab Agent') }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
new file mode 100644
index 00000000000..487e512c06d
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -0,0 +1,152 @@
+<script>
+import {
+ GlButton,
+ GlLink,
+ GlModalDirective,
+ 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 { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES, TROUBLESHOOTING_LINK } from '../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlLink,
+ GlTable,
+ GlIcon,
+ GlSprintf,
+ GlTooltip,
+ GlPopover,
+ TimeAgoTooltip,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ mixins: [timeagoMixin],
+ inject: ['integrationDocsUrl'],
+ INSTALL_AGENT_MODAL_ID,
+ AGENT_STATUSES,
+ TROUBLESHOOTING_LINK,
+ props: {
+ agents: {
+ required: true,
+ type: Array,
+ },
+ },
+ computed: {
+ fields() {
+ return [
+ {
+ key: 'name',
+ label: s__('ClusterAgents|Name'),
+ },
+ {
+ key: 'status',
+ label: s__('ClusterAgents|Connection status'),
+ },
+ {
+ key: 'lastContact',
+ label: s__('ClusterAgents|Last contact'),
+ },
+ {
+ key: 'configuration',
+ label: s__('ClusterAgents|Configuration'),
+ },
+ ];
+ },
+ },
+};
+</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-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(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>
+
+ <span v-else>.gitlab/agents/{{ item.name }}</span>
+ <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
+ </span>
+ </template>
+ </gl-table>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
new file mode 100644
index 00000000000..ed44c1f5fa7
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -0,0 +1,156 @@
+<script>
+import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
+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: {
+ agents: {
+ query: getAgentsQuery,
+ variables() {
+ return {
+ defaultBranchName: this.defaultBranchName,
+ projectPath: this.projectPath,
+ ...this.cursor,
+ };
+ },
+ update(data) {
+ this.updateTreeList(data);
+ return data;
+ },
+ },
+ },
+ components: {
+ AgentEmptyState,
+ AgentTable,
+ InstallAgentModal,
+ GlAlert,
+ GlKeysetPagination,
+ GlLoadingIcon,
+ },
+ inject: ['projectPath'],
+ props: {
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ },
+ data() {
+ return {
+ cursor: {
+ first: MAX_LIST_COUNT,
+ last: null,
+ },
+ folderList: {},
+ };
+ },
+ computed: {
+ agentList() {
+ let list = this.agents?.project?.clusterAgents?.nodes;
+
+ if (list) {
+ list = list.map((agent) => {
+ const configFolder = this.folderList[agent.name];
+ const lastContact = this.getLastContact(agent);
+ const status = this.getStatus(lastContact);
+ return { ...agent, configFolder, lastContact, status };
+ });
+ }
+
+ return list;
+ },
+ agentPageInfo() {
+ return this.agents?.project?.clusterAgents?.pageInfo || {};
+ },
+ isLoading() {
+ return this.$apollo.queries.agents.loading;
+ },
+ showPagination() {
+ return this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage;
+ },
+ treePageInfo() {
+ return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
+ },
+ hasConfigurations() {
+ return Boolean(this.agents?.project?.repository?.tree?.trees?.nodes?.length);
+ },
+ },
+ methods: {
+ reloadAgents() {
+ this.$apollo.queries.agents.refetch();
+ },
+ nextPage() {
+ this.cursor = {
+ first: MAX_LIST_COUNT,
+ last: null,
+ afterAgent: this.agentPageInfo.endCursor,
+ afterTree: this.treePageInfo.endCursor,
+ };
+ },
+ prevPage() {
+ this.cursor = {
+ first: null,
+ last: MAX_LIST_COUNT,
+ beforeAgent: this.agentPageInfo.startCursor,
+ beforeTree: this.treePageInfo.endCursor,
+ };
+ },
+ updateTreeList(data) {
+ const configFolders = data?.project?.repository?.tree?.trees?.nodes;
+
+ if (configFolders) {
+ configFolders.forEach((folder) => {
+ this.folderList[folder.name] = folder;
+ });
+ }
+ },
+ getLastContact(agent) {
+ const tokens = agent?.tokens?.nodes;
+ let lastContact = null;
+ if (tokens?.length) {
+ tokens.forEach((token) => {
+ const lastContactToDate = new Date(token.lastUsedAt).getTime();
+ if (lastContactToDate > lastContact) {
+ lastContact = lastContactToDate;
+ }
+ });
+ }
+ return lastContact;
+ },
+ getStatus(lastContact) {
+ if (lastContact) {
+ const now = new Date().getTime();
+ const diff = now - lastContact;
+
+ return diff > ACTIVE_CONNECTION_TIME ? 'inactive' : 'active';
+ }
+ return 'unused';
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" />
+
+ <section v-else-if="agentList" class="gl-mt-3">
+ <div v-if="agentList.length">
+ <AgentTable :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" />
+ </section>
+
+ <gl-alert v-else variant="danger" :dismissible="false">
+ {{ s__('ClusterAgents|An error occurred while loading your GitLab Agents') }}
+ </gl-alert>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue b/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue
new file mode 100644
index 00000000000..9fb020d2f4f
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue
@@ -0,0 +1,83 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
+import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
+
+export default {
+ name: 'AvailableAgentsDropdown',
+ i18n: I18N_AVAILABLE_AGENTS_DROPDOWN,
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ inject: ['projectPath'],
+ props: {
+ isRegistering: {
+ required: true,
+ type: Boolean,
+ },
+ },
+ apollo: {
+ agents: {
+ query: agentConfigurations,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ this.populateAvailableAgents(data);
+ },
+ },
+ },
+ data() {
+ return {
+ availableAgents: [],
+ selectedAgent: null,
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.agents.loading;
+ },
+ dropdownText() {
+ if (this.isRegistering) {
+ return this.$options.i18n.registeringAgent;
+ } else if (this.selectedAgent === null) {
+ return this.$options.i18n.selectAgent;
+ }
+
+ return this.selectedAgent;
+ },
+ },
+ methods: {
+ selectAgent(agent) {
+ this.$emit('agentSelected', agent);
+ this.selectedAgent = agent;
+ },
+ isSelected(agent) {
+ return this.selectedAgent === agent;
+ },
+ populateAvailableAgents(data) {
+ const installedAgents = data?.project?.clusterAgents?.nodes.map((agent) => agent.name) ?? [];
+ const configuredAgents =
+ data?.project?.agentConfigurations?.nodes.map((config) => config.agentName) ?? [];
+
+ this.availableAgents = configuredAgents.filter((agent) => !installedAgents.includes(agent));
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown :text="dropdownText" :loading="isLoading || isRegistering">
+ <gl-dropdown-item
+ v-for="agent in availableAgents"
+ :key="agent"
+ :is-checked="isSelected(agent)"
+ is-check-item
+ @click="selectAgent(agent)"
+ >
+ {{ agent }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+</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
new file mode 100644
index 00000000000..5f192fe4d5a
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -0,0 +1,259 @@
+<script>
+import {
+ GlAlert,
+ GlButton,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlLink,
+ GlModal,
+ GlSprintf,
+} from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+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 createAgent from '../graphql/mutations/create_agent.mutation.graphql';
+import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
+import AvailableAgentsDropdown from './available_agents_dropdown.vue';
+
+export default {
+ modalId: INSTALL_AGENT_MODAL_ID,
+ i18n: I18N_INSTALL_AGENT_MODAL,
+ components: {
+ AvailableAgentsDropdown,
+ ClipboardButton,
+ CodeBlock,
+ GlAlert,
+ GlButton,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlLink,
+ GlModal,
+ GlSprintf,
+ },
+ inject: ['projectPath', 'kasAddress'],
+ data() {
+ return {
+ registering: false,
+ agentName: null,
+ agentToken: null,
+ error: null,
+ };
+ },
+ computed: {
+ registered() {
+ return Boolean(this.agentToken);
+ },
+ nextButtonDisabled() {
+ return !this.registering && this.agentName !== null;
+ },
+ canCancel() {
+ return !this.registered && !this.registering;
+ },
+ agentRegistrationCommand() {
+ return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
+ },
+ basicInstallPath() {
+ return helpPagePath('user/clusters/agent/index', {
+ anchor: 'install-the-agent-into-the-cluster',
+ });
+ },
+ advancedInstallPath() {
+ return helpPagePath('user/clusters/agent/index', { anchor: 'advanced-installation' });
+ },
+ },
+ methods: {
+ setAgentName(name) {
+ this.agentName = name;
+ },
+ cancelClicked() {
+ this.$refs.modal.hide();
+ },
+ doneClicked() {
+ this.$emit('agentRegistered');
+ this.$refs.modal.hide();
+ },
+ resetModal() {
+ this.registering = null;
+ this.agentName = null;
+ this.agentToken = null;
+ this.error = null;
+ },
+ createAgentMutation() {
+ return this.$apollo
+ .mutate({
+ mutation: createAgent,
+ variables: {
+ input: {
+ name: this.agentName,
+ projectPath: this.projectPath,
+ },
+ },
+ })
+ .then(({ data: { createClusterAgent } }) => createClusterAgent);
+ },
+ createAgentTokenMutation(agendId) {
+ return this.$apollo
+ .mutate({
+ mutation: createAgentToken,
+ variables: {
+ input: {
+ clusterAgentId: agendId,
+ name: this.agentName,
+ },
+ },
+ })
+ .then(({ data: { clusterAgentTokenCreate } }) => clusterAgentTokenCreate);
+ },
+ async registerAgent() {
+ this.registering = true;
+ this.error = null;
+
+ try {
+ const { errors: agentErrors, clusterAgent } = await this.createAgentMutation();
+
+ if (agentErrors?.length > 0) {
+ throw new Error(agentErrors[0]);
+ }
+
+ const { errors: tokenErrors, secret } = await this.createAgentTokenMutation(
+ clusterAgent.id,
+ );
+
+ 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.registering = false;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="modal"
+ :modal-id="$options.modalId"
+ :title="$options.i18n.modalTitle"
+ static
+ lazy
+ @hidden="resetModal"
+ >
+ <template v-if="!registered">
+ <p>
+ <strong>{{ $options.i18n.selectAgentTitle }}</strong>
+ </p>
+
+ <p>
+ <gl-sprintf :message="$options.i18n.selectAgentBody">
+ <template #link="{ content }">
+ <gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <form>
+ <gl-form-group label-for="agent-name">
+ <available-agents-dropdown
+ class="gl-w-70p"
+ :is-registering="registering"
+ @agentSelected="setAgentName"
+ />
+ </gl-form-group>
+ </form>
+
+ <p v-if="error">
+ <gl-alert
+ :title="$options.i18n.registrationErrorTitle"
+ variant="danger"
+ :dismissible="false"
+ >
+ {{ error }}
+ </gl-alert>
+ </p>
+ </template>
+
+ <template v-else>
+ <p>
+ <strong>{{ $options.i18n.tokenTitle }}</strong>
+ </p>
+
+ <p>
+ <gl-sprintf :message="$options.i18n.tokenBody">
+ <template #link="{ content }">
+ <gl-link :href="basicInstallPath" target="_blank"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <p>
+ <gl-alert
+ :title="$options.i18n.tokenSingleUseWarningTitle"
+ variant="warning"
+ :dismissible="false"
+ >
+ {{ $options.i18n.tokenSingleUseWarningBody }}
+ </gl-alert>
+ </p>
+
+ <p>
+ <gl-form-input-group readonly :value="agentToken" :select-on-click="true">
+ <template #append>
+ <clipboard-button :text="agentToken" :title="$options.i18n.copyToken" />
+ </template>
+ </gl-form-input-group>
+ </p>
+
+ <p>
+ <strong>{{ $options.i18n.basicInstallTitle }}</strong>
+ </p>
+
+ <p>
+ {{ $options.i18n.basicInstallBody }}
+ </p>
+
+ <p>
+ <code-block :code="agentRegistrationCommand" />
+ </p>
+
+ <p>
+ <strong>{{ $options.i18n.advancedInstallTitle }}</strong>
+ </p>
+
+ <p>
+ <gl-sprintf :message="$options.i18n.advancedInstallBody">
+ <template #link="{ content }">
+ <gl-link :href="advancedInstallPath" target="_blank"> {{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </template>
+
+ <template #modal-footer>
+ <gl-button v-if="canCancel" @click="cancelClicked">{{ $options.i18n.cancel }} </gl-button>
+
+ <gl-button v-if="registered" variant="confirm" category="primary" @click="doneClicked"
+ >{{ $options.i18n.done }}
+ </gl-button>
+
+ <gl-button
+ v-else
+ :disabled="!nextButtonDisabled"
+ variant="confirm"
+ category="primary"
+ @click="registerAgent"
+ >{{ $options.i18n.next }}
+ </gl-button>
+ </template>
+ </gl-modal>
+</template>