summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue19
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue46
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql9
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/queries/get_integrations.query.graphql11
-rw-r--r--app/assets/javascripts/alerts_settings/index.js20
-rw-r--r--app/assets/javascripts/feature_flags/components/edit_feature_flag.vue2
-rw-r--r--app/assets/javascripts/pages/admin/runners/index.js13
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js24
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js46
-rw-r--r--app/assets/javascripts/pages/shared/mount_runner_instructions.js32
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue5
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue15
-rw-r--r--app/assets/javascripts/static_site_editor/services/front_matterify.js2
-rw-r--r--app/assets/javascripts/static_site_editor/services/parse_source_file.js18
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql20
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql16
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue214
17 files changed, 450 insertions, 62 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
index d377f0f2654..432271d2075 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTable, GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlTable, GlIcon, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import { trackAlertIntegrationsViewsOptions } from '../constants';
@@ -27,6 +27,7 @@ export default {
components: {
GlTable,
GlIcon,
+ GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -37,10 +38,15 @@ export default {
required: false,
default: () => [],
},
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
fields: [
{
- key: 'activated',
+ key: 'active',
label: __('Status'),
},
{
@@ -78,12 +84,13 @@ export default {
:empty-text="$options.i18n.emptyState"
:items="integrations"
:fields="$options.fields"
+ :busy="loading"
stacked="md"
:tbody-tr-class="tbodyTrClass"
show-empty
>
- <template #cell(activated)="{ item }">
- <span v-if="item.activated" data-testid="integration-activated-status">
+ <template #cell(active)="{ item }">
+ <span v-if="item.active" data-testid="integration-activated-status">
<gl-icon
v-gl-tooltip
name="check-circle-filled"
@@ -104,6 +111,10 @@ export default {
{{ $options.i18n.status.disabled.name }}
</span>
</template>
+
+ <template #table-busy>
+ <gl-loading-icon size="lg" color="dark" class="mt-3" />
+ </template>
</gl-table>
</div>
</template>
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
index 1edb8f1c921..1576faf13a9 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -1,6 +1,8 @@
<script>
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { fetchPolicies } from '~/lib/graphql';
+import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue';
@@ -19,19 +21,52 @@ export default {
prometheus: {
default: {},
},
+ projectPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ integrations: {
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ query: getIntegrationsQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ const { alertManagementIntegrations: { nodes: list = [] } = {} } = data.project || {};
+
+ return {
+ list,
+ };
+ },
+ error() {
+ this.errored = true;
+ },
+ },
+ },
+ data() {
+ return {
+ errored: false,
+ integrations: {},
+ };
},
computed: {
- integrations() {
+ loading() {
+ return this.$apollo.queries.integrations.loading;
+ },
+ intergrationsOptionsOld() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
- activated: this.generic.activated,
+ active: this.generic.activated,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
- activated: this.prometheus.activated,
+ active: this.prometheus.activated,
},
];
},
@@ -41,7 +76,10 @@ export default {
<template>
<div>
- <integrations-list :integrations="integrations" />
+ <integrations-list
+ :integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
+ :loading="loading"
+ />
<settings-form-new v-if="glFeatures.httpIntegrationsList" />
<settings-form-old v-else />
</div>
diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql
new file mode 100644
index 00000000000..6d9307959df
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql
@@ -0,0 +1,9 @@
+fragment IntegrationItem on AlertManagementIntegration {
+ id
+ type
+ active
+ name
+ url
+ token
+ apiUrl
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/get_integrations.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/get_integrations.query.graphql
new file mode 100644
index 00000000000..228dd5fb176
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/queries/get_integrations.query.graphql
@@ -0,0 +1,11 @@
+#import "../fragments/integration_item.fragment.graphql"
+
+query getIntegrations($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ alertManagementIntegrations {
+ nodes {
+ ...IntegrationItem
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
index 80f06a094b7..8f22b8d1dcd 100644
--- a/app/assets/javascripts/alerts_settings/index.js
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -1,7 +1,11 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
+Vue.use(VueApollo);
+
export default el => {
if (!el) {
return null;
@@ -24,8 +28,22 @@ export default el => {
opsgenieMvcFormPath,
opsgenieMvcEnabled,
opsgenieMvcTargetUrl,
+ projectPath,
} = el.dataset;
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {},
+ },
+ ),
+ });
+
+ apolloProvider.clients.defaultClient.cache.writeData({
+ data: {},
+ });
+
return new Vue({
el,
provide: {
@@ -51,7 +69,9 @@ export default el => {
opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
},
+ projectPath,
},
+ apolloProvider,
components: {
AlertSettingsWrapper,
},
diff --git a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
index 26b18f9bf5a..9ec65bb0b43 100644
--- a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
+++ b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
@@ -103,7 +103,7 @@ export default {
>
{{ $options.translations.newFlagAlert }}
</gl-alert>
- <gl-loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-7" />
<template v-else-if="!isLoading && !hasError">
<gl-alert v-if="deprecatedAndEditable" variant="warning" :dismissible="false" class="gl-my-5">
diff --git a/app/assets/javascripts/pages/admin/runners/index.js b/app/assets/javascripts/pages/admin/runners/index.js
index e60c6133c7c..104b7eeaf96 100644
--- a/app/assets/javascripts/pages/admin/runners/index.js
+++ b/app/assets/javascripts/pages/admin/runners/index.js
@@ -1,11 +1,12 @@
import initFilteredSearch from '~/pages/search/init_filtered_search';
import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
+import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
-document.addEventListener('DOMContentLoaded', () => {
- initFilteredSearch({
- page: FILTERED_SEARCH.ADMIN_RUNNERS,
- filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
- useDefaultState: true,
- });
+initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
+ useDefaultState: true,
});
+
+initInstallRunner();
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index e8d8c985ade..3456048d718 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -4,18 +4,18 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import initSharedRunnersForm from '~/group_settings/mount_shared_runners';
+import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
-document.addEventListener('DOMContentLoaded', () => {
- // Initialize expandable settings panels
- initSettingsPanels();
+// Initialize expandable settings panels
+initSettingsPanels();
- initFilteredSearch({
- page: FILTERED_SEARCH.ADMIN_RUNNERS,
- filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
- anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
- useDefaultState: false,
- });
-
- initSharedRunnersForm();
- initVariableList();
+initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
+ anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
+ useDefaultState: false,
});
+
+initSharedRunnersForm();
+initVariableList();
+initInstallRunner();
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index d18cde4ac87..5d4c1595342 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -4,32 +4,32 @@ import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
+import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
-document.addEventListener('DOMContentLoaded', () => {
- // Initialize expandable settings panels
- initSettingsPanels();
+// Initialize expandable settings panels
+initSettingsPanels();
- const runnerToken = document.querySelector('.js-secret-runner-token');
- if (runnerToken) {
- const runnerTokenSecretValue = new SecretValues({
- container: runnerToken,
- });
- runnerTokenSecretValue.init();
- }
-
- initVariableList();
-
- // hide extra auto devops settings based checkbox state
- const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
- const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
- document.querySelector('.js-toggle-extra-settings').addEventListener('click', event => {
- const { target } = event;
- if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
- autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
+const runnerToken = document.querySelector('.js-secret-runner-token');
+if (runnerToken) {
+ const runnerTokenSecretValue = new SecretValues({
+ container: runnerToken,
});
+ runnerTokenSecretValue.init();
+}
- registrySettingsApp();
- initDeployFreeze();
+initVariableList();
- initSettingsPipelinesTriggers();
+// hide extra auto devops settings based checkbox state
+const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
+const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
+document.querySelector('.js-toggle-extra-settings').addEventListener('click', event => {
+ const { target } = event;
+ if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
+ autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
});
+
+registrySettingsApp();
+initDeployFreeze();
+
+initSettingsPipelinesTriggers();
+initInstallRunner();
diff --git a/app/assets/javascripts/pages/shared/mount_runner_instructions.js b/app/assets/javascripts/pages/shared/mount_runner_instructions.js
new file mode 100644
index 00000000000..b7662155339
--- /dev/null
+++ b/app/assets/javascripts/pages/shared/mount_runner_instructions.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
+
+Vue.use(VueApollo);
+
+export function initInstallRunner(componentId = 'js-install-runner') {
+ const installRunnerEl = document.getElementById(componentId);
+ const { projectPath, groupPath } = installRunnerEl?.dataset;
+
+ if (installRunnerEl) {
+ const defaultClient = createDefaultClient();
+
+ const apolloProvider = new VueApollo({
+ defaultClient,
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: installRunnerEl,
+ apolloProvider,
+ provide: {
+ projectPath,
+ groupPath,
+ },
+ render(createElement) {
+ return createElement(InstallRunnerInstructions);
+ },
+ });
+ }
+}
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index 7aee2573ce1..09f40601fbf 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -119,6 +119,9 @@ export default {
},
},
methods: {
+ hideTooltips() {
+ this.$root.$emit('bv::hide::tooltip');
+ },
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
@@ -135,7 +138,7 @@ export default {
:class="jobClasses"
class="js-pipeline-graph-job-link qa-job-link menu-item"
data-testid="job-with-link"
- @click.stop
+ @click.stop="hideTooltips"
>
<job-name-component :name="job.name" :status="job.status" />
</gl-link>
diff --git a/app/assets/javascripts/static_site_editor/components/edit_area.vue b/app/assets/javascripts/static_site_editor/components/edit_area.vue
index 56f1a26f005..5b2d0207e98 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_area.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue
@@ -50,12 +50,12 @@ export default {
},
data() {
return {
- saveable: false,
parsedSource: parseSourceFile(this.preProcess(true, this.content)),
editorMode: EDITOR_TYPES.wysiwyg,
- isModified: false,
hasMatter: false,
isDrawerOpen: false,
+ isModified: false,
+ isSaveable: false,
};
},
imageRepository: imageRepository(),
@@ -85,8 +85,11 @@ export default {
return templatedContent;
},
refreshEditHelpers() {
- this.isModified = this.parsedSource.isModified();
- this.hasMatter = this.parsedSource.hasMatter();
+ const { isModified, hasMatter, isMatterValid } = this.parsedSource;
+ this.isModified = isModified();
+ this.hasMatter = hasMatter();
+ const hasValidMatter = this.hasMatter ? isMatterValid() : true;
+ this.isSaveable = this.isModified && hasValidMatter;
},
onDrawerOpen() {
this.isDrawerOpen = true;
@@ -142,12 +145,12 @@ export default {
@input="onInputChange"
@uploadImage="onUploadImage"
/>
- <unsaved-changes-confirm-dialog :modified="isModified" />
+ <unsaved-changes-confirm-dialog :modified="isSaveable" />
<publish-toolbar
class="gl-fixed gl-left-0 gl-bottom-0 gl-w-full"
:has-settings="hasSettings"
:return-url="returnUrl"
- :saveable="isModified"
+ :saveable="isSaveable"
:saving-changes="savingChanges"
@editSettings="onDrawerOpen"
@submit="onSubmit"
diff --git a/app/assets/javascripts/static_site_editor/services/front_matterify.js b/app/assets/javascripts/static_site_editor/services/front_matterify.js
index cbf0fffd515..60a5d799d11 100644
--- a/app/assets/javascripts/static_site_editor/services/front_matterify.js
+++ b/app/assets/javascripts/static_site_editor/services/front_matterify.js
@@ -16,6 +16,7 @@ export const frontMatterify = source => {
const NO_FRONTMATTER = {
source,
matter: null,
+ hasMatter: false,
spacing: null,
content: source,
delimiter: null,
@@ -53,6 +54,7 @@ export const frontMatterify = source => {
return {
source,
matter,
+ hasMatter: true,
spacing,
content,
delimiter,
diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file.js b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
index d4fc8b2edb6..39126eb7bcc 100644
--- a/app/assets/javascripts/static_site_editor/services/parse_source_file.js
+++ b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
@@ -1,15 +1,18 @@
import { frontMatterify, stringify } from './front_matterify';
const parseSourceFile = raw => {
- const remake = source => frontMatterify(source);
-
- let editable = remake(raw);
+ let editable;
const syncContent = (newVal, isBody) => {
if (isBody) {
editable.content = newVal;
} else {
- editable = remake(newVal);
+ try {
+ editable = frontMatterify(newVal);
+ editable.isMatterValid = true;
+ } catch (e) {
+ editable.isMatterValid = false;
+ }
}
};
@@ -23,10 +26,15 @@ const parseSourceFile = raw => {
const isModified = () => stringify(editable) !== raw;
- const hasMatter = () => Boolean(editable.matter);
+ const hasMatter = () => editable.hasMatter;
+
+ const isMatterValid = () => editable.isMatterValid;
+
+ syncContent(raw);
return {
matter,
+ isMatterValid,
syncMatter,
content,
syncContent,
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql
new file mode 100644
index 00000000000..ff0626167a9
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql
@@ -0,0 +1,20 @@
+query getRunnerPlatforms($projectPath: ID!, $groupPath: ID!) {
+ runnerPlatforms {
+ nodes {
+ name
+ humanReadableName
+ architectures {
+ nodes {
+ name
+ downloadLocation
+ }
+ }
+ }
+ }
+ project(fullPath: $projectPath) {
+ id
+ }
+ group(fullPath: $groupPath) {
+ id
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql
new file mode 100644
index 00000000000..643c1991807
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql
@@ -0,0 +1,16 @@
+query runnerSetupInstructions(
+ $platform: String!
+ $architecture: String!
+ $projectId: ID!
+ $groupId: ID!
+) {
+ runnerSetup(
+ platform: $platform
+ architecture: $architecture
+ projectId: $projectId
+ groupId: $groupId
+ ) {
+ installInstructions
+ registerInstructions
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue
new file mode 100644
index 00000000000..995922454c4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions.vue
@@ -0,0 +1,214 @@
+<script>
+import {
+ GlAlert,
+ GlButton,
+ GlModal,
+ GlModalDirective,
+ GlButtonGroup,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+} from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import getRunnerPlatforms from './graphql/queries/get_runner_platforms.query.graphql';
+import getRunnerSetupInstructions from './graphql/queries/get_runner_setup.query.graphql';
+
+export default {
+ components: {
+ GlAlert,
+ GlButton,
+ GlButtonGroup,
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlIcon,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ groupPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ runnerPlatforms: {
+ query: getRunnerPlatforms,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ groupPath: this.groupPath,
+ };
+ },
+ update(data) {
+ return data;
+ },
+ error() {
+ this.showAlert = true;
+ },
+ },
+ },
+ data() {
+ return {
+ showAlert: false,
+ selectedPlatformArchitectures: [],
+ selectedPlatform: {},
+ selectedArchitecture: {},
+ runnerPlatforms: {},
+ instructions: {},
+ };
+ },
+ computed: {
+ isPlatformSelected() {
+ return Object.keys(this.selectedPlatform).length > 0;
+ },
+ instructionsEmpty() {
+ return this.instructions && Object.keys(this.instructions).length === 0;
+ },
+ groupId() {
+ return this.runnerPlatforms?.group?.id ?? '';
+ },
+ projectId() {
+ return this.runnerPlatforms?.project?.id ?? '';
+ },
+ platforms() {
+ return this.runnerPlatforms.runnerPlatforms?.nodes;
+ },
+ },
+ methods: {
+ selectPlatform(name) {
+ this.selectedPlatform = this.platforms.find(platform => platform.name === name);
+ this.selectedPlatformArchitectures = this.selectedPlatform?.architectures?.nodes;
+ [this.selectedArchitecture] = this.selectedPlatformArchitectures;
+ this.selectArchitecture(this.selectedArchitecture);
+ },
+ selectArchitecture(architecture) {
+ this.selectedArchitecture = architecture;
+
+ this.$apollo.addSmartQuery('instructions', {
+ variables() {
+ return {
+ platform: this.selectedPlatform.name,
+ architecture: this.selectedArchitecture.name,
+ projectId: this.projectId,
+ groupId: this.groupId,
+ };
+ },
+ query: getRunnerSetupInstructions,
+ update(data) {
+ return data?.runnerSetup;
+ },
+ error() {
+ this.showAlert = true;
+ },
+ });
+ },
+ toggleAlert(state) {
+ this.showAlert = state;
+ },
+ },
+ modalId: 'installation-instructions-modal',
+ i18n: {
+ installARunner: __('Install a Runner'),
+ architecture: s__('Runners|Architecture'),
+ downloadInstallBinary: s__('Runners|Download and Install Binary'),
+ downloadLatestBinary: s__('Runners|Download Latest Binary'),
+ registerRunner: s__('Runners|Register Runner'),
+ method: __('Method'),
+ fetchError: s__('An error has occurred fetching instructions'),
+ instructions: __('Show Runner installation instructions'),
+ },
+ closeButton: {
+ text: __('Close'),
+ attributes: [{ variant: 'default' }],
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-button v-gl-modal-directive="$options.modalId" data-testid="show-modal-button">
+ {{ $options.i18n.instructions }}
+ </gl-button>
+ <gl-modal
+ :modal-id="$options.modalId"
+ :title="$options.i18n.installARunner"
+ :action-secondary="$options.closeButton"
+ >
+ <gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)">
+ {{ $options.i18n.fetchError }}
+ </gl-alert>
+ <h5>{{ __('Environment') }}</h5>
+ <gl-button-group class="gl-mb-5">
+ <gl-button
+ v-for="platform in platforms"
+ :key="platform.name"
+ data-testid="platform-button"
+ @click="selectPlatform(platform.name)"
+ >
+ {{ platform.humanReadableName }}
+ </gl-button>
+ </gl-button-group>
+ <template v-if="isPlatformSelected">
+ <h5>
+ {{ $options.i18n.architecture }}
+ </h5>
+ <gl-dropdown class="gl-mb-5" :text="selectedArchitecture.name">
+ <gl-dropdown-item
+ v-for="architecture in selectedPlatformArchitectures"
+ :key="architecture.name"
+ data-testid="architecture-dropdown-item"
+ @click="selectArchitecture(architecture)"
+ >
+ {{ architecture.name }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ <div class="gl-display-flex gl-align-items-center gl-mb-5">
+ <h5>{{ $options.i18n.downloadInstallBinary }}</h5>
+ <gl-button
+ class="gl-ml-auto"
+ :href="selectedArchitecture.downloadLocation"
+ download
+ data-testid="binary-download-button"
+ >
+ {{ $options.i18n.downloadLatestBinary }}
+ </gl-button>
+ </div>
+ </template>
+ <template v-if="!instructionsEmpty">
+ <div class="gl-display-flex">
+ <pre class="bg-light gl-flex-fill-1" data-testid="binary-instructions">
+ {{ instructions.installInstructions }}
+ </pre>
+ <gl-button
+ class="gl-align-self-start gl-ml-2 gl-mt-2"
+ category="tertiary"
+ variant="link"
+ :data-clipboard-text="instructions.installationInstructions"
+ >
+ <gl-icon name="copy-to-clipboard" />
+ </gl-button>
+ </div>
+
+ <hr />
+ <h5 class="gl-mb-5">{{ $options.i18n.registerRunner }}</h5>
+ <h5 class="gl-mb-5">{{ $options.i18n.method }}</h5>
+ <div class="gl-display-flex">
+ <pre class="bg-light gl-flex-fill-1" data-testid="runner-instructions">
+ {{ instructions.registerInstructions }}
+ </pre>
+ <gl-button
+ class="gl-align-self-start gl-ml-2 gl-mt-2"
+ category="tertiary"
+ variant="link"
+ :data-clipboard-text="instructions.registerInstructions"
+ >
+ <gl-icon name="copy-to-clipboard" />
+ </gl-button>
+ </div>
+ </template>
+ </gl-modal>
+ </div>
+</template>