summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/runner
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 13:16:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 13:16:36 +0000
commit311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch)
tree07e7870bca8aed6d61fdcc810731c50d2c40af47 /app/assets/javascripts/runner
parent27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff)
downloadgitlab-ce-311b0269b4eb9839fa63f80c8d7a58f32b8138a0.tar.gz
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/runner')
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue87
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js29
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_actions_cell.vue4
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_status_cell.vue40
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_summary_cell.vue27
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_type_cell.vue47
-rw-r--r--app/assets/javascripts/runner/components/helpers/masked_value.vue60
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_dropdown.vue112
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token.vue83
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue (renamed from app/assets/javascripts/runner/components/runner_registration_token_reset.vue)19
-rw-r--r--app/assets/javascripts/runner/components/runner_contacted_state_badge.vue69
-rw-r--r--app/assets/javascripts/runner/components/runner_filtered_search_bar.vue27
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue39
-rw-r--r--app/assets/javascripts/runner/components/runner_manual_setup_help.vue108
-rw-r--r--app/assets/javascripts/runner/components/runner_paused_badge.vue (renamed from app/assets/javascripts/runner/components/runner_state_paused_badge.vue)0
-rw-r--r--app/assets/javascripts/runner/components/runner_state_locked_badge.vue25
-rw-r--r--app/assets/javascripts/runner/components/runner_tag.vue35
-rw-r--r--app/assets/javascripts/runner/components/runner_tags.vue10
-rw-r--r--app/assets/javascripts/runner/components/runner_type_alert.vue5
-rw-r--r--app/assets/javascripts/runner/components/runner_type_badge.vue5
-rw-r--r--app/assets/javascripts/runner/components/runner_type_tabs.vue66
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/status_token_config.js28
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/type_token_config.js20
-rw-r--r--app/assets/javascripts/runner/constants.js16
-rw-r--r--app/assets/javascripts/runner/graphql/runner_actions_update.mutation.graphql14
-rw-r--r--app/assets/javascripts/runner/graphql/runner_node.fragment.graphql3
-rw-r--r--app/assets/javascripts/runner/graphql/runner_update.mutation.graphql3
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue24
-rw-r--r--app/assets/javascripts/runner/group_runners/index.js9
-rw-r--r--app/assets/javascripts/runner/runner_details/index.js7
-rw-r--r--app/assets/javascripts/runner/runner_search_utils.js88
31 files changed, 720 insertions, 389 deletions
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index c8513a0b803..3edb658eaf5 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -1,18 +1,26 @@
<script>
-import { GlLink } from '@gitlab/ui';
+import { GlBadge, GlLink } from '@gitlab/ui';
import createFlash from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
-import { formatNumber, sprintf, __ } from '~/locale';
+import { sprintf, __ } from '~/locale';
+
+import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
-import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue';
import RunnerName from '../components/runner_name.vue';
import RunnerPagination from '../components/runner_pagination.vue';
+import RunnerTypeTabs from '../components/runner_type_tabs.vue';
+
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
import { tagTokenConfig } from '../components/search_tokens/tag_token_config';
-import { typeTokenConfig } from '../components/search_tokens/type_token_config';
-import { ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, I18N_FETCH_ERROR } from '../constants';
+import {
+ ADMIN_FILTERED_SEARCH_NAMESPACE,
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ I18N_FETCH_ERROR,
+} from '../constants';
import getRunnersQuery from '../graphql/get_runners.query.graphql';
import {
fromUrlQueryToSearch,
@@ -24,19 +32,37 @@ import { captureException } from '../sentry_utils';
export default {
name: 'AdminRunnersApp',
components: {
+ GlBadge,
GlLink,
+ RegistrationDropdown,
RunnerFilteredSearchBar,
RunnerList,
- RunnerManualSetupHelp,
RunnerName,
RunnerPagination,
+ RunnerTypeTabs,
},
props: {
+ registrationToken: {
+ type: String,
+ required: true,
+ },
activeRunnersCount: {
- type: Number,
+ type: String,
required: true,
},
- registrationToken: {
+ allRunnersCount: {
+ type: String,
+ required: true,
+ },
+ instanceRunnersCount: {
+ type: String,
+ required: true,
+ },
+ groupRunnersCount: {
+ type: String,
+ required: true,
+ },
+ projectRunnersCount: {
type: String,
required: true,
},
@@ -86,13 +112,12 @@ export default {
},
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
- active_runners_count: formatNumber(this.activeRunnersCount),
+ active_runners_count: this.activeRunnersCount,
});
},
searchTokens() {
return [
statusTokenConfig,
- typeTokenConfig,
{
...tagTokenConfig,
recentTokenValuesStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`,
@@ -116,6 +141,20 @@ export default {
this.reportToSentry(error);
},
methods: {
+ tabCount({ runnerType }) {
+ switch (runnerType) {
+ case null:
+ return this.allRunnersCount;
+ case INSTANCE_TYPE:
+ return this.instanceRunnersCount;
+ case GROUP_TYPE:
+ return this.groupRunnersCount;
+ case PROJECT_TYPE:
+ return this.projectRunnersCount;
+ default:
+ return null;
+ }
+ },
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
@@ -126,10 +165,30 @@ export default {
</script>
<template>
<div>
- <runner-manual-setup-help
- :registration-token="registrationToken"
- :type="$options.INSTANCE_TYPE"
- />
+ <div
+ class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
+ >
+ <runner-type-tabs
+ v-model="search"
+ class="gl-w-full"
+ content-class="gl-display-none"
+ nav-class="gl-border-none!"
+ >
+ <template #title="{ tab }">
+ {{ tab.title }}
+ <gl-badge v-if="tabCount(tab)" class="gl-ml-1" size="sm">
+ {{ tabCount(tab) }}
+ </gl-badge>
+ </template>
+ </runner-type-tabs>
+
+ <registration-dropdown
+ class="gl-w-full gl-sm-w-auto gl-mr-auto"
+ :registration-token="registrationToken"
+ :type="$options.INSTANCE_TYPE"
+ right
+ />
+ </div>
<runner-filtered-search-bar
v-model="search"
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 1eec1019b73..62da6cbfa2b 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -1,8 +1,10 @@
+import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import AdminRunnersApp from './admin_runners_app.vue';
+Vue.use(GlToast);
Vue.use(VueApollo);
export const initAdminRunners = (selector = '#js-admin-runners') => {
@@ -14,15 +16,19 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
// TODO `activeRunnersCount` should be implemented using a GraphQL API
// https://gitlab.com/gitlab-org/gitlab/-/issues/333806
- const { activeRunnersCount, registrationToken, runnerInstallHelpPage } = el.dataset;
+ const {
+ runnerInstallHelpPage,
+ registrationToken,
+
+ activeRunnersCount,
+ allRunnersCount,
+ instanceRunnersCount,
+ groupRunnersCount,
+ projectRunnersCount,
+ } = el.dataset;
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(
- {},
- {
- assumeImmutableResults: true,
- },
- ),
+ defaultClient: createDefaultClient(),
});
return new Vue({
@@ -34,8 +40,15 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
render(h) {
return h(AdminRunnersApp, {
props: {
- activeRunnersCount: parseInt(activeRunnersCount, 10),
registrationToken,
+
+ // All runner counts are returned as formatted
+ // strings, we do not use `parseInt`.
+ activeRunnersCount,
+ allRunnersCount,
+ instanceRunnersCount,
+ groupRunnersCount,
+ projectRunnersCount,
},
});
},
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,
-};
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index a2fb9d9efd8..3952e2398e0 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -1,21 +1,33 @@
import { s__ } from '~/locale';
export const RUNNER_PAGE_SIZE = 20;
-export const RUNNER_JOB_COUNT_LIMIT = 1000;
export const GROUP_RUNNER_COUNT_LIMIT = 1000;
export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.');
export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}');
+// Type
export const I18N_INSTANCE_RUNNER_DESCRIPTION = s__('Runners|Available to all projects');
export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
'Runners|Available to all projects and subgroups in the group',
);
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
+
+// Status
+export const I18N_ONLINE_RUNNER_DESCRIPTION = s__(
+ 'Runners|Runner is online; last contact was %{timeAgo}',
+);
+export const I18N_OFFLINE_RUNNER_DESCRIPTION = s__(
+ 'Runners|No recent contact from this runner; last contact was %{timeAgo}',
+);
+export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__(
+ 'Runners|This runner has never connected to this instance',
+);
+
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects');
export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs');
-export const RUNNER_TAG_BADGE_VARIANT = 'info';
+export const RUNNER_TAG_BADGE_VARIANT = 'neutral';
export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100';
// Filtered search parameter names
diff --git a/app/assets/javascripts/runner/graphql/runner_actions_update.mutation.graphql b/app/assets/javascripts/runner/graphql/runner_actions_update.mutation.graphql
new file mode 100644
index 00000000000..547cc43907c
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/runner_actions_update.mutation.graphql
@@ -0,0 +1,14 @@
+#import "~/runner/graphql/runner_node.fragment.graphql"
+
+# Mutation for updates within the runners list via action
+# buttons (play, pause, ...), loads attributes shown in the
+# runner list.
+
+mutation runnerActionsUpdate($input: RunnerUpdateInput!) {
+ runnerUpdate(input: $input) {
+ runner {
+ ...RunnerNode
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
index 68d6f02f799..98f2dab26ca 100644
--- a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
@@ -10,6 +10,5 @@ fragment RunnerNode on CiRunner {
locked
tagList
contactedAt
- jobCount
- projectCount
+ status
}
diff --git a/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
index dcc7fdf24f1..ea622fd4958 100644
--- a/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
@@ -1,5 +1,8 @@
#import "ee_else_ce/runner/graphql/runner_details.fragment.graphql"
+# Mutation for updates from the runner form, loads
+# attributes shown in the runner details.
+
mutation runnerUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index 4bb28796dfa..c3dfa885f27 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -5,14 +5,14 @@ import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber, sprintf, s__ } from '~/locale';
+import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
-import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue';
import RunnerName from '../components/runner_name.vue';
import RunnerPagination from '../components/runner_pagination.vue';
+import RunnerTypeTabs from '../components/runner_type_tabs.vue';
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
-import { typeTokenConfig } from '../components/search_tokens/type_token_config';
import {
I18N_FETCH_ERROR,
GROUP_FILTERED_SEARCH_NAMESPACE,
@@ -31,11 +31,12 @@ export default {
name: 'GroupRunnersApp',
components: {
GlLink,
+ RegistrationDropdown,
RunnerFilteredSearchBar,
RunnerList,
- RunnerManualSetupHelp,
RunnerName,
RunnerPagination,
+ RunnerTypeTabs,
},
props: {
registrationToken: {
@@ -112,7 +113,7 @@ export default {
});
},
searchTokens() {
- return [statusTokenConfig, typeTokenConfig];
+ return [statusTokenConfig];
},
filteredSearchNamespace() {
return `${GROUP_FILTERED_SEARCH_NAMESPACE}/${this.groupFullPath}`;
@@ -144,7 +145,20 @@ export default {
<template>
<div>
- <runner-manual-setup-help :registration-token="registrationToken" :type="$options.GROUP_TYPE" />
+ <div class="gl-display-flex gl-align-items-center">
+ <runner-type-tabs
+ v-model="search"
+ content-class="gl-display-none"
+ nav-class="gl-border-none!"
+ />
+
+ <registration-dropdown
+ class="gl-ml-auto"
+ :registration-token="registrationToken"
+ :type="$options.GROUP_TYPE"
+ right
+ />
+ </div>
<runner-filtered-search-bar
v-model="search"
diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js
index 9545764c68d..60b7a7ab541 100644
--- a/app/assets/javascripts/runner/group_runners/index.js
+++ b/app/assets/javascripts/runner/group_runners/index.js
@@ -1,8 +1,10 @@
+import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import GroupRunnersApp from './group_runners_app.vue';
+Vue.use(GlToast);
Vue.use(VueApollo);
export const initGroupRunners = (selector = '#js-group-runners') => {
@@ -21,12 +23,7 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
} = el.dataset;
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(
- {},
- {
- assumeImmutableResults: true,
- },
- ),
+ defaultClient: createDefaultClient(),
});
return new Vue({
diff --git a/app/assets/javascripts/runner/runner_details/index.js b/app/assets/javascripts/runner/runner_details/index.js
index 05e6f86869d..db8f239a3c3 100644
--- a/app/assets/javascripts/runner/runner_details/index.js
+++ b/app/assets/javascripts/runner/runner_details/index.js
@@ -15,12 +15,7 @@ export const initRunnerDetail = (selector = '#js-runner-details') => {
const { runnerId } = el.dataset;
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(
- {},
- {
- assumeImmutableResults: true,
- },
- ),
+ defaultClient: createDefaultClient(),
});
return new Vue({
diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js
index 0a817ea0acf..b88023720e8 100644
--- a/app/assets/javascripts/runner/runner_search_utils.js
+++ b/app/assets/javascripts/runner/runner_search_utils.js
@@ -18,6 +18,50 @@ import {
RUNNER_PAGE_SIZE,
} from './constants';
+/**
+ * The filters and sorting of the runners are built around
+ * an object called "search" that contains the current state
+ * of search in the UI. For example:
+ *
+ * ```
+ * const search = {
+ * // The current tab
+ * runnerType: 'INSTANCE_TYPE',
+ *
+ * // Filters in the search bar
+ * filters: [
+ * { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
+ * { type: 'filtered-search-term', value: { data: '' } },
+ * ],
+ *
+ * // Current sorting value
+ * sort: 'CREATED_DESC',
+ *
+ * // Pagination information
+ * pagination: { page: 1 },
+ * };
+ * ```
+ *
+ * An object in this format can be used to generate URLs
+ * with the search parameters or by runner components
+ * a input using a v-model.
+ *
+ * @module runner_search_utils
+ */
+
+/**
+ * Validates a search value
+ * @param {Object} search
+ * @returns {boolean} True if the value follows the search format.
+ */
+export const searchValidator = ({ runnerType, filters, sort }) => {
+ return (
+ (runnerType === null || typeof runnerType === 'string') &&
+ Array.isArray(filters) &&
+ typeof sort === 'string'
+ );
+};
+
const getPaginationFromParams = (params) => {
const page = parseInt(params[PARAM_KEY_PAGE], 10);
const after = params[PARAM_KEY_AFTER];
@@ -35,13 +79,20 @@ const getPaginationFromParams = (params) => {
};
};
+/**
+ * Takes a URL query and transforms it into a "search" object
+ * @param {String?} query
+ * @returns {Object} A search object
+ */
export const fromUrlQueryToSearch = (query = window.location.search) => {
const params = queryToObject(query, { gatherArrays: true });
+ const runnerType = params[PARAM_KEY_RUNNER_TYPE]?.[0] || null;
return {
+ runnerType,
filters: prepareTokens(
urlQueryToFilter(query, {
- filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG],
+ filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_TAG],
filteredSearchTermKey: PARAM_KEY_SEARCH,
}),
),
@@ -50,8 +101,15 @@ export const fromUrlQueryToSearch = (query = window.location.search) => {
};
};
+/**
+ * Takes a "search" object and transforms it into a URL.
+ *
+ * @param {Object} search
+ * @param {String} url
+ * @returns {String} New URL for the page
+ */
export const fromSearchToUrl = (
- { filters = [], sort = null, pagination = {} },
+ { runnerType = null, filters = [], sort = null, pagination = {} },
url = window.location.href,
) => {
const filterParams = {
@@ -65,6 +123,10 @@ export const fromSearchToUrl = (
}),
};
+ if (runnerType) {
+ filterParams[PARAM_KEY_RUNNER_TYPE] = [runnerType];
+ }
+
if (!filterParams[PARAM_KEY_SEARCH]) {
filterParams[PARAM_KEY_SEARCH] = null;
}
@@ -82,21 +144,31 @@ export const fromSearchToUrl = (
return setUrlParams({ ...filterParams, ...otherParams }, url, false, true, true);
};
-export const fromSearchToVariables = ({ filters = [], sort = null, pagination = {} } = {}) => {
+/**
+ * Takes a "search" object and transforms it into variables for runner a GraphQL query.
+ *
+ * @param {Object} search
+ * @returns {Object} Hash of filter values
+ */
+export const fromSearchToVariables = ({
+ runnerType = null,
+ filters = [],
+ sort = null,
+ pagination = {},
+} = {}) => {
const variables = {};
const queryObj = filterToQueryObject(processFilters(filters), {
filteredSearchTermKey: PARAM_KEY_SEARCH,
});
- variables.search = queryObj[PARAM_KEY_SEARCH];
-
- // TODO Get more than one value when GraphQL API supports OR for "status" or "runner_type"
[variables.status] = queryObj[PARAM_KEY_STATUS] || [];
- [variables.type] = queryObj[PARAM_KEY_RUNNER_TYPE] || [];
-
+ variables.search = queryObj[PARAM_KEY_SEARCH];
variables.tagList = queryObj[PARAM_KEY_TAG];
+ if (runnerType) {
+ variables.type = runnerType;
+ }
if (sort) {
variables.sort = sort;
}