diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
commit | 3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch) | |
tree | 3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /app/assets/javascripts/ci/runner | |
parent | 9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff) |
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'app/assets/javascripts/ci/runner')
34 files changed, 707 insertions, 335 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 e4d47fba464..f0a41a5949e 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,6 +1,6 @@ <script> import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated +import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; @@ -32,7 +32,7 @@ export default { message: s__('Runners|Runner created.'), variant: VARIANT_SUCCESS, }); - redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated + visitUrl(ephemeralRegisterUrl); }, onError(error) { createAlert({ message: error.message }); @@ -60,7 +60,7 @@ export default { <hr aria-hidden="true" /> - <h2 class="gl-font-weight-normal gl-font-lg gl-my-5"> + <h2 class="gl-font-size-h2 gl-my-5"> {{ s__('Runners|Platform') }} </h2> <runner-platforms-radio-group v-model="platform" /> 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 668a55d2437..d385d32fd9d 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 @@ -2,7 +2,7 @@ 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'; // eslint-disable-line import/no-deprecated +import { visitUrl } from '~/lib/utils/url_utility'; 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); // eslint-disable-line import/no-deprecated + visitUrl(this.runnersPath); }, }, }; diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue index 4d04b5d4b14..e287e4e17d1 100644 --- a/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue +++ b/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue @@ -20,7 +20,13 @@ export default { }, computed: { paused() { - return !this.runner.active; + return this.runner.paused; + }, + contactedAt() { + return this.runner.contactedAt; + }, + status() { + return this.runner.status; }, }, }; @@ -29,7 +35,8 @@ export default { <template> <div> <runner-status-badge - :runner="runner" + :contacted-at="contactedAt" + :status="status" class="gl-display-inline-block gl-max-w-full gl-text-truncate" /> <runner-paused-badge 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 f24fb5575ae..9f4ce14f704 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 @@ -8,6 +8,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import RunnerName from '../runner_name.vue'; import RunnerTags from '../runner_tags.vue'; import RunnerTypeBadge from '../runner_type_badge.vue'; +import RunnerManagersBadge from '../runner_managers_badge.vue'; import { formatJobCount } from '../../utils'; import { @@ -29,6 +30,7 @@ export default { RunnerName, RunnerTags, RunnerTypeBadge, + RunnerManagersBadge, RunnerUpgradeStatusIcon: () => import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'), UserAvatarLink, @@ -44,6 +46,9 @@ export default { }, }, computed: { + managersCount() { + return this.runner.managers?.count || 0; + }, jobCount() { return formatJobCount(this.runner.jobCount); }, @@ -75,6 +80,8 @@ export default { <slot :runner="runner" name="runner-name"> <runner-name :runner="runner" /> </slot> + + <runner-managers-badge :count="managersCount" size="sm" class="gl-vertical-align-middle" /> <gl-icon v-if="runner.locked" v-gl-tooltip @@ -87,7 +94,7 @@ export default { <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" /> + <runner-upgrade-status-icon :upgrade-status="runner.upgradeStatus" /> <gl-sprintf :message="$options.i18n.I18N_VERSION_LABEL"> <template #version>{{ runner.version }}</template> </gl-sprintf> diff --git a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue index ff182c61ccf..9cf2572c924 100644 --- a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue +++ b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue @@ -42,8 +42,8 @@ export default { }; }, computed: { - drawerHeightOffset() { - return getContentWrapperHeight('.content-wrapper'); + getDrawerHeaderHeight() { + return getContentWrapperHeight(); }, architectureOptions() { return platformArchitectures({ platform: this.selectedPlatform }); @@ -86,7 +86,7 @@ export default { <template> <gl-drawer :open="open" - :header-height="drawerHeightOffset" + :header-height="getDrawerHeaderHeight" :z-index="$options.DRAWER_Z_INDEX" data-testid="runner-platforms-drawer" @close="onClose" 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 index fe19977f783..6fd4edf5847 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue @@ -1,5 +1,5 @@ <script> -import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/multi-editor_all_changes_committed_empty.svg?url'; +import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/rocket-launch-md.svg?url'; import { GlBanner } from '@gitlab/ui'; import { s__ } from '~/locale'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; 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 6107b4dd3ea..1b363174d28 100644 --- a/app/assets/javascripts/ci/runner/components/runner_create_form.vue +++ b/app/assets/javascripts/ci/runner/components/runner_create_form.vue @@ -9,7 +9,7 @@ import { DEFAULT_ACCESS_LEVEL, PROJECT_TYPE, GROUP_TYPE, - INSTANCE_TYPE, + I18N_CREATE_ERROR, } from '../constants'; export default { @@ -40,11 +40,13 @@ export default { return { saving: false, runner: { + runnerType: this.runnerType, description: '', maintenanceNote: '', paused: false, accessLevel: DEFAULT_ACCESS_LEVEL, runUntagged: false, + locked: false, tagList: '', maximumTimeout: '', }, @@ -57,26 +59,22 @@ export default { 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, - }; + return input; }, }, methods: { async onSubmit() { this.saving = true; + try { const { data: { @@ -90,16 +88,29 @@ export default { }); if (errors?.length) { - this.$emit('error', new Error(errors.join(' '))); - } else { - this.onSuccess(runner); + this.onError(new Error(errors.join(' ')), true); + return; + } + + if (!runner?.ephemeralRegisterUrl) { + // runner is missing information, report issue and + // fail naviation to register page. + this.onError(new Error(I18N_CREATE_ERROR)); + return; } + + this.onSuccess(runner); } catch (error) { + this.onError(error); + } + }, + onError(error, isValidationError = false) { + if (!isValidationError) { captureException({ error, component: this.$options.name }); - this.$emit('error', error); - } finally { - this.saving = false; } + + this.$emit('error', error); + this.saving = false; }, onSuccess(runner) { this.$emit('saved', runner); @@ -111,9 +122,9 @@ export default { <gl-form @submit.prevent="onSubmit"> <runner-form-fields v-model="runner" /> - <div class="gl-display-flex"> + <div class="gl-display-flex gl-mt-6"> <gl-button type="submit" variant="confirm" class="js-no-auto-disable" :loading="saving"> - {{ __('Submit') }} + {{ s__('Runners|Create runner') }} </gl-button> </div> </gl-form> 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 020487fc727..3560521e8d7 100644 --- a/app/assets/javascripts/ci/runner/components/runner_delete_button.vue +++ b/app/assets/javascripts/ci/runner/components/runner_delete_button.vue @@ -45,6 +45,9 @@ export default { runnerName() { return `#${this.runnerId} (${this.runner.shortSha})`; }, + runnerManagersCount() { + return this.runner.managers?.count || 0; + }, runnerDeleteModalId() { return `delete-runner-modal-${this.runnerId}`; }, @@ -150,6 +153,7 @@ export default { <runner-delete-modal :modal-id="runnerDeleteModalId" :runner-name="runnerName" + :managers-count="runnerManagersCount" @primary="onDelete" /> </div> diff --git a/app/assets/javascripts/ci/runner/components/runner_delete_modal.vue b/app/assets/javascripts/ci/runner/components/runner_delete_modal.vue index 8be216a7eb5..93f79fd67ea 100644 --- a/app/assets/javascripts/ci/runner/components/runner_delete_modal.vue +++ b/app/assets/javascripts/ci/runner/components/runner_delete_modal.vue @@ -1,12 +1,9 @@ <script> import { GlModal } from '@gitlab/ui'; -import { __, s__, sprintf } from '~/locale'; +import { __, s__, n__, sprintf } from '~/locale'; const I18N_TITLE = s__('Runners|Delete runner %{name}?'); -const I18N_BODY = s__( - 'Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?', -); -const I18N_PRIMARY = s__('Runners|Delete runner'); +const I18N_TITLE_PLURAL = s__('Runners|Delete %{count} runners?'); const I18N_CANCEL = __('Cancel'); export default { @@ -18,10 +15,40 @@ export default { type: String, required: true, }, + managersCount: { + type: Number, + required: false, + default: 0, + }, }, computed: { + count() { + // Only show count if MORE than 1 manager, for 0 we still + // assume 1 runner that happens to be disconnected. + return this.managersCount > 1 ? this.managersCount : 1; + }, title() { - return sprintf(I18N_TITLE, { name: this.runnerName }); + if (this.count === 1) { + return sprintf(I18N_TITLE, { name: this.runnerName }); + } + return sprintf(I18N_TITLE_PLURAL, { count: this.count }); + }, + body() { + return n__( + 'Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?', + 'Runners|%d runners will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?', + this.count, + ); + }, + actionPrimary() { + return { + text: n__( + 'Runners|Permanently delete runner', + 'Runners|Permanently delete %d runners', + this.count, + ), + attributes: { variant: 'danger' }, + }; }, }, methods: { @@ -29,9 +56,7 @@ export default { this.$refs.modal.hide(); }, }, - actionPrimary: { text: I18N_PRIMARY, attributes: { variant: 'danger' } }, - actionCancel: { text: I18N_CANCEL }, - I18N_BODY, + ACTION_CANCEL: { text: I18N_CANCEL }, }; </script> @@ -40,12 +65,12 @@ export default { ref="modal" size="sm" :title="title" - :action-primary="$options.actionPrimary" - :action-cancel="$options.actionCancel" + :action-primary="actionPrimary" + :action-cancel="$options.ACTION_CANCEL" v-bind="$attrs" v-on="$listeners" @primary="onPrimary" > - {{ $options.I18N_BODY }} + {{ body }} </gl-modal> </template> diff --git a/app/assets/javascripts/ci/runner/components/runner_details.vue b/app/assets/javascripts/ci/runner/components/runner_details.vue index 6eba8f2e49f..8c1280cffb9 100644 --- a/app/assets/javascripts/ci/runner/components/runner_details.vue +++ b/app/assets/javascripts/ci/runner/components/runner_details.vue @@ -1,20 +1,28 @@ <script> -import { GlIntersperse, GlLink } from '@gitlab/ui'; +import { GlIntersperse, GlLink, GlSprintf } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import HelpPopover from '~/vue_shared/components/help_popover.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; -import { ACCESS_LEVEL_REF_PROTECTED, GROUP_TYPE, PROJECT_TYPE } from '../constants'; +import { + ACCESS_LEVEL_REF_PROTECTED, + GROUP_TYPE, + PROJECT_TYPE, + RUNNER_MANAGERS_HELP_URL, + I18N_STATUS_NEVER_CONTACTED, +} from '../constants'; import RunnerDetail from './runner_detail.vue'; import RunnerGroups from './runner_groups.vue'; import RunnerProjects from './runner_projects.vue'; import RunnerTags from './runner_tags.vue'; +import RunnerManagersDetail from './runner_managers_detail.vue'; export default { components: { GlIntersperse, GlLink, + GlSprintf, HelpPopover, RunnerDetail, RunnerMaintenanceNoteDetail: () => @@ -26,6 +34,7 @@ export default { RunnerUpgradeStatusAlert: () => import('ee_component/ci/runner/components/runner_upgrade_status_alert.vue'), RunnerTags, + RunnerManagersDetail, TimeAgo, }, props: { @@ -76,6 +85,8 @@ export default { }, }, ACCESS_LEVEL_REF_PROTECTED, + RUNNER_MANAGERS_HELP_URL, + I18N_STATUS_NEVER_CONTACTED, }; </script> @@ -90,7 +101,7 @@ export default { <runner-detail :label="s__('Runners|Description')" :value="runner.description" /> <runner-detail :label="s__('Runners|Last contact')" - :empty-value="s__('Runners|Never contacted')" + :empty-value="$options.I18N_STATUS_NEVER_CONTACTED" > <template v-if="runner.contactedAt" #value> <time-ago :time="runner.contactedAt" /> @@ -150,6 +161,33 @@ export default { class="gl-pt-4 gl-border-t-gray-100 gl-border-t-1 gl-border-t-solid" :value="runner.maintenanceNoteHtml" /> + + <runner-detail> + <template #label> + {{ s__('Runners|Runners') }} + <help-popover> + <gl-sprintf + :message=" + s__( + 'Runners|Runners are grouped when they have the same authentication token. This happens when you re-use a runner configuration in more than one runner manager. %{linkStart}How does this work?%{linkEnd}', + ) + " + > + <template #link="{ content }" + ><gl-link + :href="$options.RUNNER_MANAGERS_HELP_URL" + target="_blank" + class="gl-reset-font-size" + >{{ content }}</gl-link + ></template + > + </gl-sprintf> + </help-popover> + </template> + <template #value> + <runner-managers-detail :runner="runner" /> + </template> + </runner-detail> </dl> </div> diff --git a/app/assets/javascripts/ci/runner/components/runner_form_fields.vue b/app/assets/javascripts/ci/runner/components/runner_form_fields.vue index e37ac5e6e26..d090a562ff7 100644 --- a/app/assets/javascripts/ci/runner/components/runner_form_fields.vue +++ b/app/assets/javascripts/ci/runner/components/runner_form_fields.vue @@ -1,7 +1,16 @@ <script> -import { GlFormGroup, GlFormCheckbox, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui'; +import { isEqual } from 'lodash'; +import { + GlFormGroup, + GlFormCheckbox, + GlFormInput, + GlIcon, + GlLink, + GlSprintf, + GlSkeletonLoader, +} from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; -import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED } from '../constants'; +import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants'; export default { name: 'RunnerFormFields', @@ -9,8 +18,10 @@ export default { GlFormGroup, GlFormCheckbox, GlFormInput, + GlIcon, GlLink, GlSprintf, + GlSkeletonLoader, RunnerMaintenanceNoteField: () => import('ee_component/ci/runner/components/runner_maintenance_note_field.vue'), }, @@ -20,15 +31,32 @@ export default { default: null, required: false, }, + loading: { + type: Boolean, + default: false, + required: false, + }, }, data() { return { - model: { - ...this.value, - }, + model: null, }; }, + computed: { + canBeLockedToProject() { + return this.value?.runnerType === PROJECT_TYPE; + }, + }, watch: { + value: { + handler(newVal, oldVal) { + // update only when values change, avoids infinite loop + if (!isEqual(newVal, oldVal)) { + this.model = { ...newVal }; + } + }, + immediate: true, + }, model: { handler() { this.$emit('input', this.model); @@ -45,96 +73,122 @@ export default { </script> <template> <div> - <h2 class="gl-font-weight-normal gl-font-lg gl-my-5"> + <h2 class="gl-font-size-h2 gl-my-5"> + {{ s__('Runners|Tags') }} + </h2> + <gl-skeleton-loader v-if="loading" :lines="12" /> + <template v-else-if="model"> + <gl-form-group :label="__('Tags')" label-for="runner-tags"> + <template #description> + <gl-sprintf + :message=" + s__('Runners|Multiple tags must be separated by a comma. For example, %{example}.') + " + > + <template #example> + <!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings --> + <code>macos, shared</code> + </template> + </gl-sprintf> + </template> + <template #label-description> + <gl-sprintf + :message=" + s__( + 'Runners|Add tags for the types of jobs the runner processes to ensure that the runner only runs jobs that you intend it to. %{helpLinkStart}Learn more.%{helpLinkEnd}', + ) + " + > + <template #helpLink="{ content }"> + <gl-link :href="$options.HELP_LABELS_PAGE_PATH" target="_blank">{{ + content + }}</gl-link> + </template> + </gl-sprintf> + </template> + <gl-form-input id="runner-tags" v-model="model.tagList" name="tags" /> + </gl-form-group> + <gl-form-checkbox v-model="model.runUntagged" name="run-untagged"> + {{ __('Run untagged jobs') }} + <template #help> + {{ s__('Runners|Use the runner for jobs without tags in addition to tagged jobs.') }} + </template> + </gl-form-checkbox> + </template> + + <hr aria-hidden="true" /> + + <h2 class="gl-font-size-h2 gl-my-5"> {{ s__('Runners|Details') }} {{ __('(optional)') }} </h2> - <gl-form-group :label="s__('Runners|Runner description')" label-for="runner-description"> - <gl-form-input id="runner-description" v-model="model.description" name="description" /> - </gl-form-group> - <runner-maintenance-note-field v-model="model.maintenanceNote" class="gl-mt-5" /> + <gl-skeleton-loader v-if="loading" :lines="15" /> + <template v-else-if="model"> + <gl-form-group :label="s__('Runners|Runner description')" label-for="runner-description"> + <gl-form-input id="runner-description" v-model="model.description" name="description" /> + </gl-form-group> + <runner-maintenance-note-field v-model="model.maintenanceNote" class="gl-mt-5" /> + </template> <hr aria-hidden="true" /> - <h2 class="gl-font-weight-normal gl-font-lg gl-my-5"> + <h2 class="gl-font-size-h2 gl-my-5"> {{ s__('Runners|Configuration') }} {{ __('(optional)') }} </h2> - <div class="gl-mb-5"> - <gl-form-checkbox v-model="model.paused" name="paused"> - {{ __('Paused') }} - <template #help> - {{ s__('Runners|Stop the runner from accepting new jobs.') }} - </template> - </gl-form-checkbox> - - <gl-form-checkbox - v-model="model.accessLevel" - name="protected" - :value="$options.ACCESS_LEVEL_REF_PROTECTED" - :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED" - > - {{ __('Protected') }} - <template #help> - {{ s__('Runners|Use the runner on pipelines for protected branches only.') }} - </template> - </gl-form-checkbox> - - <gl-form-checkbox v-model="model.runUntagged" name="run-untagged"> - {{ __('Run untagged jobs') }} - <template #help> - {{ s__('Runners|Use the runner for jobs without tags in addition to tagged jobs.') }} - </template> - </gl-form-checkbox> - </div> + <gl-skeleton-loader v-if="loading" :lines="15" /> + <template v-else-if="model"> + <div class="gl-mb-5"> + <gl-form-checkbox v-model="model.paused" name="paused"> + {{ __('Paused') }} + <template #help> + {{ s__('Runners|Stop the runner from accepting new jobs.') }} + </template> + </gl-form-checkbox> - <gl-form-group :label="__('Tags')" label-for="runner-tags"> - <template #description> - <gl-sprintf - :message=" - s__('Runners|Multiple tags must be separated by a comma. For example, %{example}.') - " + <gl-form-checkbox + v-model="model.accessLevel" + name="protected" + :value="$options.ACCESS_LEVEL_REF_PROTECTED" + :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED" > - <template #example> - <!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings --> - <code>macos, shared</code> + {{ __('Protected') }} + <template #help> + {{ s__('Runners|Use the runner on pipelines for protected branches only.') }} </template> - </gl-sprintf> - </template> - <template #label-description> - <gl-sprintf - :message=" - s__( - 'Runners|Add tags for the types of jobs the runner processes to ensure that the runner only runs jobs that you intend it to. %{helpLinkStart}Learn more.%{helpLinkEnd}', - ) - " - > - <template #helpLink="{ content }"> - <gl-link :href="$options.HELP_LABELS_PAGE_PATH" target="_blank">{{ content }}</gl-link> + </gl-form-checkbox> + + <gl-form-checkbox v-if="canBeLockedToProject" v-model="model.locked" name="locked"> + {{ __('Lock to current projects') }} <gl-icon name="lock" /> + <template #help> + {{ + s__( + 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.', + ) + }} </template> - </gl-sprintf> - </template> - <gl-form-input id="runner-tags" v-model="model.tagList" name="tags" /> - </gl-form-group> + </gl-form-checkbox> + </div> - <gl-form-group - :label="__('Maximum job timeout')" - :label-description=" - s__( - 'Runners|Maximum amount of time the runner can run before it terminates. If a project has a shorter job timeout period, the job timeout period of the instance runner is used instead.', - ) - " - label-for="runner-max-timeout" - :description="s__('Runners|Enter the number of seconds.')" - > - <gl-form-input - id="runner-max-timeout" - v-model.number="model.maximumTimeout" - name="max-timeout" - type="number" - /> - </gl-form-group> + <gl-form-group + :label="__('Maximum job timeout')" + :label-description=" + s__( + 'Runners|Maximum amount of time the runner can run before it terminates. If a project has a shorter job timeout period, the job timeout period of the instance runner is used instead.', + ) + " + label-for="runner-max-timeout" + :description="s__('Runners|Enter the number of seconds.')" + > + <gl-form-input + id="runner-max-timeout" + v-model.number="model.maximumTimeout" + name="max-timeout" + type="number" + /> + </gl-form-group> + </template> </div> </template> diff --git a/app/assets/javascripts/ci/runner/components/runner_header.vue b/app/assets/javascripts/ci/runner/components/runner_header.vue index 874c234ca4c..f46e894bf2e 100644 --- a/app/assets/javascripts/ci/runner/components/runner_header.vue +++ b/app/assets/javascripts/ci/runner/components/runner_header.vue @@ -1,9 +1,8 @@ <script> import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; -import { sprintf } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { I18N_DETAILS_TITLE, I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants'; +import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants'; +import { formatRunnerName } from '../utils'; import RunnerTypeBadge from './runner_type_badge.vue'; import RunnerStatusBadge from './runner_status_badge.vue'; @@ -25,12 +24,8 @@ export default { }, }, computed: { - paused() { - return !this.runner.active; - }, - heading() { - const id = getIdFromGraphQLId(this.runner.id); - return sprintf(I18N_DETAILS_TITLE, { runner_id: id }); + name() { + return formatRunnerName(this.runner); }, }, I18N_LOCKED_RUNNER_DESCRIPTION, @@ -38,16 +33,16 @@ export default { </script> <template> <div - class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-gap-3 gl-flex-wrap gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" + class="gl-display-flex gl-justify-content-space-between gl-align-items-flex-start gl-gap-3 gl-flex-wrap gl-py-5" > - <div class="gl-display-flex gl-align-items-flex-start gl-gap-3 gl-flex-wrap"> - <runner-status-badge :runner="runner" /> - <runner-type-badge v-if="runner" :type="runner.runnerType" /> - <span> - <template v-if="runner.createdAt"> - <gl-sprintf :message="__('%{runner} created %{timeago}')"> - <template #runner> - <strong>{{ heading }}</strong> + <div> + <h1 class="gl-font-size-h-display gl-my-0">{{ name }}</h1> + <div class="gl-display-flex gl-align-items-flex-start gl-gap-3 gl-flex-wrap gl-mt-3"> + <runner-status-badge :contacted-at="runner.contactedAt" :status="runner.status" /> + <runner-type-badge :type="runner.runnerType" /> + <span v-if="runner.createdAt"> + <gl-sprintf :message="__('%{locked} created %{timeago}')"> + <template #locked> <gl-icon v-if="runner.locked" v-gl-tooltip="$options.I18N_LOCKED_RUNNER_DESCRIPTION" @@ -59,11 +54,8 @@ export default { <time-ago :time="runner.createdAt" /> </template> </gl-sprintf> - </template> - <template v-else> - <strong>{{ heading }}</strong> - </template> - </span> + </span> + </div> </div> <div class="gl-display-flex gl-gap-3 gl-flex-wrap"><slot name="actions"></slot></div> </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 index c30a824120d..4e68c2ea71a 100644 --- a/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue +++ b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue @@ -1,5 +1,5 @@ <script> -import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url'; +import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; import { GlEmptyState } from '@gitlab/ui'; import { s__ } from '~/locale'; @@ -19,7 +19,11 @@ export default { </script> <template> - <gl-empty-state :svg-path="$options.EMPTY_STATE_SVG_URL" :title="$options.i18n.title"> + <gl-empty-state + :svg-path="$options.EMPTY_STATE_SVG_URL" + :svg-height="150" + :title="$options.i18n.title" + > <template #description> <p>{{ $options.i18n.description }}</p> </template> 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 8606c22db34..d2836962a97 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,10 +1,20 @@ <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 EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; +import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.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'; +import { + I18N_GET_STARTED, + I18N_RUNNERS_ARE_AGENTS, + I18N_CREATE_RUNNER_LINK, + I18N_STILL_USING_REGISTRATION_TOKENS, + I18N_CONTACT_ADMIN_TO_REGISTER, + I18N_FOLLOW_REGISTRATION_INSTRUCTIONS, + I18N_NO_RESULTS, + I18N_EDIT_YOUR_SEARCH, +} from '~/ci/runner/constants'; export default { components: { @@ -38,9 +48,8 @@ export default { shouldShowCreateRunnerWorkflow() { // create_runner_workflow_for_admin or create_runner_workflow_for_namespace return ( - this.newRunnerPath && - (this.glFeatures?.createRunnerWorkflowForAdmin || - this.glFeatures?.createRunnerWorkflowForNamespace) + this.glFeatures?.createRunnerWorkflowForAdmin || + this.glFeatures?.createRunnerWorkflowForNamespace ); }, }, @@ -48,35 +57,59 @@ export default { svgHeight: 145, EMPTY_STATE_SVG_URL, FILTERED_SVG_URL, + + I18N_GET_STARTED, + I18N_RUNNERS_ARE_AGENTS, + I18N_CREATE_RUNNER_LINK, + I18N_STILL_USING_REGISTRATION_TOKENS, + I18N_CONTACT_ADMIN_TO_REGISTER, + I18N_FOLLOW_REGISTRATION_INSTRUCTIONS, + I18N_NO_RESULTS, + I18N_EDIT_YOUR_SEARCH, }; </script> <template> <gl-empty-state v-if="isSearchFiltered" - :title="s__('Runners|No results found')" + :title="$options.I18N_NO_RESULTS" :svg-path="$options.FILTERED_SVG_URL" :svg-height="$options.svgHeight" - :description="s__('Runners|Edit your search and try again')" + :description="$options.I18N_EDIT_YOUR_SEARCH" /> <gl-empty-state v-else - :title="s__('Runners|Get started with runners')" + :title="$options.I18N_GET_STARTED" :svg-path="$options.EMPTY_STATE_SVG_URL" :svg-height="$options.svgHeight" > - <template v-if="registrationToken" #description> + <template #description> + {{ $options.I18N_RUNNERS_ARE_AGENTS }} + <template v-if="shouldShowCreateRunnerWorkflow"> + <gl-sprintf v-if="newRunnerPath" :message="$options.I18N_CREATE_RUNNER_LINK"> + <template #link="{ content }"> + <gl-link :href="newRunnerPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + <template v-if="registrationToken"> + <br /> + <gl-link v-gl-modal="$options.modalId">{{ + $options.I18N_STILL_USING_REGISTRATION_TOKENS + }}</gl-link> + <runner-instructions-modal + :modal-id="$options.modalId" + :registration-token="registrationToken" + /> + </template> + <template v-if="!newRunnerPath && !registrationToken"> + {{ $options.I18N_CONTACT_ADMIN_TO_REGISTER }} + </template> + </template> <gl-sprintf - :message=" - s__( - 'Runners|Runners are the agents that run your CI/CD jobs. Follow the %{linkStart}installation and registration instructions%{linkEnd} to set up a runner.', - ) - " + v-else-if="registrationToken" + :message="$options.I18N_FOLLOW_REGISTRATION_INSTRUCTIONS" > - <template v-if="shouldShowCreateRunnerWorkflow" #link="{ content }"> - <gl-link :href="newRunnerPath">{{ content }}</gl-link> - </template> - <template v-else #link="{ content }"> + <template #link="{ content }"> <gl-link v-gl-modal="$options.modalId">{{ content }}</gl-link> <runner-instructions-modal :modal-id="$options.modalId" @@ -84,13 +117,9 @@ export default { /> </template> </gl-sprintf> - </template> - <template v-else #description> - {{ - s__( - 'Runners|Runners are the agents that run your CI/CD jobs. To register new runners, please contact your administrator.', - ) - }} + <template v-else> + {{ $options.I18N_CONTACT_ADMIN_TO_REGISTER }} + </template> </template> </gl-empty-state> </template> diff --git a/app/assets/javascripts/ci/runner/components/runner_managers_badge.vue b/app/assets/javascripts/ci/runner/components/runner_managers_badge.vue new file mode 100644 index 00000000000..d298d8ded82 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/runner_managers_badge.vue @@ -0,0 +1,47 @@ +<script> +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { formatNumber, s__, sprintf } from '~/locale'; + +export default { + name: 'RunnerManagersBadge', + components: { + GlBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + count: { + type: Number, + required: false, + default: 0, + }, + }, + computed: { + shouldShowBadge() { + // runner managers can be grouped, but this information is only shown + // when we have 2 or more. + return this.count >= 2; + }, + formattedCount() { + return formatNumber(this.count); + }, + tooltip() { + return sprintf(s__('Runners|%{count} runners in this group'), { + count: this.formattedCount, + }); + }, + }, +}; +</script> +<template> + <gl-badge + v-if="shouldShowBadge" + v-gl-tooltip="tooltip" + variant="muted" + icon="container-image" + v-bind="$attrs" + > + {{ formattedCount }} + </gl-badge> +</template> diff --git a/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue b/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue new file mode 100644 index 00000000000..5cc1bbef481 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue @@ -0,0 +1,111 @@ +<script> +import { GlCollapse, GlButton, GlIcon, GlSkeletonLoader } from '@gitlab/ui'; +import { __, s__, formatNumber } from '~/locale'; +import { createAlert } from '~/alert'; +import runnerManagersQuery from '../graphql/show/runner_managers.query.graphql'; +import { I18N_FETCH_ERROR } from '../constants'; +import { captureException } from '../sentry_utils'; +import { tableField } from '../utils'; +import RunnerManagersTable from './runner_managers_table.vue'; + +export default { + name: 'RunnerManagersDetail', + components: { + GlCollapse, + GlButton, + GlIcon, + GlSkeletonLoader, + RunnerManagersTable, + }, + props: { + runner: { + type: Object, + required: true, + validator: (runner) => { + return Boolean(runner?.id); + }, + }, + }, + data() { + return { + skip: true, + expanded: false, + managers: [], + }; + }, + apollo: { + managers: { + query: runnerManagersQuery, + skip() { + return this.skip; + }, + variables() { + return { runnerId: this.runner.id }; + }, + update({ runner }) { + return runner?.managers?.nodes || []; + }, + error(error) { + createAlert({ message: I18N_FETCH_ERROR }); + captureException({ error, component: this.$options.name }); + }, + }, + }, + computed: { + runnerManagersCount() { + return this.runner?.managers?.count || 0; + }, + runnerManagersCountFormatted() { + return formatNumber(this.runnerManagersCount); + }, + icon() { + return this.expanded ? 'chevron-down' : 'chevron-right'; + }, + text() { + return this.expanded ? __('Hide details') : __('Show details'); + }, + loading() { + return this.$apollo?.queries.managers.loading; + }, + }, + methods: { + fetchManagers() { + this.skip = false; + }, + toggleExpanded() { + this.expanded = !this.expanded; + }, + }, + fields: [ + tableField({ key: 'systemId', label: s__('Runners|System ID') }), + tableField({ + key: 'contactedAt', + label: s__('Runners|Last contact'), + tdClass: ['gl-text-right'], + thClasses: ['gl-text-right'], + }), + ], +}; +</script> + +<template> + <div> + <gl-icon name="container-image" class="gl-text-secondary" /> + {{ runnerManagersCountFormatted }} + <gl-button + v-if="runnerManagersCount" + variant="link" + @mouseover.once="fetchManagers" + @focus.once="fetchManagers" + @click.once="fetchManagers" + @click="toggleExpanded" + > + <gl-icon :name="icon" /> {{ text }} + </gl-button> + + <gl-collapse :visible="expanded" class="gl-mt-5"> + <gl-skeleton-loader v-if="loading" /> + <runner-managers-table v-else-if="managers.length" :items="managers" /> + </gl-collapse> + </div> +</template> diff --git a/app/assets/javascripts/ci/runner/components/runner_managers_table.vue b/app/assets/javascripts/ci/runner/components/runner_managers_table.vue new file mode 100644 index 00000000000..10790c398b0 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/runner_managers_table.vue @@ -0,0 +1,75 @@ +<script> +import { GlIntersperse, GlTableLite } from '@gitlab/ui'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; +import { s__ } from '~/locale'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { tableField } from '../utils'; +import { I18N_STATUS_NEVER_CONTACTED } from '../constants'; +import RunnerStatusBadge from './runner_status_badge.vue'; + +export default { + name: 'RunnerManagersTable', + components: { + GlTableLite, + TimeAgo, + HelpPopover, + GlIntersperse, + RunnerStatusBadge, + RunnerUpgradeStatusIcon: () => + import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'), + }, + props: { + items: { + type: Array, + required: false, + default: () => [], + }, + }, + fields: [ + tableField({ key: 'systemId', label: s__('Runners|System ID') }), + tableField({ key: 'status', label: s__('Runners|Status') }), + tableField({ key: 'version', label: s__('Runners|Version') }), + tableField({ key: 'ipAddress', label: s__('Runners|IP Address') }), + tableField({ key: 'executorName', label: s__('Runners|Executor') }), + tableField({ key: 'architecturePlatform', label: s__('Runners|Arch/Platform') }), + tableField({ + key: 'contactedAt', + label: s__('Runners|Last contact'), + tdClass: ['gl-text-right'], + thClasses: ['gl-text-right'], + }), + ], + I18N_STATUS_NEVER_CONTACTED, +}; +</script> + +<template> + <gl-table-lite :fields="$options.fields" :items="items"> + <template #head(systemId)="{ label }"> + {{ label }} + <help-popover> + {{ s__('Runners|The unique ID for each runner that uses this configuration.') }} + </help-popover> + </template> + <template #cell(status)="{ item = {} }"> + <runner-status-badge :contacted-at="item.contactedAt" :status="item.status" /> + </template> + <template #cell(version)="{ item = {} }"> + {{ item.version }} + <template v-if="item.revision">({{ item.revision }})</template> + <runner-upgrade-status-icon :upgrade-status="item.upgradeStatus" /> + </template> + <template #cell(architecturePlatform)="{ item = {} }"> + <gl-intersperse separator="/"> + <span v-if="item.architectureName">{{ item.architectureName }}</span> + <span v-if="item.platformName">{{ item.platformName }}</span> + </gl-intersperse> + </template> + <template #cell(contactedAt)="{ item = {} }"> + <template v-if="item.contactedAt"> + <time-ago :time="item.contactedAt" /> + </template> + <template v-else>{{ $options.I18N_STATUS_NEVER_CONTACTED }}</template> + </template> + </gl-table-lite> +</template> diff --git a/app/assets/javascripts/ci/runner/components/runner_name.vue b/app/assets/javascripts/ci/runner/components/runner_name.vue index d4ecfd2d776..a877ff0f06c 100644 --- a/app/assets/javascripts/ci/runner/components/runner_name.vue +++ b/app/assets/javascripts/ci/runner/components/runner_name.vue @@ -1,5 +1,5 @@ <script> -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { formatRunnerName } from '../utils'; export default { props: { @@ -8,13 +8,13 @@ export default { required: true, }, }, - methods: { - getIdFromGraphQLId, + computed: { + name() { + return formatRunnerName(this.runner); + }, }, }; </script> <template> - <span class="gl-font-weight-bold gl-vertical-align-middle" - >#{{ getIdFromGraphQLId(runner.id) }} ({{ runner.shortSha }})</span - > + <span class="gl-font-weight-bold gl-vertical-align-middle">{{ name }}</span> </template> 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 a27af232e97..d16c8f98bad 100644 --- a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue +++ b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql'; +import runnerTogglePausedMutation from '~/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql'; import { createAlert } from '~/alert'; import { captureException } from '~/ci/runner/sentry_utils'; import { I18N_PAUSE, I18N_PAUSE_TOOLTIP, I18N_RESUME, I18N_RESUME_TOOLTIP } from '../constants'; @@ -31,14 +31,14 @@ export default { }; }, computed: { - isActive() { - return this.runner.active; + isPaused() { + return this.runner.paused; }, icon() { - return this.isActive ? 'pause' : 'play'; + return this.isPaused ? 'play' : 'pause'; }, label() { - return this.isActive ? I18N_PAUSE : I18N_RESUME; + return this.isPaused ? I18N_RESUME : I18N_PAUSE; }, buttonContent() { if (this.compact) { @@ -56,7 +56,7 @@ export default { // Prevent a "sticky" tooltip: If this button is disabled, // mouseout listeners don't run leaving the tooltip stuck if (!this.updating) { - return this.isActive ? I18N_PAUSE_TOOLTIP : I18N_RESUME_TOOLTIP; + return this.isPaused ? I18N_RESUME_TOOLTIP : I18N_PAUSE_TOOLTIP; } return ''; }, @@ -67,7 +67,7 @@ export default { try { const input = { id: this.runner.id, - active: !this.isActive, + paused: !this.isPaused, }; const { @@ -75,7 +75,7 @@ export default { runnerUpdate: { errors }, }, } = await this.$apollo.mutate({ - mutation: runnerToggleActiveMutation, + mutation: runnerTogglePausedMutation, variables: { input, }, diff --git a/app/assets/javascripts/ci/runner/components/runner_status_badge.vue b/app/assets/javascripts/ci/runner/components/runner_status_badge.vue index d084408781e..c2c52bd756a 100644 --- a/app/assets/javascripts/ci/runner/components/runner_status_badge.vue +++ b/app/assets/javascripts/ci/runner/components/runner_status_badge.vue @@ -26,21 +26,27 @@ export default { GlTooltip: GlTooltipDirective, }, props: { - runner: { - required: true, - type: Object, + contactedAt: { + type: String, + required: false, + default: null, + }, + status: { + type: String, + required: false, + default: null, }, }, computed: { contactedAtTimeAgo() { - if (this.runner.contactedAt) { - return getTimeago().format(this.runner.contactedAt); + if (this.contactedAt) { + return getTimeago().format(this.contactedAt); } // Prevent "just now" from being rendered, in case data is missing. return __('never'); }, badge() { - switch (this.runner?.status) { + switch (this.status) { case STATUS_ONLINE: return { icon: 'status-active', @@ -68,7 +74,7 @@ export default { variant: 'warning', label: I18N_STATUS_STALE, // runner may have contacted (or not) and be stale: consider both cases. - tooltip: this.runner.contactedAt + tooltip: this.contactedAt ? this.timeAgoTooltip(I18N_STALE_TIMEAGO_TOOLTIP) : I18N_STALE_NEVER_CONTACTED_TOOLTIP, }; 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 2d34c551d6d..6b94e594f1c 100644 --- a/app/assets/javascripts/ci/runner/components/runner_update_form.vue +++ b/app/assets/javascripts/ci/runner/components/runner_update_form.vue @@ -1,23 +1,16 @@ <script> -import { - GlButton, - GlIcon, - GlForm, - GlFormCheckbox, - GlFormGroup, - GlFormInputGroup, - GlSkeletonLoader, - GlTooltipDirective, -} from '@gitlab/ui'; +import { GlButton, GlForm } from '@gitlab/ui'; +import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue'; +import { createAlert, VARIANT_SUCCESS } from '~/alert'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; +import { captureException } from '~/ci/runner/sentry_utils'; + import { modelToUpdateMutationVariables, runnerToModel, } from 'ee_else_ce/ci/runner/runner_update_form_utils'; -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'; +import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED } from '../constants'; import runnerUpdateMutation from '../graphql/edit/runner_update.mutation.graphql'; import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage'; @@ -25,20 +18,11 @@ export default { name: 'RunnerUpdateForm', components: { GlButton, - GlIcon, GlForm, - GlFormCheckbox, - GlFormGroup, - GlFormInputGroup, - GlSkeletonLoader, - RunnerMaintenanceNoteField: () => - import('ee_component/ci/runner/components/runner_maintenance_note_field.vue'), + RunnerFormFields, RunnerUpdateCostFactorFields: () => import('ee_component/ci/runner/components/runner_update_cost_factor_fields.vue'), }, - directives: { - GlTooltip: GlTooltipDirective, - }, props: { runner: { type: Object, @@ -59,19 +43,17 @@ export default { data() { return { saving: false, - model: runnerToModel(this.runner), + model: null, }; }, computed: { - canBeLockedToProject() { - return this.runner?.runnerType === PROJECT_TYPE; + runnerType() { + return this.runner?.runnerType; }, }, watch: { - runner(newVal, oldVal) { - if (oldVal === null) { - this.model = runnerToModel(newVal); - } + runner(val) { + this.model = runnerToModel(val); }, }, methods: { @@ -101,7 +83,7 @@ export default { }, onSuccess() { saveAlertToLocalStorage({ message: __('Changes saved.'), variant: VARIANT_SUCCESS }); - redirectTo(this.runnerPath); // eslint-disable-line import/no-deprecated + visitUrl(this.runnerPath); }, onError(message) { this.saving = false; @@ -114,99 +96,8 @@ export default { </script> <template> <gl-form @submit.prevent="onSubmit"> - <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Details') }}</h4> - - <gl-skeleton-loader v-if="loading" /> - - <template v-else> - <gl-form-group :label="__('Description')" data-testid="runner-field-description"> - <gl-form-input-group v-model="model.description" /> - </gl-form-group> - <runner-maintenance-note-field v-model="model.maintenanceNote" /> - </template> - - <hr /> - - <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Configuration') }}</h4> - - <template v-if="loading"> - <gl-skeleton-loader v-for="i in 3" :key="i" /> - </template> - <template v-else> - <div class="gl-mb-5"> - <gl-form-checkbox - v-model="model.active" - data-testid="runner-field-paused" - :value="false" - :unchecked-value="true" - > - {{ __('Paused') }} - <template #help> - {{ s__('Runners|Stop the runner from accepting new jobs.') }} - </template> - </gl-form-checkbox> - - <gl-form-checkbox - v-model="model.accessLevel" - data-testid="runner-field-protected" - :value="$options.ACCESS_LEVEL_REF_PROTECTED" - :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED" - > - {{ __('Protected') }} - <template #help> - {{ s__('Runners|Use the runner on pipelines for protected branches only.') }} - </template> - </gl-form-checkbox> - - <gl-form-checkbox v-model="model.runUntagged" data-testid="runner-field-run-untagged"> - {{ __('Run untagged jobs') }} - <template #help> - {{ s__('Runners|Use the runner for jobs without tags, in addition to tagged jobs.') }} - </template> - </gl-form-checkbox> - - <gl-form-checkbox - v-if="canBeLockedToProject" - v-model="model.locked" - data-testid="runner-field-locked" - > - {{ __('Lock to current projects') }} <gl-icon name="lock" /> - <template #help> - {{ - s__( - 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.', - ) - }} - </template> - </gl-form-checkbox> - </div> - - <gl-form-group - data-testid="runner-field-max-timeout" - :label="__('Maximum job timeout')" - :description=" - s__( - 'Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project.', - ) - " - > - <gl-form-input-group v-model.number="model.maximumTimeout" type="number" /> - </gl-form-group> - - <gl-form-group - data-testid="runner-field-tags" - :label="__('Tags')" - :description=" - __( - 'You can set up jobs to only use runners with specific tags. Separate tags with commas.', - ) - " - > - <gl-form-input-group v-model="model.tagList" /> - </gl-form-group> - - <runner-update-cost-factor-fields v-model="model" /> - </template> + <runner-form-fields v-model="model" :loading="loading" /> + <runner-update-cost-factor-fields v-model="model" :runner-type="runnerType" /> <div class="gl-mt-6"> <gl-button diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js index 4e36a410a66..40841696ead 100644 --- a/app/assets/javascripts/ci/runner/constants.js +++ b/app/assets/javascripts/ci/runner/constants.js @@ -9,7 +9,9 @@ export const RUNNER_DETAILS_PROJECTS_PAGE_SIZE = 5; export const RUNNER_DETAILS_JOBS_PAGE_SIZE = 30; export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.'); -export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}'); +export const I18N_CREATE_ERROR = s__( + 'Runners|An error occurred while creating the runner. Please try again.', +); export const FILTER_CSS_CLASSES = 'gl-bg-gray-10 gl-p-5 gl-border-solid gl-border-gray-100 gl-border-0 gl-border-t-1 gl-border-b-1'; @@ -103,6 +105,26 @@ export const I18N_CREATED_AT_BY_LABEL = s__('Runners|Created %{timeAgo} by %{ava export const I18N_SHOW_ONLY_INHERITED = s__('Runners|Show only inherited'); export const I18N_ADMIN = s__('Runners|Administrator'); +// No runners registered +export const I18N_GET_STARTED = s__('Runners|Get started with runners'); +export const I18N_RUNNERS_ARE_AGENTS = s__( + 'Runners|Runners are the agents that run your CI/CD jobs.', +); +export const I18N_CREATE_RUNNER_LINK = s__( + 'Runners|%{linkStart}Create a new runner%{linkEnd} to get started.', +); +export const I18N_STILL_USING_REGISTRATION_TOKENS = s__('Runners|Still using registration tokens?'); +export const I18N_CONTACT_ADMIN_TO_REGISTER = s__( + 'Runners|To register new runners, contact your administrator.', +); +export const I18N_FOLLOW_REGISTRATION_INSTRUCTIONS = s__( + 'Runners|Follow the %{linkStart}installation and registration instructions%{linkEnd} to set up a runner.', +); + +// No runners found +export const I18N_NO_RESULTS = s__('Runners|No results found'); +export const I18N_EDIT_YOUR_SEARCH = s__('Runners|Edit your search and try again'); + // Runner details export const JOBS_ROUTE_PATH = '/jobs'; // vue-router route path @@ -256,3 +278,5 @@ export const SERVICE_COMMANDS_HELP_URL = 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'; +export const RUNNER_MANAGERS_HELP_URL = + 'https://docs.gitlab.com/runner/fleet_scaling/#workers-executors-and-autoscaling-capabilities'; 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 d18b80511fb..41ec9967d90 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 @@ -2,7 +2,7 @@ fragment RunnerFieldsShared on CiRunner { id shortSha runnerType - active + paused accessLevel runUntagged locked 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 4eebcd01be6..c0b888e758b 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 @@ -7,7 +7,7 @@ fragment ListItemShared on CiRunner { shortSha version ipAddress - active + paused locked jobCount tagList @@ -22,6 +22,9 @@ fragment ListItemShared on CiRunner { updateRunner deleteRunner } + managers { + count + } groups(first: 1) { nodes { id diff --git a/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql b/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql index 9b15570dbc0..e862a20750f 100644 --- a/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql +++ b/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql @@ -1,11 +1,11 @@ # Mutation executed for the pause/resume button in the # runner list and details views. -mutation runnerToggleActive($input: RunnerUpdateInput!) { +mutation runnerTogglePaused($input: RunnerUpdateInput!) { runnerUpdate(input: $input) { runner { id - active + paused } errors } 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 bd53fb29bd0..1a2ad59650e 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 @@ -2,7 +2,7 @@ fragment RunnerDetailsShared on CiRunner { id shortSha runnerType - active + paused accessLevel runUntagged locked @@ -20,6 +20,9 @@ fragment RunnerDetailsShared on CiRunner { tokenExpiresAt version editAdminUrl + managers { + count + } userPermissions { updateRunner deleteRunner diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_manager.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_manager.fragment.graphql new file mode 100644 index 00000000000..b630786b3d5 --- /dev/null +++ b/app/assets/javascripts/ci/runner/graphql/show/runner_manager.fragment.graphql @@ -0,0 +1,5 @@ +#import "./runner_manager_shared.fragment.graphql" + +fragment CiRunnerManager on CiRunnerManager { + ...CiRunnerManagerShared +} diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_manager_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_manager_shared.fragment.graphql new file mode 100644 index 00000000000..ead005d1252 --- /dev/null +++ b/app/assets/javascripts/ci/runner/graphql/show/runner_manager_shared.fragment.graphql @@ -0,0 +1,12 @@ +fragment CiRunnerManagerShared on CiRunnerManager { + id + systemId + status + version + revision + executorName + architectureName + platformName + ipAddress + contactedAt +} diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql new file mode 100644 index 00000000000..cc16267e619 --- /dev/null +++ b/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql @@ -0,0 +1,13 @@ +#import "ee_else_ce/ci/runner/graphql/show/runner_manager.fragment.graphql" + +query getRunnerManagers($runnerId: CiRunnerID!) { + runner(id: $runnerId) { + id + managers { + count + nodes { + ...CiRunnerManager + } + } + } +} 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 index 67d29daf66f..2e1706ddae9 100644 --- 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 @@ -1,6 +1,6 @@ <script> import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated +import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; @@ -38,7 +38,7 @@ export default { message: s__('Runners|Runner created.'), variant: VARIANT_SUCCESS, }); - redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated + visitUrl(ephemeralRegisterUrl); }, onError(error) { createAlert({ message: error.message }); @@ -66,7 +66,7 @@ export default { <hr aria-hidden="true" /> - <h2 class="gl-font-weight-normal gl-font-lg gl-my-5"> + <h2 class="gl-font-size-h2 gl-my-5"> {{ s__('Runners|Platform') }} </h2> <runner-platforms-radio-group v-model="platform" /> 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 1318bf5a2e6..e885cf45c5a 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 @@ -2,7 +2,7 @@ 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'; // eslint-disable-line import/no-deprecated +import { visitUrl } from '~/lib/utils/url_utility'; 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); // eslint-disable-line import/no-deprecated + visitUrl(this.runnersPath); }, }, }; 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 index f0ae54c0232..51f5a9ce8d9 100644 --- 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 @@ -1,6 +1,6 @@ <script> import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated +import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; @@ -38,7 +38,7 @@ export default { message: s__('Runners|Runner created.'), variant: VARIANT_SUCCESS, }); - redirectTo(ephemeralRegisterUrl); // eslint-disable-line import/no-deprecated + visitUrl(ephemeralRegisterUrl); }, onError(error) { createAlert({ message: error.message }); @@ -66,7 +66,7 @@ export default { <hr aria-hidden="true" /> - <h2 class="gl-font-weight-normal gl-font-lg gl-my-5"> + <h2 class="gl-font-size-h2 gl-my-5"> {{ s__('Runners|Platform') }} </h2> <runner-platforms-radio-group v-model="platform" /> diff --git a/app/assets/javascripts/ci/runner/runner_update_form_utils.js b/app/assets/javascripts/ci/runner/runner_update_form_utils.js index 3b519fa7d71..6f6c9f64af0 100644 --- a/app/assets/javascripts/ci/runner/runner_update_form_utils.js +++ b/app/assets/javascripts/ci/runner/runner_update_form_utils.js @@ -4,7 +4,7 @@ export const runnerToModel = (runner) => { description, maximumTimeout, accessLevel, - active, + paused, locked, runUntagged, tagList = [], @@ -15,7 +15,7 @@ export const runnerToModel = (runner) => { description, maximumTimeout, accessLevel, - active, + paused, locked, runUntagged, tagList: tagList.join(', '), diff --git a/app/assets/javascripts/ci/runner/utils.js b/app/assets/javascripts/ci/runner/utils.js index 1ca0a9e86b5..bb1ffca62ee 100644 --- a/app/assets/javascripts/ci/runner/utils.js +++ b/app/assets/javascripts/ci/runner/utils.js @@ -1,4 +1,5 @@ import { formatNumber } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { RUNNER_JOB_COUNT_LIMIT } from './constants'; /** @@ -81,3 +82,13 @@ export const getPaginationVariables = (pagination, pageSize = 10) => { export const parseInterval = (interval) => { return typeof interval === 'string' ? parseInt(interval, 10) : null; }; + +/** + * Creates formatted runner name + * + * @param {Object} runner - Runner object + * @returns Formatted name + */ +export const formatRunnerName = ({ id, shortSha }) => { + return `#${getIdFromGraphQLId(id)} (${shortSha})`; +}; |