diff options
Diffstat (limited to 'app/assets/javascripts/runner')
31 files changed, 389 insertions, 293 deletions
diff --git a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue index c2db3b9facd..40787cf72da 100644 --- a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue +++ b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue @@ -5,7 +5,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils'; import RunnerHeader from '../components/runner_header.vue'; import RunnerUpdateForm from '../components/runner_update_form.vue'; import { I18N_FETCH_ERROR } from '../constants'; -import runnerQuery from '../graphql/details/runner.query.graphql'; +import runnerFormQuery from '../graphql/edit/runner_form.query.graphql'; import { captureException } from '../sentry_utils'; export default { @@ -19,6 +19,11 @@ export default { type: String, required: true, }, + runnerPath: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -27,7 +32,7 @@ export default { }, apollo: { runner: { - query: runnerQuery, + query: runnerFormQuery, variables() { return { id: convertToGraphQLId(TYPE_CI_RUNNER, this.runnerId), @@ -40,6 +45,11 @@ export default { }, }, }, + computed: { + loading() { + return this.$apollo.queries.runner.loading; + }, + }, errorCaptured(error) { this.reportToSentry(error); }, @@ -53,6 +63,11 @@ export default { <template> <div> <runner-header v-if="runner" :runner="runner" /> - <runner-update-form :runner="runner" class="gl-my-5" /> + <runner-update-form + :loading="loading" + :runner="runner" + :runner-path="runnerPath" + class="gl-my-5" + /> </div> </template> diff --git a/app/assets/javascripts/runner/admin_runner_edit/index.js b/app/assets/javascripts/runner/admin_runner_edit/index.js index adb420f9963..a2ac5731a62 100644 --- a/app/assets/javascripts/runner/admin_runner_edit/index.js +++ b/app/assets/javascripts/runner/admin_runner_edit/index.js @@ -12,7 +12,7 @@ export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => { return null; } - const { runnerId } = el.dataset; + const { runnerId, runnerPath } = el.dataset; const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), @@ -25,6 +25,7 @@ export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => { return h(AdminRunnerEditApp, { props: { runnerId, + runnerPath, }, }); }, diff --git a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue index 86ad912f017..c3f317b40b0 100644 --- a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue +++ b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue @@ -1,19 +1,23 @@ <script> import { GlTooltipDirective } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert, VARIANT_SUCCESS } from '~/flash'; import { TYPE_CI_RUNNER } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { redirectTo } from '~/lib/utils/url_utility'; +import RunnerDeleteButton from '../components/runner_delete_button.vue'; import RunnerEditButton from '../components/runner_edit_button.vue'; import RunnerPauseButton from '../components/runner_pause_button.vue'; import RunnerHeader from '../components/runner_header.vue'; import RunnerDetails from '../components/runner_details.vue'; import { I18N_FETCH_ERROR } from '../constants'; -import runnerQuery from '../graphql/details/runner.query.graphql'; +import runnerQuery from '../graphql/show/runner.query.graphql'; import { captureException } from '../sentry_utils'; +import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage'; export default { name: 'AdminRunnerShowApp', components: { + RunnerDeleteButton, RunnerEditButton, RunnerPauseButton, RunnerHeader, @@ -27,6 +31,10 @@ export default { type: String, required: true, }, + runnersPath: { + type: String, + required: true, + }, }, data() { return { @@ -52,6 +60,9 @@ export default { canUpdate() { return this.runner.userPermissions?.updateRunner; }, + canDelete() { + return this.runner.userPermissions?.deleteRunner; + }, }, errorCaptured(error) { this.reportToSentry(error); @@ -60,6 +71,10 @@ export default { reportToSentry(error) { captureException({ error, component: this.$options.name }); }, + onDeleted({ message }) { + saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS }); + redirectTo(this.runnersPath); + }, }, }; </script> @@ -69,6 +84,7 @@ export default { <template #actions> <runner-edit-button v-if="canUpdate && runner.editAdminUrl" :href="runner.editAdminUrl" /> <runner-pause-button v-if="canUpdate" :runner="runner" /> + <runner-delete-button v-if="canDelete" :runner="runner" @deleted="onDeleted" /> </template> </runner-header> diff --git a/app/assets/javascripts/runner/admin_runner_show/index.js b/app/assets/javascripts/runner/admin_runner_show/index.js index a781898cf8d..ea455416648 100644 --- a/app/assets/javascripts/runner/admin_runner_show/index.js +++ b/app/assets/javascripts/runner/admin_runner_show/index.js @@ -1,18 +1,21 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage'; import AdminRunnerShowApp from './admin_runner_show_app.vue'; Vue.use(VueApollo); export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => { + showAlertFromLocalStorage(); + const el = document.querySelector(selector); if (!el) { return null; } - const { runnerId } = el.dataset; + const { runnerId, runnersPath } = el.dataset; const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), @@ -25,6 +28,7 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => { return h(AdminRunnerShowApp, { props: { runnerId, + runnersPath, }, }); }, 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 accc9926a57..c2bb635e056 100644 --- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -38,7 +38,7 @@ import { } from '../runner_search_utils'; import { captureException } from '../sentry_utils'; -const runnersCountSmartQuery = { +const countSmartQuery = () => ({ query: runnersAdminCountQuery, fetchPolicy: fetchPolicies.NETWORK_ONLY, update(data) { @@ -47,6 +47,39 @@ const runnersCountSmartQuery = { error(error) { this.reportToSentry(error); }, +}); + +const tabCountSmartQuery = ({ type }) => { + return { + ...countSmartQuery(), + variables() { + return { + ...this.countVariables, + type, + }; + }, + }; +}; + +const statusCountSmartQuery = ({ status, name }) => { + return { + ...countSmartQuery(), + skip() { + // skip if filtering by status and not using _this_ status as filter + if (this.countVariables.status && this.countVariables.status !== status) { + // reset count for given status + this[name] = null; + return true; + } + return false; + }, + variables() { + return { + ...this.countVariables, + status, + }; + }, + }; }; export default { @@ -101,65 +134,30 @@ export default { this.reportToSentry(error); }, }, + + // Tabs counts allRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: null, - }; - }, + ...tabCountSmartQuery({ type: null }), }, instanceRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: INSTANCE_TYPE, - }; - }, + ...tabCountSmartQuery({ type: INSTANCE_TYPE }), }, groupRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: GROUP_TYPE, - }; - }, + ...tabCountSmartQuery({ type: GROUP_TYPE }), }, projectRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: PROJECT_TYPE, - }; - }, + ...tabCountSmartQuery({ type: PROJECT_TYPE }), }, + + // Runner stats onlineRunnersTotal: { - ...runnersCountSmartQuery, - variables() { - return { - status: STATUS_ONLINE, - }; - }, + ...statusCountSmartQuery({ status: STATUS_ONLINE, name: 'onlineRunnersTotal' }), }, offlineRunnersTotal: { - ...runnersCountSmartQuery, - variables() { - return { - status: STATUS_OFFLINE, - }; - }, + ...statusCountSmartQuery({ status: STATUS_OFFLINE, name: 'offlineRunnersTotal' }), }, staleRunnersTotal: { - ...runnersCountSmartQuery, - variables() { - return { - status: STATUS_STALE, - }; - }, + ...statusCountSmartQuery({ status: STATUS_STALE, name: 'staleRunnersTotal' }), }, }, computed: { @@ -263,12 +261,6 @@ export default { </script> <template> <div> - <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" > @@ -300,6 +292,12 @@ export default { :namespace="$options.filteredSearchNamespace" /> + <runner-stats + :online-runners-count="onlineRunnersTotal" + :offline-runners-count="offlineRunnersTotal" + :stale-runners-count="staleRunnersTotal" + /> + <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> {{ __('No runners found') }} </div> diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js index 12e2cb2ee9f..b1d8442bb32 100644 --- a/app/assets/javascripts/runner/admin_runners/index.js +++ b/app/assets/javascripts/runner/admin_runners/index.js @@ -5,12 +5,15 @@ import { visitUrl } from '~/lib/utils/url_utility'; import { updateOutdatedUrl } from '~/runner/runner_search_utils'; import createDefaultClient from '~/lib/graphql'; import { createLocalState } from '../graphql/list/local_state'; +import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage'; import AdminRunnersApp from './admin_runners_app.vue'; Vue.use(GlToast); Vue.use(VueApollo); export const initAdminRunners = (selector = '#js-admin-runners') => { + showAlertFromLocalStorage(); + const el = document.querySelector(selector); if (!el) { diff --git a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue index 3fbe3c1be74..bb2a8ddf151 100644 --- a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue +++ b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue @@ -96,7 +96,7 @@ export default { <runner-instructions-modal v-if="instructionsModalOpened" ref="runnerInstructionsModal" - :registration-token="registrationToken" + :registration-token="currentRegistrationToken" data-testid="runner-instructions-modal" /> </gl-dropdown-item> 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 1234054c660..09d46ce3e66 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 @@ -115,14 +115,14 @@ export default { <gl-modal size="sm" :modal-id="$options.modalId" - :action-primary="{ + :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { text: $options.i18n.modalAction, attributes: [{ variant: 'danger' }], - }" - :action-secondary="{ + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + :action-secondary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { text: $options.i18n.modalCancel, attributes: [{ variant: 'default' }], - }" + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :title="$options.i18n.modalTitle" @primary="handleModalPrimary" > diff --git a/app/assets/javascripts/runner/components/runner_delete_button.vue b/app/assets/javascripts/runner/components/runner_delete_button.vue index b58665ecbc9..62382891df0 100644 --- a/app/assets/javascripts/runner/components/runner_delete_button.vue +++ b/app/assets/javascripts/runner/components/runner_delete_button.vue @@ -165,6 +165,8 @@ export default { :loading="deleting" :disabled="disabled" variant="danger" + category="secondary" + v-bind="$attrs" > {{ buttonContent }} </gl-button> diff --git a/app/assets/javascripts/runner/components/runner_details.vue b/app/assets/javascripts/runner/components/runner_details.vue index b6a5ffc7a64..3734f436034 100644 --- a/app/assets/javascripts/runner/components/runner_details.vue +++ b/app/assets/javascripts/runner/components/runner_details.vue @@ -84,6 +84,9 @@ export default { </runner-detail> <runner-detail :label="s__('Runners|Version')" :value="runner.version" /> <runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" /> + <runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" /> + <runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" /> + <runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" /> <runner-detail :label="s__('Runners|Configuration')"> <template #value> <gl-intersperse v-if="configTextProtected || configTextUntagged"> diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue index b25d92d049e..4eb1312b204 100644 --- a/app/assets/javascripts/runner/components/runner_jobs.vue +++ b/app/assets/javascripts/runner/components/runner_jobs.vue @@ -1,7 +1,7 @@ <script> import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { createAlert } from '~/flash'; -import runnerJobsQuery from '../graphql/details/runner_jobs.query.graphql'; +import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql'; import { I18N_FETCH_ERROR, I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '../constants'; import { captureException } from '../sentry_utils'; import { getPaginationVariables } from '../utils'; diff --git a/app/assets/javascripts/runner/components/runner_pagination.vue b/app/assets/javascripts/runner/components/runner_pagination.vue index b683a7f2330..cfc21d1407b 100644 --- a/app/assets/javascripts/runner/components/runner_pagination.vue +++ b/app/assets/javascripts/runner/components/runner_pagination.vue @@ -21,10 +21,10 @@ export default { }, computed: { prevPage() { - return this.pageInfo?.hasPreviousPage ? this.value?.page - 1 : null; + return this.pageInfo?.hasPreviousPage ? this.value.page - 1 : null; }, nextPage() { - return this.pageInfo?.hasNextPage ? this.value?.page + 1 : null; + return this.pageInfo?.hasNextPage ? this.value.page + 1 : null; }, }, methods: { diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue index d080d34fdd3..daca718e2b5 100644 --- a/app/assets/javascripts/runner/components/runner_projects.vue +++ b/app/assets/javascripts/runner/components/runner_projects.vue @@ -2,7 +2,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { sprintf, formatNumber } from '~/locale'; import { createAlert } from '~/flash'; -import runnerProjectsQuery from '../graphql/details/runner_projects.query.graphql'; +import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql'; import { I18N_ASSIGNED_PROJECTS, I18N_NONE, diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue index 119e5236f85..56c9007a781 100644 --- a/app/assets/javascripts/runner/components/runner_update_form.vue +++ b/app/assets/javascripts/runner/components/runner_update_form.vue @@ -1,10 +1,12 @@ <script> import { GlButton, + GlIcon, GlForm, GlFormCheckbox, GlFormGroup, GlFormInputGroup, + GlSkeletonLoader, GlTooltipDirective, } from '@gitlab/ui'; import { @@ -12,19 +14,23 @@ import { runnerToModel, } from 'ee_else_ce/runner/runner_update_form_utils'; import { createAlert, VARIANT_SUCCESS } from '~/flash'; +import { redirectTo } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import { captureException } from '~/runner/sentry_utils'; import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants'; -import runnerUpdateMutation from '../graphql/details/runner_update.mutation.graphql'; +import runnerUpdateMutation from '../graphql/edit/runner_update.mutation.graphql'; +import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage'; export default { name: 'RunnerUpdateForm', components: { GlButton, + GlIcon, GlForm, GlFormCheckbox, GlFormGroup, GlFormInputGroup, + GlSkeletonLoader, RunnerUpdateCostFactorFields: () => import('ee_component/runner/components/runner_update_cost_factor_fields.vue'), }, @@ -37,6 +43,16 @@ export default { required: false, default: null, }, + loading: { + type: Boolean, + required: false, + default: false, + }, + runnerPath: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -48,9 +64,6 @@ export default { canBeLockedToProject() { return this.runner?.runnerType === PROJECT_TYPE; }, - readonlyIpAddress() { - return this.runner?.ipAddress; - }, }, watch: { runner(newVal, oldVal) { @@ -74,24 +87,23 @@ export default { }); if (errors?.length) { - // Validation errors need not be thrown - createAlert({ message: errors[0] }); - return; + this.onError(errors[0]); + } else { + this.onSuccess(); } - - this.onSuccess(); } catch (error) { const { message } = error; - - createAlert({ message }); + this.onError(message); captureException({ error, component: this.$options.name }); - } finally { - this.saving = false; } }, onSuccess() { - createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS }); - this.model = runnerToModel(this.runner); + saveAlertToLocalStorage({ message: __('Changes saved.'), variant: VARIANT_SUCCESS }); + redirectTo(this.runnerPath); + }, + onError(message) { + this.saving = false; + createAlert({ message }); }, }, ACCESS_LEVEL_NOT_PROTECTED, @@ -100,104 +112,108 @@ export default { </script> <template> <gl-form @submit.prevent="onSubmit"> - <gl-form-checkbox - v-model="model.active" - data-testid="runner-field-paused" - :value="false" - :unchecked-value="true" - > - {{ __('Paused') }} - <template #help> - {{ s__('Runners|Stop the runner from accepting new jobs.') }} - </template> - </gl-form-checkbox> + <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Details') }}</h4> - <gl-form-checkbox - v-model="model.accessLevel" - data-testid="runner-field-protected" - :value="$options.ACCESS_LEVEL_REF_PROTECTED" - :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED" - > - {{ __('Protected') }} - <template #help> - {{ s__('Runners|Use the runner on pipelines for protected branches only.') }} - </template> - </gl-form-checkbox> + <gl-skeleton-loader v-if="loading" /> + <gl-form-group v-else :label="__('Description')" data-testid="runner-field-description"> + <gl-form-input-group v-model="model.description" /> + </gl-form-group> - <gl-form-checkbox v-model="model.runUntagged" data-testid="runner-field-run-untagged"> - {{ __('Run untagged jobs') }} - <template #help> - {{ s__('Runners|Use the runner for jobs without tags, in addition to tagged jobs.') }} - </template> - </gl-form-checkbox> + <hr /> - <gl-form-checkbox - v-if="canBeLockedToProject" - v-model="model.locked" - data-testid="runner-field-locked" - > - {{ __('Lock to current projects') }} - <template #help> - {{ - s__( - 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.', - ) - }} - </template> - </gl-form-checkbox> + <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Configuration') }}</h4> - <gl-form-group :label="__('IP Address')" data-testid="runner-field-ip-address"> - <gl-form-input-group :value="readonlyIpAddress" readonly select-on-click> - <template #append> - <gl-button - v-gl-tooltip.hover - :title="__('Copy IP Address')" - :aria-label="__('Copy IP Address')" - :data-clipboard-text="readonlyIpAddress" - icon="copy-to-clipboard" - class="d-inline-flex" - /> - </template> - </gl-form-input-group> - </gl-form-group> + <template v-if="loading"> + <gl-skeleton-loader v-for="i in 3" :key="i" /> + </template> + <template v-else> + <div class="gl-mb-5"> + <gl-form-checkbox + v-model="model.active" + data-testid="runner-field-paused" + :value="false" + :unchecked-value="true" + > + {{ __('Paused') }} + <template #help> + {{ s__('Runners|Stop the runner from accepting new jobs.') }} + </template> + </gl-form-checkbox> - <gl-form-group :label="__('Description')" data-testid="runner-field-description"> - <gl-form-input-group v-model="model.description" /> - </gl-form-group> + <gl-form-checkbox + v-model="model.accessLevel" + data-testid="runner-field-protected" + :value="$options.ACCESS_LEVEL_REF_PROTECTED" + :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED" + > + {{ __('Protected') }} + <template #help> + {{ s__('Runners|Use the runner on pipelines for protected branches only.') }} + </template> + </gl-form-checkbox> - <gl-form-group - data-testid="runner-field-max-timeout" - :label="__('Maximum job timeout')" - :description=" - s__( - 'Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project.', - ) - " - > - <gl-form-input-group v-model.number="model.maximumTimeout" type="number" /> - </gl-form-group> + <gl-form-checkbox v-model="model.runUntagged" data-testid="runner-field-run-untagged"> + {{ __('Run untagged jobs') }} + <template #help> + {{ s__('Runners|Use the runner for jobs without tags, in addition to tagged jobs.') }} + </template> + </gl-form-checkbox> - <gl-form-group - data-testid="runner-field-tags" - :label="__('Tags')" - :description=" - __('You can set up jobs to only use runners with specific tags. Separate tags with commas.') - " - > - <gl-form-input-group v-model="model.tagList" /> - </gl-form-group> + <gl-form-checkbox + v-if="canBeLockedToProject" + v-model="model.locked" + data-testid="runner-field-locked" + > + {{ __('Lock to current projects') }} <gl-icon name="lock" /> + <template #help> + {{ + s__( + 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.', + ) + }} + </template> + </gl-form-checkbox> + </div> - <runner-update-cost-factor-fields v-model="model" /> + <gl-form-group + data-testid="runner-field-max-timeout" + :label="__('Maximum job timeout')" + :description=" + s__( + 'Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project.', + ) + " + > + <gl-form-input-group v-model.number="model.maximumTimeout" type="number" /> + </gl-form-group> + + <gl-form-group + data-testid="runner-field-tags" + :label="__('Tags')" + :description=" + __( + 'You can set up jobs to only use runners with specific tags. Separate tags with commas.', + ) + " + > + <gl-form-input-group v-model="model.tagList" /> + </gl-form-group> + + <runner-update-cost-factor-fields v-model="model" /> + </template> - <div class="form-actions"> + <div class="gl-mt-6"> <gl-button type="submit" variant="confirm" class="js-no-auto-disable" - :loading="saving || !runner" + :loading="loading || saving" > {{ __('Save changes') }} </gl-button> + <gl-button :href="runnerPath"> + {{ __('Cancel') }} + </gl-button> </div> </gl-form> </template> diff --git a/app/assets/javascripts/runner/graphql/details/runner.query.graphql b/app/assets/javascripts/runner/graphql/details/runner.query.graphql deleted file mode 100644 index 4792a186160..00000000000 --- a/app/assets/javascripts/runner/graphql/details/runner.query.graphql +++ /dev/null @@ -1,9 +0,0 @@ -#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql" - -query getRunner($id: CiRunnerID!) { - # We have an id in deeply nested fragment - # eslint-disable-next-line @graphql-eslint/require-id-when-available - runner(id: $id) { - ...RunnerDetails - } -} diff --git a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql deleted file mode 100644 index 2449ee0fc0f..00000000000 --- a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql +++ /dev/null @@ -1,5 +0,0 @@ -#import "./runner_details_shared.fragment.graphql" - -fragment RunnerDetails on CiRunner { - ...RunnerDetailsShared -} diff --git a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql deleted file mode 100644 index d8c67728fac..00000000000 --- a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql +++ /dev/null @@ -1,35 +0,0 @@ -fragment RunnerDetailsShared on CiRunner { - __typename - id - runnerType - active - accessLevel - runUntagged - locked - ipAddress - description - maximumTimeout - jobCount - tagList - createdAt - status(legacyMode: null) - contactedAt - version - editAdminUrl - userPermissions { - updateRunner - deleteRunner - } - groups { - # Only a single group can be loaded here, while projects - # are loaded separately using the query with pagination - # parameters `runner_projects.query.graphql`. - nodes { - id - avatarUrl - name - fullName - webUrl - } - } -} diff --git a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql deleted file mode 100644 index e4bf51e2c30..00000000000 --- a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql +++ /dev/null @@ -1,15 +0,0 @@ -#import "ee_else_ce/runner/graphql/details/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) { - # We have an id in deep nested fragment - # eslint-disable-next-line @graphql-eslint/require-id-when-available - runner { - ...RunnerDetails - } - errors - } -} diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql new file mode 100644 index 00000000000..b732d587d70 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql @@ -0,0 +1,5 @@ +#import "./runner_fields_shared.fragment.graphql" + +fragment RunnerFields on CiRunner { + ...RunnerFieldsShared +} diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql new file mode 100644 index 00000000000..f900a0450e5 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql @@ -0,0 +1,15 @@ +fragment RunnerFieldsShared on CiRunner { + __typename + id + shortSha + runnerType + active + accessLevel + runUntagged + locked + description + maximumTimeout + tagList + createdAt + status(legacyMode: null) +} diff --git a/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql new file mode 100644 index 00000000000..0bf66c223fc --- /dev/null +++ b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql @@ -0,0 +1,7 @@ +#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql" + +query getRunnerForm($id: CiRunnerID!) { + runner(id: $id) { + ...RunnerFields + } +} diff --git a/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql new file mode 100644 index 00000000000..8694a51b5a4 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql @@ -0,0 +1,13 @@ +#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql" + +# Mutation for updates from the runner form, loads +# attributes shown in the runner details. + +mutation runnerUpdate($input: RunnerUpdateInput!) { + runnerUpdate(input: $input) { + runner { + ...RunnerFields + } + errors + } +} diff --git a/app/assets/javascripts/runner/graphql/show/runner.query.graphql b/app/assets/javascripts/runner/graphql/show/runner.query.graphql new file mode 100644 index 00000000000..178816b58bd --- /dev/null +++ b/app/assets/javascripts/runner/graphql/show/runner.query.graphql @@ -0,0 +1,41 @@ +query getRunner($id: CiRunnerID!) { + runner(id: $id) { + __typename + id + shortSha + runnerType + active + accessLevel + runUntagged + locked + ipAddress + executorName + architectureName + platformName + description + maximumTimeout + jobCount + tagList + createdAt + status(legacyMode: null) + contactedAt + version + editAdminUrl + userPermissions { + updateRunner + deleteRunner + } + groups { + # Only a single group can be loaded here, while projects + # are loaded separately using the query with pagination + # parameters `runner_projects.query.graphql`. + nodes { + id + avatarUrl + name + fullName + webUrl + } + } + } +} diff --git a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql index 14585e62bf2..14585e62bf2 100644 --- a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql +++ b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql diff --git a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql index cb27de7c200..cb27de7c200 100644 --- a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql +++ b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql 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 b299d7c40fe..b5bd4b111fd 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -34,7 +34,7 @@ import { } from '../runner_search_utils'; import { captureException } from '../sentry_utils'; -const runnersCountSmartQuery = { +const countSmartQuery = () => ({ query: groupRunnersCountQuery, fetchPolicy: fetchPolicies.NETWORK_ONLY, update(data) { @@ -43,6 +43,39 @@ const runnersCountSmartQuery = { error(error) { this.reportToSentry(error); }, +}); + +const tabCountSmartQuery = ({ type }) => { + return { + ...countSmartQuery(), + variables() { + return { + ...this.countVariables, + type, + }; + }, + }; +}; + +const statusCountSmartQuery = ({ status, name }) => { + return { + ...countSmartQuery(), + skip() { + // skip if filtering by status and not using _this_ status as filter + if (this.countVariables.status && this.countVariables.status !== status) { + // reset count for given status + this[name] = null; + return true; + } + return false; + }, + variables() { + return { + ...this.countVariables, + status, + }; + }, + }; }; export default { @@ -116,59 +149,27 @@ export default { 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, - }; - }, - }, + + // Tabs counts allRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: null, - }; - }, + ...tabCountSmartQuery({ type: null }), }, groupRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: GROUP_TYPE, - }; - }, + ...tabCountSmartQuery({ type: GROUP_TYPE }), }, projectRunnersCount: { - ...runnersCountSmartQuery, - variables() { - return { - ...this.countVariables, - type: PROJECT_TYPE, - }; - }, + ...tabCountSmartQuery({ type: PROJECT_TYPE }), + }, + + // Runner status summary + onlineRunnersTotal: { + ...statusCountSmartQuery({ status: STATUS_ONLINE, name: 'onlineRunnersTotal' }), + }, + offlineRunnersTotal: { + ...statusCountSmartQuery({ status: STATUS_OFFLINE, name: 'offlineRunnersTotal' }), + }, + staleRunnersTotal: { + ...statusCountSmartQuery({ status: STATUS_STALE, name: 'staleRunnersTotal' }), }, }, computed: { @@ -263,12 +264,6 @@ export default { <template> <div> - <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 v-model="search" @@ -298,6 +293,12 @@ export default { :namespace="filteredSearchNamespace" /> + <runner-stats + :online-runners-count="onlineRunnersTotal" + :offline-runners-count="offlineRunnersTotal" + :stale-runners-count="staleRunnersTotal" + /> + <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> {{ __('No runners found') }} </div> diff --git a/app/assets/javascripts/runner/local_storage_alert/constants.js b/app/assets/javascripts/runner/local_storage_alert/constants.js new file mode 100644 index 00000000000..69b7418f898 --- /dev/null +++ b/app/assets/javascripts/runner/local_storage_alert/constants.js @@ -0,0 +1 @@ +export const LOCAL_STORAGE_ALERT_KEY = 'local-storage-alert'; diff --git a/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js b/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js new file mode 100644 index 00000000000..ca7c627459a --- /dev/null +++ b/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js @@ -0,0 +1,8 @@ +import AccessorUtilities from '~/lib/utils/accessor'; +import { LOCAL_STORAGE_ALERT_KEY } from './constants'; + +export const saveAlertToLocalStorage = (alertOptions) => { + if (AccessorUtilities.canUseLocalStorage()) { + localStorage.setItem(LOCAL_STORAGE_ALERT_KEY, JSON.stringify(alertOptions)); + } +}; diff --git a/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js b/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js new file mode 100644 index 00000000000..d768a06494a --- /dev/null +++ b/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js @@ -0,0 +1,18 @@ +import AccessorUtilities from '~/lib/utils/accessor'; +import { LOCAL_STORAGE_ALERT_KEY } from './constants'; + +export const showAlertFromLocalStorage = async () => { + if (AccessorUtilities.canUseLocalStorage()) { + const alertOptions = localStorage.getItem(LOCAL_STORAGE_ALERT_KEY); + + if (alertOptions) { + try { + const { createAlert } = await import('~/flash'); + createAlert(JSON.parse(alertOptions)); + } catch { + // ignore when the alert data cannot be parsed + } + } + localStorage.removeItem(LOCAL_STORAGE_ALERT_KEY); + } +}; diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js index 5e3c412ddb6..0d688ed65ef 100644 --- a/app/assets/javascripts/runner/runner_search_utils.js +++ b/app/assets/javascripts/runner/runner_search_utils.js @@ -18,7 +18,6 @@ import { PARAM_KEY_BEFORE, DEFAULT_SORT, RUNNER_PAGE_SIZE, - STATUS_NEVER_CONTACTED, } from './constants'; import { getPaginationVariables } from './utils'; @@ -84,7 +83,6 @@ const getPaginationFromParams = (params) => { }; // Outdated URL parameters -const STATUS_NOT_CONNECTED = 'NOT_CONNECTED'; const STATUS_ACTIVE = 'ACTIVE'; const STATUS_PAUSED = 'PAUSED'; @@ -116,10 +114,6 @@ export const updateOutdatedUrl = (url = window.location.href) => { const status = params[PARAM_KEY_STATUS]?.[0] || null; switch (status) { - case STATUS_NOT_CONNECTED: - return updateUrlParams(url, { - [PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED], - }); case STATUS_ACTIVE: return updateUrlParams(url, { [PARAM_KEY_PAUSED]: ['false'], diff --git a/app/assets/javascripts/runner/utils.js b/app/assets/javascripts/runner/utils.js index 1f7794720de..cb2917a92fd 100644 --- a/app/assets/javascripts/runner/utils.js +++ b/app/assets/javascripts/runner/utils.js @@ -1,5 +1,4 @@ import { formatNumber } from '~/locale'; -import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants'; import { RUNNER_JOB_COUNT_LIMIT } from './constants'; /** @@ -28,7 +27,7 @@ export const tableField = ({ key, label = '', thClasses = [], ...options }) => { return { key, label, - thClass: [DEFAULT_TH_CLASSES, ...thClasses], + thClass: thClasses, tdAttr: { 'data-testid': `td-${key}`, }, |