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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-15 18:16:18 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-15 18:16:18 +0300
commit5e8d344de5658ace62c367fc19256488416cdc49 (patch)
tree5a54f9bdb0c0107d43e7d92181a753b908cfd173 /app
parent792ffb0daf235b6150f696fc1b5ea63fc9845b94 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/runner/admin_new_runner/admin_new_runner_app.vue14
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_create_form.vue36
-rw-r--r--app/assets/javascripts/ci/runner/constants.js1
-rw-r--r--app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue98
-rw-r--r--app/assets/javascripts/ci/runner/group_new_runner/index.js33
-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_runners/group_runners_app.vue19
-rw-r--r--app/assets/javascripts/ci/runner/group_runners/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/runners/new/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/runners/register/index.js3
-rw-r--r--app/controllers/groups/runners_controller.rb28
-rw-r--r--app/graphql/types/ci/runner_type.rb27
-rw-r--r--app/views/groups/runners/index.html.haml2
-rw-r--r--app/views/groups/runners/new.html.haml5
-rw-r--r--app/views/groups/runners/register.html.haml7
17 files changed, 370 insertions, 15 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 79600012838..43d0dae6e78 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
@@ -6,7 +6,7 @@ import { s__ } from '~/locale';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.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 } from '../constants';
+import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, INSTANCE_TYPE } from '../constants';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
@@ -34,21 +34,21 @@ export default {
},
methods: {
onSaved(runner) {
- const registerUrl = setUrlParams(
- { [PARAM_KEY_PLATFORM]: this.platform },
- runner.registerAdminUrl,
- );
+ const params = { [PARAM_KEY_PLATFORM]: this.platform };
+ const ephemeralRegisterUrl = setUrlParams(params, runner.ephemeralRegisterUrl);
+
saveAlertToLocalStorage({
message: s__('Runners|Runner created.'),
variant: VARIANT_SUCCESS,
});
- redirectTo(registerUrl);
+ redirectTo(ephemeralRegisterUrl);
},
onError(error) {
createAlert({ message: error.message });
},
},
modalId: 'runners-legacy-registration-instructions-modal',
+ INSTANCE_TYPE,
};
</script>
@@ -84,6 +84,6 @@ export default {
<hr aria-hidden="true" />
- <runner-create-form @saved="onSaved" @error="onError" />
+ <runner-create-form :runner-type="$options.INSTANCE_TYPE" @saved="onSaved" @error="onError" />
</div>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/runner_create_form.vue b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
index 2bad08b4f48..d3e02f5cd6e 100644
--- a/app/assets/javascripts/ci/runner/components/runner_create_form.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
@@ -4,7 +4,7 @@ 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 { DEFAULT_ACCESS_LEVEL, INSTANCE_TYPE } from '../constants';
+import { RUNNER_TYPES, DEFAULT_ACCESS_LEVEL, GROUP_TYPE, INSTANCE_TYPE } from '../constants';
export default {
name: 'RunnerCreateForm',
@@ -13,11 +13,22 @@ export default {
GlButton,
RunnerFormFields,
},
+ props: {
+ runnerType: {
+ type: String,
+ required: true,
+ validator: (t) => RUNNER_TYPES.includes(t),
+ },
+ groupId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
data() {
return {
saving: false,
runner: {
- runnerType: INSTANCE_TYPE,
description: '',
maintenanceNote: '',
paused: false,
@@ -28,6 +39,23 @@ export default {
},
};
},
+ computed: {
+ mutationInput() {
+ const { input } = modelToUpdateMutationVariables(this.runner);
+
+ if (this.runnerType === GROUP_TYPE) {
+ return {
+ ...input,
+ runnerType: GROUP_TYPE,
+ groupId: this.groupId,
+ };
+ }
+ return {
+ ...input,
+ runnerType: INSTANCE_TYPE,
+ };
+ },
+ },
methods: {
async onSubmit() {
this.saving = true;
@@ -38,7 +66,9 @@ export default {
},
} = await this.$apollo.mutate({
mutation: runnerCreateMutation,
- variables: modelToUpdateMutationVariables(this.runner),
+ variables: {
+ input: this.mutationInput,
+ },
});
if (errors?.length) {
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 6237dcd0c03..6fcf4b1730a 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -141,6 +141,7 @@ export const PARAM_KEY_PLATFORM = 'platform';
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
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
index d14a594e378..07236808dca 100644
--- a/app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/new/runner_create.mutation.graphql
@@ -2,7 +2,7 @@ mutation runnerCreate($input: RunnerCreateInput!) {
runnerCreate(input: $input) {
runner {
id
- registerAdminUrl
+ ephemeralRegisterUrl
}
errors
}
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..35c75a917c7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue
@@ -0,0 +1,98 @@
+<script>
+import { GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
+import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.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: {
+ GlLink,
+ GlSprintf,
+ RunnerInstructionsModal,
+ RunnerPlatformsRadioGroup,
+ RunnerCreateForm,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ groupId: {
+ type: String,
+ required: true,
+ },
+ legacyRegistrationToken: {
+ 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);
+ },
+ onError(error) {
+ createAlert({ message: error.message });
+ },
+ },
+ modalId: 'runners-legacy-registration-instructions-modal',
+ GROUP_TYPE,
+};
+</script>
+
+<template>
+ <div>
+ <h1 class="gl-font-size-h2">{{ s__('Runners|New group runner') }}</h1>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Runners|Create a group 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>
+ </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..b314c3aa1e7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/group_new_runner/index.js
@@ -0,0 +1,33 @@
+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 { legacyRegistrationToken, groupId } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(GroupNewRunnerApp, {
+ props: {
+ groupId,
+ legacyRegistrationToken,
+ },
+ });
+ },
+ });
+};
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_runners/group_runners_app.vue b/app/assets/javascripts/ci/runner/group_runners/group_runners_app.vue
index 294d06a66e7..f8386214698 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,5 +1,5 @@
<script>
-import { GlLink } from '@gitlab/ui';
+import { GlButton, GlLink } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { updateHistory } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
@@ -42,6 +42,7 @@ import { captureException } from '../sentry_utils';
export default {
name: 'GroupRunnersApp',
components: {
+ GlButton,
GlLink,
RegistrationDropdown,
RunnerFilteredSearchBar,
@@ -58,6 +59,11 @@ export default {
mixins: [glFeatureFlagMixin()],
inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath'],
props: {
+ newRunnerPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
registrationToken: {
type: String,
required: false,
@@ -150,6 +156,10 @@ export default {
isSearchFiltered() {
return isSearchFiltered(this.search);
},
+ shouldShowCreateRunnerWorkflow() {
+ // create_runner_workflow_for_namespace feature flag
+ return this.glFeatures.createRunnerWorkflowForNamespace;
+ },
},
watch: {
search: {
@@ -219,8 +229,13 @@ export default {
nav-class="gl-border-none!"
/>
+ <template v-if="shouldShowCreateRunnerWorkflow">
+ <gl-button v-if="newRunnerPath" :href="newRunnerPath" variant="confirm">
+ {{ s__('Runners|New group runner') }}
+ </gl-button>
+ </template>
<registration-dropdown
- v-if="registrationToken"
+ v-else-if="registrationToken"
class="gl-ml-auto"
:registration-token="registrationToken"
:type="$options.GROUP_TYPE"
diff --git a/app/assets/javascripts/ci/runner/group_runners/index.js b/app/assets/javascripts/ci/runner/group_runners/index.js
index 46514d5afe8..4fcf484317d 100644
--- a/app/assets/javascripts/ci/runner/group_runners/index.js
+++ b/app/assets/javascripts/ci/runner/group_runners/index.js
@@ -18,6 +18,7 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
const {
registrationToken,
runnerInstallHelpPage,
+ newRunnerPath,
groupId,
groupFullPath,
onlineContactTimeoutSecs,
@@ -49,6 +50,7 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
props: {
registrationToken,
groupFullPath,
+ newRunnerPath,
},
});
},
diff --git a/app/assets/javascripts/pages/groups/runners/new/index.js b/app/assets/javascripts/pages/groups/runners/new/index.js
new file mode 100644
index 00000000000..318643d95a4
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/runners/new/index.js
@@ -0,0 +1,3 @@
+import { initGroupNewRunner } from '~/ci/runner/group_new_runner';
+
+initGroupNewRunner();
diff --git a/app/assets/javascripts/pages/groups/runners/register/index.js b/app/assets/javascripts/pages/groups/runners/register/index.js
new file mode 100644
index 00000000000..b02e33e21f2
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/runners/register/index.js
@@ -0,0 +1,3 @@
+import { initGroupRegisterRunner } from '~/ci/runner/group_register_runner';
+
+initGroupRegisterRunner();
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index 859bb0adb4e..f267c1cb857 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -2,14 +2,20 @@
class Groups::RunnersController < Groups::ApplicationController
before_action :authorize_read_group_runners!, only: [:index, :show]
+ before_action :authorize_create_group_runners!, only: [:new, :register]
before_action :authorize_update_runner!, only: [:edit, :update, :destroy, :pause, :resume]
- before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
+ before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show, :register]
+
+ before_action only: [:index] do
+ push_frontend_feature_flag(:create_runner_workflow_for_namespace, group)
+ end
feature_category :runner
urgency :low
def index
@group_runner_registration_token = @group.runners_token if can?(current_user, :register_group_runners, group)
+ @group_new_runner_path = new_group_runner_path(@group) if can?(current_user, :create_runner, group)
Gitlab::Tracking.event(self.class.name, 'index', user: current_user, namespace: @group)
end
@@ -28,6 +34,16 @@ class Groups::RunnersController < Groups::ApplicationController
end
end
+ def new
+ render_404 unless create_runner_workflow_for_namespace_enabled?
+
+ @group_runner_registration_token = @group.runners_token
+ end
+
+ def register
+ render_404 unless create_runner_workflow_for_namespace_enabled? && runner.registration_available?
+ end
+
private
def runner
@@ -47,6 +63,16 @@ class Groups::RunnersController < Groups::ApplicationController
render_404
end
+
+ def authorize_create_group_runners!
+ return if can?(current_user, :create_runner, group)
+
+ render_404
+ end
+
+ def create_runner_workflow_for_namespace_enabled?
+ Feature.enabled?(:create_runner_workflow_for_namespace, group)
+ end
end
Groups::RunnersController.prepend_mod
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index 8b0a969f7f5..20e8b506a3f 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -42,6 +42,9 @@ module Types
description: 'Ephemeral authentication token used for runner manager registration. Only available for the creator of the runner for a limited time during registration.',
authorize: :read_ephemeral_token,
alpha: { milestone: '15.9' }
+ field :ephemeral_register_url, GraphQL::Types::String, null: true,
+ description: 'URL of the registration page of the runner manager. Only available for the creator of the runner for a limited time during registration.',
+ alpha: { milestone: '15.11' }
field :executor_name, GraphQL::Types::String, null: true,
description: 'Executor last advertised by the runner.',
method: :executor_name
@@ -147,6 +150,17 @@ module Types
Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners?
end
+ def ephemeral_register_url
+ return unless ephemeral_register_url_access_allowed?(runner)
+
+ case runner.runner_type
+ when 'instance_type'
+ Gitlab::Routing.url_helpers.register_admin_runner_url(runner)
+ when 'group_type'
+ Gitlab::Routing.url_helpers.register_group_runner_url(runner.groups[0], runner)
+ end
+ end
+
def register_admin_url
return unless can_admin_runners? && runner.registration_available?
@@ -187,6 +201,19 @@ module Types
def can_admin_runners?
context[:current_user]&.can_admin_all_resources?
end
+
+ def ephemeral_register_url_access_allowed?(runner)
+ return unless runner.registration_available?
+
+ case runner.runner_type
+ when 'instance_type'
+ can_admin_runners?
+ when 'group_type'
+ group = runner.groups[0]
+
+ group && context[:current_user]&.can?(:register_group_runners, group)
+ end
+ end
end
end
end
diff --git a/app/views/groups/runners/index.html.haml b/app/views/groups/runners/index.html.haml
index 7e98f6035a6..d619635d3e0 100644
--- a/app/views/groups/runners/index.html.haml
+++ b/app/views/groups/runners/index.html.haml
@@ -1,3 +1,3 @@
- page_title s_('Runners|Runners')
-#js-group-runners{ data: group_runners_data_attributes(@group).merge({ registration_token: @group_runner_registration_token }) }
+#js-group-runners{ data: group_runners_data_attributes(@group).merge({ registration_token: @group_runner_registration_token, new_runner_path: @group_new_runner_path }) }
diff --git a/app/views/groups/runners/new.html.haml b/app/views/groups/runners/new.html.haml
new file mode 100644
index 00000000000..12e7e458a79
--- /dev/null
+++ b/app/views/groups/runners/new.html.haml
@@ -0,0 +1,5 @@
+- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
+- breadcrumb_title s_('Runners|New')
+- page_title s_('Runners|Create a group runner')
+
+#js-group-new-runner{ data: { legacy_registration_token: @group_runner_registration_token, group_id: @group.to_global_id } }
diff --git a/app/views/groups/runners/register.html.haml b/app/views/groups/runners/register.html.haml
new file mode 100644
index 00000000000..fdee1675475
--- /dev/null
+++ b/app/views/groups/runners/register.html.haml
@@ -0,0 +1,7 @@
+- runner_name = "##{@runner.id} (#{@runner.short_sha})"
+- breadcrumb_title s_('Runners|Register')
+- page_title s_('Runners|Register'), "##{@runner.id} (#{@runner.short_sha})"
+- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
+- add_to_breadcrumbs runner_name, register_group_runner_path(@runner)
+
+#js-group-register-runner{ data: { runner_id: @runner.id, runners_path: group_runners_path(@group) } }