summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/runner
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-20 09:16:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-20 09:16:11 +0000
commitedaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch)
tree11f143effbfeba52329fb7afbd05e6e2a3790241 /app/assets/javascripts/runner
parentd8a5691316400a0f7ec4f83832698f1988eb27c1 (diff)
downloadgitlab-ce-edaa33dee2ff2f7ea3fac488d41558eb5f86d68c.tar.gz
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/runner')
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue (renamed from app/assets/javascripts/runner/runner_details/runner_details_app.vue)27
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/index.js (renamed from app/assets/javascripts/runner/runner_details/index.js)6
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue131
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js30
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_actions_cell.vue17
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_status_cell.vue12
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue34
-rw-r--r--app/assets/javascripts/runner/components/runner_header.vue52
-rw-r--r--app/assets/javascripts/runner/components/runner_status_badge.vue8
-rw-r--r--app/assets/javascripts/runner/components/runner_type_alert.vue54
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue10
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/status_token_config.js4
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/tag_token.vue4
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_online_stat.vue17
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_stats.vue49
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_status_stat.vue65
-rw-r--r--app/assets/javascripts/runner/constants.js5
-rw-r--r--app/assets/javascripts/runner/graphql/get_group_runners.query.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql20
-rw-r--r--app/assets/javascripts/runner/graphql/get_runners.query.graphql1
-rw-r--r--app/assets/javascripts/runner/graphql/get_runners_count.query.graphql10
-rw-r--r--app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/runner_node.fragment.graphql4
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue56
-rw-r--r--app/assets/javascripts/runner/runner_search_utils.js28
-rw-r--r--app/assets/javascripts/runner/runner_update_form_utils.js (renamed from app/assets/javascripts/runner/runner_details/runner_update_form_utils.js)0
26 files changed, 465 insertions, 183 deletions
diff --git a/app/assets/javascripts/runner/runner_details/runner_details_app.vue b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
index 6557a7834e7..4d2ca9b0c58 100644
--- a/app/assets/javascripts/runner/runner_details/runner_details_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
@@ -1,20 +1,17 @@
<script>
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { sprintf } from '~/locale';
-import RunnerTypeAlert from '../components/runner_type_alert.vue';
-import RunnerTypeBadge from '../components/runner_type_badge.vue';
+import RunnerHeader from '../components/runner_header.vue';
import RunnerUpdateForm from '../components/runner_update_form.vue';
-import { I18N_DETAILS_TITLE, I18N_FETCH_ERROR } from '../constants';
+import { I18N_FETCH_ERROR } from '../constants';
import getRunnerQuery from '../graphql/get_runner.query.graphql';
import { captureException } from '../sentry_utils';
export default {
- name: 'RunnerDetailsApp',
+ name: 'AdminRunnerEditApp',
components: {
- RunnerTypeAlert,
- RunnerTypeBadge,
+ RunnerHeader,
RunnerUpdateForm,
},
props: {
@@ -37,17 +34,12 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
- computed: {
- pageTitle() {
- return sprintf(I18N_DETAILS_TITLE, { runner_id: this.runnerId });
- },
- },
errorCaptured(error) {
this.reportToSentry(error);
},
@@ -60,12 +52,7 @@ export default {
</script>
<template>
<div>
- <h2 class="page-title">
- {{ pageTitle }} <runner-type-badge v-if="runner" :type="runner.runnerType" />
- </h2>
-
- <runner-type-alert v-if="runner" :type="runner.runnerType" />
-
+ <runner-header v-if="runner" :runner="runner" />
<runner-update-form :runner="runner" class="gl-my-5" />
</div>
</template>
diff --git a/app/assets/javascripts/runner/runner_details/index.js b/app/assets/javascripts/runner/admin_runner_edit/index.js
index db8f239a3c3..adb420f9963 100644
--- a/app/assets/javascripts/runner/runner_details/index.js
+++ b/app/assets/javascripts/runner/admin_runner_edit/index.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import RunnerDetailsApp from './runner_details_app.vue';
+import AdminRunnerEditApp from './admin_runner_edit_app.vue';
Vue.use(VueApollo);
-export const initRunnerDetail = (selector = '#js-runner-details') => {
+export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => {
const el = document.querySelector(selector);
if (!el) {
@@ -22,7 +22,7 @@ export const initRunnerDetail = (selector = '#js-runner-details') => {
el,
apolloProvider,
render(h) {
- return h(RunnerDetailsApp, {
+ return h(AdminRunnerEditApp, {
props: {
runnerId,
},
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 f8220553db6..bb2bac531a7 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -1,14 +1,15 @@
<script>
import { GlBadge, GlLink } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
+import { formatNumber } 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 RunnerName from '../components/runner_name.vue';
-import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
+import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
@@ -19,9 +20,13 @@ import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
I18N_FETCH_ERROR,
} from '../constants';
import getRunnersQuery from '../graphql/get_runners.query.graphql';
+import getRunnersCountQuery from '../graphql/get_runners_count.query.graphql';
import {
fromUrlQueryToSearch,
fromSearchToUrl,
@@ -29,6 +34,17 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
+const runnersCountSmartQuery = {
+ query: getRunnersCountQuery,
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ return data?.runners?.count;
+ },
+ error(error) {
+ this.reportToSentry(error);
+ },
+};
+
export default {
name: 'AdminRunnersApp',
components: {
@@ -38,7 +54,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
- RunnerOnlineStat,
+ RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
@@ -47,26 +63,6 @@ export default {
type: String,
required: true,
},
- activeRunnersCount: {
- type: String,
- required: true,
- },
- allRunnersCount: {
- type: String,
- required: true,
- },
- instanceRunnersCount: {
- type: String,
- required: true,
- },
- groupRunnersCount: {
- type: String,
- required: true,
- },
- projectRunnersCount: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -95,16 +91,78 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
+ allRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return this.countVariables;
+ },
+ },
+ instanceRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: INSTANCE_TYPE,
+ };
+ },
+ },
+ groupRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: GROUP_TYPE,
+ };
+ },
+ },
+ projectRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: PROJECT_TYPE,
+ };
+ },
+ },
+ onlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_ONLINE,
+ };
+ },
+ },
+ offlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_OFFLINE,
+ };
+ },
+ },
+ staleRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_STALE,
+ };
+ },
+ },
},
computed: {
variables() {
return fromSearchToVariables(this.search);
},
+ countVariables() {
+ // Exclude pagination variables, leave only filters variables
+ const { sort, before, last, after, first, ...countVariables } = this.variables;
+ return countVariables;
+ },
runnersLoading() {
return this.$apollo.queries.runners.loading;
},
@@ -125,7 +183,7 @@ export default {
search: {
deep: true,
handler() {
- // TODO Implement back button reponse using onpopstate
+ // TODO Implement back button response using onpopstate
updateHistory({
url: fromSearchToUrl(this.search),
title: document.title,
@@ -138,18 +196,27 @@ export default {
},
methods: {
tabCount({ runnerType }) {
+ let count;
switch (runnerType) {
case null:
- return this.allRunnersCount;
+ count = this.allRunnersCount;
+ break;
case INSTANCE_TYPE:
- return this.instanceRunnersCount;
+ count = this.instanceRunnersCount;
+ break;
case GROUP_TYPE:
- return this.groupRunnersCount;
+ count = this.groupRunnersCount;
+ break;
case PROJECT_TYPE:
- return this.projectRunnersCount;
+ count = this.projectRunnersCount;
+ break;
default:
return null;
}
+ if (typeof count === 'number') {
+ return formatNumber(count);
+ }
+ return '';
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
@@ -161,7 +228,11 @@ export default {
</script>
<template>
<div>
- <runner-online-stat class="gl-py-6 gl-px-5" :value="activeRunnersCount" />
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
<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"
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 62da6cbfa2b..3b8a8fe9cd1 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -2,6 +2,8 @@ import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { updateOutdatedUrl } from '~/runner/runner_search_utils';
import AdminRunnersApp from './admin_runners_app.vue';
Vue.use(GlToast);
@@ -14,18 +16,16 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return null;
}
- // TODO `activeRunnersCount` should be implemented using a GraphQL API
- // https://gitlab.com/gitlab-org/gitlab/-/issues/333806
- const {
- runnerInstallHelpPage,
- registrationToken,
+ // Redirect outdated URLs
+ const updatedUrlQuery = updateOutdatedUrl();
+ if (updatedUrlQuery) {
+ visitUrl(updatedUrlQuery);
- activeRunnersCount,
- allRunnersCount,
- instanceRunnersCount,
- groupRunnersCount,
- projectRunnersCount,
- } = el.dataset;
+ // Prevent mounting the rest of the app, redirecting now.
+ return null;
+ }
+
+ const { runnerInstallHelpPage, registrationToken } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -41,14 +41,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return h(AdminRunnersApp, {
props: {
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 33f7a67aba4..0934508c87f 100644
--- a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlButtonGroup, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { __, s__, sprintf } from '~/locale';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
@@ -69,6 +69,12 @@ export default {
runnerDeleteModalId() {
return `delete-runner-modal-${this.runnerId}`;
},
+ canUpdate() {
+ return this.runner.userPermissions?.updateRunner;
+ },
+ canDelete() {
+ return this.runner.userPermissions?.deleteRunner;
+ },
},
methods: {
async onToggleActive() {
@@ -133,7 +139,7 @@ export default {
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
@@ -156,14 +162,15 @@ export default {
See https://gitlab.com/gitlab-org/gitlab/-/issues/334802
-->
<gl-button
- v-if="runner.adminUrl"
+ v-if="canUpdate && runner.editAdminUrl"
v-gl-tooltip.hover.viewport="$options.I18N_EDIT"
- :href="runner.adminUrl"
+ :href="runner.editAdminUrl"
:aria-label="$options.I18N_EDIT"
icon="pencil"
data-testid="edit-runner"
/>
<gl-button
+ v-if="canUpdate"
v-gl-tooltip.hover.viewport="toggleActiveTitle"
:aria-label="toggleActiveTitle"
:icon="toggleActiveIcon"
@@ -172,6 +179,7 @@ export default {
@click="onToggleActive"
/>
<gl-button
+ v-if="canDelete"
v-gl-tooltip.hover.viewport="deleteTitle"
v-gl-modal="runnerDeleteModalId"
:aria-label="deleteTitle"
@@ -182,6 +190,7 @@ export default {
/>
<runner-delete-modal
+ v-if="canDelete"
:ref="runnerDeleteModalId"
:modal-id="runnerDeleteModalId"
:runner-name="runnerName"
diff --git a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
index 473cd7e9794..93f86ae2a2c 100644
--- a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
@@ -28,7 +28,15 @@ export default {
<template>
<div>
- <runner-status-badge :runner="runner" size="sm" />
- <runner-paused-badge v-if="paused" size="sm" />
+ <runner-status-badge
+ :runner="runner"
+ size="sm"
+ class="gl-display-inline-block gl-max-w-full gl-text-truncate"
+ />
+ <runner-paused-badge
+ v-if="paused"
+ size="sm"
+ class="gl-display-inline-block gl-max-w-full gl-text-truncate"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
index 3bb15bff8d8..0e259807f98 100644
--- a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -1,6 +1,6 @@
<script>
-import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
+import { createAlert } from '~/flash';
import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -10,9 +10,17 @@ import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants';
export default {
name: 'RunnerRegistrationTokenReset',
+ i18n: {
+ modalTitle: __('Reset registration token'),
+ modalCopy: __('Are you sure you want to reset the registration token?'),
+ },
components: {
GlDropdownItem,
GlLoadingIcon,
+ GlModal,
+ },
+ directives: {
+ GlModal: GlModalDirective,
},
inject: {
groupId: {
@@ -22,6 +30,7 @@ export default {
default: null,
},
},
+ modalID: 'token-reset-modal',
props: {
type: {
type: String,
@@ -59,14 +68,10 @@ export default {
},
},
methods: {
+ handleModalPrimary() {
+ this.resetToken();
+ },
async resetToken() {
- // TODO Replace confirmation with gl-modal
- // See: https://gitlab.com/gitlab-org/gitlab/-/issues/333810
- // eslint-disable-next-line no-alert
- if (!window.confirm(__('Are you sure you want to reset the registration token?'))) {
- return;
- }
-
this.loading = true;
try {
const {
@@ -91,7 +96,7 @@ export default {
},
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
@@ -106,8 +111,15 @@ export default {
};
</script>
<template>
- <gl-dropdown-item @click.capture.native.stop="resetToken">
+ <gl-dropdown-item v-gl-modal="$options.modalID">
{{ __('Reset registration token') }}
+ <gl-modal
+ :modal-id="$options.modalID"
+ :title="$options.i18n.modalTitle"
+ @primary="handleModalPrimary"
+ >
+ <p>{{ $options.i18n.modalCopy }}</p>
+ </gl-modal>
<gl-loading-icon v-if="loading" inline />
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_header.vue b/app/assets/javascripts/runner/components/runner_header.vue
new file mode 100644
index 00000000000..09f58df7bd0
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_header.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlSprintf } 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 } from '../constants';
+import RunnerTypeBadge from './runner_type_badge.vue';
+import RunnerStatusBadge from './runner_status_badge.vue';
+
+export default {
+ components: {
+ GlSprintf,
+ TimeAgo,
+ RunnerTypeBadge,
+ RunnerStatusBadge,
+ },
+ props: {
+ runner: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ paused() {
+ return !this.runner.active;
+ },
+ heading() {
+ const id = getIdFromGraphQLId(this.runner.id);
+ return sprintf(I18N_DETAILS_TITLE, { runner_id: id });
+ },
+ },
+};
+</script>
+<template>
+ <div class="gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
+ <runner-status-badge :runner="runner" />
+ <runner-type-badge v-if="runner" :type="runner.runnerType" />
+ <template v-if="runner.createdAt">
+ <gl-sprintf :message="__('%{runner} created %{timeago}')">
+ <template #runner>
+ <strong>{{ heading }}</strong>
+ </template>
+ <template #timeago>
+ <time-ago :time="runner.createdAt" />
+ </template>
+ </gl-sprintf>
+ </template>
+ <template v-else>
+ <strong>{{ heading }}</strong>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_status_badge.vue b/app/assets/javascripts/runner/components/runner_status_badge.vue
index 0823876a187..6d0445ecb7a 100644
--- a/app/assets/javascripts/runner/components/runner_status_badge.vue
+++ b/app/assets/javascripts/runner/components/runner_status_badge.vue
@@ -4,11 +4,10 @@ import { __, s__, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import {
I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION,
- I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
+ I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION,
I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION,
I18N_STALE_RUNNER_DESCRIPTION,
STATUS_ONLINE,
- STATUS_NOT_CONNECTED,
STATUS_NEVER_CONTACTED,
STATUS_OFFLINE,
STATUS_STALE,
@@ -45,12 +44,11 @@ export default {
timeAgo: this.contactedAtTimeAgo,
}),
};
- case STATUS_NOT_CONNECTED:
case STATUS_NEVER_CONTACTED:
return {
variant: 'muted',
- label: s__('Runners|not connected'),
- tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
+ label: s__('Runners|never contacted'),
+ tooltip: I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION,
};
case STATUS_OFFLINE:
return {
diff --git a/app/assets/javascripts/runner/components/runner_type_alert.vue b/app/assets/javascripts/runner/components/runner_type_alert.vue
deleted file mode 100644
index 1400875a1d6..00000000000
--- a/app/assets/javascripts/runner/components/runner_type_alert.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<script>
-import { GlAlert, GlLink } from '@gitlab/ui';
-import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
-import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants';
-
-const ALERT_DATA = {
- [INSTANCE_TYPE]: {
- message: s__(
- 'Runners|This runner is available to all groups and projects in your GitLab instance.',
- ),
- anchor: 'shared-runners',
- },
- [GROUP_TYPE]: {
- message: s__('Runners|This runner is available to all projects and subgroups in a group.'),
- anchor: 'group-runners',
- },
- [PROJECT_TYPE]: {
- message: s__('Runners|This runner is associated with one or more projects.'),
- anchor: 'specific-runners',
- },
-};
-
-export default {
- components: {
- GlAlert,
- GlLink,
- },
- props: {
- type: {
- type: String,
- required: false,
- default: null,
- validator(type) {
- return Boolean(ALERT_DATA[type]);
- },
- },
- },
- computed: {
- alert() {
- return ALERT_DATA[this.type];
- },
- helpHref() {
- return helpPagePath('ci/runners/runners_scope', { anchor: this.alert.anchor });
- },
- },
-};
-</script>
-<template>
- <gl-alert v-if="alert" variant="info" :dismissible="false">
- {{ alert.message }}
- <gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link>
- </gl-alert>
-</template>
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index 9a6fc07f6dd..e3deb94236e 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -10,8 +10,8 @@ import {
import {
modelToUpdateMutationVariables,
runnerToModel,
-} from 'ee_else_ce/runner/runner_details/runner_update_form_utils';
-import createFlash, { FLASH_TYPES } from '~/flash';
+} from 'ee_else_ce/runner/runner_update_form_utils';
+import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
@@ -75,14 +75,14 @@ export default {
if (errors?.length) {
// Validation errors need not be thrown
- createFlash({ message: errors[0] });
+ createAlert({ message: errors[0] });
return;
}
this.onSuccess();
} catch (error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
} finally {
@@ -90,7 +90,7 @@ export default {
}
},
onSuccess() {
- createFlash({ message: __('Changes saved.'), type: FLASH_TYPES.SUCCESS });
+ createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
this.model = runnerToModel(this.runner);
},
reportToSentry(error) {
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 4b356fa47ed..79038eb8228 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
@@ -6,7 +6,7 @@ import {
STATUS_PAUSED,
STATUS_ONLINE,
STATUS_OFFLINE,
- STATUS_NOT_CONNECTED,
+ STATUS_NEVER_CONTACTED,
STATUS_STALE,
PARAM_KEY_STATUS,
} from '../../constants';
@@ -16,7 +16,7 @@ const options = [
{ 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') },
+ { value: STATUS_NEVER_CONTACTED, title: s__('Runners|Never contacted') },
{ value: STATUS_STALE, title: s__('Runners|Stale') },
];
diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
index 7461308ab91..59230bb809e 100644
--- a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
@@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
@@ -50,7 +50,7 @@ export default {
try {
this.tags = await this.getTagsOptions(searchTerm);
} catch {
- createFlash({
+ createAlert({
message: s__('Runners|Something went wrong while fetching the tags suggestions'),
});
} finally {
diff --git a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue b/app/assets/javascripts/runner/components/stat/runner_online_stat.vue
deleted file mode 100644
index b92b9badef0..00000000000
--- a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script>
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
-
-export default {
- components: {
- GlSingleStat,
- },
-};
-</script>
-<template>
- <gl-single-stat
- v-bind="$attrs"
- variant="success"
- :title="s__('Runners|Online Runners')"
- :meta-text="s__('Runners|online')"
- />
-</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_stats.vue b/app/assets/javascripts/runner/components/stat/runner_stats.vue
new file mode 100644
index 00000000000..d3693ee593e
--- /dev/null
+++ b/app/assets/javascripts/runner/components/stat/runner_stats.vue
@@ -0,0 +1,49 @@
+<script>
+import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
+import RunnerStatusStat from './runner_status_stat.vue';
+
+export default {
+ components: {
+ RunnerStatusStat,
+ },
+ props: {
+ onlineRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ offlineRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ staleRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-py-6">
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_ONLINE"
+ :value="onlineRunnersCount"
+ />
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_OFFLINE"
+ :value="offlineRunnersCount"
+ />
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_STALE"
+ :value="staleRunnersCount"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
new file mode 100644
index 00000000000..b77bbe15541
--- /dev/null
+++ b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { s__, formatNumber } from '~/locale';
+import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
+
+export default {
+ components: {
+ GlSingleStat,
+ },
+ props: {
+ value: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ formattedValue() {
+ if (typeof this.value === 'number') {
+ return formatNumber(this.value);
+ }
+ return '-';
+ },
+ stat() {
+ switch (this.status) {
+ case STATUS_ONLINE:
+ return {
+ variant: 'success',
+ title: s__('Runners|Online runners'),
+ metaText: s__('Runners|online'),
+ };
+ case STATUS_OFFLINE:
+ return {
+ variant: 'muted',
+ title: s__('Runners|Offline runners'),
+ metaText: s__('Runners|offline'),
+ };
+ case STATUS_STALE:
+ return {
+ variant: 'warning',
+ title: s__('Runners|Stale runners'),
+ metaText: s__('Runners|stale'),
+ };
+ default:
+ return {
+ title: s__('Runners|Runners'),
+ };
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-single-stat
+ v-if="stat"
+ :value="formattedValue"
+ :variant="stat.variant"
+ :title="stat.title"
+ :meta-text="stat.metaText"
+ />
+</template>
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index 355f3054917..ce8019ffaa0 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -18,8 +18,8 @@ export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one
export const I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|Runner is online; last contact was %{timeAgo}',
);
-export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__(
- 'Runners|This runner has never connected to this instance',
+export const I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION = s__(
+ 'Runners|This runner has never contacted this instance',
);
export const I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|No recent contact from this runner; last contact was %{timeAgo}',
@@ -60,7 +60,6 @@ export const STATUS_ACTIVE = 'ACTIVE';
export const STATUS_PAUSED = 'PAUSED';
export const STATUS_ONLINE = 'ONLINE';
-export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE';
diff --git a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
index 6da9e276f74..f7bcd683718 100644
--- a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
@@ -13,7 +13,7 @@ query getGroupRunners(
$sort: CiRunnerSort
) {
group(fullPath: $groupFullPath) {
- id
+ id # Apollo required
runners(
membership: DESCENDANTS
before: $before
diff --git a/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql
new file mode 100644
index 00000000000..554eb09e372
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql
@@ -0,0 +1,20 @@
+query getGroupRunnersCount(
+ $groupFullPath: ID!
+ $status: CiRunnerStatus
+ $type: CiRunnerType
+ $tagList: [String!]
+ $search: String
+) {
+ group(fullPath: $groupFullPath) {
+ id # Apollo required
+ runners(
+ membership: DESCENDANTS
+ status: $status
+ type: $type
+ tagList: $tagList
+ search: $search
+ ) {
+ count
+ }
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/get_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_runners.query.graphql
index 51a91b9eb96..05df399fa6a 100644
--- a/app/assets/javascripts/runner/graphql/get_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/get_runners.query.graphql
@@ -26,6 +26,7 @@ query getRunners(
nodes {
...RunnerNode
adminUrl
+ editAdminUrl
}
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql
new file mode 100644
index 00000000000..181a4495cae
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql
@@ -0,0 +1,10 @@
+query getRunnersCount(
+ $status: CiRunnerStatus
+ $type: CiRunnerType
+ $tagList: [String!]
+ $search: String
+) {
+ runners(status: $status, type: $type, tagList: $tagList, search: $search) {
+ count
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
index 8c50cba7de3..8e968343b9b 100644
--- a/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
@@ -9,4 +9,6 @@ fragment RunnerDetailsShared on CiRunner {
description
maximumTimeout
tagList
+ createdAt
+ status(legacyMode: null)
}
diff --git a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
index 169f6ffd2ea..4a771d779dc 100644
--- a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
@@ -12,4 +12,8 @@ fragment RunnerNode on CiRunner {
tagList
contactedAt
status(legacyMode: null)
+ userPermissions {
+ updateRunner
+ deleteRunner
+ }
}
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 a58a53a6a0d..3a7b58e3dc9 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -1,6 +1,6 @@
<script>
import { GlLink } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber, sprintf, s__ } from '~/locale';
@@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
-import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
+import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
@@ -19,8 +19,12 @@ import {
GROUP_FILTERED_SEARCH_NAMESPACE,
GROUP_TYPE,
GROUP_RUNNER_COUNT_LIMIT,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
} from '../constants';
import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql';
+import getGroupRunnersCountQuery from '../graphql/get_group_runners_count.query.graphql';
import {
fromUrlQueryToSearch,
fromSearchToUrl,
@@ -28,6 +32,17 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
+const runnersCountSmartQuery = {
+ query: getGroupRunnersCountQuery,
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ return data?.group?.runners?.count;
+ },
+ error(error) {
+ this.reportToSentry(error);
+ },
+};
+
export default {
name: 'GroupRunnersApp',
components: {
@@ -36,7 +51,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
- RunnerOnlineStat,
+ RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
@@ -84,11 +99,38 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
+ onlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_ONLINE,
+ };
+ },
+ },
+ offlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_OFFLINE,
+ };
+ },
+ },
+ staleRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_STALE,
+ };
+ },
+ },
},
computed: {
variables() {
@@ -147,7 +189,11 @@ export default {
<template>
<div>
- <runner-online-stat class="gl-py-6 gl-px-5" :value="groupRunnersCount" />
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
<div class="gl-display-flex gl-align-items-center">
<runner-type-tabs
diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js
index b88023720e8..c80a73948b8 100644
--- a/app/assets/javascripts/runner/runner_search_utils.js
+++ b/app/assets/javascripts/runner/runner_search_utils.js
@@ -16,6 +16,7 @@ import {
PARAM_KEY_BEFORE,
DEFAULT_SORT,
RUNNER_PAGE_SIZE,
+ STATUS_NEVER_CONTACTED,
} from './constants';
/**
@@ -79,6 +80,33 @@ const getPaginationFromParams = (params) => {
};
};
+// Outdated URL parameters
+const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
+
+/**
+ * Returns an updated URL for old (or deprecated) admin runner URLs.
+ *
+ * Use for redirecting users to currently used URLs.
+ *
+ * @param {String?} URL
+ * @returns Updated URL if outdated, `null` otherwise
+ */
+export const updateOutdatedUrl = (url = window.location.href) => {
+ const urlObj = new URL(url);
+ const query = urlObj.search;
+
+ const params = queryToObject(query, { gatherArrays: true });
+
+ const runnerType = params[PARAM_KEY_STATUS]?.[0] || null;
+ if (runnerType === STATUS_NOT_CONNECTED) {
+ const updatedParams = {
+ [PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED],
+ };
+ return setUrlParams(updatedParams, url, false, true, true);
+ }
+ return null;
+};
+
/**
* Takes a URL query and transforms it into a "search" object
* @param {String?} query
diff --git a/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js b/app/assets/javascripts/runner/runner_update_form_utils.js
index 3b519fa7d71..3b519fa7d71 100644
--- a/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js
+++ b/app/assets/javascripts/runner/runner_update_form_utils.js