diff options
Diffstat (limited to 'app/assets/javascripts/runner/components')
21 files changed, 491 insertions, 338 deletions
diff --git a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue index e26bdbf1aea..c4bddb7b398 100644 --- a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue @@ -3,7 +3,7 @@ import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import createFlash from '~/flash'; import { __, s__ } from '~/locale'; import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; -import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; +import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql'; import { captureException } from '~/runner/sentry_utils'; const i18n = { @@ -71,7 +71,7 @@ export default { runnerUpdate: { errors }, }, } = await this.$apollo.mutate({ - mutation: runnerUpdateMutation, + mutation: runnerActionsUpdateMutation, variables: { input: { id: this.runner.id, diff --git a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue new file mode 100644 index 00000000000..9ba1192bc8c --- /dev/null +++ b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue @@ -0,0 +1,40 @@ +<script> +import { GlTooltipDirective } from '@gitlab/ui'; + +import RunnerContactedStateBadge from '../runner_contacted_state_badge.vue'; +import RunnerPausedBadge from '../runner_paused_badge.vue'; + +import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants'; + +export default { + components: { + RunnerContactedStateBadge, + RunnerPausedBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + runner: { + type: Object, + required: true, + }, + }, + computed: { + paused() { + return !this.runner.active; + }, + }, + i18n: { + I18N_LOCKED_RUNNER_DESCRIPTION, + I18N_PAUSED_RUNNER_DESCRIPTION, + }, +}; +</script> + +<template> + <div> + <runner-contacted-state-badge :runner="runner" size="sm" /> + <runner-paused-badge v-if="paused" size="sm" /> + </div> +</template> diff --git a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue index 886b5cb29fc..3b476997915 100644 --- a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue @@ -1,11 +1,21 @@ <script> +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; + import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import RunnerName from '../runner_name.vue'; +import RunnerTypeBadge from '../runner_type_badge.vue'; + +import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../../constants'; export default { components: { + GlIcon, TooltipOnTruncate, RunnerName, + RunnerTypeBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, }, props: { runner: { @@ -14,10 +24,19 @@ export default { }, }, computed: { + runnerType() { + return this.runner.runnerType; + }, + locked() { + return this.runner.locked; + }, description() { return this.runner.description; }, }, + i18n: { + I18N_LOCKED_RUNNER_DESCRIPTION, + }, }; </script> @@ -26,6 +45,14 @@ export default { <slot :runner="runner" name="runner-name"> <runner-name :runner="runner" /> </slot> + + <runner-type-badge :type="runnerType" size="sm" /> + <gl-icon + v-if="locked" + v-gl-tooltip + :title="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION" + name="lock" + /> <tooltip-on-truncate class="gl-display-block" :title="description" truncate-target="child"> <div class="gl-text-truncate"> {{ description }} diff --git a/app/assets/javascripts/runner/components/cells/runner_type_cell.vue b/app/assets/javascripts/runner/components/cells/runner_type_cell.vue deleted file mode 100644 index c8cb0bf6088..00000000000 --- a/app/assets/javascripts/runner/components/cells/runner_type_cell.vue +++ /dev/null @@ -1,47 +0,0 @@ -<script> -import { GlTooltipDirective } from '@gitlab/ui'; -import RunnerTypeBadge from '../runner_type_badge.vue'; -import RunnerStateLockedBadge from '../runner_state_locked_badge.vue'; -import RunnerStatePausedBadge from '../runner_state_paused_badge.vue'; -import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants'; - -export default { - components: { - RunnerTypeBadge, - RunnerStateLockedBadge, - RunnerStatePausedBadge, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - runner: { - type: Object, - required: true, - }, - }, - computed: { - runnerType() { - return this.runner.runnerType; - }, - locked() { - return this.runner.locked; - }, - paused() { - return !this.runner.active; - }, - }, - i18n: { - I18N_LOCKED_RUNNER_DESCRIPTION, - I18N_PAUSED_RUNNER_DESCRIPTION, - }, -}; -</script> - -<template> - <div> - <runner-type-badge :type="runnerType" size="sm" /> - <runner-state-locked-badge v-if="locked" size="sm" /> - <runner-state-paused-badge v-if="paused" size="sm" /> - </div> -</template> diff --git a/app/assets/javascripts/runner/components/helpers/masked_value.vue b/app/assets/javascripts/runner/components/helpers/masked_value.vue deleted file mode 100644 index feccb37de81..00000000000 --- a/app/assets/javascripts/runner/components/helpers/masked_value.vue +++ /dev/null @@ -1,60 +0,0 @@ -<script> -import { GlButton } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export default { - components: { - GlButton, - }, - props: { - value: { - type: String, - required: false, - default: '', - }, - }, - data() { - return { - isMasked: true, - }; - }, - computed: { - label() { - if (this.isMasked) { - return __('Click to reveal'); - } - return __('Click to hide'); - }, - icon() { - if (this.isMasked) { - return 'eye'; - } - return 'eye-slash'; - }, - displayedValue() { - if (this.isMasked && this.value?.length) { - return '*'.repeat(this.value.length); - } - return this.value; - }, - }, - methods: { - toggleMasked() { - this.isMasked = !this.isMasked; - }, - }, -}; -</script> -<template> - <span - >{{ displayedValue }} - <gl-button - :aria-label="label" - :icon="icon" - class="gl-text-body!" - data-testid="toggle-masked" - variant="link" - @click="toggleMasked" - /> - </span> -</template> diff --git a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue new file mode 100644 index 00000000000..3fbe3c1be74 --- /dev/null +++ b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue @@ -0,0 +1,112 @@ +<script> +import { + GlFormGroup, + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlDropdownDivider, +} from '@gitlab/ui'; +import { s__ } from '~/locale'; +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; +import RegistrationToken from './registration_token.vue'; +import RegistrationTokenResetDropdownItem from './registration_token_reset_dropdown_item.vue'; + +export default { + i18n: { + showInstallationInstructions: s__( + 'Runners|Show runner installation and registration instructions', + ), + registrationToken: s__('Runners|Registration token'), + }, + components: { + GlFormGroup, + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlDropdownDivider, + RegistrationToken, + RunnerInstructionsModal, + RegistrationTokenResetDropdownItem, + }, + props: { + registrationToken: { + type: String, + required: true, + }, + type: { + type: String, + required: true, + validator(type) { + return [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE].includes(type); + }, + }, + }, + data() { + return { + currentRegistrationToken: this.registrationToken, + instructionsModalOpened: false, + }; + }, + computed: { + dropdownText() { + switch (this.type) { + case INSTANCE_TYPE: + return s__('Runners|Register an instance runner'); + case GROUP_TYPE: + return s__('Runners|Register a group runner'); + case PROJECT_TYPE: + return s__('Runners|Register a project runner'); + default: + return s__('Runners|Register a runner'); + } + }, + }, + methods: { + onShowInstructionsClick() { + // Rendering the modal on demand, to avoid + // loading instructions prematurely from API. + this.instructionsModalOpened = true; + + this.$nextTick(() => { + // $refs.runnerInstructionsModal is defined in + // the tick after the modal is rendered + this.$refs.runnerInstructionsModal.show(); + }); + }, + onTokenReset(token) { + this.currentRegistrationToken = token; + + this.$refs.runnerRegistrationDropdown.hide(true); + }, + }, +}; +</script> + +<template> + <gl-dropdown + ref="runnerRegistrationDropdown" + menu-class="gl-w-auto!" + :text="dropdownText" + variant="confirm" + v-bind="$attrs" + > + <gl-dropdown-item @click.capture.native.stop="onShowInstructionsClick"> + {{ $options.i18n.showInstallationInstructions }} + <runner-instructions-modal + v-if="instructionsModalOpened" + ref="runnerInstructionsModal" + :registration-token="registrationToken" + data-testid="runner-instructions-modal" + /> + </gl-dropdown-item> + <gl-dropdown-divider /> + <gl-dropdown-form class="gl-p-4!"> + <gl-form-group class="gl-mb-0" :label="$options.i18n.registrationToken"> + <registration-token :value="currentRegistrationToken" /> + </gl-form-group> + </gl-dropdown-form> + <gl-dropdown-divider /> + <registration-token-reset-dropdown-item :type="type" @tokenReset="onTokenReset" /> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/runner/components/registration/registration_token.vue b/app/assets/javascripts/runner/components/registration/registration_token.vue new file mode 100644 index 00000000000..d54a66ff0e4 --- /dev/null +++ b/app/assets/javascripts/runner/components/registration/registration_token.vue @@ -0,0 +1,83 @@ +<script> +import { GlButtonGroup, GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; + +export default { + components: { + GlButtonGroup, + GlButton, + ModalCopyButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + value: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + isMasked: true, + }; + }, + computed: { + maskLabel() { + if (this.isMasked) { + return __('Click to reveal'); + } + return __('Click to hide'); + }, + maskIcon() { + if (this.isMasked) { + return 'eye'; + } + return 'eye-slash'; + }, + displayedValue() { + if (this.isMasked && this.value?.length) { + return '*'.repeat(this.value.length); + } + return this.value; + }, + }, + methods: { + onToggleMasked() { + this.isMasked = !this.isMasked; + }, + onCopied() { + // value already in the clipboard, simply notify the user + this.$toast?.show(s__('Runners|Registration token copied!')); + }, + }, + i18n: { + copyLabel: s__('Runners|Copy registration token'), + }, +}; +</script> +<template> + <gl-button-group> + <gl-button class="gl-font-monospace" data-testid="token-value" label> + {{ displayedValue }} + </gl-button> + <gl-button + v-gl-tooltip + :aria-label="maskLabel" + :title="maskLabel" + :icon="maskIcon" + class="gl-w-auto! gl-flex-shrink-0!" + data-testid="toggle-masked" + @click.stop="onToggleMasked" + /> + <modal-copy-button + class="gl-w-auto! gl-flex-shrink-0!" + :aria-label="$options.i18n.copyLabel" + :title="$options.i18n.copyLabel" + :text="value" + @success="onCopied" + /> + </gl-button-group> +</template> diff --git a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue index cdf14abd4f9..3bb15bff8d8 100644 --- a/app/assets/javascripts/runner/components/runner_registration_token_reset.vue +++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue @@ -1,17 +1,18 @@ <script> -import { GlButton } from '@gitlab/ui'; -import createFlash, { FLASH_TYPES } from '~/flash'; +import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import createFlash from '~/flash'; import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { __, s__ } from '~/locale'; import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql'; import { captureException } from '~/runner/sentry_utils'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; export default { name: 'RunnerRegistrationTokenReset', components: { - GlButton, + GlDropdownItem, + GlLoadingIcon, }, inject: { groupId: { @@ -95,10 +96,7 @@ export default { this.reportToSentry(error); }, onSuccess(token) { - createFlash({ - message: s__('Runners|New registration token generated!'), - type: FLASH_TYPES.SUCCESS, - }); + this.$toast?.show(s__('Runners|New registration token generated!')); this.$emit('tokenReset', token); }, reportToSentry(error) { @@ -108,7 +106,8 @@ export default { }; </script> <template> - <gl-button :loading="loading" @click="resetToken"> + <gl-dropdown-item @click.capture.native.stop="resetToken"> {{ __('Reset registration token') }} - </gl-button> + <gl-loading-icon v-if="loading" inline /> + </gl-dropdown-item> </template> diff --git a/app/assets/javascripts/runner/components/runner_contacted_state_badge.vue b/app/assets/javascripts/runner/components/runner_contacted_state_badge.vue new file mode 100644 index 00000000000..b4727f832f8 --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_contacted_state_badge.vue @@ -0,0 +1,69 @@ +<script> +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import { + I18N_ONLINE_RUNNER_DESCRIPTION, + I18N_OFFLINE_RUNNER_DESCRIPTION, + I18N_NOT_CONNECTED_RUNNER_DESCRIPTION, + STATUS_ONLINE, + STATUS_OFFLINE, + STATUS_NOT_CONNECTED, +} from '../constants'; + +export default { + components: { + GlBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + runner: { + required: true, + type: Object, + }, + }, + computed: { + contactedAtTimeAgo() { + if (this.runner.contactedAt) { + return getTimeago().format(this.runner.contactedAt); + } + return null; + }, + badge() { + switch (this.runner.status) { + case STATUS_ONLINE: + return { + variant: 'success', + label: s__('Runners|online'), + tooltip: sprintf(I18N_ONLINE_RUNNER_DESCRIPTION, { + timeAgo: this.contactedAtTimeAgo, + }), + }; + case STATUS_OFFLINE: + return { + variant: 'muted', + label: s__('Runners|offline'), + tooltip: sprintf(I18N_OFFLINE_RUNNER_DESCRIPTION, { + timeAgo: this.contactedAtTimeAgo, + }), + }; + case STATUS_NOT_CONNECTED: + return { + variant: 'muted', + label: s__('Runners|not connected'), + tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION, + }; + default: + return null; + } + }, + }, +}; +</script> +<template> + <gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs"> + {{ badge.label }} + </gl-badge> +</template> diff --git a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue index e04ca8ddca0..a9dfec35479 100644 --- a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue +++ b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue @@ -2,6 +2,7 @@ import { cloneDeep } from 'lodash'; import { __ } from '~/locale'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; +import { searchValidator } from '~/runner/runner_search_utils'; import { CREATED_DESC, CREATED_ASC, CONTACTED_DESC, CONTACTED_ASC } from '../constants'; const sortOptions = [ @@ -31,9 +32,12 @@ export default { value: { type: Object, required: true, - validator(val) { - return Array.isArray(val?.filters) && typeof val?.sort === 'string'; - }, + validator: searchValidator, + }, + tokens: { + type: Array, + required: false, + default: () => [], }, namespace: { type: String, @@ -43,7 +47,7 @@ export default { data() { // filtered_search_bar_root.vue may mutate the inital // filters. Use `cloneDeep` to prevent those mutations - // from affecting this component + // from affecting this component const { filters, sort } = cloneDeep(this.value); return { initialFilterValue: filters, @@ -52,19 +56,17 @@ export default { }, methods: { onFilter(filters) { - const { sort } = this.value; - + // Apply new filters, from page 1 this.$emit('input', { + ...this.value, filters, - sort, pagination: { page: 1 }, }); }, onSort(sort) { - const { filters } = this.value; - + // Apply new sort, from page 1 this.$emit('input', { - filters, + ...this.value, sort, pagination: { page: 1 }, }); @@ -74,13 +76,16 @@ export default { }; </script> <template> - <div> + <div + class="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" + > <filtered-search v-bind="$attrs" :namespace="namespace" recent-searches-storage-key="runners-search" :sort-options="$options.sortOptions" :initial-filter-value="initialFilterValue" + :tokens="tokens" :initial-sort-by="initialSortBy" :search-input-placeholder="__('Search or filter results...')" data-testid="runners-filtered-search" diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index 3f6ea389288..f8dbc469c22 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -1,12 +1,11 @@ <script> import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { formatNumber, __, s__ } from '~/locale'; +import { __, s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; -import { RUNNER_JOB_COUNT_LIMIT } from '../constants'; import RunnerActionsCell from './cells/runner_actions_cell.vue'; import RunnerSummaryCell from './cells/runner_summary_cell.vue'; -import RunnerTypeCell from './cells/runner_type_cell.vue'; +import RunnerStatusCell from './cells/runner_status_cell.vue'; import RunnerTags from './runner_tags.vue'; const tableField = ({ key, label = '', width = 10 }) => { @@ -37,7 +36,7 @@ export default { RunnerActionsCell, RunnerSummaryCell, RunnerTags, - RunnerTypeCell, + RunnerStatusCell, }, directives: { GlTooltip: GlTooltipDirective, @@ -54,18 +53,6 @@ export default { }, }, methods: { - formatProjectCount(projectCount) { - if (projectCount === null) { - return __('n/a'); - } - return formatNumber(projectCount); - }, - formatJobCount(jobCount) { - if (jobCount > RUNNER_JOB_COUNT_LIMIT) { - return `${formatNumber(RUNNER_JOB_COUNT_LIMIT)}+`; - } - return formatNumber(jobCount); - }, runnerTrAttr(runner) { if (runner) { return { @@ -76,13 +63,11 @@ export default { }, }, fields: [ - tableField({ key: 'type', label: __('Type/State') }), - tableField({ key: 'summary', label: s__('Runners|Runner'), width: 30 }), + tableField({ key: 'status', label: s__('Runners|Status') }), + tableField({ key: 'summary', label: s__('Runners|Runner ID'), width: 30 }), tableField({ key: 'version', label: __('Version') }), tableField({ key: 'ipAddress', label: __('IP Address') }), - tableField({ key: 'projectCount', label: __('Projects'), width: 5 }), - tableField({ key: 'jobCount', label: __('Jobs'), width: 5 }), - tableField({ key: 'tagList', label: __('Tags') }), + tableField({ key: 'tagList', label: __('Tags'), width: 20 }), tableField({ key: 'contactedAt', label: __('Last contact') }), tableField({ key: 'actions', label: '' }), ], @@ -103,8 +88,8 @@ export default { <gl-skeleton-loader v-for="i in 4" :key="i" /> </template> - <template #cell(type)="{ item }"> - <runner-type-cell :runner="item" /> + <template #cell(status)="{ item }"> + <runner-status-cell :runner="item" /> </template> <template #cell(summary)="{ item, index }"> @@ -123,14 +108,6 @@ export default { {{ ipAddress }} </template> - <template #cell(projectCount)="{ item: { projectCount } }"> - {{ formatProjectCount(projectCount) }} - </template> - - <template #cell(jobCount)="{ item: { jobCount } }"> - {{ formatJobCount(jobCount) }} - </template> - <template #cell(tagList)="{ item: { tagList } }"> <runner-tags :tag-list="tagList" size="sm" /> </template> diff --git a/app/assets/javascripts/runner/components/runner_manual_setup_help.vue b/app/assets/javascripts/runner/components/runner_manual_setup_help.vue deleted file mode 100644 index 475d362bb52..00000000000 --- a/app/assets/javascripts/runner/components/runner_manual_setup_help.vue +++ /dev/null @@ -1,108 +0,0 @@ -<script> -import { GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import MaskedValue from '~/runner/components/helpers/masked_value.vue'; -import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants'; - -export default { - components: { - GlLink, - GlSprintf, - ClipboardButton, - MaskedValue, - RunnerInstructions, - RunnerRegistrationTokenReset, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - inject: { - runnerInstallHelpPage: { - default: null, - }, - }, - props: { - registrationToken: { - type: String, - required: true, - }, - type: { - type: String, - required: true, - validator(type) { - return [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE].includes(type); - }, - }, - }, - data() { - return { - currentRegistrationToken: this.registrationToken, - }; - }, - computed: { - rootUrl() { - return gon.gitlab_url || ''; - }, - typeName() { - switch (this.type) { - case INSTANCE_TYPE: - return s__('Runners|shared'); - case GROUP_TYPE: - return s__('Runners|group'); - case PROJECT_TYPE: - return s__('Runners|specific'); - default: - return ''; - } - }, - }, - methods: { - onTokenReset(token) { - this.currentRegistrationToken = token; - }, - }, -}; -</script> - -<template> - <div class="bs-callout"> - <h5 data-testid="runner-help-title"> - <gl-sprintf :message="__('Set up a %{type} runner manually')"> - <template #type> - {{ typeName }} - </template> - </gl-sprintf> - </h5> - - <ol> - <li> - <gl-link :href="runnerInstallHelpPage" data-testid="runner-help-link" target="_blank"> - {{ __("Install GitLab Runner and ensure it's running.") }} - </gl-link> - </li> - <li> - {{ __('Register the runner with this URL:') }} - <br /> - - <code data-testid="coordinator-url">{{ rootUrl }}</code> - <clipboard-button :title="__('Copy URL')" :text="rootUrl" /> - </li> - <li> - {{ __('And this registration token:') }} - <br /> - - <code data-testid="registration-token" - ><masked-value :value="currentRegistrationToken" - /></code> - <clipboard-button :title="__('Copy token')" :text="currentRegistrationToken" /> - </li> - </ol> - - <runner-registration-token-reset :type="type" @tokenReset="onTokenReset" /> - - <runner-instructions /> - </div> -</template> diff --git a/app/assets/javascripts/runner/components/runner_state_paused_badge.vue b/app/assets/javascripts/runner/components/runner_paused_badge.vue index d1e6fa05e4d..d1e6fa05e4d 100644 --- a/app/assets/javascripts/runner/components/runner_state_paused_badge.vue +++ b/app/assets/javascripts/runner/components/runner_paused_badge.vue diff --git a/app/assets/javascripts/runner/components/runner_state_locked_badge.vue b/app/assets/javascripts/runner/components/runner_state_locked_badge.vue deleted file mode 100644 index 458526010bc..00000000000 --- a/app/assets/javascripts/runner/components/runner_state_locked_badge.vue +++ /dev/null @@ -1,25 +0,0 @@ -<script> -import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; -import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants'; - -export default { - components: { - GlBadge, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - i18n: { - I18N_LOCKED_RUNNER_DESCRIPTION, - }, -}; -</script> -<template> - <gl-badge - v-gl-tooltip="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION" - variant="warning" - v-bind="$attrs" - > - {{ s__('Runners|locked') }} - </gl-badge> -</template> diff --git a/app/assets/javascripts/runner/components/runner_tag.vue b/app/assets/javascripts/runner/components/runner_tag.vue index 06562e618a8..6ad2023a866 100644 --- a/app/assets/javascripts/runner/components/runner_tag.vue +++ b/app/assets/javascripts/runner/components/runner_tag.vue @@ -1,11 +1,15 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlBadge, GlTooltipDirective, GlResizeObserverDirective } from '@gitlab/ui'; import { RUNNER_TAG_BADGE_VARIANT } from '../constants'; export default { components: { GlBadge, }, + directives: { + GlTooltip: GlTooltipDirective, + GlResizeObserver: GlResizeObserverDirective, + }, props: { tag: { type: String, @@ -14,14 +18,39 @@ export default { size: { type: String, required: false, - default: 'md', + default: 'sm', + }, + }, + data() { + return { + overflowing: false, + }; + }, + computed: { + tooltip() { + if (this.overflowing) { + return this.tag; + } + return ''; + }, + }, + methods: { + onResize() { + const { scrollWidth, offsetWidth } = this.$el; + this.overflowing = scrollWidth > offsetWidth; }, }, RUNNER_TAG_BADGE_VARIANT, }; </script> <template> - <gl-badge :size="size" :variant="$options.RUNNER_TAG_BADGE_VARIANT"> + <gl-badge + v-gl-tooltip="tooltip" + v-gl-resize-observer="onResize" + class="gl-display-inline-block gl-max-w-full gl-text-truncate" + :size="size" + :variant="$options.RUNNER_TAG_BADGE_VARIANT" + > {{ tag }} </gl-badge> </template> diff --git a/app/assets/javascripts/runner/components/runner_tags.vue b/app/assets/javascripts/runner/components/runner_tags.vue index aec0d8e2c66..8da5e33076f 100644 --- a/app/assets/javascripts/runner/components/runner_tags.vue +++ b/app/assets/javascripts/runner/components/runner_tags.vue @@ -14,13 +14,19 @@ export default { size: { type: String, required: false, - default: 'md', + default: 'sm', }, }, }; </script> <template> <div> - <runner-tag v-for="tag in tagList" :key="tag" :tag="tag" :size="size" /> + <runner-tag + v-for="tag in tagList" + :key="tag" + class="gl-display-inline gl-mr-1" + :tag="tag" + :size="size" + /> </div> </template> diff --git a/app/assets/javascripts/runner/components/runner_type_alert.vue b/app/assets/javascripts/runner/components/runner_type_alert.vue index aa435aaa823..1400875a1d6 100644 --- a/app/assets/javascripts/runner/components/runner_type_alert.vue +++ b/app/assets/javascripts/runner/components/runner_type_alert.vue @@ -9,17 +9,14 @@ const ALERT_DATA = { message: s__( 'Runners|This runner is available to all groups and projects in your GitLab instance.', ), - variant: 'success', anchor: 'shared-runners', }, [GROUP_TYPE]: { message: s__('Runners|This runner is available to all projects and subgroups in a group.'), - variant: 'success', anchor: 'group-runners', }, [PROJECT_TYPE]: { message: s__('Runners|This runner is associated with one or more projects.'), - variant: 'info', anchor: 'specific-runners', }, }; @@ -50,7 +47,7 @@ export default { }; </script> <template> - <gl-alert v-if="alert" :variant="alert.variant" :dismissible="false"> + <gl-alert v-if="alert" variant="info" :dismissible="false"> {{ alert.message }} <gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link> </gl-alert> diff --git a/app/assets/javascripts/runner/components/runner_type_badge.vue b/app/assets/javascripts/runner/components/runner_type_badge.vue index 1a61b80184b..b885dcefdcb 100644 --- a/app/assets/javascripts/runner/components/runner_type_badge.vue +++ b/app/assets/javascripts/runner/components/runner_type_badge.vue @@ -12,17 +12,14 @@ import { const BADGE_DATA = { [INSTANCE_TYPE]: { - variant: 'success', text: s__('Runners|shared'), tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION, }, [GROUP_TYPE]: { - variant: 'success', text: s__('Runners|group'), tooltip: I18N_GROUP_RUNNER_DESCRIPTION, }, [PROJECT_TYPE]: { - variant: 'info', text: s__('Runners|specific'), tooltip: I18N_PROJECT_RUNNER_DESCRIPTION, }, @@ -53,7 +50,7 @@ export default { }; </script> <template> - <gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs"> + <gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" variant="info" v-bind="$attrs"> {{ badge.text }} </gl-badge> </template> diff --git a/app/assets/javascripts/runner/components/runner_type_tabs.vue b/app/assets/javascripts/runner/components/runner_type_tabs.vue new file mode 100644 index 00000000000..b767dafaccf --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_type_tabs.vue @@ -0,0 +1,66 @@ +<script> +import { GlTabs, GlTab } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { searchValidator } from '~/runner/runner_search_utils'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants'; + +const tabs = [ + { + title: s__('Runners|All'), + runnerType: null, + }, + { + title: s__('Runners|Instance'), + runnerType: INSTANCE_TYPE, + }, + { + title: s__('Runners|Group'), + runnerType: GROUP_TYPE, + }, + { + title: s__('Runners|Project'), + runnerType: PROJECT_TYPE, + }, +]; + +export default { + components: { + GlTabs, + GlTab, + }, + props: { + value: { + type: Object, + required: true, + validator: searchValidator, + }, + }, + methods: { + onTabSelected({ runnerType }) { + this.$emit('input', { + ...this.value, + runnerType, + pagination: { page: 1 }, + }); + }, + isTabActive({ runnerType }) { + return runnerType === this.value.runnerType; + }, + }, + tabs, +}; +</script> +<template> + <gl-tabs v-bind="$attrs" data-testid="runner-type-tabs"> + <gl-tab + v-for="tab in $options.tabs" + :key="`${tab.runnerType}`" + :active="isTabActive(tab)" + @click="onTabSelected(tab)" + > + <template #title> + <slot name="title" :tab="tab">{{ tab.title }}</slot> + </template> + </gl-tab> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js index 03dff5e61a5..9963048ae1d 100644 --- a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js +++ b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js @@ -10,23 +10,29 @@ import { PARAM_KEY_STATUS, } from '../../constants'; +const options = [ + { value: STATUS_ACTIVE, title: s__('Runners|Active') }, + { value: STATUS_PAUSED, title: s__('Runners|Paused') }, + { value: STATUS_ONLINE, title: s__('Runners|Online') }, + { value: STATUS_OFFLINE, title: s__('Runners|Offline') }, + { value: STATUS_NOT_CONNECTED, title: s__('Runners|Not connected') }, +]; + export const statusTokenConfig = { icon: 'status', title: __('Status'), type: PARAM_KEY_STATUS, token: BaseToken, unique: true, - options: [ - { value: STATUS_ACTIVE, title: s__('Runners|Active') }, - { value: STATUS_PAUSED, title: s__('Runners|Paused') }, - { value: STATUS_ONLINE, title: s__('Runners|Online') }, - { value: STATUS_OFFLINE, title: s__('Runners|Offline') }, - - // Added extra quotes in this title to avoid splitting this value: + options: options.map(({ value, title }) => ({ + value, + // Replace whitespace with a special character to avoid + // splitting this value. + // Replacing in each option, as translations may also + // contain spaces! + // see: https://gitlab.com/gitlab-org/gitlab/-/issues/344142 // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438 - { value: STATUS_NOT_CONNECTED, title: `"${s__('Runners|Not connected')}"` }, - ], - // TODO In principle we could support more complex search rules, - // this can be added to a separate issue. + title: title.replace(' ', '\u00a0'), + })), operators: OPERATOR_IS_ONLY, }; diff --git a/app/assets/javascripts/runner/components/search_tokens/type_token_config.js b/app/assets/javascripts/runner/components/search_tokens/type_token_config.js deleted file mode 100644 index 1da61c53386..00000000000 --- a/app/assets/javascripts/runner/components/search_tokens/type_token_config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { __, s__ } from '~/locale'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; -import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, PARAM_KEY_RUNNER_TYPE } from '../../constants'; - -export const typeTokenConfig = { - icon: 'file-tree', - title: __('Type'), - type: PARAM_KEY_RUNNER_TYPE, - token: BaseToken, - unique: true, - options: [ - { value: INSTANCE_TYPE, title: s__('Runners|instance') }, - { value: GROUP_TYPE, title: s__('Runners|group') }, - { value: PROJECT_TYPE, title: s__('Runners|project') }, - ], - // TODO We should support more complex search rules, - // search for multiple states (OR) or have NOT operators - operators: OPERATOR_IS_ONLY, -}; |