Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
commit43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch)
treedceebdc68925362117480a5d672bcff122fb625b /app/assets/javascripts/ci/runner
parent20c84b99005abd1c82101dfeff264ac50d2df211 (diff)
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/ci/runner')
-rw-r--r--app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue84
-rw-r--r--app/assets/javascripts/ci/runner/admin_new_runner/index.js8
-rw-r--r--app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue69
-rw-r--r--app/assets/javascripts/ci/runner/admin_register_runner/index.js36
-rw-r--r--app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue6
-rw-r--r--app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue30
-rw-r--r--app/assets/javascripts/ci/runner/admin_runners/index.js4
-rw-r--r--app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue71
-rw-r--r--app/assets/javascripts/ci/runner/components/cells/runner_summary_field.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/cli_command.vue42
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue135
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue50
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue80
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue41
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue256
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_token.vue6
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue6
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh12
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh11
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps113
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/utils.js81
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_create_form.vue120
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_delete_button.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs.vue7
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue27
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_jobs_table.vue6
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue27
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_pause_button.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_platforms_radio.vue12
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_platforms_radio_group.vue26
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_projects.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_update_form.vue6
-rw-r--r--app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue2
-rw-r--r--app/assets/javascripts/ci/runner/constants.js72
-rw-r--r--app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql7
-rw-r--r--app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql9
-rw-r--r--app/assets/javascripts/ci/runner/graphql/register/runner_for_registration.query.graphql8
-rw-r--r--app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue83
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/index.js32
-rw-r--r--app/assets/javascripts/ci/runner/group_register_runner/group_register_runner_app.vue69
-rw-r--r--app/assets/javascripts/ci/runner/group_register_runner/index.js36
-rw-r--r--app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue6
-rw-r--r--app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue46
-rw-r--r--app/assets/javascripts/ci/runner/group_runners/index.js6
-rw-r--r--app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js2
-rw-r--r--app/assets/javascripts/ci/runner/project_new_runner/index.js32
-rw-r--r--app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue83
-rw-r--r--app/assets/javascripts/ci/runner/project_register_runner/index.js36
-rw-r--r--app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue69
-rw-r--r--app/assets/javascripts/ci/runner/project_runners/register/index.js39
-rw-r--r--app/assets/javascripts/ci/runner/runner_edit/runner_edit_app.vue2
54 files changed, 1736 insertions, 189 deletions
diff --git a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
index 5401c7c1c28..e4d47fba464 100644
--- a/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue
@@ -1,67 +1,61 @@
<script>
-import { GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
-import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
+import { s__ } from '~/locale';
+
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
+import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
-import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
-import { DEFAULT_PLATFORM, DEFAULT_ACCESS_LEVEL } from '../constants';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, INSTANCE_TYPE } from '../constants';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'AdminNewRunnerApp',
components: {
- GlLink,
- GlSprintf,
- RunnerInstructionsModal,
+ RegistrationCompatibilityAlert,
+ RegistrationFeedbackBanner,
RunnerPlatformsRadioGroup,
- RunnerFormFields,
- },
- directives: {
- GlModal: GlModalDirective,
- },
- props: {
- legacyRegistrationToken: {
- type: String,
- required: true,
- },
+ RunnerCreateForm,
},
data() {
return {
platform: DEFAULT_PLATFORM,
- runner: {
- description: '',
- maintenanceNote: '',
- paused: false,
- accessLevel: DEFAULT_ACCESS_LEVEL,
- runUntagged: false,
- tagList: '',
- maximumTimeout: ' ',
- },
};
},
- modalId: 'runners-legacy-registration-instructions-modal',
+ methods: {
+ onSaved(runner) {
+ const params = { [PARAM_KEY_PLATFORM]: this.platform };
+ const ephemeralRegisterUrl = setUrlParams(params, runner.ephemeralRegisterUrl);
+
+ saveAlertToLocalStorage({
+ message: s__('Runners|Runner created.'),
+ variant: VARIANT_SUCCESS,
+ });
+ redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated
+ },
+ onError(error) {
+ createAlert({ message: error.message });
+ },
+ },
+ INSTANCE_TYPE,
};
</script>
<template>
<div>
+ <registration-feedback-banner />
+
<h1 class="gl-font-size-h2">{{ s__('Runners|New instance runner') }}</h1>
+
+ <registration-compatibility-alert :alert-key="$options.INSTANCE_TYPE" />
+
<p>
- <gl-sprintf
- :message="
- s__(
- 'Runners|Create an instance runner to generate a command that registers the runner with all its configurations. %{linkStart}Prefer to use a registration token to create a runner?%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link v-gl-modal="$options.modalId" data-testid="legacy-instructions-link">{{
- content
- }}</gl-link>
- <runner-instructions-modal
- :modal-id="$options.modalId"
- :registration-token="legacyRegistrationToken"
- />
- </template>
- </gl-sprintf>
+ {{
+ s__(
+ 'Runners|Create an instance runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
</p>
<hr aria-hidden="true" />
@@ -73,6 +67,6 @@ export default {
<hr aria-hidden="true" />
- <runner-form-fields v-model="runner" />
+ <runner-create-form :runner-type="$options.INSTANCE_TYPE" @saved="onSaved" @error="onError" />
</div>
</template>
diff --git a/app/assets/javascripts/ci/runner/admin_new_runner/index.js b/app/assets/javascripts/ci/runner/admin_new_runner/index.js
index 502d9d33b4d..434c1197f71 100644
--- a/app/assets/javascripts/ci/runner/admin_new_runner/index.js
+++ b/app/assets/javascripts/ci/runner/admin_new_runner/index.js
@@ -12,8 +12,6 @@ export const initAdminNewRunner = (selector = '#js-admin-new-runner') => {
return null;
}
- const { legacyRegistrationToken } = el.dataset;
-
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
@@ -22,11 +20,7 @@ export const initAdminNewRunner = (selector = '#js-admin-new-runner') => {
el,
apolloProvider,
render(h) {
- return h(AdminNewRunnerApp, {
- props: {
- legacyRegistrationToken,
- },
- });
+ return h(AdminNewRunnerApp);
},
});
};
diff --git a/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue
new file mode 100644
index 00000000000..cd38dc07157
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants';
+import RegistrationInstructions from '../components/registration/registration_instructions.vue';
+import PlatformsDrawer from '../components/registration/platforms_drawer.vue';
+
+export default {
+ name: 'AdminRegisterRunnerApp',
+ components: {
+ GlButton,
+ RegistrationInstructions,
+ PlatformsDrawer,
+ },
+ props: {
+ runnerId: {
+ type: String,
+ required: true,
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM,
+ isDrawerOpen: false,
+ };
+ },
+ watch: {
+ platform(platform) {
+ updateHistory({
+ url: mergeUrlParams({ [PARAM_KEY_PLATFORM]: platform }, window.location.href),
+ });
+ },
+ },
+ methods: {
+ onSelectPlatform(platform) {
+ this.platform = platform;
+ },
+ onToggleDrawer(val = !this.isDrawerOpen) {
+ this.isDrawerOpen = val;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <registration-instructions
+ :runner-id="runnerId"
+ :platform="platform"
+ @toggleDrawer="onToggleDrawer"
+ >
+ <template #runner-list-name>{{ s__('Runners|Admin area › Runners') }}</template>
+ </registration-instructions>
+
+ <platforms-drawer
+ :platform="platform"
+ :open="isDrawerOpen"
+ @selectPlatform="onSelectPlatform"
+ @close="onToggleDrawer(false)"
+ />
+
+ <gl-button :href="runnersPath" variant="confirm">{{
+ s__('Runners|Go to runners page')
+ }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/admin_register_runner/index.js b/app/assets/javascripts/ci/runner/admin_register_runner/index.js
new file mode 100644
index 00000000000..bd43a5e8ce9
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/admin_register_runner/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
+import AdminRegisterRunnerApp from './admin_register_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initAdminRegisterRunner = (selector = '#js-admin-register-runner') => {
+ showAlertFromLocalStorage();
+
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { runnerId, runnersPath } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(AdminRegisterRunnerApp, {
+ props: {
+ runnerId,
+ runnersPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue
index 8d4303778af..668a55d2437 100644
--- a/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue
@@ -1,8 +1,8 @@
<script>
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
import RunnerDeleteButton from '../components/runner_delete_button.vue';
import RunnerEditButton from '../components/runner_edit_button.vue';
@@ -71,7 +71,7 @@ export default {
},
onDeleted({ message }) {
saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
- redirectTo(this.runnersPath);
+ redirectTo(this.runnersPath); // eslint-disable-line import/no-deprecated
},
},
};
diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
index ce2c511ddd4..4d88feebe53 100644
--- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlLink } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { updateHistory } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -54,7 +54,6 @@ export default {
RunnerJobStatusBadge,
},
mixins: [glFeatureFlagMixin()],
- inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath'],
props: {
newRunnerPath: {
type: String,
@@ -128,8 +127,8 @@ export default {
return isSearchFiltered(this.search);
},
shouldShowCreateRunnerWorkflow() {
- // create_runner_workflow feature flag
- return this.glFeatures.createRunnerWorkflow;
+ // create_runner_workflow_for_admin feature flag
+ return this.glFeatures.createRunnerWorkflowForAdmin;
},
},
watch: {
@@ -193,16 +192,17 @@ export default {
nav-class="gl-border-none!"
/>
- <gl-button v-if="shouldShowCreateRunnerWorkflow" :href="newRunnerPath" variant="confirm">
- {{ s__('Runners|New instance runner') }}
- </gl-button>
- <registration-dropdown
- v-else
- class="gl-w-full gl-sm-w-auto gl-mr-auto"
- :registration-token="registrationToken"
- :type="$options.INSTANCE_TYPE"
- right
- />
+ <div class="gl-w-full gl-md-w-auto gl-display-flex">
+ <gl-button v-if="shouldShowCreateRunnerWorkflow" :href="newRunnerPath" variant="confirm">
+ {{ s__('Runners|New instance runner') }}
+ </gl-button>
+ <registration-dropdown
+ class="gl-ml-3"
+ :registration-token="registrationToken"
+ :type="$options.INSTANCE_TYPE"
+ right
+ />
+ </div>
</div>
<runner-filtered-search-bar
@@ -219,8 +219,6 @@ export default {
:registration-token="registrationToken"
:is-search-filtered="isSearchFiltered"
:new-runner-path="newRunnerPath"
- :svg-path="emptyStateSvgPath"
- :filtered-svg-path="emptyStateFilteredSvgPath"
/>
<template v-else>
<runner-list
diff --git a/app/assets/javascripts/ci/runner/admin_runners/index.js b/app/assets/javascripts/ci/runner/admin_runners/index.js
index 881dc3613e9..54eb37f8c90 100644
--- a/app/assets/javascripts/ci/runner/admin_runners/index.js
+++ b/app/assets/javascripts/ci/runner/admin_runners/index.js
@@ -35,8 +35,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
registrationToken,
onlineContactTimeoutSecs,
staleTimeoutSecs,
- emptyStateSvgPath,
- emptyStateFilteredSvgPath,
} = el.dataset;
const { cacheConfig, typeDefs, localMutations } = createLocalState();
@@ -53,8 +51,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
localMutations,
onlineContactTimeoutSecs,
staleTimeoutSecs,
- emptyStateSvgPath,
- emptyStateFilteredSvgPath,
},
render(h) {
return h(AdminRunnersApp, {
diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
index 97dfbe1a051..f24fb5575ae 100644
--- a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
+++ b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
@@ -1,6 +1,8 @@
<script>
import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { sprintf, __ } from '~/locale';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerName from '../runner_name.vue';
@@ -14,6 +16,7 @@ import {
I18N_VERSION_LABEL,
I18N_LAST_CONTACT_LABEL,
I18N_CREATED_AT_LABEL,
+ I18N_CREATED_AT_BY_LABEL,
} from '../../constants';
import RunnerSummaryField from './runner_summary_field.vue';
@@ -28,6 +31,7 @@ export default {
RunnerTypeBadge,
RunnerUpgradeStatusIcon: () =>
import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'),
+ UserAvatarLink,
TooltipOnTruncate,
},
directives: {
@@ -43,6 +47,16 @@ export default {
jobCount() {
return formatJobCount(this.runner.jobCount);
},
+ createdBy() {
+ return this.runner?.createdBy;
+ },
+ createdByImgAlt() {
+ const name = this.createdBy?.name;
+ if (name) {
+ return sprintf(__("%{name}'s avatar"), { name });
+ }
+ return null;
+ },
},
i18n: {
I18N_NO_DESCRIPTION,
@@ -50,13 +64,14 @@ export default {
I18N_VERSION_LABEL,
I18N_LAST_CONTACT_LABEL,
I18N_CREATED_AT_LABEL,
+ I18N_CREATED_AT_BY_LABEL,
},
};
</script>
<template>
<div>
- <div>
+ <div class="gl-mb-3">
<slot :runner="runner" name="runner-name">
<runner-name :runner="runner" />
</slot>
@@ -69,22 +84,23 @@ export default {
<runner-type-badge :type="runner.runnerType" size="sm" class="gl-vertical-align-middle" />
</div>
- <div class="gl-ml-auto gl-display-inline-flex gl-max-w-full gl-py-2">
- <div class="gl-flex-shrink-0">
- <runner-upgrade-status-icon :runner="runner" />
- <gl-sprintf v-if="runner.version" :message="$options.i18n.I18N_VERSION_LABEL">
- <template #version>{{ runner.version }}</template>
- </gl-sprintf>
- </div>
- <div class="gl-text-secondary gl-mx-2" aria-hidden="true">·</div>
+ <div class="gl-mb-3 gl-ml-auto gl-display-inline-flex gl-max-w-full">
+ <template v-if="runner.version">
+ <div class="gl-flex-shrink-0">
+ <runner-upgrade-status-icon :runner="runner" />
+ <gl-sprintf :message="$options.i18n.I18N_VERSION_LABEL">
+ <template #version>{{ runner.version }}</template>
+ </gl-sprintf>
+ </div>
+ <div class="gl-text-secondary gl-mx-2" aria-hidden="true">·</div>
+ </template>
<tooltip-on-truncate
- v-if="runner.description"
class="gl-text-truncate gl-display-block"
+ :class="{ 'gl-text-secondary': !runner.description }"
:title="runner.description"
>
- {{ runner.description }}
+ {{ runner.description || $options.i18n.I18N_NO_DESCRIPTION }}
</tooltip-on-truncate>
- <span v-else class="gl-text-secondary">{{ $options.i18n.I18N_NO_DESCRIPTION }}</span>
</div>
<div>
@@ -106,14 +122,33 @@ export default {
</runner-summary-field>
<runner-summary-field icon="calendar">
- <gl-sprintf :message="$options.i18n.I18N_CREATED_AT_LABEL">
- <template #timeAgo>
- <time-ago v-if="runner.createdAt" :time="runner.createdAt" />
- </template>
- </gl-sprintf>
+ <template v-if="createdBy">
+ <gl-sprintf :message="$options.i18n.I18N_CREATED_AT_BY_LABEL">
+ <template #timeAgo>
+ <time-ago v-if="runner.createdAt" :time="runner.createdAt" />
+ </template>
+ <template #avatar>
+ <user-avatar-link
+ :link-href="createdBy.webUrl"
+ :img-src="createdBy.avatarUrl"
+ img-css-classes="gl-vertical-align-top"
+ :img-size="16"
+ :img-alt="createdByImgAlt"
+ :tooltip-text="createdBy.username"
+ />
+ </template>
+ </gl-sprintf>
+ </template>
+ <template v-else>
+ <gl-sprintf :message="$options.i18n.I18N_CREATED_AT_LABEL">
+ <template #timeAgo>
+ <time-ago v-if="runner.createdAt" :time="runner.createdAt" />
+ </template>
+ </gl-sprintf>
+ </template>
</runner-summary-field>
</div>
- <runner-tags class="gl-display-block gl-pt-2" :tag-list="runner.tagList" size="sm" />
+ <runner-tags class="gl-display-block" :tag-list="runner.tagList" size="sm" />
</div>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_summary_field.vue b/app/assets/javascripts/ci/runner/components/cells/runner_summary_field.vue
index 1bbbd55089a..742259ee491 100644
--- a/app/assets/javascripts/ci/runner/components/cells/runner_summary_field.vue
+++ b/app/assets/javascripts/ci/runner/components/cells/runner_summary_field.vue
@@ -24,7 +24,7 @@ export default {
</script>
<template>
- <div v-gl-tooltip="tooltip" class="gl-display-inline-block gl-text-secondary gl-my-2 gl-mr-2">
+ <div v-gl-tooltip="tooltip" class="gl-display-inline-block gl-text-secondary gl-mb-3 gl-mr-4">
<gl-icon v-if="icon" :name="icon" />
<!-- display tooltip as a label for screen readers -->
<span class="gl-sr-only">{{ tooltip }}</span>
diff --git a/app/assets/javascripts/ci/runner/components/registration/cli_command.vue b/app/assets/javascripts/ci/runner/components/registration/cli_command.vue
new file mode 100644
index 00000000000..95b135c83a7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/cli_command.vue
@@ -0,0 +1,42 @@
+<script>
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+ components: {
+ ClipboardButton,
+ },
+ props: {
+ prompt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ command: {
+ type: [Array, String],
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ lines() {
+ if (typeof this.command === 'string') {
+ return [this.command];
+ }
+ return this.command;
+ },
+ clipboard() {
+ return this.lines.join('');
+ },
+ },
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-gap-3 gl-align-items-flex-start">
+ <!-- eslint-disable vue/require-v-for-key-->
+ <pre
+ class="gl-w-full"
+ ><span v-if="prompt" class="gl-user-select-none">{{ prompt }} </span><template v-for="line in lines">{{ line }}<br class="gl-user-select-none"/></template></pre>
+ <!-- eslint-enable vue/require-v-for-key-->
+ <clipboard-button :text="clipboard" :title="__('Copy')" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue
new file mode 100644
index 00000000000..ff182c61ccf
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue
@@ -0,0 +1,135 @@
+<script>
+import { GlDrawer, GlFormGroup, GlFormSelect, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
+import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
+
+import {
+ DEFAULT_PLATFORM,
+ MACOS_PLATFORM,
+ LINUX_PLATFORM,
+ WINDOWS_PLATFORM,
+ INSTALL_HELP_URL,
+} from '../../constants';
+import { installScript, platformArchitectures } from './utils';
+
+import CliCommand from './cli_command.vue';
+
+export default {
+ components: {
+ GlDrawer,
+ GlFormGroup,
+ GlFormSelect,
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ CliCommand,
+ },
+ props: {
+ open: {
+ type: Boolean,
+ required: true,
+ },
+ platform: {
+ type: String,
+ required: false,
+ default: DEFAULT_PLATFORM,
+ },
+ },
+ data() {
+ return {
+ selectedPlatform: this.platform,
+ selectedArchitecture: null,
+ };
+ },
+ computed: {
+ drawerHeightOffset() {
+ return getContentWrapperHeight('.content-wrapper');
+ },
+ architectureOptions() {
+ return platformArchitectures({ platform: this.selectedPlatform });
+ },
+ script() {
+ return installScript({
+ platform: this.selectedPlatform,
+ architecture: this.selectedArchitecture,
+ });
+ },
+ },
+ watch: {
+ selectedPlatform() {
+ this.selectedArchitecture =
+ this.architectureOptions.find((value) => value === this.selectedArchitecture) ||
+ this.architectureOptions[0];
+
+ this.$emit('selectPlatform', this.selectedPlatform);
+ },
+ },
+ created() {
+ [this.selectedArchitecture] = this.architectureOptions;
+ },
+ methods: {
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ platformOptions: [
+ /* eslint-disable @gitlab/require-i18n-strings */
+ { value: LINUX_PLATFORM, text: 'Linux' },
+ { value: MACOS_PLATFORM, text: 'macOS' },
+ { value: WINDOWS_PLATFORM, text: 'Windows' },
+ /* eslint-enable @gitlab/require-i18n-strings */
+ ],
+ INSTALL_HELP_URL,
+ DRAWER_Z_INDEX,
+};
+</script>
+<template>
+ <gl-drawer
+ :open="open"
+ :header-height="drawerHeightOffset"
+ :z-index="$options.DRAWER_Z_INDEX"
+ data-testid="runner-platforms-drawer"
+ @close="onClose"
+ >
+ <template #title>
+ <h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">
+ {{ s__('Runners|Install GitLab Runner') }}
+ </h2>
+ </template>
+ <div>
+ <p>{{ s__('Runners|Select platform specifications to install GitLab Runner.') }}</p>
+
+ <gl-form-group :label="s__('Runners|Environment')" label-for="runner-environment-select">
+ <gl-form-select
+ id="runner-environment-select"
+ v-model="selectedPlatform"
+ :options="$options.platformOptions"
+ />
+ </gl-form-group>
+
+ <gl-form-group :label="s__('Runners|Architecture')" label-for="runner-architecture-select">
+ <gl-form-select
+ id="runner-architecture-select"
+ v-model="selectedArchitecture"
+ :options="architectureOptions"
+ />
+ </gl-form-group>
+
+ <cli-command :command="script" />
+
+ <p>
+ <gl-sprintf
+ :message="
+ s__('Runners|See more %{linkStart}installation methods and architectures%{linkEnd}.')
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="$options.INSTALL_HELP_URL">
+ {{ content }} <gl-icon name="external-link" />
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </gl-drawer>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue b/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue
new file mode 100644
index 00000000000..4ebb5754e61
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_compatibility_alert.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import DismissibleFeedbackAlert from '~/vue_shared/components/dismissible_feedback_alert.vue';
+import { s__ } from '~/locale';
+import { CHANGELOG_URL } from '../../constants';
+
+export default {
+ name: 'RegistrationCompatibilityAlert',
+ components: {
+ GlLink,
+ GlSprintf,
+ DismissibleFeedbackAlert,
+ },
+ props: {
+ alertKey: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ alertFeatureName() {
+ return `new_runner_compatibility_${this.alertKey}`;
+ },
+ },
+ CHANGELOG_URL,
+ i18n: {
+ title: s__(
+ 'Runners|This registration process is only supported in GitLab Runner 15.10 or later',
+ ),
+ message: s__(
+ 'Runners|This registration process is not supported in GitLab Runner 15.9 or earlier and only available as an experimental feature in GitLab Runner 15.10 and 15.11. You should upgrade to %{linkStart}GitLab Runner 16.0%{linkEnd} or later to use a stable version of this registration process.',
+ ),
+ },
+};
+</script>
+
+<template>
+ <dismissible-feedback-alert
+ :feature-name="alertFeatureName"
+ class="gl-mb-4"
+ variant="warning"
+ :title="$options.i18n.title"
+ >
+ <gl-sprintf :message="$options.i18n.message">
+ <template #link="{ content }">
+ <gl-link :href="$options.CHANGELOG_URL" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </dismissible-feedback-alert>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
index 212ad5fa5a0..2fdf8456615 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue
@@ -1,8 +1,17 @@
<script>
-import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
-import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants';
+import {
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ I18N_REGISTER_INSTANCE_TYPE,
+ I18N_REGISTER_GROUP_TYPE,
+ I18N_REGISTER_PROJECT_TYPE,
+ I18N_REGISTER_RUNNER,
+} from '../../constants';
import RegistrationToken from './registration_token.vue';
import RegistrationTokenResetDropdownItem from './registration_token_reset_dropdown_item.vue';
@@ -17,10 +26,12 @@ export default {
GlDropdownForm,
GlDropdownItem,
GlDropdownDivider,
+ GlIcon,
RegistrationToken,
RunnerInstructionsModal,
RegistrationTokenResetDropdownItem,
},
+ mixins: [glFeatureFlagMixin()],
props: {
registrationToken: {
type: String,
@@ -40,17 +51,49 @@ export default {
};
},
computed: {
- dropdownText() {
+ isDeprecated() {
+ // Show a compact version when used as secondary option
+ // create_runner_workflow_for_admin or create_runner_workflow_for_namespace
+ return (
+ this.glFeatures?.createRunnerWorkflowForAdmin ||
+ this.glFeatures?.createRunnerWorkflowForNamespace
+ );
+ },
+ actionText() {
switch (this.type) {
case INSTANCE_TYPE:
- return s__('Runners|Register an instance runner');
+ return I18N_REGISTER_INSTANCE_TYPE;
case GROUP_TYPE:
- return s__('Runners|Register a group runner');
+ return I18N_REGISTER_GROUP_TYPE;
case PROJECT_TYPE:
- return s__('Runners|Register a project runner');
+ return I18N_REGISTER_PROJECT_TYPE;
default:
- return s__('Runners|Register a runner');
+ return I18N_REGISTER_RUNNER;
+ }
+ },
+ dropdownText() {
+ if (this.isDeprecated) {
+ return '';
+ }
+ return this.actionText;
+ },
+ dropdownToggleClass() {
+ if (this.isDeprecated) {
+ return ['gl-px-3!'];
+ }
+ return [];
+ },
+ dropdownCategory() {
+ if (this.isDeprecated) {
+ return 'tertiary';
}
+ return 'primary';
+ },
+ dropdownVariant() {
+ if (this.isDeprecated) {
+ return 'default';
+ }
+ return 'confirm';
},
},
methods: {
@@ -71,9 +114,26 @@ export default {
ref="runnerRegistrationDropdown"
menu-class="gl-w-auto!"
:text="dropdownText"
- variant="confirm"
+ :toggle-class="dropdownToggleClass"
+ :variant="dropdownVariant"
+ :category="dropdownCategory"
v-bind="$attrs"
>
+ <template v-if="isDeprecated" #button-content>
+ <span class="gl-sr-only">{{ actionText }}</span>
+ <gl-icon name="ellipsis_v" />
+ </template>
+ <gl-dropdown-form class="gl-p-4!">
+ <registration-token input-id="token-value" :value="currentRegistrationToken">
+ <template v-if="isDeprecated" #label-description>
+ <gl-icon name="warning" class="gl-text-orange-500" />
+ <span class="gl-text-secondary">
+ {{ s__('Runners|Support for registration tokens is deprecated') }}
+ </span>
+ </template>
+ </registration-token>
+ </gl-dropdown-form>
+ <gl-dropdown-divider />
<gl-dropdown-item @click.capture.native.stop="onShowInstructionsClick">
{{ $options.i18n.showInstallationInstructions }}
<runner-instructions-modal
@@ -83,10 +143,6 @@ export default {
/>
</gl-dropdown-item>
<gl-dropdown-divider />
- <gl-dropdown-form class="gl-p-4!">
- <registration-token input-id="token-value" :value="currentRegistrationToken" />
- </gl-dropdown-form>
- <gl-dropdown-divider />
<registration-token-reset-dropdown-item :type="type" @tokenReset="onTokenReset" />
</gl-dropdown>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue
new file mode 100644
index 00000000000..fe19977f783
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue
@@ -0,0 +1,41 @@
+<script>
+import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/multi-editor_all_changes_committed_empty.svg?url';
+import { GlBanner } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
+
+const FEEDBACK_ISSUE_URL = 'https://gitlab.com/gitlab-org/gitlab/-/issues/387993';
+
+export default {
+ components: {
+ GlBanner,
+ UserCalloutDismisser,
+ },
+ i18n: {
+ title: s__("Runners|We've made some changes and want your feedback"),
+ body: s__(
+ "Runners|We've been making improvements to how you register runners so that it's more secure and efficient. Tell us how we're doing.",
+ ),
+ button: s__('Runners|Add your feedback to this issue'),
+ },
+ ILLUSTRATION_URL,
+ FEEDBACK_ISSUE_URL,
+};
+</script>
+<template>
+ <user-callout-dismisser feature-name="create_runner_workflow_banner">
+ <template #default="{ dismiss, shouldShowCallout }">
+ <gl-banner
+ v-if="shouldShowCallout"
+ class="gl-my-6"
+ :title="$options.i18n.title"
+ :svg-path="$options.ILLUSTRATION_URL"
+ :button-text="$options.i18n.button"
+ :button-link="$options.FEEDBACK_ISSUE_URL"
+ @close="dismiss"
+ >
+ <p>{{ $options.i18n.body }}</p>
+ </gl-banner>
+ </template>
+ </user-callout-dismisser>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue
new file mode 100644
index 00000000000..69021dde0e9
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue
@@ -0,0 +1,256 @@
+<script>
+import { GlIcon, GlLink, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { createAlert } from '~/alert';
+import { s__, sprintf } from '~/locale';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
+
+import runnerForRegistrationQuery from '../../graphql/register/runner_for_registration.query.graphql';
+import {
+ STATUS_ONLINE,
+ EXECUTORS_HELP_URL,
+ SERVICE_COMMANDS_HELP_URL,
+ RUNNER_REGISTRATION_POLLING_INTERVAL_MS,
+ I18N_FETCH_ERROR,
+ I18N_REGISTRATION_SUCCESS,
+} from '../../constants';
+import { captureException } from '../../sentry_utils';
+
+import CliCommand from './cli_command.vue';
+import { commandPrompt, registerCommand, runCommand } from './utils';
+
+export default {
+ name: 'RegistrationInstructions',
+ components: {
+ GlIcon,
+ GlLink,
+ GlSkeletonLoader,
+ GlSprintf,
+ ClipboardButton,
+ CliCommand,
+ },
+ props: {
+ runnerId: {
+ type: String,
+ required: true,
+ },
+ platform: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ runner: null,
+ token: null,
+ };
+ },
+ apollo: {
+ runner: {
+ query: runnerForRegistrationQuery,
+ variables() {
+ return {
+ id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
+ };
+ },
+ manual: true,
+ result({ data }) {
+ if (data?.runner) {
+ const { ephemeralAuthenticationToken, ...runner } = data.runner;
+ this.runner = runner;
+
+ // The token is available in the API for a limited amount of time
+ // preserve its original value if it is missing after polling.
+ this.token = ephemeralAuthenticationToken || this.token;
+ }
+ },
+ error(error) {
+ createAlert({ message: I18N_FETCH_ERROR });
+ captureException({ error, component: this.$options.name });
+ },
+ pollInterval() {
+ if (this.isRunnerOnline) {
+ // stop polling
+ return 0;
+ }
+ return RUNNER_REGISTRATION_POLLING_INTERVAL_MS;
+ },
+ },
+ },
+ computed: {
+ loading() {
+ return this.$apollo.queries.runner.loading;
+ },
+ description() {
+ return this.runner?.description;
+ },
+ heading() {
+ if (this.description) {
+ return sprintf(
+ s__('Runners|Register "%{runnerDescription}" runner'),
+ {
+ runnerDescription: this.description,
+ },
+ false,
+ );
+ }
+ return s__('Runners|Register runner');
+ },
+ tokenMessage() {
+ if (this.token) {
+ return s__(
+ 'Runners|The %{boldStart}runner token%{boldEnd} %{token} displays %{boldStart}only for a short time%{boldEnd}, and is stored in the %{codeStart}config.toml%{codeEnd} after you register the runner. It will not be visible once the runner is registered.',
+ );
+ }
+ return s__(
+ 'Runners|The %{boldStart}runner token%{boldEnd} is no longer visible, it is stored in the %{codeStart}config.toml%{codeEnd} if you have registered the runner.',
+ );
+ },
+ commandPrompt() {
+ return commandPrompt({ platform: this.platform });
+ },
+ registerCommand() {
+ return registerCommand({
+ platform: this.platform,
+ token: this.token,
+ });
+ },
+ runCommand() {
+ return runCommand({ platform: this.platform });
+ },
+ isRunnerOnline() {
+ return this.runner?.status === STATUS_ONLINE;
+ },
+ },
+ created() {
+ window.addEventListener('beforeunload', this.onBeforeunload);
+ },
+ destroyed() {
+ window.removeEventListener('beforeunload', this.onBeforeunload);
+ },
+ methods: {
+ toggleDrawer() {
+ this.$emit('toggleDrawer');
+ },
+ onBeforeunload(event) {
+ if (this.isRunnerOnline) {
+ return undefined;
+ }
+
+ const str = s__('Runners|You may lose access to the runner token if you leave this page.');
+ event.preventDefault();
+ // eslint-disable-next-line no-param-reassign
+ event.returnValue = str; // Chrome requires returnValue to be set
+ return str;
+ },
+ },
+ EXECUTORS_HELP_URL,
+ SERVICE_COMMANDS_HELP_URL,
+ I18N_REGISTRATION_SUCCESS,
+};
+</script>
+<template>
+ <div>
+ <h1 class="gl-font-size-h1">{{ heading }}</h1>
+
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Runners|GitLab Runner must be installed before you can register a runner. %{linkStart}How do I install GitLab Runner?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link data-testid="runner-install-link" @click="toggleDrawer">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <section>
+ <h2 class="gl-font-size-h2">{{ s__('Runners|Step 1') }}</h2>
+ <p>
+ {{
+ s__(
+ 'Runners|Copy and paste the following command into your command line to register the runner.',
+ )
+ }}
+ </p>
+ <gl-skeleton-loader v-if="loading" />
+ <template v-else>
+ <cli-command :prompt="commandPrompt" :command="registerCommand" />
+ <p>
+ <gl-icon name="information-o" class="gl-text-blue-600!" />
+ <gl-sprintf :message="tokenMessage">
+ <template #token>
+ <code data-testid="runner-token">{{ token }}</code>
+ <clipboard-button
+ :text="token"
+ :title="__('Copy')"
+ size="small"
+ category="tertiary"
+ class="gl-border-none!"
+ />
+ </template>
+ <template #bold="{ content }"
+ ><span class="gl-font-weight-bold">{{ content }}</span></template
+ >
+ <template #code="{ content }"
+ ><code>{{ content }}</code></template
+ >
+ </gl-sprintf>
+ </p>
+ </template>
+ </section>
+ <section>
+ <h2 class="gl-font-size-h2">{{ s__('Runners|Step 2') }}</h2>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Runners|Choose an executor when prompted by the command line. Executors run builds in different environments. %{linkStart}Not sure which one to select?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="$options.EXECUTORS_HELP_URL" target="_blank">
+ {{ content }} <gl-icon name="external-link" />
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </section>
+ <section>
+ <h2 class="gl-font-size-h2">{{ s__('Runners|Step 3 (optional)') }}</h2>
+ <p>{{ s__('Runners|Manually verify that the runner is available to pick up jobs.') }}</p>
+ <cli-command :prompt="commandPrompt" :command="runCommand" />
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Runners|This may not be needed if you manage your runner as a %{linkStart}system or user service%{linkEnd}.',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="$options.SERVICE_COMMANDS_HELP_URL" target="_blank">
+ {{ content }} <gl-icon name="external-link" />
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </section>
+ <section v-if="isRunnerOnline">
+ <h2 class="gl-font-size-h2">🎉 {{ $options.I18N_REGISTRATION_SUCCESS }}</h2>
+
+ <p class="gl-pl-6">
+ <gl-sprintf :message="s__('Runners|To view the runner, go to %{runnerListName}.')">
+ <template #runnerListName>
+ <span class="gl-font-weight-bold"><slot name="runner-list-name"></slot></span>
+ </template>
+ </gl-sprintf>
+ </p>
+ </section>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
index 6b4e6a929b7..b196bccf66f 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue
@@ -45,5 +45,9 @@ export default {
:copy-button-title="$options.I18N_COPY_BUTTON_TITLE"
:form-input-group-props="formInputGroupProps"
@copy="onCopy"
- />
+ >
+ <template v-for="slot in Object.keys($scopedSlots)" #[slot]>
+ <slot :name="slot"></slot>
+ </template>
+ </input-copy-toggle-visibility>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
index ac2793654c8..6ce88fc54de 100644
--- a/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -1,6 +1,6 @@
<script>
import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TYPENAME_GROUP, TYPENAME_PROJECT } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -73,13 +73,13 @@ export default {
actionPrimary() {
return {
text: i18n.modalAction,
- attributes: [{ variant: 'danger' }],
+ attributes: { variant: 'danger' },
};
},
actionSecondary() {
return {
text: i18n.modalCancel,
- attributes: [{ variant: 'default' }],
+ attributes: { variant: 'default' },
};
},
},
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh b/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh
new file mode 100644
index 00000000000..a8ba2592128
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/linux/install.sh
@@ -0,0 +1,12 @@
+# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner ${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh b/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh
new file mode 100644
index 00000000000..76c893bacfc
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/osx/install.sh
@@ -0,0 +1,11 @@
+# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner ${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start
diff --git a/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1 b/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1
new file mode 100644
index 00000000000..019363fc3f7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/scripts/windows/install.ps1
@@ -0,0 +1,13 @@
+# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\GitLab-Runner
+New-Item -Path 'C:\GitLab-Runner' -ItemType Directory
+
+# Change to the folder
+cd 'C:\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri "${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}" -OutFile "gitlab-runner.exe"
+
+# Register the runner (steps below), then run
+.\gitlab-runner.exe install
+.\gitlab-runner.exe start
diff --git a/app/assets/javascripts/ci/runner/components/registration/utils.js b/app/assets/javascripts/ci/runner/components/registration/utils.js
new file mode 100644
index 00000000000..c8a75506c9c
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/registration/utils.js
@@ -0,0 +1,81 @@
+import {
+ DEFAULT_PLATFORM,
+ LINUX_PLATFORM,
+ MACOS_PLATFORM,
+ WINDOWS_PLATFORM,
+ DOWNLOAD_LOCATIONS,
+} from '../../constants';
+import linuxInstall from './scripts/linux/install.sh?raw';
+import osxInstall from './scripts/osx/install.sh?raw';
+import windowsInstall from './scripts/windows/install.ps1?raw';
+
+const OS = {
+ [LINUX_PLATFORM]: {
+ shell: 'bash',
+ commandPrompt: '$',
+ executable: 'gitlab-runner',
+ },
+ [MACOS_PLATFORM]: {
+ shell: 'bash',
+ commandPrompt: '$',
+ executable: 'gitlab-runner',
+ },
+ [WINDOWS_PLATFORM]: {
+ shell: 'powershell',
+ commandPrompt: '>',
+ executable: '.\\gitlab-runner.exe',
+ },
+};
+
+export const commandPrompt = ({ platform }) => {
+ return (OS[platform] || OS[DEFAULT_PLATFORM]).commandPrompt;
+};
+
+export const executable = ({ platform }) => {
+ return (OS[platform] || OS[DEFAULT_PLATFORM]).executable;
+};
+
+export const registerCommand = ({ platform, url = gon.gitlab_url, token }) => {
+ const lines = [`${executable({ platform })} register`]; // eslint-disable-line @gitlab/require-i18n-strings
+ if (url) {
+ lines.push(` --url ${url}`);
+ }
+ if (token) {
+ lines.push(` --token ${token}`);
+ }
+ return lines;
+};
+
+export const runCommand = ({ platform }) => {
+ return `${executable({ platform })} run`; // eslint-disable-line @gitlab/require-i18n-strings
+};
+
+const importInstallScript = ({ platform = DEFAULT_PLATFORM }) => {
+ switch (platform) {
+ case LINUX_PLATFORM:
+ return linuxInstall;
+ case MACOS_PLATFORM:
+ return osxInstall;
+ case WINDOWS_PLATFORM:
+ return windowsInstall;
+ default:
+ return '';
+ }
+};
+
+export const platformArchitectures = ({ platform }) => {
+ return DOWNLOAD_LOCATIONS[platform].map(({ arch }) => arch);
+};
+
+export const installScript = ({ platform, architecture }) => {
+ const downloadLocation = DOWNLOAD_LOCATIONS[platform].find(({ arch }) => arch === architecture)
+ .url;
+
+ return importInstallScript({ platform })
+ .replace(
+ // eslint-disable-next-line no-template-curly-in-string
+ '${GITLAB_CI_RUNNER_DOWNLOAD_LOCATION}',
+ downloadLocation,
+ )
+ .trim();
+};
diff --git a/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
index 8dde3ac4e19..e7b26ec6d4e 100644
--- a/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_bulk_delete.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlModalDirective, GlModal, GlSprintf } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { __, s__, n__, sprintf } from '~/locale';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import BulkRunnerDelete from '../graphql/list/bulk_runner_delete.mutation.graphql';
diff --git a/app/assets/javascripts/ci/runner/components/runner_create_form.vue b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
new file mode 100644
index 00000000000..6107b4dd3ea
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
@@ -0,0 +1,120 @@
+<script>
+import { GlForm, GlButton } from '@gitlab/ui';
+import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
+import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql';
+import { modelToUpdateMutationVariables } from 'ee_else_ce/ci/runner/runner_update_form_utils';
+import { captureException } from '../sentry_utils';
+import {
+ RUNNER_TYPES,
+ DEFAULT_ACCESS_LEVEL,
+ PROJECT_TYPE,
+ GROUP_TYPE,
+ INSTANCE_TYPE,
+} from '../constants';
+
+export default {
+ name: 'RunnerCreateForm',
+ components: {
+ GlForm,
+ GlButton,
+ RunnerFormFields,
+ },
+ props: {
+ runnerType: {
+ type: String,
+ required: true,
+ validator: (t) => RUNNER_TYPES.includes(t),
+ },
+ groupId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ projectId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ saving: false,
+ runner: {
+ description: '',
+ maintenanceNote: '',
+ paused: false,
+ accessLevel: DEFAULT_ACCESS_LEVEL,
+ runUntagged: false,
+ tagList: '',
+ maximumTimeout: '',
+ },
+ };
+ },
+ computed: {
+ mutationInput() {
+ const { input } = modelToUpdateMutationVariables(this.runner);
+
+ if (this.runnerType === GROUP_TYPE) {
+ return {
+ ...input,
+ runnerType: GROUP_TYPE,
+ groupId: this.groupId,
+ };
+ }
+ if (this.runnerType === PROJECT_TYPE) {
+ return {
+ ...input,
+ runnerType: PROJECT_TYPE,
+ projectId: this.projectId,
+ };
+ }
+ return {
+ ...input,
+ runnerType: INSTANCE_TYPE,
+ };
+ },
+ },
+ methods: {
+ async onSubmit() {
+ this.saving = true;
+ try {
+ const {
+ data: {
+ runnerCreate: { errors, runner },
+ },
+ } = await this.$apollo.mutate({
+ mutation: runnerCreateMutation,
+ variables: {
+ input: this.mutationInput,
+ },
+ });
+
+ if (errors?.length) {
+ this.$emit('error', new Error(errors.join(' ')));
+ } else {
+ this.onSuccess(runner);
+ }
+ } catch (error) {
+ captureException({ error, component: this.$options.name });
+ this.$emit('error', error);
+ } finally {
+ this.saving = false;
+ }
+ },
+ onSuccess(runner) {
+ this.$emit('saved', runner);
+ },
+ },
+};
+</script>
+<template>
+ <gl-form @submit.prevent="onSubmit">
+ <runner-form-fields v-model="runner" />
+
+ <div class="gl-display-flex">
+ <gl-button type="submit" variant="confirm" class="js-no-auto-disable" :loading="saving">
+ {{ __('Submit') }}
+ </gl-button>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/runner_delete_button.vue b/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
index f02e6bce5c3..020487fc727 100644
--- a/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_delete_button.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import runnerDeleteMutation from '~/ci/runner/graphql/shared/runner_delete.mutation.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { sprintf, s__ } from '~/locale';
import { captureException } from '~/ci/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs.vue b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
index 9003eba3636..62e09346c2c 100644
--- a/app/assets/javascripts/ci/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs.vue
@@ -1,12 +1,13 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql';
import { I18N_FETCH_ERROR, I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '../constants';
import { captureException } from '../sentry_utils';
import { getPaginationVariables } from '../utils';
import RunnerJobsTable from './runner_jobs_table.vue';
import RunnerPagination from './runner_pagination.vue';
+import RunnerJobsEmptyState from './runner_jobs_empty_state.vue';
export default {
name: 'RunnerJobs',
@@ -14,7 +15,9 @@ export default {
GlSkeletonLoader,
RunnerJobsTable,
RunnerPagination,
+ RunnerJobsEmptyState,
},
+
props: {
runner: {
type: Object,
@@ -75,7 +78,7 @@ export default {
<gl-skeleton-loader />
</div>
<runner-jobs-table v-else-if="jobs.items.length" :jobs="jobs.items" />
- <p v-else>{{ $options.I18N_NO_JOBS_FOUND }}</p>
+ <runner-jobs-empty-state v-else />
<runner-pagination :disabled="loading" :page-info="jobs.pageInfo" @input="onPaginationInput" />
</div>
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue
new file mode 100644
index 00000000000..c30a824120d
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue
@@ -0,0 +1,27 @@
+<script>
+import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url';
+
+import { GlEmptyState } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ i18n: {
+ title: s__('Runners|This runner has not run any jobs'),
+ description: s__(
+ 'Runners|Make sure the runner is online and available to run jobs (not paused). Jobs display here when the runner picks them up.',
+ ),
+ },
+ components: {
+ GlEmptyState,
+ },
+ EMPTY_STATE_SVG_URL,
+};
+</script>
+
+<template>
+ <gl-empty-state :svg-path="$options.EMPTY_STATE_SVG_URL" :title="$options.i18n.title">
+ <template #description>
+ <p>{{ $options.i18n.description }}</p>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue b/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue
index ebcda4f0ac3..5d8e9dcdee2 100644
--- a/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_jobs_table.vue
@@ -2,7 +2,7 @@
import { GlTableLite } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { durationTimeFormatted } from '~/lib/utils/datetime_utility';
+import { formatTime } from '~/lib/utils/datetime_utility';
import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
import RunnerTags from '~/ci/runner/components/runner_tags.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -50,11 +50,11 @@ export default {
},
duration(job) {
const { duration } = job;
- return duration ? durationTimeFormatted(duration) : '';
+ return duration ? formatTime(duration * 1000) : '';
},
queued(job) {
const { queuedDuration } = job;
- return queuedDuration ? durationTimeFormatted(queuedDuration) : '';
+ return queuedDuration ? formatTime(queuedDuration * 1000) : '';
},
},
fields: [
diff --git a/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue b/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue
index d2f7912fabb..8606c22db34 100644
--- a/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue
@@ -1,4 +1,7 @@
<script>
+import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url';
+import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/magnifying-glass.svg?url';
+
import { GlEmptyState, GlLink, GlSprintf, GlModalDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
@@ -20,16 +23,6 @@ export default {
required: false,
default: false,
},
- svgPath: {
- type: String,
- required: false,
- default: '',
- },
- filteredSvgPath: {
- type: String,
- required: false,
- default: '',
- },
registrationToken: {
type: String,
required: false,
@@ -43,12 +36,18 @@ export default {
},
computed: {
shouldShowCreateRunnerWorkflow() {
- // create_runner_workflow feature flag
- return this.newRunnerPath && this.glFeatures?.createRunnerWorkflow;
+ // create_runner_workflow_for_admin or create_runner_workflow_for_namespace
+ return (
+ this.newRunnerPath &&
+ (this.glFeatures?.createRunnerWorkflowForAdmin ||
+ this.glFeatures?.createRunnerWorkflowForNamespace)
+ );
},
},
modalId: 'runners-empty-state-instructions-modal',
svgHeight: 145,
+ EMPTY_STATE_SVG_URL,
+ FILTERED_SVG_URL,
};
</script>
@@ -56,14 +55,14 @@ export default {
<gl-empty-state
v-if="isSearchFiltered"
:title="s__('Runners|No results found')"
- :svg-path="filteredSvgPath"
+ :svg-path="$options.FILTERED_SVG_URL"
:svg-height="$options.svgHeight"
:description="s__('Runners|Edit your search and try again')"
/>
<gl-empty-state
v-else
:title="s__('Runners|Get started with runners')"
- :svg-path="svgPath"
+ :svg-path="$options.EMPTY_STATE_SVG_URL"
:svg-height="$options.svgHeight"
>
<template v-if="registrationToken" #description>
diff --git a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
index 2c80518e772..a27af232e97 100644
--- a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { captureException } from '~/ci/runner/sentry_utils';
import { I18N_PAUSE, I18N_PAUSE_TOOLTIP, I18N_RESUME, I18N_RESUME_TOOLTIP } from '../constants';
diff --git a/app/assets/javascripts/ci/runner/components/runner_platforms_radio.vue b/app/assets/javascripts/ci/runner/components/runner_platforms_radio.vue
index d70c51e83f9..48c5d2f9721 100644
--- a/app/assets/javascripts/ci/runner/components/runner_platforms_radio.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_platforms_radio.vue
@@ -30,6 +30,9 @@ export default {
isChecked() {
return this.value && this.value === this.checked;
},
+ imgClass() {
+ return 'gl-h-6 gl-mt-n2 gl-mr-2';
+ },
},
methods: {
onInput($event) {
@@ -47,23 +50,22 @@ export default {
<template>
<div
- class="runner-platforms-radio gl-display-flex gl-border gl-rounded-base gl-px-5 gl-py-6"
+ class="runner-platforms-radio gl-border gl-rounded-base gl-px-5 gl-pt-6 gl-pb-5"
:class="{ 'gl-bg-blue-50 gl-border-blue-500': isChecked, 'gl-cursor-pointer': value }"
@click="onInput(value)"
>
<gl-form-radio
v-if="value"
- class="gl-min-h-5"
:checked="checked"
:value="value"
@input="onInput($event)"
@change="onChange($event)"
>
- <img v-if="image" :src="image" aria-hidden="true" class="gl-h-5 gl-mr-2" />
+ <img v-if="image" :src="image" :class="imgClass" aria-hidden="true" />
<span class="gl-font-weight-bold"><slot></slot></span>
</gl-form-radio>
- <div v-else class="gl-h-5">
- <img v-if="image" :src="image" aria-hidden="true" class="gl-h-5 gl-mr-2" />
+ <div v-else class="gl-mb-3">
+ <img v-if="image" :src="image" :class="imgClass" aria-hidden="true" />
<span class="gl-font-weight-bold"><slot></slot></span>
</div>
</div>
diff --git a/app/assets/javascripts/ci/runner/components/runner_platforms_radio_group.vue b/app/assets/javascripts/ci/runner/components/runner_platforms_radio_group.vue
index 273226141d2..a49641194a7 100644
--- a/app/assets/javascripts/ci/runner/components/runner_platforms_radio_group.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_platforms_radio_group.vue
@@ -1,6 +1,6 @@
<script>
-import AWS_LOGO_URL from '@gitlab/svgs/dist/illustrations/logos/aws.svg?url';
import DOCKER_LOGO_URL from '@gitlab/svgs/dist/illustrations/third-party-logos/ci_cd-template-logos/docker.png';
+import LINUX_LOGO_URL from '@gitlab/svgs/dist/illustrations/third-party-logos/linux.svg?url';
import KUBERNETES_LOGO_URL from '@gitlab/svgs/dist/illustrations/logos/kubernetes.svg?url';
import { GlFormRadioGroup, GlIcon, GlLink } from '@gitlab/ui';
@@ -8,7 +8,6 @@ import {
LINUX_PLATFORM,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
- AWS_PLATFORM,
DOCKER_HELP_URL,
KUBERNETES_HELP_URL,
} from '../constants';
@@ -40,11 +39,10 @@ export default {
},
},
LINUX_PLATFORM,
+ LINUX_LOGO_URL,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
- AWS_PLATFORM,
- AWS_LOGO_URL,
DOCKER_HELP_URL,
DOCKER_LOGO_URL,
KUBERNETES_HELP_URL,
@@ -59,7 +57,11 @@ export default {
<div class="gl-display-flex gl-flex-wrap gl-gap-5">
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
- <runner-platforms-radio v-model="model" :value="$options.LINUX_PLATFORM">
+ <runner-platforms-radio
+ v-model="model"
+ :image="$options.LINUX_LOGO_URL"
+ :value="$options.LINUX_PLATFORM"
+ >
Linux
</runner-platforms-radio>
<runner-platforms-radio v-model="model" :value="$options.MACOS_PLATFORM">
@@ -72,20 +74,6 @@ export default {
</div>
<div class="gl-mt-3 gl-mb-6">
- <label>{{ s__('Runners|Cloud templates') }}</label>
- <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
- <div class="gl-display-flex gl-flex-wrap gl-gap-5">
- <runner-platforms-radio
- v-model="model"
- :image="$options.AWS_LOGO_URL"
- :value="$options.AWS_PLATFORM"
- >
- AWS
- </runner-platforms-radio>
- </div>
- </div>
-
- <div class="gl-mt-3 gl-mb-6">
<label>{{ s__('Runners|Containers') }}</label>
<div class="gl-display-flex gl-flex-wrap gl-gap-5">
diff --git a/app/assets/javascripts/ci/runner/components/runner_projects.vue b/app/assets/javascripts/ci/runner/components/runner_projects.vue
index 4a6e90b44a9..4cfc57340f5 100644
--- a/app/assets/javascripts/ci/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_projects.vue
@@ -1,7 +1,7 @@
<script>
import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { sprintf, formatNumber } from '~/locale';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql';
import {
I18N_ASSIGNED_PROJECTS,
diff --git a/app/assets/javascripts/ci/runner/components/runner_update_form.vue b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
index a9790d06ca7..2d34c551d6d 100644
--- a/app/assets/javascripts/ci/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
@@ -13,8 +13,8 @@ import {
modelToUpdateMutationVariables,
runnerToModel,
} from 'ee_else_ce/ci/runner/runner_update_form_utils';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
import { __ } from '~/locale';
import { captureException } from '~/ci/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
@@ -101,7 +101,7 @@ export default {
},
onSuccess() {
saveAlertToLocalStorage({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
- redirectTo(this.runnerPath);
+ redirectTo(this.runnerPath); // eslint-disable-line import/no-deprecated
},
onError(message) {
this.saving = false;
diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
index 6e7c41885f8..1de7775090a 100644
--- a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue
@@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 318eb7e74bd..4e36a410a66 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -71,6 +71,12 @@ export const I18N_STALE_NEVER_CONTACTED_TOOLTIP = s__(
'Runners|Runner is stale; it has never contacted this instance',
);
+// Registration dropdown
+export const I18N_REGISTER_INSTANCE_TYPE = s__('Runners|Register an instance runner');
+export const I18N_REGISTER_GROUP_TYPE = s__('Runners|Register a group runner');
+export const I18N_REGISTER_PROJECT_TYPE = s__('Runners|Register a project runner');
+export const I18N_REGISTER_RUNNER = s__('Runners|Register a runner');
+
// Actions
export const I18N_EDIT = __('Edit');
@@ -93,6 +99,7 @@ export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
export const I18N_VERSION_LABEL = s__('Runners|Version %{version}');
export const I18N_LAST_CONTACT_LABEL = s__('Runners|Last contact: %{timeAgo}');
export const I18N_CREATED_AT_LABEL = s__('Runners|Created %{timeAgo}');
+export const I18N_CREATED_AT_BY_LABEL = s__('Runners|Created %{timeAgo} by %{avatar}');
export const I18N_SHOW_ONLY_INHERITED = s__('Runners|Show only inherited');
export const I18N_ADMIN = s__('Runners|Administrator');
@@ -105,10 +112,15 @@ export const I18N_JOBS = s__('Runners|Jobs');
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
export const I18N_FILTER_PROJECTS = s__('Runners|Filter projects');
export const I18N_CLEAR_FILTER_PROJECTS = __('Clear');
-export const I18N_NONE = __('None');
export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');
export const I18N_NO_PROJECTS_FOUND = __('No projects found');
+// Runner registration
+
+export const I18N_REGISTRATION_SUCCESS = s__("Runners|You've created a new runner!");
+
+export const RUNNER_REGISTRATION_POLLING_INTERVAL_MS = 2000;
+
// Styles
export const RUNNER_TAG_BADGE_VARIANT = 'info';
@@ -129,11 +141,14 @@ export const PARAM_KEY_SORT = 'sort';
export const PARAM_KEY_AFTER = 'after';
export const PARAM_KEY_BEFORE = 'before';
+export const PARAM_KEY_PLATFORM = 'platform';
+
// CiRunnerType
export const INSTANCE_TYPE = 'INSTANCE_TYPE';
export const GROUP_TYPE = 'GROUP_TYPE';
export const PROJECT_TYPE = 'PROJECT_TYPE';
+export const RUNNER_TYPES = [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE];
// CiRunnerStatus
@@ -180,11 +195,64 @@ export const GROUP_FILTERED_SEARCH_NAMESPACE = 'group_runners';
export const LINUX_PLATFORM = 'linux';
export const MACOS_PLATFORM = 'osx';
export const WINDOWS_PLATFORM = 'windows';
-export const AWS_PLATFORM = 'aws';
+
+export const DOWNLOAD_LOCATIONS = {
+ [LINUX_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64',
+ },
+ {
+ arch: '386',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
+ },
+ {
+ arch: 'arm',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm',
+ },
+ {
+ arch: 'arm64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64',
+ },
+ ],
+ [MACOS_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64',
+ },
+ {
+ arch: 'arm64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64',
+ },
+ ],
+ [WINDOWS_PLATFORM]: [
+ {
+ arch: 'amd64',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
+ },
+ {
+ arch: '386',
+ url:
+ 'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe',
+ },
+ ],
+};
export const DEFAULT_PLATFORM = LINUX_PLATFORM;
// Runner docs are in a separate repository and are not shipped with GitLab
// they are rendered as external URLs.
+export const INSTALL_HELP_URL = 'https://docs.gitlab.com/runner/install';
+export const EXECUTORS_HELP_URL = 'https://docs.gitlab.com/runner/executors/';
+export const SERVICE_COMMANDS_HELP_URL =
+ 'https://docs.gitlab.com/runner/commands/#service-related-commands';
+export const CHANGELOG_URL = 'https://gitlab.com/gitlab-org/gitlab-runner/blob/main/CHANGELOG.md';
export const DOCKER_HELP_URL = 'https://docs.gitlab.com/runner/install/docker.html';
export const KUBERNETES_HELP_URL = 'https://docs.gitlab.com/runner/install/kubernetes.html';
diff --git a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
index 29abddf84f5..d18b80511fb 100644
--- a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
@@ -10,5 +10,5 @@ fragment RunnerFieldsShared on CiRunner {
maximumTimeout
tagList
createdAt
- status(legacyMode: null)
+ status
}
diff --git a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
index 6f72509f599..4eebcd01be6 100644
--- a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
@@ -1,3 +1,5 @@
+#import "~/graphql_shared/fragments/user.fragment.graphql"
+
fragment ListItemShared on CiRunner {
id
description
@@ -10,8 +12,11 @@ fragment ListItemShared on CiRunner {
jobCount
tagList
createdAt
+ createdBy {
+ ...User
+ }
contactedAt
- status(legacyMode: null)
+ status
jobExecutionStatus
userPermissions {
updateRunner
diff --git a/app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql b/app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql
new file mode 100644
index 00000000000..07236808dca
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql
@@ -0,0 +1,9 @@
+mutation runnerCreate($input: RunnerCreateInput!) {
+ runnerCreate(input: $input) {
+ runner {
+ id
+ ephemeralRegisterUrl
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/ci/runner/graphql/register/runner_for_registration.query.graphql b/app/assets/javascripts/ci/runner/graphql/register/runner_for_registration.query.graphql
new file mode 100644
index 00000000000..f6cee807620
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/graphql/register/runner_for_registration.query.graphql
@@ -0,0 +1,8 @@
+query getRunnerForRegistration($id: CiRunnerID!) {
+ runner(id: $id) {
+ id
+ description
+ ephemeralAuthenticationToken
+ status
+ }
+}
diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
index b5689ff7687..bd53fb29bd0 100644
--- a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
@@ -15,7 +15,7 @@ fragment RunnerDetailsShared on CiRunner {
jobCount
tagList
createdAt
- status(legacyMode: null)
+ status
contactedAt
tokenExpiresAt
version
diff --git a/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
new file mode 100644
index 00000000000..67d29daf66f
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
@@ -0,0 +1,83 @@
+<script>
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
+import { s__ } from '~/locale';
+
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
+import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue';
+import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import { DEFAULT_PLATFORM, GROUP_TYPE, PARAM_KEY_PLATFORM } from '../constants';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
+
+export default {
+ name: 'GroupNewRunnerApp',
+ components: {
+ RegistrationCompatibilityAlert,
+ RegistrationFeedbackBanner,
+ RunnerPlatformsRadioGroup,
+ RunnerCreateForm,
+ },
+ props: {
+ groupId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: DEFAULT_PLATFORM,
+ };
+ },
+ methods: {
+ onSaved(runner) {
+ const params = { [PARAM_KEY_PLATFORM]: this.platform };
+ const ephemeralRegisterUrl = setUrlParams(params, runner.ephemeralRegisterUrl);
+
+ saveAlertToLocalStorage({
+ message: s__('Runners|Runner created.'),
+ variant: VARIANT_SUCCESS,
+ });
+ redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated
+ },
+ onError(error) {
+ createAlert({ message: error.message });
+ },
+ },
+ GROUP_TYPE,
+};
+</script>
+
+<template>
+ <div>
+ <registration-feedback-banner />
+
+ <h1 class="gl-font-size-h2">{{ s__('Runners|New group runner') }}</h1>
+
+ <registration-compatibility-alert :alert-key="groupId" />
+
+ <p>
+ {{
+ s__(
+ 'Runners|Create a group runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
+ </p>
+
+ <hr aria-hidden="true" />
+
+ <h2 class="gl-font-weight-normal gl-font-lg gl-my-5">
+ {{ s__('Runners|Platform') }}
+ </h2>
+ <runner-platforms-radio-group v-model="platform" />
+
+ <hr aria-hidden="true" />
+
+ <runner-create-form
+ :runner-type="$options.GROUP_TYPE"
+ :group-id="groupId"
+ @saved="onSaved"
+ @error="onError"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/group_new_runner/index.js b/app/assets/javascripts/ci/runner/group_new_runner/index.js
new file mode 100644
index 00000000000..9e056081e03
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_new_runner/index.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import GroupNewRunnerApp from './group_new_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initGroupNewRunner = (selector = '#js-group-new-runner') => {
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { groupId } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(GroupNewRunnerApp, {
+ props: {
+ groupId,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/group_register_runner/group_register_runner_app.vue b/app/assets/javascripts/ci/runner/group_register_runner/group_register_runner_app.vue
new file mode 100644
index 00000000000..533d31b70a3
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_register_runner/group_register_runner_app.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants';
+import RegistrationInstructions from '../components/registration/registration_instructions.vue';
+import PlatformsDrawer from '../components/registration/platforms_drawer.vue';
+
+export default {
+ name: 'GroupRegisterRunnerApp',
+ components: {
+ GlButton,
+ RegistrationInstructions,
+ PlatformsDrawer,
+ },
+ props: {
+ runnerId: {
+ type: String,
+ required: true,
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM,
+ isDrawerOpen: false,
+ };
+ },
+ watch: {
+ platform(platform) {
+ updateHistory({
+ url: mergeUrlParams({ [PARAM_KEY_PLATFORM]: platform }, window.location.href),
+ });
+ },
+ },
+ methods: {
+ onSelectPlatform(platform) {
+ this.platform = platform;
+ },
+ onToggleDrawer(val = !this.isDrawerOpen) {
+ this.isDrawerOpen = val;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <registration-instructions
+ :runner-id="runnerId"
+ :platform="platform"
+ @toggleDrawer="onToggleDrawer"
+ >
+ <template #runner-list-name>{{ s__('Runners|Group area › Runners') }}</template>
+ </registration-instructions>
+
+ <platforms-drawer
+ :platform="platform"
+ :open="isDrawerOpen"
+ @selectPlatform="onSelectPlatform"
+ @close="onToggleDrawer(false)"
+ />
+
+ <gl-button :href="runnersPath" variant="confirm">{{
+ s__('Runners|Go to runners page')
+ }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/group_register_runner/index.js b/app/assets/javascripts/ci/runner/group_register_runner/index.js
new file mode 100644
index 00000000000..a00db8853a2
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_register_runner/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
+import GroupRegisterRunnerApp from './group_register_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initGroupRegisterRunner = (selector = '#js-group-register-runner') => {
+ showAlertFromLocalStorage();
+
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { runnerId, runnersPath } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(GroupRegisterRunnerApp, {
+ props: {
+ runnerId,
+ runnersPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue b/app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue
index 273a9aa823c..1318bf5a2e6 100644
--- a/app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue
+++ b/app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue
@@ -1,8 +1,8 @@
<script>
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
import RunnerDeleteButton from '../components/runner_delete_button.vue';
import RunnerEditButton from '../components/runner_edit_button.vue';
@@ -76,7 +76,7 @@ export default {
},
onDeleted({ message }) {
saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
- redirectTo(this.runnersPath);
+ redirectTo(this.runnersPath); // eslint-disable-line import/no-deprecated
},
},
};
diff --git a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
index e66a1c7b1aa..74523bc335f 100644
--- a/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
@@ -1,6 +1,6 @@
<script>
-import { GlLink } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { GlButton, GlLink } from '@gitlab/ui';
+import { createAlert } from '~/alert';
import { updateHistory } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import { upgradeStatusTokenConfig } from 'ee_else_ce/ci/runner/components/search_tokens/upgrade_status_token_config';
@@ -42,6 +42,7 @@ import { captureException } from '../sentry_utils';
export default {
name: 'GroupRunnersApp',
components: {
+ GlButton,
GlLink,
RegistrationDropdown,
RunnerFilteredSearchBar,
@@ -56,8 +57,12 @@ export default {
RunnerJobStatusBadge,
},
mixins: [glFeatureFlagMixin()],
- inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath'],
props: {
+ newRunnerPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
registrationToken: {
type: String,
required: false,
@@ -150,6 +155,10 @@ export default {
isSearchFiltered() {
return isSearchFiltered(this.search);
},
+ shouldShowCreateRunnerWorkflow() {
+ // create_runner_workflow_for_namespace feature flag
+ return this.glFeatures.createRunnerWorkflowForNamespace;
+ },
},
watch: {
search: {
@@ -207,7 +216,9 @@ export default {
<template>
<div>
- <div class="gl-display-flex gl-align-items-center">
+ <div
+ class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
+ >
<runner-type-tabs
ref="runner-type-tabs"
v-model="search"
@@ -219,15 +230,23 @@ export default {
nav-class="gl-border-none!"
/>
- <registration-dropdown
- v-if="registrationToken"
- class="gl-ml-auto"
- :registration-token="registrationToken"
- :type="$options.GROUP_TYPE"
- right
- />
+ <div class="gl-w-full gl-md-w-auto gl-display-flex">
+ <gl-button
+ v-if="shouldShowCreateRunnerWorkflow && newRunnerPath"
+ :href="newRunnerPath"
+ variant="confirm"
+ >
+ {{ s__('Runners|New group runner') }}
+ </gl-button>
+ <registration-dropdown
+ v-if="registrationToken"
+ class="gl-ml-3"
+ :registration-token="registrationToken"
+ :type="$options.GROUP_TYPE"
+ right
+ />
+ </div>
</div>
-
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-gap-3"
:class="$options.FILTER_CSS_CLASSES"
@@ -250,8 +269,7 @@ export default {
v-if="noRunnersFound"
:registration-token="registrationToken"
:is-search-filtered="isSearchFiltered"
- :svg-path="emptyStateSvgPath"
- :filtered-svg-path="emptyStateFilteredSvgPath"
+ :new-runner-path="newRunnerPath"
/>
<template v-else>
<runner-list
diff --git a/app/assets/javascripts/ci/runner/group_runners/index.js b/app/assets/javascripts/ci/runner/group_runners/index.js
index 46514d5afe8..a5e2521ede5 100644
--- a/app/assets/javascripts/ci/runner/group_runners/index.js
+++ b/app/assets/javascripts/ci/runner/group_runners/index.js
@@ -18,12 +18,11 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
const {
registrationToken,
runnerInstallHelpPage,
+ newRunnerPath,
groupId,
groupFullPath,
onlineContactTimeoutSecs,
staleTimeoutSecs,
- emptyStateSvgPath,
- emptyStateFilteredSvgPath,
} = el.dataset;
const { cacheConfig, typeDefs, localMutations } = createLocalState();
@@ -41,14 +40,13 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
groupId,
onlineContactTimeoutSecs: parseInt(onlineContactTimeoutSecs, 10),
staleTimeoutSecs: parseInt(staleTimeoutSecs, 10),
- emptyStateSvgPath,
- emptyStateFilteredSvgPath,
},
render(h) {
return h(GroupRunnersApp, {
props: {
registrationToken,
groupFullPath,
+ newRunnerPath,
},
});
},
diff --git a/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js b/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
index d768a06494a..bad3ca6024e 100644
--- a/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
+++ b/app/assets/javascripts/ci/runner/local_storage_alert/show_alert_from_local_storage.js
@@ -7,7 +7,7 @@ export const showAlertFromLocalStorage = async () => {
if (alertOptions) {
try {
- const { createAlert } = await import('~/flash');
+ const { createAlert } = await import('~/alert');
createAlert(JSON.parse(alertOptions));
} catch {
// ignore when the alert data cannot be parsed
diff --git a/app/assets/javascripts/ci/runner/project_new_runner/index.js b/app/assets/javascripts/ci/runner/project_new_runner/index.js
new file mode 100644
index 00000000000..44f1a0ffdab
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_new_runner/index.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import ProjectNewRunnerApp from './project_new_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initProjectNewRunner = (selector = '#js-project-new-runner') => {
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { projectId } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(ProjectNewRunnerApp, {
+ props: {
+ projectId,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
new file mode 100644
index 00000000000..f0ae54c0232
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
@@ -0,0 +1,83 @@
+<script>
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
+import { s__ } from '~/locale';
+
+import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue';
+import RegistrationFeedbackBanner from '~/ci/runner/components/registration/registration_feedback_banner.vue';
+import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, PROJECT_TYPE } from '../constants';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
+
+export default {
+ name: 'ProjectNewRunnerApp',
+ components: {
+ RegistrationCompatibilityAlert,
+ RegistrationFeedbackBanner,
+ RunnerPlatformsRadioGroup,
+ RunnerCreateForm,
+ },
+ props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: DEFAULT_PLATFORM,
+ };
+ },
+ methods: {
+ onSaved(runner) {
+ const params = { [PARAM_KEY_PLATFORM]: this.platform };
+ const ephemeralRegisterUrl = setUrlParams(params, runner.ephemeralRegisterUrl);
+
+ saveAlertToLocalStorage({
+ message: s__('Runners|Runner created.'),
+ variant: VARIANT_SUCCESS,
+ });
+ redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated
+ },
+ onError(error) {
+ createAlert({ message: error.message });
+ },
+ },
+ PROJECT_TYPE,
+};
+</script>
+
+<template>
+ <div>
+ <registration-feedback-banner />
+
+ <h1 class="gl-font-size-h2">{{ s__('Runners|New project runner') }}</h1>
+
+ <registration-compatibility-alert :alert-key="projectId" />
+
+ <p>
+ {{
+ s__(
+ 'Runners|Create a project runner to generate a command that registers the runner with all its configurations.',
+ )
+ }}
+ </p>
+
+ <hr aria-hidden="true" />
+
+ <h2 class="gl-font-weight-normal gl-font-lg gl-my-5">
+ {{ s__('Runners|Platform') }}
+ </h2>
+ <runner-platforms-radio-group v-model="platform" />
+
+ <hr aria-hidden="true" />
+
+ <runner-create-form
+ :runner-type="$options.PROJECT_TYPE"
+ :project-id="projectId"
+ @saved="onSaved"
+ @error="onError"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/project_register_runner/index.js b/app/assets/javascripts/ci/runner/project_register_runner/index.js
new file mode 100644
index 00000000000..63e88359ee7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_register_runner/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
+import ProjectRegisterRunnerApp from './project_register_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initProjectRegisterRunner = (selector = '#js-project-register-runner') => {
+ showAlertFromLocalStorage();
+
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { runnerId, runnersPath } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(ProjectRegisterRunnerApp, {
+ props: {
+ runnerId,
+ runnersPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue b/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue
new file mode 100644
index 00000000000..b3fad595c7e
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants';
+import RegistrationInstructions from '../components/registration/registration_instructions.vue';
+import PlatformsDrawer from '../components/registration/platforms_drawer.vue';
+
+export default {
+ name: 'ProjectRegisterRunnerApp',
+ components: {
+ GlButton,
+ RegistrationInstructions,
+ PlatformsDrawer,
+ },
+ props: {
+ runnerId: {
+ type: String,
+ required: true,
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM,
+ isDrawerOpen: false,
+ };
+ },
+ watch: {
+ platform(platform) {
+ updateHistory({
+ url: mergeUrlParams({ [PARAM_KEY_PLATFORM]: platform }, window.location.href),
+ });
+ },
+ },
+ methods: {
+ onSelectPlatform(platform) {
+ this.platform = platform;
+ },
+ onToggleDrawer(val = !this.isDrawerOpen) {
+ this.isDrawerOpen = val;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <registration-instructions
+ :runner-id="runnerId"
+ :platform="platform"
+ @toggleDrawer="onToggleDrawer"
+ >
+ <template #runner-list-name>{{ s__('Runners|Project › CI/CD Settings › Runners') }}</template>
+ </registration-instructions>
+
+ <platforms-drawer
+ :platform="platform"
+ :open="isDrawerOpen"
+ @selectPlatform="onSelectPlatform"
+ @close="onToggleDrawer(false)"
+ />
+
+ <gl-button :href="runnersPath" variant="confirm">{{
+ s__('Runners|Go to runners page')
+ }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ci/runner/project_runners/register/index.js b/app/assets/javascripts/ci/runner/project_runners/register/index.js
new file mode 100644
index 00000000000..9986c93c918
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_runners/register/index.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import RegistrationDropdown from '~/ci/runner/components/registration/registration_dropdown.vue';
+import { PROJECT_TYPE } from '~/ci/runner/constants';
+
+Vue.use(VueApollo);
+
+export const initProjectRunnersRegistrationDropdown = (
+ selector = '#js-project-runner-registration-dropdown',
+) => {
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { registrationToken, projectId } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ provide: {
+ projectId,
+ },
+ render(h) {
+ return h(RegistrationDropdown, {
+ props: {
+ registrationToken,
+ type: PROJECT_TYPE,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/runner_edit/runner_edit_app.vue b/app/assets/javascripts/ci/runner/runner_edit/runner_edit_app.vue
index 4593c9ae52b..843342b20df 100644
--- a/app/assets/javascripts/ci/runner/runner_edit/runner_edit_app.vue
+++ b/app/assets/javascripts/ci/runner/runner_edit/runner_edit_app.vue
@@ -1,5 +1,5 @@
<script>
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '../components/runner_header.vue';